diff --git a/Habitica/build.gradle b/Habitica/build.gradle
index 416838c7d..54b58a6fc 100644
--- a/Habitica/build.gradle
+++ b/Habitica/build.gradle
@@ -162,8 +162,8 @@ android {
multiDexEnabled true
resConfigs "en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "zh", "zh-rTW"
- versionCode 2426
- versionName "2.6.2"
+ versionCode 2428
+ versionName "2.7"
}
viewBinding {
diff --git a/Habitica/res/values/ids.xml b/Habitica/res/values/ids.xml
deleted file mode 100644
index a6b3daec9..000000000
--- a/Habitica/res/values/ids.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index 5c8fd4231..ced470800 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -1052,4 +1052,8 @@
You earned 5 Achievements and 100 Gold for your efforts.
If you want even more, check out Achievements and start collecting!
You completed your OnboardingTasks!
+ Excess Items
+ You only need %d %s to hatch all possible pets. Are you sure you want to purchase %d?
+ Purchase %d
+ You\'ve already hatched all possible %s pets. Are you sure you want to purchase %d %s?
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 5ee21ed3e..b0e438c68 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
@@ -15,11 +15,14 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.events.GearPurchasedEvent
import com.habitrpg.android.habitica.events.ShowSnackbarEvent
+import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
+import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Equipment
+import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.models.shops.ShopItem
@@ -247,69 +250,20 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
}
private fun onBuyButtonClicked() {
- val snackbarText = arrayOf("")
if (shopItem.isValid && !shopItem.locked) {
val gemsLeft = if (shopItem.limitedNumberLeft != null) shopItem.limitedNumberLeft else 0
if ((gemsLeft == 0 && shopItem.purchaseType == "gems") || shopItem.canAfford(user, purchaseQuantity)) {
- val observable: Flowable
- if (shopIdentifier != null && shopIdentifier == Shop.TIME_TRAVELERS_SHOP || "mystery_set" == shopItem.purchaseType || shopItem.currency == "hourglasses") {
- observable = if (shopItem.purchaseType == "gear") {
- inventoryRepository.purchaseMysterySet(shopItem.key)
- } else {
- inventoryRepository.purchaseHourglassItem(shopItem.purchaseType, shopItem.key)
- }
- } else if (shopItem.purchaseType == "quests" && shopItem.currency == "gold") {
- observable = inventoryRepository.purchaseQuest(shopItem.key)
- } else if (shopItem.purchaseType == "card") {
- purchaseCardAction?.invoke(shopItem)
- dismiss()
- return
- } else if ("gold" == shopItem.currency && "gem" != shopItem.key) {
- observable = inventoryRepository.buyItem(user, shopItem.key, shopItem.value.toDouble(), purchaseQuantity).map { buyResponse ->
- if (shopItem.key == "armoire") {
- snackbarText[0] = when {
- buyResponse.armoire["type"] == "gear" -> context.getString(R.string.armoireEquipment, buyResponse.armoire["dropText"])
- buyResponse.armoire["type"] == "food" -> context.getString(R.string.armoireFood, buyResponse.armoire["dropArticle"] ?: "", buyResponse.armoire["dropText"])
- else -> context.getString(R.string.armoireExp)
- }
+ remainingPurchaseQuantity { quantity ->
+ if (quantity >= 0) {
+ if (quantity < purchaseQuantity) {
+ displayPurchaseConfirmationDialog(quantity)
+ dismiss()
+ return@remainingPurchaseQuantity
}
- buyResponse
}
- } else {
- observable = inventoryRepository.purchaseItem(shopItem.purchaseType, shopItem.key, purchaseQuantity)
+ buyItem(purchaseQuantity)
}
- val subscription = observable
- .doOnNext {
- val event = ShowSnackbarEvent()
- if (snackbarText[0].isNotEmpty()) {
- event.text = snackbarText[0]
- } else {
- event.text = context.getString(R.string.successful_purchase, shopItem.text)
- }
- event.type = HabiticaSnackbar.SnackbarDisplayType.NORMAL
- event.rightIcon = priceLabel.compoundDrawables[0]
- when (item.currency) {
- "gold" -> event.rightTextColor = ContextCompat.getColor(context, R.color.yellow_5)
- "gems" -> event.rightTextColor = ContextCompat.getColor(context, R.color.green_10)
- "hourglasses" -> event.rightTextColor = ContextCompat.getColor(context, R.color.brand_300)
- }
- event.rightText = "-" + priceLabel.text
- EventBus.getDefault().post(event)
- }
- .flatMap { userRepository.retrieveUser(withTasks = false, forced = true) }
- .flatMap { inventoryRepository.retrieveInAppRewards() }
- .subscribe({
- if (item.isTypeGear || item.currency == "hourglasses") {
- EventBus.getDefault().post(GearPurchasedEvent(item))
- }
- }) { throwable ->
- if (throwable.javaClass.isAssignableFrom(retrofit2.HttpException::class.java)) {
- val error = throwable as retrofit2.HttpException
- if (error.code() == 401 && shopItem.currency == "gems") {
- MainNavigationController.navigate(R.id.gemPurchaseActivity, bundleOf(Pair("openSubscription", false)))
- }
- }
- }
+
} else {
when {
"gems" == shopItem.purchaseType -> {
@@ -330,5 +284,143 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
dismiss()
}
+ private fun buyItem(quantity: Int) {
+ val snackbarText = arrayOf("")
+ val observable: Flowable
+ if (shopIdentifier != null && shopIdentifier == Shop.TIME_TRAVELERS_SHOP || "mystery_set" == shopItem.purchaseType || shopItem.currency == "hourglasses") {
+ observable = if (shopItem.purchaseType == "gear") {
+ inventoryRepository.purchaseMysterySet(shopItem.key)
+ } else {
+ inventoryRepository.purchaseHourglassItem(shopItem.purchaseType, shopItem.key)
+ }
+ } else if (shopItem.purchaseType == "quests" && shopItem.currency == "gold") {
+ observable = inventoryRepository.purchaseQuest(shopItem.key)
+ } else if (shopItem.purchaseType == "card") {
+ purchaseCardAction?.invoke(shopItem)
+ dismiss()
+ return
+ } else if ("gold" == shopItem.currency && "gem" != shopItem.key) {
+ observable = inventoryRepository.buyItem(user, shopItem.key, shopItem.value.toDouble(), quantity).map { buyResponse ->
+ if (shopItem.key == "armoire") {
+ snackbarText[0] = when {
+ buyResponse.armoire["type"] == "gear" -> context.getString(R.string.armoireEquipment, buyResponse.armoire["dropText"])
+ buyResponse.armoire["type"] == "food" -> context.getString(R.string.armoireFood, buyResponse.armoire["dropArticle"] ?: "", buyResponse.armoire["dropText"])
+ else -> context.getString(R.string.armoireExp)
+ }
+ }
+ buyResponse
+ }
+ } else {
+ observable = inventoryRepository.purchaseItem(shopItem.purchaseType, shopItem.key, quantity)
+ }
+ val subscription = observable
+ .doOnNext {
+ val event = ShowSnackbarEvent()
+ if (snackbarText[0].isNotEmpty()) {
+ event.text = snackbarText[0]
+ } else {
+ event.text = context.getString(R.string.successful_purchase, shopItem.text)
+ }
+ event.type = HabiticaSnackbar.SnackbarDisplayType.NORMAL
+ event.rightIcon = priceLabel.compoundDrawables[0]
+ when (item.currency) {
+ "gold" -> event.rightTextColor = ContextCompat.getColor(context, R.color.yellow_5)
+ "gems" -> event.rightTextColor = ContextCompat.getColor(context, R.color.green_10)
+ "hourglasses" -> event.rightTextColor = ContextCompat.getColor(context, R.color.brand_300)
+ }
+ event.rightText = "-" + priceLabel.text
+ EventBus.getDefault().post(event)
+ }
+ .flatMap { userRepository.retrieveUser(withTasks = false, forced = true) }
+ .flatMap { inventoryRepository.retrieveInAppRewards() }
+ .subscribe({
+ if (item.isTypeGear || item.currency == "hourglasses") {
+ EventBus.getDefault().post(GearPurchasedEvent(item))
+ }
+ }) { throwable ->
+ if (throwable.javaClass.isAssignableFrom(retrofit2.HttpException::class.java)) {
+ val error = throwable as retrofit2.HttpException
+ if (error.code() == 401 && shopItem.currency == "gems") {
+ MainNavigationController.navigate(R.id.gemPurchaseActivity, bundleOf(Pair("openSubscription", false)))
+ }
+ }
+ }
+ }
+
+ private fun displayPurchaseConfirmationDialog(quantity: Int) {
+ if (quantity == 0) {
+ displayNoRemainingConfirmationDialog()
+ } else {
+ displaySomeRemainingConfirmationDialog(quantity)
+ }
+ }
+
+ private fun displaySomeRemainingConfirmationDialog(quantity: Int) {
+ val alert = HabiticaAlertDialog(context)
+ alert.setTitle(R.string.excess_items)
+ alert.setMessage(context.getString(R.string.excessItemsXLeft, quantity, item.text, purchaseQuantity))
+ alert.addButton(context.getString(R.string.purchaseX, purchaseQuantity), true, false) { _, _ ->
+ buyItem(purchaseQuantity)
+ }
+ alert.addButton(context.getString(R.string.purchaseX, quantity), false, false) { _, _ ->
+ buyItem(quantity)
+ }
+ alert.show()
+ }
+
+ private fun displayNoRemainingConfirmationDialog() {
+ val alert = HabiticaAlertDialog(context)
+ alert.setTitle(R.string.excess_items)
+ alert.setMessage(context.getString(R.string.excessItemsNoneLeft, item.text, purchaseQuantity, item.text))
+ alert.addButton(context.getString(R.string.purchaseX, purchaseQuantity), true, false) { _, _ ->
+ buyItem(purchaseQuantity)
+ }
+ alert.addCancelButton()
+ alert.show()
+ }
+
+ private fun remainingPurchaseQuantity(onResult: (Int) -> Unit) {
+ if (item.purchaseType == "eggs") {
+ var ownedCount = 0
+ inventoryRepository.getPets(item.key, "quest", null).filter {
+ return@filter it.size > 0
+ }.flatMap { inventoryRepository.getOwnedPets() }.doOnNext {
+ for (pet in it) {
+ if (pet.key?.contains(item.key) == true) {
+ ownedCount += if (pet.trained > 0) 1 else 0
+ }
+ }
+ }.flatMap { inventoryRepository.getOwnedMounts() }.doOnNext {
+ for (mount in it) {
+ if (mount.key?.contains(item.key) == true) {
+ ownedCount += if (mount.owned) 1 else 0
+ }
+ }
+ }.firstElement().subscribe {
+ val remaining = 20 - ownedCount
+ onResult(remaining)
+ }
+ } else if (item.purchaseType == "hatchingPotions") {
+ var ownedCount = 0
+ inventoryRepository.getPets("Wolf", "quest", item.key).filter {
+ return@filter it.size > 0
+ }.flatMap { inventoryRepository.getOwnedPets() }.doOnNext { for (pet in it) {
+ if (pet.key?.contains(item.key) == true) {
+ ownedCount += if (pet.trained > 0) 1 else 0
+ }
+ }
+ }.flatMap { inventoryRepository.getOwnedMounts() }.doOnNext {
+ for (mount in it) {
+ if (mount.key?.contains(item.key) == true) {
+ ownedCount += if (mount.owned) 1 else 0
+ }
+ }
+ }.firstElement().subscribe {
+ val remaining = 18 - ownedCount
+ onResult(remaining)
+ }
+ }
+ onResult(-1)
+ }
}