diff --git a/Habitica/res/layout/fragment_gem_purchase.xml b/Habitica/res/layout/fragment_gem_purchase.xml
index 048960d32..e33760d2f 100644
--- a/Habitica/res/layout/fragment_gem_purchase.xml
+++ b/Habitica/res/layout/fragment_gem_purchase.xml
@@ -1,257 +1,288 @@
-
+ android:layout_height="match_parent"
+ android:scrollbarSize="3dp"
+ android:scrollbarThumbVertical="@color/scrollbarThumb"
+ android:scrollbars="vertical">
+
+
+
+ android:id="@+id/promo_compose_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone" />
+
+ android:id="@+id/promo_banner"
+ android:layout_width="match_parent"
+ android:layout_height="80dp"
+ android:background="@drawable/g1g1_box"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:layout_marginBottom="@dimen/spacing_large"
+ android:clipChildren="true"
+ android:clipToPadding="true"
+ android:focusable="true"
+ android:clickable="true"
+ android:visibility="gone"
+ tools:visibility="visible">
+
+ android:id="@+id/promo_banner_left_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="center"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentBottom="false"
+ android:importantForAccessibility="no" />
+
+ 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:importantForAccessibility="no" />
+
-
-
+ android:orientation="vertical">
+
+
+
+
+
+
+
+
-
-
+ android:layout_marginStart="60dp"
+ android:layout_marginEnd="60dp"
+ android:gravity="center_horizontal"
+ android:textColor="@color/white"
+ android:textSize="16sp"
+ android:fontFamily="@string/font_family_medium"
+ android:layout_marginBottom="12dp"
+ android:layout_centerInParent="true" />
+
-
-
+ android:layout_height="wrap_content">
-
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gem_footer"
+ android:layout_gravity="bottom"
+ android:scaleType="fitXY"
+ tools:ignore="ContentDescription" />
-
-
-
-
-
-
-
-
-
+
+
-
+
+
-
+ android:text="@string/gem_purchase_subtitle"
+ android:gravity="center"
+ android:textStyle="normal|bold"
+ android:textColor="?colorPrimaryText"
+ android:textSize="16sp"
+ android:lineSpacingExtra="4dp"
+ android:layout_marginTop="23dp"
+ android:layout_marginBottom="12dp"
+ android:layout_marginStart="@dimen/spacing_xlarge"
+ android:layout_marginEnd="@dimen/spacing_xlarge" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+ android:orientation="vertical"
+ android:visibility="gone"
+ android:layout_marginTop="@dimen/spacing_large">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index 9cb206998..e182d28b4 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -1529,6 +1529,8 @@
Customization Shop
Equipment
customizing your avatar
+ Error
+ There was an error loading gems
- You
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt
index f54c4b5ea..03df50a0b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt
@@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.helpers
import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
+import android.util.Log
import androidx.core.content.edit
import androidx.lifecycle.asFlow
import com.android.billingclient.api.AcknowledgePurchaseParams
@@ -22,6 +23,7 @@ import com.android.billingclient.api.acknowledgePurchase
import com.android.billingclient.api.consumePurchase
import com.android.billingclient.api.queryProductDetails
import com.android.billingclient.api.queryPurchasesAsync
+import com.google.api.Billing
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
@@ -56,7 +58,7 @@ class PurchaseHandler(
private val apiClient: ApiClient,
private val userViewModel: MainUserViewModel,
) : PurchasesUpdatedListener, PurchasesResponseListener {
- private val billingClient =
+ private var billingClient =
BillingClient.newBuilder(context).setListener(this).enablePendingPurchases().build()
override fun onPurchasesUpdated(
@@ -160,6 +162,7 @@ class PurchaseHandler(
return
}
billingClientState = BillingClientState.CONNECTING
+ billingClient = BillingClient.newBuilder(context).setListener(this).enablePendingPurchases().build()
billingClient.startConnection(
object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
@@ -172,34 +175,42 @@ class PurchaseHandler(
}
BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> {
- retryListening()
+ CoroutineScope(Dispatchers.IO).launchCatching {
+ retryListening()
+ }
}
BillingClient.BillingResponseCode.SERVICE_TIMEOUT -> {
- retryListening()
+ CoroutineScope(Dispatchers.IO).launchCatching {
+ retryListening()
+ }
+ }
+
+ BillingClient.BillingResponseCode.BILLING_UNAVAILABLE -> {
+ billingClientState = BillingClientState.UNAVAILABLE
}
else -> {
- billingClientState = BillingClientState.UNAVAILABLE
+ billingClientState = BillingClientState.DISCONNECTED
}
}
}
override fun onBillingServiceDisconnected() {
billingClientState = BillingClientState.DISCONNECTED
- retryListening()
+ CoroutineScope(Dispatchers.IO).launchCatching {
+ retryListening()
+ }
}
},
)
}
- private fun retryListening() {
+ private suspend fun retryListening() {
listeningRetryCount += 1
- CoroutineScope(Dispatchers.IO).launchCatching {
- // try again after 30 seconds
- delay(30.seconds)
- startListening()
- }
+ // try again after 30 seconds
+ delay(20.seconds)
+ startListening()
}
fun stopListening() {
@@ -260,8 +271,9 @@ class PurchaseHandler(
type: String,
skus: List,
): List? {
- retryUntil {
- if (billingClientState == BillingClientState.DISCONNECTED) {
+ retryUntil(8, initialDelay = 500, maxDelay = 2000) {
+ if (billingClient.connectionState == BillingClient.ConnectionState.DISCONNECTED ||
+ billingClient.connectionState == BillingClient.ConnectionState.CLOSED) {
startListening()
}
billingClientState.canMaybePurchase && billingClient.isReady
@@ -276,6 +288,15 @@ class PurchaseHandler(
withContext(Dispatchers.IO) {
billingClient.queryProductDetails(params)
}
+ if (skuDetailsResult.billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
+ Log.e("PurchaseHandler", "Failed to load inventory: ${skuDetailsResult.billingResult.debugMessage}")
+ FirebaseCrashlytics.getInstance().recordException(
+ Throwable(
+ "Failed to load inventory: ${skuDetailsResult.billingResult.debugMessage}",
+ ),
+ )
+ return null
+ }
return skuDetailsResult.productDetailsList
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt
index 022c43443..2bf16aa1c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt
@@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentGemPurchaseBinding
import com.habitrpg.android.habitica.extensions.addCancelButton
+import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.PurchaseHandler
@@ -34,6 +35,7 @@ import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
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 dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -132,12 +134,31 @@ class GemsPurchaseFragment : BaseFragment() {
}
private fun loadInventory() {
+ if (binding?.gems4View?.sku == null) {
+ binding?.loadingIndicator?.setContent {
+ HabiticaCircularProgressView()
+ }
+ binding?.loadingIndicator?.isVisible = true
+ binding?.gemPurchaseOptions?.isVisible = false
+ }
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
val skus = purchaseHandler.getAllGemSKUs()
withContext(Dispatchers.Main) {
+ if (skus.isEmpty()) {
+ binding?.loadingIndicator?.isVisible = false
+ binding?.gemPurchaseOptions?.isVisible = false
+ val dialog = HabiticaAlertDialog(requireActivity())
+ dialog.setTitle(getString(R.string.error))
+ dialog.setMessage(getString(R.string.error_loading_gems))
+ dialog.addCloseButton()
+ dialog.show()
+ return@withContext
+ }
for (sku in skus) {
updateButtonLabel(sku)
}
+ binding?.loadingIndicator?.isVisible = false
+ binding?.gemPurchaseOptions?.isVisible = true
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt
index 24cd56fc4..95b0273d4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt
@@ -91,10 +91,10 @@ class InsufficientGemsDialog(val parentActivity: Activity, var gemPrice: Int) :
purchaseTextView.text = "4"
PurchaseTypes.PURCHASE_4_GEMS
}
- CoroutineScope(Dispatchers.IO).launch {
+ CoroutineScope(Dispatchers.IO).launchCatching {
val sku =
purchaseHandler.getInAppPurchaseSKU(gemSku)
- ?: return@launch
+ ?: return@launchCatching
withContext(Dispatchers.Main) {
purchaseButton?.text = sku.oneTimePurchaseOfferDetails?.formattedPrice
contentView.findViewById(R.id.loading_indicator).isVisible = false
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
index a7638875a..ccaf4e990 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
@@ -23,13 +23,10 @@ import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.getShortRemainingString
import com.habitrpg.android.habitica.helpers.Analytics
-import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.EventCategory
import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
import com.habitrpg.android.habitica.helpers.HitType
-import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.helpers.SoundManager
-import com.habitrpg.android.habitica.interactors.InsufficientGemsUseCase
import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.models.user.OwnedItem
@@ -42,7 +39,6 @@ import com.habitrpg.android.habitica.ui.views.CurrencyViews
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.SnackbarActivity
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
-import com.habitrpg.android.habitica.ui.views.getTranslatedClassName
import com.habitrpg.android.habitica.ui.views.getTranslatedClassNamePlural
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGoldDialog
diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts
index 273cbefab..2e5bb6bc2 100644
--- a/shared/build.gradle.kts
+++ b/shared/build.gradle.kts
@@ -49,6 +49,12 @@ android {
buildTypes {
release {
}
+ create("debugIAP") {
+ initWith(buildTypes["debug"])
+ isMinifyEnabled = false
+ isJniDebuggable = true
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ }
}
compileOptions {
diff --git a/version.properties b/version.properties
index 544142d9b..3c5a3f96f 100644
--- a/version.properties
+++ b/version.properties
@@ -1,2 +1,2 @@
NAME=4.3.7
-CODE=7831
\ No newline at end of file
+CODE=7841
\ No newline at end of file