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) + } }