Merge branch 'version/4.3.0'

This commit is contained in:
Phillip Thelen 2023-11-29 13:32:06 +01:00
commit ac6223a50c
75 changed files with 835 additions and 509 deletions

View file

@ -95,7 +95,6 @@ dependencies {
implementation 'com.google.firebase:firebase-messaging-ktx'
implementation 'com.google.firebase:firebase-config-ktx'
implementation 'com.google.firebase:firebase-perf-ktx'
implementation 'com.google.android.gms:play-services-ads:22.4.0'
implementation "com.google.android.gms:play-services-auth:$play_auth_version"
implementation 'com.google.android.flexbox:flexbox:3.0.0'
implementation "com.google.android.gms:play-services-wearable:$play_wearables_version"
@ -116,7 +115,7 @@ dependencies {
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
implementation 'com.google.android.play:core:1.10.3'
implementation 'androidx.activity:activity-compose:1.7.2'
implementation 'androidx.activity:activity-compose:1.8.0'
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.animation:animation:$compose_version"

View file

@ -31,9 +31,6 @@
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
},
@ -62,9 +59,6 @@
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
}

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/red_100" />
<solid android:color="@color/maroon_100" />
<corners android:radius="100dp" />
<stroke android:width="1dp" android:color="@color/black_20_alpha" />
</shape>
</shape>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/confetti_container"
@ -17,7 +17,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
@ -35,7 +35,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:src="@drawable/death_ghost" />
android:src="@drawable/death_ghost"
android:importantForAccessibility="no" />
<ImageView
android:id="@+id/heart_view"
@ -43,7 +44,8 @@
android:layout_height="110dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_broken_heart" />
android:src="@drawable/ic_broken_heart"
android:importantForAccessibility="no" />
</RelativeLayout>
<TextView
@ -52,9 +54,9 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/spacing_xlarge"
android:gravity="center"
android:paddingHorizontal="24dp"
android:text="@string/you_ran_out_of_health"
android:textStyle="bold"
android:paddingHorizontal="24dp" />
android:textStyle="bold" />
<TextView
android:id="@+id/loss_description"
@ -64,9 +66,9 @@
android:layout_marginVertical="@dimen/spacing_medium"
android:gravity="center"
android:lineSpacingExtra="4dp"
android:paddingHorizontal="12dp"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:paddingHorizontal="12dp" />
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
@ -75,130 +77,128 @@
android:gravity="center"
android:text="@string/faint_broken_equipment"
android:textColor="@color/text_secondary"
android:textSize="16sp"/>
android:textSize="16sp" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:id="@+id/restart_button"
style="@style/HabiticaButton.Maroon"
android:layout_width="match_parent"
android:layout_height="69dp"
android:layout_marginHorizontal="24dp"
android:layout_marginBottom="6dp"
android:text="@string/faint_button"
android:textStyle="bold"
android:layout_marginHorizontal="24dp" />
android:textStyle="bold" />
<com.habitrpg.android.habitica.ui.views.ads.AdButton
android:id="@+id/ad_button"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginHorizontal="24dp"
app:activeBackground="@drawable/ad_button_background_content"
app:text="@string/watch_ad_to_hang_on"
app:textColor="@color/text_primary"
android:layout_marginHorizontal="24dp" />
app:textColor="@color/text_primary" />
<LinearLayout
android:id="@+id/revive_subscriber_wrapper"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_large">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="65dp"
android:paddingStart="24dp"
android:paddingEnd="18dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/revive_subscriber_button"
android:layout_marginBottom="@dimen/spacing_large"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="@string/subscriber_button_faint"
android:textStyle="bold"
android:background="@drawable/subscriber_benefit_button_bg"
android:gravity="center"
android:textSize="16sp"
android:backgroundTint="@null"
android:textColor="@color/green_1"
android:layout_marginEnd="6dp"
android:layout_marginTop="5dp"
android:padding="0dp"/>
android:layout_height="65dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="8dp"
android:paddingStart="24dp"
android:paddingEnd="18dp">
<androidx.compose.ui.platform.ComposeView
android:id="@+id/revive_subscriber_button"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="6dp"
android:background="@drawable/subscriber_benefit_button_bg"
android:backgroundTint="@null" />
<TextView
style="@style/Caption2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:background="@drawable/sub_perk_bg"
android:paddingHorizontal="6dp"
android:paddingVertical="4dp"
android:text="@string/sub_perk"
android:textColor="@color/green_500" />
</FrameLayout>
<TextView
style="@style/Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/sub_perk_bg"
android:textColor="@color/green_500"
android:paddingHorizontal="6dp"
android:paddingVertical="4dp"
style="@style/Caption2"
android:layout_gravity="top|end"
android:text="@string/sub_perk"/>
</FrameLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/faint_subscriber_description"
android:layout_marginHorizontal="42dp"
android:gravity="center"
android:textColor="@color/text_teal"
style="@style/Body1"
/>
android:text="@string/faint_subscriber_description"
android:textColor="@color/text_teal" />
</LinearLayout>
<TextView
android:id="@+id/subscriber_benefit_used_view"
style="@style/Body1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="42dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="@dimen/spacing_large"
android:gravity="center_horizontal"
android:textColor="@color/text_teal"
android:layout_marginHorizontal="42dp"
tools:text="@string/subscriber_benefit_used_faint"
style="@style/Body1"
android:layout_marginBottom="@dimen/spacing_large"
android:layout_marginTop="8dp"/>
tools:text="@string/subscriber_benefit_used_faint" />
<LinearLayout
android:id="@+id/unsubbed_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginTop="@dimen/spacing_large"
android:orientation="vertical"
android:paddingTop="16dp"
android:paddingBottom="12dp"
android:paddingHorizontal="24dp"
android:background="@drawable/subscribe_incentive_bg_topround"
>
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingTop="16dp"
android:paddingBottom="12dp">
<Button
android:id="@+id/subscribe_modal_button"
style="@style/HabiticaButton.White"
android:layout_width="match_parent"
android:layout_height="69dp"
android:padding="0dp"
android:text="@string/subscribe_incentive_button_faint"
android:textStyle="bold"
style="@style/HabiticaButton.White"
android:textColor="@color/teal_10"
android:padding="0dp"/>
android:textStyle="bold" />
<TextView
style="@style/Body1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="3dp"
android:gravity="center_horizontal"
android:text="@string/subscribe_incentive_text_faint"
android:textColor="@color/teal_1"
android:layout_marginHorizontal="16dp"
style="@style/Body1"
android:layout_marginTop="3dp"/>
android:textColor="@color/teal_1" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<FrameLayout
android:id="@+id/snackbar_container"
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_gravity="bottom"
android:paddingBottom="20dp"/>
android:paddingBottom="20dp" />
</FrameLayout>

View file

@ -39,15 +39,73 @@
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/notification_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="?android:listDivider"
android:visibility="invisible"
android:orientation="vertical"
android:showDividers="middle" />
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="20dp"
android:paddingTop="10dp"
android:paddingEnd="10dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingTop="@dimen/spacing_medium">
<TextView
android:id="@android:id/title"
style="@style/Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dip"
android:background="@color/transparent"
android:text="@string/notifications"
android:textAllCaps="true"
android:textColor="@color/text_quad"
android:textSize="12sp" />
<TextView
android:id="@+id/notifications_title_badge"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginStart="10dp"
android:background="@drawable/badge_gray"
android:gravity="center"
android:minWidth="24dp"
android:textColor="@color/text_quad"
android:textSize="12sp"
tools:text="1" />
</LinearLayout>
<Button
android:id="@+id/dismiss_all_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/transparent"
android:text="@string/dismiss_all"
android:textColor="@color/text_brand" />
</LinearLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/progress_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<LinearLayout
android:id="@+id/notification_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="?android:listDivider"
android:visibility="invisible"
android:orientation="vertical"
android:showDividers="middle" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout>

View file

@ -4,7 +4,7 @@
android:layout_height="match_parent"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:gravity="center_vertical|right">
android:gravity="center_vertical|end">
<ImageView
android:id="@+id/classIconView"
android:layout_width="32dp"
@ -15,4 +15,4 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>

View file

@ -9,10 +9,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:scaleType="center" />
android:scaleType="center"
android:importantForAccessibility="no" />
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"/>
</LinearLayout>
</LinearLayout>

View file

@ -24,7 +24,8 @@
android:layout_height="3dp"
android:layout_gravity="center_horizontal"
android:layout_margin="@dimen/spacing_large"
android:src="@color/offset_background" />
android:src="@color/offset_background"
android:importantForAccessibility="no" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/subscriber_benefit_banner"
@ -51,7 +52,8 @@
android:layout_alignParentTop="true"
android:layout_alignParentBottom="false"
android:scaleType="centerCrop"
android:src="@drawable/subscription_banner_image_left" />
android:src="@drawable/subscription_banner_image_left"
android:importantForAccessibility="no" />
<ImageView
android:id="@+id/banner_right_image"
@ -60,7 +62,8 @@
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:scaleType="centerCrop"
android:src="@drawable/subscription_banner_image_right" />
android:src="@drawable/subscription_banner_image_right"
android:importantForAccessibility="no" />
<LinearLayout
android:layout_width="match_parent"
@ -83,7 +86,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:text="SUBSCRIBER BENEFIT"
android:text="@string/subscriber_benefit"
android:textAllCaps="true"
android:textColor="@color/white" />
</LinearLayout>
</RelativeLayout>
@ -116,7 +120,8 @@
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:src="@drawable/separator_fancy" />
android:src="@drawable/separator_fancy"
android:importantForAccessibility="no" />
<com.habitrpg.android.habitica.ui.views.subscriptions.SubscriberBenefitView
@ -130,7 +135,8 @@
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:layout_marginBottom="12dp"
android:src="@drawable/separator_fancy" />
android:src="@drawable/separator_fancy"
android:importantForAccessibility="no" />
<ProgressBar
android:id="@+id/loadingIndicator"
@ -155,7 +161,6 @@
android:id="@+id/subscription3month"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:flagText="@string/save_20"
app:gemCapText="@string/subscribe3month_gemcap"
app:hourGlassCount="1"
app:recurringText="@string/three_months" />

View file

@ -72,7 +72,8 @@
android:layout_weight="1"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="@dimen/spacing_medium">
android:paddingStart="@dimen/spacing_medium"
android:paddingEnd="0dp">
<TextView
android:id="@+id/quest_title_view"
android:layout_width="match_parent"
@ -92,7 +93,8 @@
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_keyboard_arrow_right_gray_24dp"/>
app:srcCompat="@drawable/ic_keyboard_arrow_right_gray_24dp"
android:importantForAccessibility="no" />
</LinearLayout>
<LinearLayout
android:visibility="gone"

View file

@ -25,14 +25,16 @@
android:scaleType="center"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="false"/>
android:layout_alignParentBottom="false"
android:importantForAccessibility="no" />
<ImageView
android:id="@+id/promo_banner_right_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="center"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"/>
android:layout_alignParentEnd="true"
android:importantForAccessibility="no" />
<LinearLayout
android:layout_centerInParent="true"
android:layout_width="wrap_content"
@ -135,4 +137,4 @@
tools:text="This is some text"
android:lineSpacingExtra="3dp"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.core.widget.NestedScrollView>

View file

@ -18,7 +18,8 @@
android:layout_height="3dp"
android:layout_gravity="center_horizontal"
android:layout_margin="@dimen/spacing_large"
android:src="@color/offset_background" />
android:src="@color/offset_background"
android:importantForAccessibility="no" />
<com.google.android.material.appbar.AppBarLayout
@ -172,4 +173,4 @@
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</com.google.android.material.card.MaterialCardView>

View file

@ -23,7 +23,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/creator_hills_bg"
android:layout_centerInParent="true" />
android:layout_centerInParent="true"
android:importantForAccessibility="no" />
<com.habitrpg.common.habitica.views.AvatarView
android:id="@+id/avatarView"
android:layout_width="@dimen/avatar_small_width"
@ -56,4 +57,4 @@
android:layout_height="wrap_content"
/>
</LinearLayout>
</LinearLayout>

View file

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingStart="@dimen/spacing_medium"
android:paddingTop="44dp"

View file

@ -24,7 +24,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/spacing_medium"
android:layout_marginEnd="3dp"
android:layout_marginStart="@dimen/spacing_large"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
@ -47,12 +47,14 @@
android:layout_width="wrap_content"
android:layout_height="24dp"
android:src="@drawable/flag_flap"
android:layout_gravity="center_vertical" />
android:layout_gravity="center_vertical"
android:importantForAccessibility="no" />
<TextView
android:id="@+id/flag_textview"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:paddingEnd="@dimen/spacing_medium"
android:paddingStart="0dp"
android:background="@color/green_50"
android:textColor="@color/white"
style="@style/Caption2"
@ -89,4 +91,4 @@
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View file

@ -38,8 +38,7 @@
android:layout_height="wrap_content"
tools:text="This is the Title"
android:gravity="center"
style="@style/Title2"
android:textSize="18sp"
android:textSize="16sp"
android:visibility="gone"
tools:visibility="visible"
android:textColor="@color/white"/>
@ -49,8 +48,7 @@
android:layout_height="wrap_content"
tools:text="This is the Content"
android:gravity="center"
style="@style/Body1"
android:textSize="13sp"
android:textSize="14sp"
android:visibility="gone"
tools:visibility="visible"
android:textColor="@color/white"/>

View file

@ -1467,6 +1467,7 @@
<string name="revive_broken_equipment">Your %s broke</string>
<string name="subscribe_gems_for_gold_incentive_text">Subscribe to buy Gems with Gold and receive these other exclusive benefits!</string>
<string name="subscribe_hourglass_incentive_text">Subscribers get Mystic Hourglasses to buy items in the Time Travelers Shop and these other exclusive benefits!</string>
<string name="subscriber_benefit">SUBSCRIBER BENEFIT</string>
<plurals name="you_x_others">

View file

@ -114,7 +114,10 @@ class ApiClientImpl(
val calendar = GregorianCalendar()
val timeZone = calendar.timeZone
val timezoneOffset = -TimeUnit.MINUTES.convert(timeZone.getOffset(calendar.timeInMillis).toLong(), TimeUnit.MILLISECONDS)
val timezoneOffset = -TimeUnit.MINUTES.convert(
timeZone.getOffset(calendar.timeInMillis).toLong(),
TimeUnit.MILLISECONDS
)
val cacheSize: Long = 10 * 1024 * 1024 // 10 MB
@ -157,7 +160,8 @@ class ApiClientImpl(
}
else -> {
return@addNetworkInterceptor response.newBuilder().header("Cache-Control", "no-store").build()
return@addNetworkInterceptor response.newBuilder()
.header("Cache-Control", "no-store").build()
}
}
} else {
@ -208,7 +212,11 @@ class ApiClientImpl(
return process { this.apiService.connectLocal(auth) }
}
override suspend fun connectSocial(network: String, userId: String, accessToken: String): UserAuthResponse? {
override suspend fun connectSocial(
network: String,
userId: String,
accessToken: String
): UserAuthResponse? {
val auth = UserAuthSocial()
auth.network = network
val authResponse = UserAuthSocialTokens()
@ -232,34 +240,51 @@ class ApiClientImpl(
if (SocketTimeoutException::class.java.isAssignableFrom(throwableClass)) {
return
}
var isUserInputCall = false
@Suppress("DEPRECATION")
if (SocketException::class.java.isAssignableFrom(throwableClass) || SSLException::class.java.isAssignableFrom(throwableClass)) {
this.showConnectionProblemDialog(R.string.internal_error_api)
if (SocketException::class.java.isAssignableFrom(throwableClass)
|| SSLException::class.java.isAssignableFrom(throwableClass)
) {
this.showConnectionProblemDialog(R.string.internal_error_api, isUserInputCall)
} else if (throwableClass == SocketTimeoutException::class.java || UnknownHostException::class.java == throwableClass || IOException::class.java == throwableClass) {
this.showConnectionProblemDialog(R.string.network_error_no_network_body)
this.showConnectionProblemDialog(
R.string.network_error_no_network_body,
isUserInputCall
)
} else if (HttpException::class.java.isAssignableFrom(throwable.javaClass)) {
val error = throwable as HttpException
val res = getErrorResponse(error)
val status = error.code()
val requestUrl = error.response()?.raw()?.request?.url
val path = requestUrl?.encodedPath?.removePrefix("/api/v4") ?: ""
isUserInputCall = when {
path.startsWith("/groups") && path.endsWith("invite") -> true
else -> false
}
if (res.message != null && res.message == "RECEIPT_ALREADY_USED") {
return
}
if (error.response()?.raw()?.request?.url?.toString()?.endsWith("/user/push-devices") == true) {
if (requestUrl?.toString()?.endsWith("/user/push-devices") == true) {
// workaround for an error that sometimes displays that the user already has this push device
return
}
if (status in 400..499) {
if (res.displayMessage.isNotEmpty()) {
showConnectionProblemDialog("", res.displayMessage)
showConnectionProblemDialog("", res.displayMessage, isUserInputCall)
} else if (status == 401) {
showConnectionProblemDialog(R.string.authentication_error_title, R.string.authentication_error_body)
showConnectionProblemDialog(
R.string.authentication_error_title,
R.string.authentication_error_body,
isUserInputCall
)
}
} else if (status in 500..599) {
this.showConnectionProblemDialog(R.string.internal_error_api)
this.showConnectionProblemDialog(R.string.internal_error_api, isUserInputCall)
} else {
showConnectionProblemDialog(R.string.internal_error_api)
showConnectionProblemDialog(R.string.internal_error_api, isUserInputCall)
}
} else if (JsonSyntaxException::class.java.isAssignableFrom(throwableClass)) {
Analytics.logError("Json Error: " + lastAPICallURL + ", " + throwable.message)
@ -268,7 +293,10 @@ class ApiClientImpl(
}
}
override suspend fun updateMember(memberID: String, updateData: Map<String, Map<String, Boolean>>): Member? {
override suspend fun updateMember(
memberID: String,
updateData: Map<String, Map<String, Boolean>>
): Member? {
return process { apiService.updateUser(memberID, updateData) }
}
@ -303,24 +331,41 @@ class ApiClientImpl(
return this.hostConfig.userID.isNotEmpty() && hostConfig.apiKey.isNotEmpty()
}
private fun showConnectionProblemDialog(resourceMessageString: Int) {
showConnectionProblemDialog(null, context.getString(resourceMessageString))
private fun showConnectionProblemDialog(
resourceMessageString: Int,
isFromUserInput: Boolean
) {
showConnectionProblemDialog(null, context.getString(resourceMessageString), isFromUserInput)
}
private fun showConnectionProblemDialog(resourceTitleString: Int, resourceMessageString: Int) {
showConnectionProblemDialog(context.getString(resourceTitleString), context.getString(resourceMessageString))
private fun showConnectionProblemDialog(
resourceTitleString: Int,
resourceMessageString: Int,
isFromUserInput: Boolean
) {
showConnectionProblemDialog(
context.getString(resourceTitleString),
context.getString(resourceMessageString),
isFromUserInput
)
}
private var erroredRequestCount = 0
private fun showConnectionProblemDialog(
resourceTitleString: String?,
resourceMessageString: String
resourceMessageString: String,
isFromUserInput: Boolean
) {
erroredRequestCount += 1
val application = (context as? HabiticaBaseApplication)
?: (context.applicationContext as? HabiticaBaseApplication)
application?.currentActivity?.get()
?.showConnectionProblem(erroredRequestCount, resourceTitleString, resourceMessageString)
?.showConnectionProblem(
erroredRequestCount,
resourceTitleString,
resourceMessageString,
isFromUserInput
)
}
private fun hideConnectionProblemDialog() {
@ -378,7 +423,13 @@ class ApiClientImpl(
}
override suspend fun purchaseItem(type: String, itemKey: String, purchaseQuantity: Int): Void? {
return process { apiService.purchaseItem(type, itemKey, mapOf(Pair("quantity", purchaseQuantity))) }
return process {
apiService.purchaseItem(
type,
itemKey,
mapOf(Pair("quantity", purchaseQuantity))
)
}
}
val lastSubscribeCall: Date? = null
@ -506,7 +557,11 @@ class ApiClientImpl(
override suspend fun revive(): Items? = process { apiService.revive() }
override suspend fun useSkill(skillName: String, targetType: String, targetId: String): SkillResponse? {
override suspend fun useSkill(
skillName: String,
targetType: String,
targetId: String
): SkillResponse? {
return process { apiService.useSkill(skillName, targetType, targetId) }
}
@ -562,22 +617,33 @@ class ApiClientImpl(
return processResponse(apiService.leaveGroup(groupId, keepChallenges))
}
override suspend fun postGroupChat(groupId: String, message: Map<String, String>): PostChatMessageResult? {
override suspend fun postGroupChat(
groupId: String,
message: Map<String, String>
): PostChatMessageResult? {
return process { apiService.postGroupChat(groupId, message) }
}
override suspend fun deleteMessage(groupId: String, messageId: String): Void? {
return process { apiService.deleteMessage(groupId, messageId) }
}
override suspend fun deleteInboxMessage(id: String): Void? {
return process { apiService.deleteInboxMessage(id) }
}
override suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List<Member>? {
override suspend fun getGroupMembers(
groupId: String,
includeAllPublicFields: Boolean?
): List<Member>? {
return processResponse(apiService.getGroupMembers(groupId, includeAllPublicFields))
}
override suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): List<Member>? {
override suspend fun getGroupMembers(
groupId: String,
includeAllPublicFields: Boolean?,
lastId: String
): List<Member>? {
return processResponse(apiService.getGroupMembers(groupId, includeAllPublicFields, lastId))
}
@ -589,7 +655,11 @@ class ApiClientImpl(
return process { apiService.reportMember(mid, data) }
}
override suspend fun flagMessage(groupId: String, mid: String, data: MutableMap<String, String>): Void? {
override suspend fun flagMessage(
groupId: String,
mid: String,
data: MutableMap<String, String>
): Void? {
return process { apiService.flagMessage(groupId, mid, data) }
}
@ -601,7 +671,10 @@ class ApiClientImpl(
return process { apiService.seenMessages(groupId) }
}
override suspend fun inviteToGroup(groupId: String, inviteData: Map<String, Any>): List<InviteResponse>? {
override suspend fun inviteToGroup(
groupId: String,
inviteData: Map<String, Any>
): List<InviteResponse>? {
return process { apiService.inviteToGroup(groupId, inviteData) }
}
@ -609,7 +682,10 @@ class ApiClientImpl(
return process { apiService.rejectGroupInvite(groupId) }
}
override suspend fun getGroupInvites(groupId: String, includeAllPublicFields: Boolean?): List<Member>? {
override suspend fun getGroupInvites(
groupId: String,
includeAllPublicFields: Boolean?
): List<Member>? {
return process { apiService.getGroupInvites(groupId, includeAllPublicFields) }
}
@ -663,14 +739,21 @@ class ApiClientImpl(
return process { apiService.retrievePartySeekingUsers(page) }
}
override suspend fun getMember(memberId: String) = processResponse(apiService.getMember(memberId))
override suspend fun getMemberWithUsername(username: String) = processResponse(apiService.getMemberWithUsername(username))
override suspend fun getMember(memberId: String) =
processResponse(apiService.getMember(memberId))
override suspend fun getMemberWithUsername(username: String) =
processResponse(apiService.getMemberWithUsername(username))
override suspend fun getMemberAchievements(memberId: String): List<Achievement>? {
return process { apiService.getMemberAchievements(memberId, languageCode) }
}
override suspend fun findUsernames(username: String, context: String?, id: String?): List<FindUsernameResult>? {
override suspend fun findUsernames(
username: String,
context: String?,
id: String?
): List<FindUsernameResult>? {
return process { apiService.findUsernames(username, context, id) }
}
@ -829,7 +912,14 @@ class ApiClientImpl(
}
override suspend fun transferGems(giftedID: String, amount: Int): Void? {
return process { apiService.transferGems(mapOf(Pair("toUserId", giftedID), Pair("gemAmount", amount))) }
return process {
apiService.transferGems(
mapOf(
Pair("toUserId", giftedID),
Pair("gemAmount", amount)
)
)
}
}
override suspend fun getTeamPlans(): List<TeamPlan>? {

View file

@ -7,14 +7,14 @@ import android.provider.Settings
import android.util.Log
import androidx.core.content.edit
import androidx.core.os.bundleOf
import com.google.android.gms.ads.AdRequest
/*import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.MobileAds
import com.google.android.gms.ads.OnUserEarnedRewardListener
import com.google.android.gms.ads.RequestConfiguration
import com.google.android.gms.ads.rewarded.RewardItem
import com.google.android.gms.ads.rewarded.RewardedAd
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback*/
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.habitrpg.android.habitica.BuildConfig
@ -63,8 +63,8 @@ fun String.md5(): String? {
}
}
class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boolean) -> Unit) : OnUserEarnedRewardListener {
private var rewardedAd: RewardedAd? = null
class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boolean) -> Unit) {
//private var rewardedAd: RewardedAd? = null
companion object {
private enum class AdStatus {
@ -111,16 +111,16 @@ class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boo
val android_id: String =
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
val deviceId: String = android_id.md5()?.uppercase() ?: ""
val configuration = RequestConfiguration.Builder().setTestDeviceIds(listOf(deviceId)).build()
MobileAds.setRequestConfiguration(configuration)
//val configuration = RequestConfiguration.Builder().setTestDeviceIds(listOf(deviceId)).build()
//MobileAds.setRequestConfiguration(configuration)
}
currentAdStatus = AdStatus.INITIALIZING
MobileAds.initialize(context) {
/*MobileAds.initialize(context) {
currentAdStatus = AdStatus.READY
onComplete()
FirebaseCrashlytics.getInstance().recordException(Throwable("Ads Initialized"))
}
}*/
}
fun whenAdsInitialized(context: Context, onComplete: () -> Unit) {
@ -156,7 +156,7 @@ class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boo
fun prepare(onComplete: ((Boolean) -> Unit)? = null) {
whenAdsInitialized(activity) {
val adRequest = AdRequest.Builder()
/*val adRequest = AdRequest.Builder()
.build()
if (BuildConfig.DEBUG || BuildConfig.TESTING_LEVEL == "staff" || BuildConfig.TESTING_LEVEL == "alpha") {
@ -184,7 +184,7 @@ class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boo
onComplete?.invoke(true)
}
}
)
)*/
}
}
@ -209,22 +209,22 @@ class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boo
}
private fun configureReward() {
rewardedAd?.run { }
//rewardedAd?.run { }
}
private fun showRewardedAd() {
if (nextAdAllowedDate(type)?.after(Date()) == true) {
return
}
if (rewardedAd != null) {
/*if (rewardedAd != null) {
rewardedAd?.show(activity, this)
setNextAllowedDate(type)
} else {
Log.d(TAG, "The rewarded ad wasn't ready yet.")
}
}*/
}
override fun onUserEarnedReward(rewardItem: RewardItem) {
/*override fun onUserEarnedReward(rewardItem: RewardItem) {
Analytics.sendEvent(
"adRewardEarned",
EventCategory.BEHAVIOUR,
@ -240,5 +240,5 @@ class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boo
)
)
rewardAction(true)
}
}*/
}

View file

@ -14,7 +14,7 @@ import com.habitrpg.android.habitica.databinding.MountImageviewBinding
import com.habitrpg.android.habitica.models.inventory.Food
import com.habitrpg.android.habitica.models.inventory.Pet
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.BackgroundScene
import com.habitrpg.android.habitica.ui.views.SnackbarActivity
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog

View file

@ -13,7 +13,7 @@ import com.habitrpg.android.habitica.databinding.PetImageviewBinding
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.models.user.Items
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.BackgroundScene
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.extensions.layoutInflater

View file

@ -14,7 +14,7 @@ import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.databinding.MountImageviewBinding
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.BackgroundScene
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.extensions.dpToPx

View file

@ -26,7 +26,7 @@ import com.habitrpg.android.habitica.databinding.PetImageviewBinding
import com.habitrpg.android.habitica.models.inventory.Food
import com.habitrpg.android.habitica.models.inventory.Pet
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.BackgroundScene
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.extensions.dpToPx

View file

@ -9,7 +9,6 @@ import android.widget.FrameLayout
import android.widget.RelativeLayout
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
@ -24,16 +23,14 @@ import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.helpers.ReviewManager
import com.habitrpg.android.habitica.ui.fragments.purchases.EventOutcomeSubscriptionBottomSheetFragment
import com.habitrpg.android.habitica.ui.fragments.purchases.EventOutcomeSubscriptionBottomSheetFragment.Companion.EVENT_ARMOIRE_OPENED
import com.habitrpg.android.habitica.ui.fragments.purchases.SubscriptionBottomSheetFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.ads.AdButton
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
import com.habitrpg.android.habitica.ui.views.progress.HabiticaCircularProgressView
import com.habitrpg.common.habitica.views.HabiticaCircularProgressView
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.Animations
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.common.habitica.helpers.launchCatching
import com.plattysoft.leonids.ParticleSystem
import dagger.hilt.android.AndroidEntryPoint

View file

@ -223,7 +223,7 @@ abstract class BaseActivity : AppCompatActivity() {
}
}
open fun showConnectionProblem(errorCount: Int, title: String?, message: String) {
open fun showConnectionProblem(errorCount: Int, title: String?, message: String, isFromUserInput: Boolean) {
val alert = HabiticaAlertDialog(this)
alert.setTitle(title)
alert.setMessage(message)

View file

@ -65,7 +65,7 @@ import com.habitrpg.android.habitica.helpers.EventCategory
import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.CurrencyText
import com.habitrpg.android.habitica.ui.views.HabiticaButton

View file

@ -1,6 +1,5 @@
package com.habitrpg.android.habitica.ui.activities
import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle

View file

@ -6,6 +6,25 @@ import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.edit
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.HabiticaApplication
@ -32,6 +51,8 @@ import com.habitrpg.common.habitica.extensions.fromHtml
import com.habitrpg.common.habitica.helpers.Animations
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.common.habitica.views.HabiticaCircularProgressView
import com.plattysoft.leonids.ParticleSystem
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.MainScope
@ -138,30 +159,59 @@ class DeathActivity : BaseActivity(), SnackbarActivity {
binding.subscriberBenefitUsedView.visibility = View.GONE
}
binding.reviveSubscriberButton.setOnClickListener {
Analytics.sendEvent("second chance perk", EventCategory.BEHAVIOUR, HitType.EVENT)
sharedPreferences.edit {
putLong("last_sub_revive", Date().time)
}
lifecycleScope.launchCatching {
delay(300)
binding.reviveSubscriberWrapper.startAnimation(Animations.fadeOutAnimation())
}
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.updateUser("stats.hp", 1)
MainScope().launchCatching {
delay(1000)
(HabiticaBaseApplication.getInstance(this@DeathActivity)?.currentActivity?.get() as? SnackbarActivity)?.let {activity ->
HabiticaSnackbar.showSnackbar(
activity.snackbarContainer(), getString(R.string.subscriber_benefit_success_faint), HabiticaSnackbar.SnackbarDisplayType.SUBSCRIBER_BENEFIT, isSubscriberBenefit = true, duration = 2500)
binding.reviveSubscriberButton.setContent {
var isUsingBenefit by remember { mutableStateOf(false) }
HabiticaTheme {
if (isUsingBenefit) {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxWidth().height(60.dp)) {
CircularProgressIndicator()
}
} else {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
.clickable {
isUsingBenefit = true
Analytics.sendEvent(
"second chance perk",
EventCategory.BEHAVIOUR,
HitType.EVENT
)
sharedPreferences.edit {
putLong("last_sub_revive", Date().time)
}
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.updateUser("stats.hp", 1)
MainScope().launchCatching {
delay(1000)
(HabiticaBaseApplication.getInstance(this@DeathActivity)?.currentActivity?.get() as? SnackbarActivity)?.let { activity ->
HabiticaSnackbar.showSnackbar(
activity.snackbarContainer(),
getString(R.string.subscriber_benefit_success_faint),
HabiticaSnackbar.SnackbarDisplayType.SUBSCRIBER_BENEFIT,
isSubscriberBenefit = true,
duration = 2500
)
}
}
finish()
}
}
) {
Text(stringResource(R.string.subscriber_button_faint),
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center)
}
}
finish()
}
}
binding.restartButton.setOnClickListener {
binding.restartButton.isEnabled = false
binding.restartButton.startAnimation(Animations.fadeOutAnimation())
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val brokenItem = userRepository.revive()
if (brokenItem != null) {

View file

@ -39,7 +39,7 @@ import com.habitrpg.android.habitica.models.user.Permission
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.ui.adapter.social.AchievementProfileAdapter
import com.habitrpg.android.habitica.ui.fragments.ReportBottomSheetFragment
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.AppHeaderView
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType

View file

@ -4,7 +4,6 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
@ -18,7 +17,6 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
@ -30,14 +28,12 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.view.children
import androidx.core.view.setPadding
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.navigation.NavDestination
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
@ -68,17 +64,17 @@ import com.habitrpg.android.habitica.interactors.CheckClassSelectionUseCase
import com.habitrpg.android.habitica.interactors.DisplayItemDropUseCase
import com.habitrpg.android.habitica.interactors.NotifyUserUseCase
import com.habitrpg.android.habitica.interactors.ShareAvatarUseCase
import com.habitrpg.android.habitica.interactors.SharePetUseCase
import com.habitrpg.android.habitica.models.TutorialStep
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.models.user.UserQuestStatus
import com.habitrpg.android.habitica.ui.TutorialView
import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.android.habitica.ui.views.AppHeaderView
import com.habitrpg.android.habitica.ui.views.ComposableAvatarView
import com.habitrpg.common.habitica.views.ComposableAvatarView
import com.habitrpg.android.habitica.ui.views.GroupPlanMemberList
import com.habitrpg.android.habitica.ui.views.HabiticaButton
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
@ -767,11 +763,11 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
private var errorJob: Job? = null
override fun showConnectionProblem(errorCount: Int, title: String?, message: String) {
if (errorCount == 1) {
override fun showConnectionProblem(errorCount: Int, title: String?, message: String, isFromUserInput: Boolean) {
if (errorCount == 1 && !isFromUserInput) {
showSnackbar(title = title, content = message, displayType = HabiticaSnackbar.SnackbarDisplayType.FAILURE)
} else if (title != null) {
super.showConnectionProblem(errorCount, title, message)
super.showConnectionProblem(errorCount, title, message, isFromUserInput)
} else {
if (errorJob?.isCancelled == false) {
// a new error resets the timer to hide the error message

View file

@ -13,7 +13,10 @@ import android.widget.LinearLayout
import android.widget.RatingBar
import android.widget.TextView
import androidx.activity.viewModels
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
@ -40,10 +43,13 @@ import com.habitrpg.common.habitica.models.notifications.NewStuffData
import com.habitrpg.common.habitica.models.notifications.PartyInvitationData
import com.habitrpg.common.habitica.models.notifications.QuestInvitationData
import com.habitrpg.common.habitica.models.notifications.UnallocatedPointsData
import com.habitrpg.common.habitica.views.HabiticaCircularProgressView
import com.habitrpg.common.habitica.views.PixelArtView
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -74,6 +80,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
private var notifications: List<Notification> = emptyList()
@OptIn(FlowPreview::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -87,17 +94,36 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater
binding.progressView.setContent {
HabiticaCircularProgressView(indicatorSize = 60.dp)
}
lifecycleScope.launchCatching {
viewModel.getNotifications().collect {
viewModel.getNotifications()
.debounce(250)
.collect {
setNotifications(it)
viewModel.markNotificationsAsSeen(it)
}
}
lifecycleScope.launchCatching {
viewModel.getNotificationCount()
.collect {
binding.notificationsTitleBadge.text = it.toString()
}
}
binding.notificationsRefreshLayout.setOnRefreshListener(this)
lifecycleScope.launchCatching {
viewModel.refreshNotifications()
}
binding.dismissAllButton.setOnClickListener {
HapticFeedbackManager.tap(it)
viewModel.dismissAllNotifications(notifications)
setNotifications(emptyList())
}
}
override fun onSupportNavigateUp(): Boolean {
@ -131,15 +157,18 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
binding.notificationItems.removeAllViewsInLayout()
binding.notificationItems.showDividers = LinearLayout.SHOW_DIVIDER_NONE
binding.notificationItems.addView(inflater?.inflate(R.layout.no_notifications, binding.notificationItems, false))
binding.progressView.isVisible = false
}
private fun displayNotificationsListView(notifications: List<Notification>) {
binding.notificationItems.showDividers = LinearLayout.SHOW_DIVIDER_MIDDLE or LinearLayout.SHOW_DIVIDER_END
val viewList = arrayListOf<View>()
createNotificationsHeaderView(notifications.count())?.let { viewList.add(it) }
lifecycleScope.launch(ExceptionHandler.coroutine()) {
notifications.map {
val currentViews = mutableSetOf<View>().apply {
addAll(binding.notificationItems.children)
}
notifications.forEach {
val item: View? = when (it.type) {
Notification.Type.NEW_CHAT_MESSAGE.type -> createNewChatMessageNotification(it)
Notification.Type.NEW_STUFF.type -> createNewStuffNotification(it)
@ -154,30 +183,22 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
else -> null
}
if (item != null) {
viewList.add(item)
item?.let { view ->
if (!currentViews.removeIf { it.tag == view.tag }) {
binding.notificationItems.addView(view)
}
}
}
updateNotificationsAndRefresh(viewList)
}
}
private fun updateNotificationsAndRefresh(newItems: List<View>) {
val currentViews = (0 until binding.notificationItems.childCount).map {
binding.notificationItems.getChildAt(it)
}
val viewsToRemove = currentViews - newItems
viewsToRemove.forEach { binding.notificationItems.removeView(it) }
val viewsToAdd = newItems - currentViews
viewsToAdd.forEach {
binding.notificationItems.addView(it)
}
// Remove views that are no longer valid
currentViews.forEach { binding.notificationItems.removeView(it) }
lifecycleScope.launch {
delay(250)
// Unnecessary but looks clean c:
if (binding.notificationItems.visibility != View.VISIBLE) {
binding.notificationItems.fadeInAnimation(200)
lifecycleScope.launch {
binding.progressView.isVisible = false
delay(250)
if (binding.notificationItems.visibility != View.VISIBLE) {
binding.notificationItems.fadeInAnimation(200)
}
}
}
}
@ -194,22 +215,6 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
}
}
private fun createNotificationsHeaderView(notificationCount: Int): View? {
val header = inflater?.inflate(R.layout.notifications_header, binding.notificationItems, false)
val badge = header?.findViewById(R.id.notifications_title_badge) as? TextView
badge?.text = notificationCount.toString()
val dismissAllButton = header?.findViewById(R.id.dismiss_all_button) as? Button
dismissAllButton?.setOnClickListener {
binding.root.flash()
HapticFeedbackManager.tap(it)
viewModel.dismissAllNotifications(notifications)
}
return header
}
private fun createNewChatMessageNotification(notification: Notification): View? {
val data = notification.data as? NewChatMessageData
val stringId = if (viewModel.isPartyMessage(data)) R.string.new_msg_party else R.string.new_msg_guild
@ -229,13 +234,16 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
)
}
private var baileyNewsNotification: Notification? = null
private suspend fun createNewStuffNotification(notification: Notification): View? = withContext(Dispatchers.IO) {
var baileyNotification = notification
val data = notification.data as? NewStuffData
val text = if (data?.title != null) {
fromHtml("<b>" + getString(R.string.new_bailey_update) + "</b><br>" + data.title)
} else {
baileyNotification = userRepository.getNewsNotification() ?: notification
baileyNotification = baileyNewsNotification ?: userRepository.getNewsNotification() ?: notification
baileyNewsNotification = baileyNotification
val baileyNewsData = baileyNotification.data as? NewStuffData
fromHtml("<b>" + getString(R.string.new_bailey_update) + "</b><br>" + baileyNewsData?.title)
}
@ -332,6 +340,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
textColor: Int? = null
): View? {
val item = inflater?.inflate(R.layout.notification_item, binding.notificationItems, false)
item?.tag = notification.id
val container = item?.findViewById(R.id.notification_item) as? View
container?.setOnClickListener {
@ -345,7 +354,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
val dismissButton = item?.findViewById(R.id.dismiss_button) as? ImageView
dismissButton?.setOnClickListener {
container?.flash()
it.flash()
HapticFeedbackManager.tap(it)
removeNotificationAndRefresh(notification)
viewModel.dismissNotification(notification)
@ -444,6 +453,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
inviterId: String? = null
): View? {
val item = inflater?.inflate(R.layout.notification_item_actionable, binding.notificationItems, false)
item?.tag = notification.id
if (openable) {
val container = item?.findViewById(R.id.notification_item) as? View
@ -463,7 +473,6 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
val acceptButton = item?.findViewById(R.id.accept_button) as? Button
acceptButton?.setOnClickListener {
binding.root.flash()
HapticFeedbackManager.tap(it)
removeNotificationAndRefresh(notification)
viewModel.accept(notification.id)
@ -471,7 +480,6 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
val rejectButton = item?.findViewById(R.id.reject_button) as? Button
rejectButton?.setOnClickListener {
binding.root.flash()
HapticFeedbackManager.tap(it)
removeNotificationAndRefresh(notification)
viewModel.reject(notification.id)

View file

@ -50,7 +50,10 @@ import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskGroupPlan
import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.android.habitica.ui.theme.textPrimaryFor
import com.habitrpg.android.habitica.ui.theme.windowBackgroundFor
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.viewmodels.TaskFormViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog

View file

@ -50,7 +50,13 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.TaskDescriptionBuilder
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.android.habitica.ui.theme.contentBackgroundFor
import com.habitrpg.android.habitica.ui.theme.primaryBackgroundFor
import com.habitrpg.android.habitica.ui.theme.textPrimaryFor
import com.habitrpg.android.habitica.ui.theme.textSecondaryFor
import com.habitrpg.android.habitica.ui.theme.windowBackgroundFor
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.CompletedAt

View file

@ -614,8 +614,8 @@ class NavigationDrawerFragment : DialogFragment() {
// set UP the drawer's list view with items and click listener
lifecycleScope.launchCatching {
viewModel.getNotifications().collect {
setNotificationsCount(it.count())
viewModel.getNotificationCount().collect {
setNotificationsCount(it)
}
}
lifecycleScope.launchCatching {

View file

@ -35,7 +35,8 @@ import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.SegmentedControl
import com.habitrpg.android.habitica.ui.views.equipment.AvatarCustomizationOverviewView

View file

@ -33,7 +33,6 @@ import com.habitrpg.android.habitica.ui.views.CurrencyText
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientHourglassesDialog
import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.RecyclerViewState
@ -256,7 +255,7 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
alert.setMessage(getString(R.string.change_class_equipment_warning))
alert.addButton(R.string.choose_class, true) { _, _ ->
val dialog = HabiticaProgressDialog.show(
context,
requireActivity(),
getString(R.string.changing_class_progress),
300
)
@ -274,7 +273,7 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
alert.setTitle(getString(R.string.class_confirmation, classIdentifier))
alert.addButton(R.string.choose_class, true) { _, _ ->
val dialog = HabiticaProgressDialog.show(
context,
requireActivity(),
getString(R.string.changing_class_progress),
300
)

View file

@ -4,9 +4,15 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.ui.fragments.purchases.EventOutcomeSubscriptionBottomSheetFragment
import com.habitrpg.android.habitica.ui.fragments.purchases.SubscriptionBottomSheetFragment
import com.habitrpg.android.habitica.ui.views.CurrencyText
import com.habitrpg.common.habitica.helpers.launchCatching
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds
@AndroidEntryPoint
class TimeTravelersShopFragment : ShopFragment() {
@ -22,6 +28,17 @@ class TimeTravelersShopFragment : ShopFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initializeCurrencyViews()
lifecycleScope.launchCatching {
val user = userViewModel.user.value
if (user?.isSubscribed != true) {
delay(2.seconds)
val subscriptionBottomSheet = EventOutcomeSubscriptionBottomSheetFragment().apply {
eventType = EventOutcomeSubscriptionBottomSheetFragment.EVENT_HOURGLASS_SHOP_OPENED
}
activity?.supportFragmentManager?.let { subscriptionBottomSheet.show(it, SubscriptionBottomSheetFragment.TAG) }
}
}
}
override fun initializeCurrencyViews() {

View file

@ -488,7 +488,7 @@ class AccountPreferenceFragment :
}
private fun deleteAccount(password: String) {
val dialog = context?.let { HabiticaProgressDialog.show(it, R.string.deleting_account) }
val dialog = activity?.let { HabiticaProgressDialog.show(it, R.string.deleting_account) }
lifecycleScope.launchCatching({ throwable ->
dialog?.dismiss()
if (throwable is HttpException && throwable.code() == 401) {
@ -537,7 +537,7 @@ class AccountPreferenceFragment :
}
private fun resetAccount() {
val dialog = context?.let { HabiticaProgressDialog.show(it, R.string.resetting_account) }
val dialog = activity?.let { HabiticaProgressDialog.show(it, R.string.resetting_account) }
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.resetAccount()
dialog?.dismiss()

View file

@ -2,6 +2,7 @@ package com.habitrpg.android.habitica.ui.fragments.purchases
import android.os.Bundle
import android.view.View
import androidx.core.view.isVisible
import com.habitrpg.android.habitica.R
class EventOutcomeSubscriptionBottomSheetFragment : SubscriptionBottomSheetFragment() {
@ -42,6 +43,7 @@ class EventOutcomeSubscriptionBottomSheetFragment : SubscriptionBottomSheetFragm
binding.subscribeBenefits.text = getString(R.string.subscribe_hourglass_incentive_text)
binding.subscriberBenefits.hideMysticHourglassBenefit()
binding.subscription1month.visibility = View.GONE
skus.firstOrNull { buttonForSku(it)?.isVisible == true }?.let { selectSubscription(it) }
}
companion object {

View file

@ -28,7 +28,7 @@ import com.habitrpg.android.habitica.ui.activities.GiftGemsActivity
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.promo.BirthdayBanner
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources

View file

@ -52,7 +52,7 @@ open class SubscriptionBottomSheetFragment : BottomSheetDialogFragment() {
lateinit var purchaseHandler: PurchaseHandler
private var selectedSubscriptionSku: ProductDetails? = null
private var skus: List<ProductDetails> = emptyList()
internal var skus: List<ProductDetails> = emptyList()
private var user: User? = null
private var hasLoadedSubscriptionOptions: Boolean = false
@ -112,7 +112,11 @@ open class SubscriptionBottomSheetFragment : BottomSheetDialogFragment() {
for (sku in subscriptions) {
updateButtonLabel(sku, sku.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.formattedPrice ?: "")
}
subscriptions.minByOrNull { it.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.priceAmountMicros ?: 0 }?.let { selectSubscription(it) }
subscriptions
.filter { buttonForSku(it)?.isVisible == true }
.minByOrNull {
it.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.priceAmountMicros ?: 0
}?.let { selectSubscription(it) }
hasLoadedSubscriptionOptions = true
updateSubscriptionInfo()
}
@ -130,7 +134,7 @@ open class SubscriptionBottomSheetFragment : BottomSheetDialogFragment() {
}
}
private fun selectSubscription(sku: ProductDetails) {
internal fun selectSubscription(sku: ProductDetails) {
if (this.selectedSubscriptionSku != null) {
val oldButton = buttonForSku(this.selectedSubscriptionSku)
oldButton?.setIsSelected(false)
@ -141,7 +145,7 @@ open class SubscriptionBottomSheetFragment : BottomSheetDialogFragment() {
binding.subscribeButton.isEnabled = true
}
private fun buttonForSku(sku: ProductDetails?): SubscriptionOptionView? {
internal fun buttonForSku(sku: ProductDetails?): SubscriptionOptionView? {
return buttonForSku(sku?.productId)
}

View file

@ -28,7 +28,7 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.GiftSubscriptionActivity
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.promo.BirthdayBanner
import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionView

View file

@ -33,7 +33,7 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.fragments.inventory.items.ItemDialogFragment
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewHolders.GroupMemberViewHolder
import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar

View file

@ -55,7 +55,8 @@ import com.habitrpg.android.habitica.databinding.FragmentComposeBinding
import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.invitations.InviteResponse
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.LoadingButtonState

View file

@ -60,13 +60,14 @@ import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.models.invitations.InviteResponse
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.LoadingButton
import com.habitrpg.android.habitica.ui.views.LoadingButtonState
import com.habitrpg.android.habitica.ui.views.LoadingButtonType
import com.habitrpg.android.habitica.ui.views.progress.HabiticaCircularProgressView
import com.habitrpg.common.habitica.views.HabiticaCircularProgressView
import com.habitrpg.android.habitica.ui.views.progress.HabiticaPullRefreshIndicator
import com.habitrpg.android.habitica.ui.views.social.PartySeekingListItem
import com.habitrpg.common.habitica.helpers.launchCatching

View file

@ -1,222 +1,83 @@
package com.habitrpg.android.habitica.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Shapes
import androidx.compose.material.Typography
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import com.google.accompanist.themeadapter.material.createMdcTheme
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.theme.HabiticaColors
import com.habitrpg.common.habitica.theme.HabiticaTheme
@Composable
fun HabiticaTheme(
content: @Composable () -> Unit
) {
val context = LocalContext.current
val layoutDirection = LocalLayoutDirection.current
val (colors, _, _) = createMdcTheme(
context = context,
layoutDirection = layoutDirection,
setTextColors = true
)
MaterialTheme(
colors = colors ?: MaterialTheme.colors,
typography = Typography(
defaultFontFamily = FontFamily.Default,
h1 = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 20.sp,
letterSpacing = (0.05).sp
),
h2 = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 28.sp,
letterSpacing = (0.05).sp
),
subtitle1 = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 16.sp
),
subtitle2 = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
letterSpacing = 0.1.sp
),
body1 = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
letterSpacing = 0.35.sp,
lineHeight = 16.sp
),
body2 = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 14.sp,
letterSpacing = 0.2.sp,
lineHeight = 16.sp
),
button = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
letterSpacing = 1.25.sp
),
caption = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 12.sp
),
overline = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 10.sp,
letterSpacing = 1.5.sp
)
),
shapes = Shapes(
RoundedCornerShape(4.dp),
RoundedCornerShape(8.dp),
RoundedCornerShape(12.dp)
),
content = content
)
fun HabiticaColors.textPrimaryFor(task: Task?): Color {
return colorResource((if (isSystemInDarkTheme()) task?.extraExtraLightTaskColor else task?.extraDarkTaskColor) ?: R.color.text_primary)
}
val Typography.caption1
get() = caption
val Typography.caption2
get() = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
letterSpacing = 0.4.sp
)
val Typography.caption3
get() = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
letterSpacing = 0.3.sp,
lineHeight = 14.sp
)
val Typography.caption4
get() = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
letterSpacing = 0.35.sp
)
val Typography.subtitle3
get() = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
letterSpacing = 0.15.sp
)
object HabiticaTheme {
val typography: Typography
@Composable
get() = MaterialTheme.typography
val shapes: Shapes
@Composable
get() = MaterialTheme.shapes
val colors: HabiticaColors
@Composable
get() {
val context = LocalContext.current
return HabiticaColors(
windowBackground = Color(context.getThemeColor(R.attr.colorWindowBackground)),
contentBackground = Color(context.getThemeColor(R.attr.colorContentBackground)),
contentBackgroundOffset = Color(context.getThemeColor(R.attr.colorContentBackgroundOffset)),
offsetBackground = Color(context.getThemeColor(R.attr.colorOffsetBackground)),
textPrimary = Color(context.getThemeColor(R.attr.textColorPrimary)),
textSecondary = Color(context.getThemeColor(R.attr.textColorSecondary)),
textTertiary = Color(ContextCompat.getColor(context, R.color.text_ternary)),
textQuad = Color(ContextCompat.getColor(context, R.color.text_quad)),
textDimmed = Color(ContextCompat.getColor(context, R.color.text_dimmed)),
tintedUiMain = Color(context.getThemeColor(R.attr.tintedUiMain)),
tintedUiSub = Color(context.getThemeColor(R.attr.tintedUiSub)),
tintedUiDetails = Color(context.getThemeColor(R.attr.tintedUiDetails)),
pixelArtBackground = Color(context.getThemeColor(R.attr.colorContentBackground)),
errorBackground = Color(ContextCompat.getColor(context, R.color.background_red)),
errorColor = Color(ContextCompat.getColor(context, R.color.text_red)),
successBackground = Color(ContextCompat.getColor(context, R.color.background_green)),
successColor = Color(ContextCompat.getColor(context, R.color.text_green))
)
}
@Composable
fun HabiticaColors.textSecondaryFor(task: Task?): Color {
return colorResource((if (isSystemInDarkTheme()) task?.extraLightTaskColor else task?.lowSaturationTaskColor) ?: R.color.brand_sub_text)
}
class HabiticaColors(
val windowBackground: Color,
val contentBackground: Color,
val contentBackgroundOffset: Color,
val offsetBackground: Color,
val textPrimary: Color,
val textSecondary: Color,
val textTertiary: Color,
val textQuad: Color,
val textDimmed: Color,
val tintedUiMain: Color,
val tintedUiSub: Color,
val tintedUiDetails: Color,
val pixelArtBackground: Color,
val errorBackground: Color,
val errorColor: Color,
val successBackground: Color,
val successColor: Color
) {
@Composable
fun HabiticaColors.primaryBackgroundFor(task: Task?): Color {
return colorResource((if (isSystemInDarkTheme()) task?.mediumTaskColor else task?.lightTaskColor) ?: R.color.brand_400)
}
@Composable
fun textPrimaryFor(task: Task?): Color {
return colorResource((if (isSystemInDarkTheme()) task?.extraExtraLightTaskColor else task?.extraDarkTaskColor) ?: R.color.text_primary)
}
@Composable
fun HabiticaColors.windowBackgroundFor(task: Task?): Color {
return (if (isSystemInDarkTheme()) task?.extraExtraDarkTaskColor else task?.extraExtraLightTaskColor)?.let { colorResource(it) } ?: windowBackground
}
@Composable
fun textSecondaryFor(task: Task?): Color {
return colorResource((if (isSystemInDarkTheme()) task?.extraLightTaskColor else task?.lowSaturationTaskColor) ?: R.color.brand_sub_text)
}
@Composable
fun HabiticaColors.contentBackgroundFor(task: Task?): Color {
return (if (isSystemInDarkTheme()) task?.darkestTaskColor else task?.lightestTaskColor)?.let { colorResource(it) } ?: windowBackground
}
@Composable
fun primaryBackgroundFor(task: Task?): Color {
return colorResource((if (isSystemInDarkTheme()) task?.mediumTaskColor else task?.lightTaskColor) ?: R.color.brand_400)
}
@Composable
fun windowBackgroundFor(task: Task?): Color {
return (if (isSystemInDarkTheme()) task?.extraExtraDarkTaskColor else task?.extraExtraLightTaskColor)?.let { colorResource(it) } ?: windowBackground
}
@Composable
fun contentBackgroundFor(task: Task?): Color {
return (if (isSystemInDarkTheme()) task?.darkestTaskColor else task?.lightestTaskColor)?.let { colorResource(it) } ?: windowBackground
}
@Composable
fun pixelArtBackground(hasIcon: Boolean): Color {
return if (isSystemInDarkTheme()) {
colorResource(if (hasIcon) R.color.gray_200 else R.color.gray_5)
} else {
colorResource(if (hasIcon) R.color.content_background else R.color.content_background_offset)
}
}
@Composable
fun basicTextColor(): Color {
return colorResource(R.color.gray200_gray400)
}
@Composable
fun basicButtonColor(): Color {
return colorResource(R.color.gray700_gray10)
@Composable
fun HabiticaColors.pixelArtBackground(hasIcon: Boolean): Color {
return if (isSystemInDarkTheme()) {
colorResource(if (hasIcon) R.color.gray_200 else R.color.gray_5)
} else {
colorResource(if (hasIcon) R.color.content_background else R.color.content_background_offset)
}
}
class HabiticaTypography
@Composable
fun HabiticaColors.basicTextColor(): Color {
return colorResource(R.color.gray200_gray400)
}
@Composable
fun HabiticaColors.basicButtonColor(): Color {
return colorResource(R.color.gray700_gray10)
}
val HabiticaTheme.colors: HabiticaColors
@Composable
get() {
val context = LocalContext.current
return HabiticaColors(
windowBackground = Color(context.getThemeColor(R.attr.colorWindowBackground)),
contentBackground = Color(context.getThemeColor(R.attr.colorContentBackground)),
contentBackgroundOffset = Color(context.getThemeColor(R.attr.colorContentBackgroundOffset)),
offsetBackground = Color(context.getThemeColor(R.attr.colorOffsetBackground)),
textPrimary = Color(context.getThemeColor(R.attr.textColorPrimary)),
textSecondary = Color(context.getThemeColor(R.attr.textColorSecondary)),
textTertiary = Color(ContextCompat.getColor(context, R.color.text_ternary)),
textQuad = Color(ContextCompat.getColor(context, R.color.text_quad)),
textDimmed = Color(ContextCompat.getColor(context, R.color.text_dimmed)),
tintedUiMain = Color(context.getThemeColor(R.attr.tintedUiMain)),
tintedUiSub = Color(context.getThemeColor(R.attr.tintedUiSub)),
tintedUiDetails = Color(context.getThemeColor(R.attr.tintedUiDetails)),
pixelArtBackground = Color(context.getThemeColor(R.attr.colorContentBackground)),
errorBackground = Color(ContextCompat.getColor(context, R.color.background_red)),
errorColor = Color(ContextCompat.getColor(context, R.color.text_red)),
successBackground = Color(ContextCompat.getColor(context, R.color.background_green)),
successColor = Color(ContextCompat.getColor(context, R.color.text_green))
)
}

View file

@ -56,6 +56,7 @@ open class NotificationsViewModel @Inject constructor(
)
private var party: UserParty? = null
private var hasStats = false
private val customNotifications = MutableStateFlow<List<Notification>>(emptyList())
@ -63,6 +64,7 @@ open class NotificationsViewModel @Inject constructor(
userViewModel.user.observeForever {
if (it == null) return@observeForever
party = it.party
hasStats = it.hasClass
val notifications = convertInvitationsToNotifications(it)
if (it.flags?.newStuff == true) {
val notification = Notification()
@ -89,7 +91,11 @@ open class NotificationsViewModel @Inject constructor(
}
fun getNotificationCount(): Flow<Int> {
return getNotifications().map { it.count() }.distinctUntilChanged()
return getNotifications().map {
it.count { notification ->
(notification.type != Notification.Type.UNALLOCATED_STATS_POINTS.type) || hasStats
}
}.distinctUntilChanged()
}
fun allNotificationsSeen(): Flow<Boolean> {

View file

@ -62,7 +62,11 @@ import com.habitrpg.android.habitica.models.user.Purchases
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.SubscriptionPlan
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.basicButtonColor
import com.habitrpg.android.habitica.ui.theme.basicTextColor
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.common.habitica.views.ComposableAvatarView
import com.habitrpg.shared.habitica.models.Avatar
import kotlin.random.Random

View file

@ -32,7 +32,8 @@ import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

View file

@ -33,7 +33,9 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.user.Authentication
import com.habitrpg.android.habitica.models.user.Profile
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.common.habitica.views.ComposableAvatarView
import kotlin.random.Random
@Composable

View file

@ -16,7 +16,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.common.habitica.theme.HabiticaTheme
@Composable
fun HabiticaButton(

View file

@ -42,7 +42,8 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.common.habitica.helpers.NumberAbbreviator
import java.text.NumberFormat

View file

@ -51,7 +51,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

View file

@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.common.habitica.views.ComposableAvatarView
import com.habitrpg.shared.habitica.models.Avatar
@Composable

View file

@ -1,19 +1,40 @@
package com.habitrpg.android.habitica.ui.views.dialogs
import android.content.Context
import com.habitrpg.android.habitica.R
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.size
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.common.habitica.views.HabiticaCircularProgressView
import com.habitrpg.common.habitica.extensions.dpToPx
class HabiticaProgressDialog(context: Context) : HabiticaAlertDialog(context) {
companion object {
fun show(context: Context, titleID: Int): HabiticaProgressDialog {
fun show(context: FragmentActivity, titleID: Int): HabiticaProgressDialog {
return show(context, context.getString(titleID))
}
fun show(context: Context, title: String?, dialogWidth: Int = 300): HabiticaProgressDialog {
fun show(context: FragmentActivity, title: String?, dialogWidth: Int = 300): HabiticaProgressDialog {
val dialog = HabiticaProgressDialog(context)
dialog.setAdditionalContentView(R.layout.circular_progress)
val composeView = ComposeView(context)
dialog.setAdditionalContentView(composeView)
composeView.setContent {
HabiticaTheme {
HabiticaCircularProgressView(Modifier.size(60.dp))
}
}
dialog.window?.let {
dialog.additionalContentView?.setViewTreeSavedStateRegistryOwner(context)
it.decorView.setViewTreeSavedStateRegistryOwner(context)
dialog.additionalContentView?.setViewTreeLifecycleOwner(context)
it.decorView.setViewTreeLifecycleOwner(context)
}
dialog.dialogWidth = dialogWidth.dpToPx(context)
dialog.setTitle(title)
dialog.enqueue()

View file

@ -26,8 +26,10 @@ import androidx.compose.ui.unit.dp
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.models.user.Outfit
import com.habitrpg.android.habitica.models.user.Preferences
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.caption2
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.android.habitica.ui.theme.pixelArtBackground
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.common.habitica.theme.caption2
import com.habitrpg.android.habitica.ui.views.PixelArtView
@Composable

View file

@ -14,7 +14,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.HabiticaButton
@Composable

View file

@ -23,7 +23,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.common.habitica.views.HabiticaCircularProgressView
import java.lang.Float.min
@OptIn(ExperimentalMaterialApi::class)

View file

@ -380,10 +380,7 @@ class PurchaseDialog(
}
parentActivity?.let { activity -> subscriptionBottomSheet.show(activity.supportFragmentManager, SubscriptionBottomSheetFragment.TAG) }
}
}
}
return
}

View file

@ -35,9 +35,10 @@ import com.habitrpg.android.habitica.models.user.ContributorInfo
import com.habitrpg.android.habitica.models.user.Profile
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.ui.fragments.social.party.InviteButton
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.ClassText
import com.habitrpg.android.habitica.ui.views.ComposableAvatarView
import com.habitrpg.common.habitica.views.ComposableAvatarView
import com.habitrpg.android.habitica.ui.views.ComposableUsernameLabel
import com.habitrpg.android.habitica.ui.views.LoadingButtonState
import com.habitrpg.common.habitica.extensions.toLocale

View file

@ -49,7 +49,8 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.interactors.ShareMountUseCase
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.BackgroundScene
import com.habitrpg.android.habitica.ui.views.HabiticaButton
import com.habitrpg.common.habitica.helpers.launchCatching

View file

@ -69,7 +69,8 @@ import com.habitrpg.android.habitica.interactors.ShareMountUseCase
import com.habitrpg.android.habitica.interactors.SharePetUseCase
import com.habitrpg.android.habitica.models.inventory.Food
import com.habitrpg.android.habitica.models.inventory.Pet
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.BackgroundScene
import com.habitrpg.android.habitica.ui.views.HabiticaButton
import com.habitrpg.android.habitica.ui.views.PixelArtView

View file

@ -27,7 +27,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.models.Assignable
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.CompletedAt
import com.habitrpg.android.habitica.ui.views.UserRow
import java.util.Date

View file

@ -39,7 +39,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.common.habitica.extensions.getThemeColor
@Composable

View file

@ -41,7 +41,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.extensions.nameRes

View file

@ -33,7 +33,8 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.theme.colors
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.common.habitica.extensions.getThemeColor
data class LabeledValue<V>(val label: String, val value: V)

View file

@ -13,7 +13,7 @@ buildscript {
amplitude_version = '1.6.1'
appcompat_version = '1.6.1'
coil_version = '2.4.0'
compose_version = '1.5.2'
compose_version = '1.5.4'
core_ktx_version = '1.12.0'
coroutines_version = '1.7.2'
daggerhilt_version = '2.47'
@ -25,7 +25,7 @@ buildscript {
markwon_version = '4.6.2'
mockk_version = '1.13.4'
moshi_version = '1.15.0'
navigation_version = '2.7.3'
navigation_version = '2.7.4'
okhttp_version = '4.11.0'
paging_version = '3.2.1'
play_wearables_version = '18.1.0'

View file

@ -37,6 +37,11 @@ android {
buildFeatures {
viewBinding = true
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.3"
}
compileOptions {
@ -84,7 +89,9 @@ android {
}
val core_ktx_version: String by rootExtra
val accompanist_version: String by rootExtra
val appcompat_version: String by rootExtra
val compose_version: String by rootExtra
val markwon_version: String by rootExtra
val coil_version: String by rootExtra
val mockk_version: String by rootExtra
@ -111,7 +118,7 @@ dependencies {
implementation("androidx.recyclerview:recyclerview:1.3.1")
implementation("androidx.navigation:navigation-common-ktx:$navigation_version")
implementation("androidx.navigation:navigation-runtime-ktx:$navigation_version")
implementation("com.google.android.material:material:1.9.0")
implementation("com.google.android.material:material:1.10.0")
testImplementation("io.mockk:mockk:$mockk_version")
testImplementation("io.mockk:mockk-android:$mockk_version")
@ -124,6 +131,15 @@ dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
implementation("androidx.activity:activity-compose:1.8.0")
implementation("androidx.compose.runtime:runtime-livedata:$compose_version")
implementation("androidx.compose.material:material:$compose_version")
implementation("androidx.compose.animation:animation:$compose_version")
implementation("androidx.compose.ui:ui-text-google-fonts:$compose_version")
implementation("androidx.compose.ui:ui-tooling:$compose_version")
implementation("androidx.compose.material3:material3:1.1.2")
implementation("com.google.accompanist:accompanist-themeadapter-material:$accompanist_version")
implementation(project(":shared"))
}

View file

@ -4,12 +4,18 @@ import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.widget.ProgressBar
import androidx.compose.foundation.layout.size
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.common.habitica.R
import com.habitrpg.common.habitica.databinding.EmptyItemBinding
import com.habitrpg.common.habitica.databinding.FailedItemBinding
import com.habitrpg.common.habitica.theme.HabiticaTheme
import com.habitrpg.common.habitica.views.HabiticaCircularProgressView
data class EmptyItem(
var title: String,
@ -87,7 +93,12 @@ class RecyclerViewStateAdapter(val showLoadingAsEmpty: Boolean = false) : Recycl
animation1.duration = 300
animation1.startOffset = 500
animation1.fillAfter = true
view.findViewById<ProgressBar>(R.id.loading_indicator).startAnimation(animation1)
view.findViewById<ComposeView>(R.id.compose_view).startAnimation(animation1)
view.findViewById<ComposeView>(R.id.compose_view).setContent {
HabiticaTheme {
HabiticaCircularProgressView(Modifier.size(60.dp))
}
}
object : RecyclerView.ViewHolder(view) {}
}
1 -> FailedViewHolder(parent.inflate(R.layout.failed_item))

View file

@ -0,0 +1,146 @@
package com.habitrpg.common.habitica.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Shapes
import androidx.compose.material.Typography
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.accompanist.themeadapter.material.createMdcTheme
@Composable
fun HabiticaTheme(
content: @Composable () -> Unit
) {
val context = LocalContext.current
val layoutDirection = LocalLayoutDirection.current
val (colors, _, _) = createMdcTheme(
context = context,
layoutDirection = layoutDirection,
setTextColors = true
)
MaterialTheme(
colors = colors ?: MaterialTheme.colors,
typography = Typography(
defaultFontFamily = FontFamily.Default,
h1 = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 20.sp,
letterSpacing = (0.05).sp
),
h2 = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 28.sp,
letterSpacing = (0.05).sp
),
subtitle1 = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 16.sp
),
subtitle2 = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
letterSpacing = 0.1.sp
),
body1 = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
letterSpacing = 0.35.sp,
lineHeight = 16.sp
),
body2 = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 14.sp,
letterSpacing = 0.2.sp,
lineHeight = 16.sp
),
button = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
letterSpacing = 1.25.sp
),
caption = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 12.sp
),
overline = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 10.sp,
letterSpacing = 1.5.sp
)
),
shapes = Shapes(
RoundedCornerShape(4.dp),
RoundedCornerShape(8.dp),
RoundedCornerShape(12.dp)
),
content = content
)
}
val Typography.caption1
get() = caption
val Typography.caption2
get() = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
letterSpacing = 0.4.sp
)
val Typography.caption3
get() = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
letterSpacing = 0.3.sp,
lineHeight = 14.sp
)
val Typography.caption4
get() = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
letterSpacing = 0.35.sp
)
val Typography.subtitle3
get() = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
letterSpacing = 0.15.sp
)
object HabiticaTheme {
val typography: Typography
@Composable
get() = MaterialTheme.typography
val shapes: Shapes
@Composable
get() = MaterialTheme.shapes
}
class HabiticaColors(
val windowBackground: Color,
val contentBackground: Color,
val contentBackgroundOffset: Color,
val offsetBackground: Color,
val textPrimary: Color,
val textSecondary: Color,
val textTertiary: Color,
val textQuad: Color,
val textDimmed: Color,
val tintedUiMain: Color,
val tintedUiSub: Color,
val tintedUiDetails: Color,
val pixelArtBackground: Color,
val errorBackground: Color,
val errorColor: Color,
val successBackground: Color,
val successColor: Color
)
class HabiticaTypography

View file

@ -1,4 +1,4 @@
package com.habitrpg.android.habitica.ui.views
package com.habitrpg.common.habitica.views
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

View file

@ -1,4 +1,4 @@
package com.habitrpg.android.habitica.ui.views.progress
package com.habitrpg.common.habitica.views
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.animateFloat
@ -28,7 +28,7 @@ import androidx.compose.ui.res.colorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.habitrpg.android.habitica.R
import com.habitrpg.common.habitica.R
@Composable
fun HabiticaCircularProgressView(

View file

@ -2,10 +2,10 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/loading_indicator"
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</FrameLayout>
</FrameLayout>

View file

@ -162,5 +162,14 @@
<color name="material_card_background_inverse_color">@color/gray_5</color>
<color name="divider_color">@color/gray_500</color>
<color name="background_red">@color/red_100</color>
<color name="background_orange">@color/orange_100</color>
<color name="background_yellow">@color/yellow_100</color>
<color name="background_green">@color/green_100</color>
<color name="background_blue">@color/blue_100</color>
<color name="background_teal">@color/teal_100</color>
<color name="background_brand">@color/brand_300</color>
<color name="background_brand_30">#4D6033B5</color>
<color name="dim_background">#40BDA8FF</color>
</resources>

View file

@ -1,9 +1,10 @@
New in 4.3:
Its easier than ever to party up with our newest feature: Look for Party and Find Members!
- Solo players can let Party leaders know they want an invite by going to Menu > Party and tapping Look for Party
- Party leaders can see players looking for Party by tapping Find Members on the Party screen and send invites
- Can gift Gems by username
- Bailey notification will show title
- Selecting a class is more consistent
- Chat notifications open Party
- Other various fixes
- Pets just got cuter! Tap a Pet or Mount to see them in an environment that changes each month.
- Pets bounce when fed
- New Subscriber benefit: Buy one Armoire, get another free!
- New Subscriber benefit: Get a second chance at life once a day when you run out of HP!
- Tap your avatar to share your latest looks at any time
- See answers to common Quest mechanic questions under your active Quest
- Updated Quest interface
- The Quest shop now shows a check by completed Quests

View file

@ -1,2 +1,2 @@
NAME=4.3
CODE=6681
CODE=6731