improve customization purchase dialog

This commit is contained in:
Phillip Thelen 2022-05-04 17:55:07 +02:00
parent 3584d06261
commit 794c2f0f12
11 changed files with 137 additions and 77 deletions

View file

@ -1,44 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:parentTag="android.widget.LinearLayout">
<ImageView
android:layout_width="@dimen/avatar_width"
android:layout_height="@dimen/avatar_height"
android:layout_width="@dimen/customization_width"
android:layout_height="@dimen/customization_width"
android:id="@+id/imageView"
android:layout_gravity="center_horizontal"
android:background="@drawable/layout_rounded_bg_window"
android:layout_marginTop="24dp"
android:scaleType="center"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:id="@+id/priceLabel"
android:layout_gravity="center_horizontal"
android:layout_marginEnd="2dp"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/gem_icon"
android:layout_gravity="center"/>
</LinearLayout>
<TextView
android:id="@+id/amount_error_label"
android:layout_height="wrap_content"
android:id="@+id/titleTextView"
android:layout_width="match_parent"
android:text="@string/purchase_amount_error"
android:textSize="12sp"
android:textColor="@color/text_ternary"
android:layout_marginTop="@dimen/spacing_medium"
android:visibility="gone"
android:gravity="center_horizontal"/>
</LinearLayout>
android:layout_height="wrap_content"
style="@style/Headline"
android:textColor="@color/text_primary"
tools:text="This is the Title"
android:gravity="center"
android:layout_marginTop="14dp"
android:layout_marginBottom="4dp"/>
</merge>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="@dimen/shopitem_dialog_content_inset"
android:paddingEnd="@dimen/shopitem_dialog_content_inset"
tools:parentTag="LinearLayout"
tools:orientation="vertical">
<ImageView
android:id="@+id/imageView"
android:layout_width="@dimen/avatar_width"
android:layout_height="@dimen/avatar_height"
android:layout_marginTop="24dp"/>
<TextView
android:id="@+id/titleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Headline"
android:textColor="@color/text_primary"
tools:text="This is the Title"
android:gravity="center"
android:layout_marginTop="14dp"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/notesTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Body2"
android:textColor="@color/text_ternary"
tools:text="These are the notes"
android:gravity="center"/>
</merge>

View file

@ -1044,7 +1044,7 @@
<string name="unequip">取消裝備</string>
<string name="excessItemsNoneLeft">你已經有得到所有%s寵物所需的一切物品。你確定你要買%2$d個%3$s</string>
<string name="purchaseX">買%d個</string>
<string name="excessItemsXLeft">你只需要%d個%2$s即可孵化所有可得到的這種寵物。你確定你想要買%1$d個</string>
<string name="excessItemsXLeft">你只需要%3$d個%2$s即可孵化所有可得到的這種寵物。你確定你想要買%1$d個</string>
<string name="feedPet_description">完成任務有概率掉落寵物食品!這樣你就可以把寵物餵大長成坐騎啦!</string>
<string name="excess_items">多餘物品</string>
<string name="onboardingComplete_achievement_title">你完成了新手任務!</string>

View file

@ -51,6 +51,7 @@ interface UserRepository : BaseRepository {
fun changeClass(selectedClass: String): Flowable<User>
fun unlockPath(user: User?, unlockPath: String?, type: String?, price: Int): Flowable<UnlockResponse>
fun unlockPath(user: User?, customization: Customization): Flowable<UnlockResponse>
fun unlockPath(set: CustomizationSet): Flowable<UnlockResponse>

View file

@ -162,9 +162,9 @@ class UserRepositoryImpl(
override fun changeClass(selectedClass: String): Flowable<User> = apiClient.changeClass(selectedClass)
.flatMap { retrieveUser(false) }
override fun unlockPath(user: User?, customization: Customization): Flowable<UnlockResponse> {
var path = customization.path
if (path.last() == '.' && customization.type == "background") {
override fun unlockPath(user: User?, unlockPath: String?, type: String?, price: Int): Flowable<UnlockResponse> {
var path = unlockPath ?: return Flowable.empty()
if (path.last() == '.' && type == "background") {
path += user?.preferences?.background
}
return zipWithLiveUser(apiClient.unlockPath(path)) { unlockResponse, copiedUser ->
@ -172,12 +172,16 @@ class UserRepositoryImpl(
user.preferences = unlockResponse.preferences
user.purchased = unlockResponse.purchased
user.items = unlockResponse.items
user.balance = copiedUser.balance - (customization.price ?: 0) / 4.0
user.balance = copiedUser.balance - (price / 4.0)
localRepository.saveUser(copiedUser, false)
unlockResponse
}
}
override fun unlockPath(user: User?, customization: Customization): Flowable<UnlockResponse> {
return unlockPath(user, customization.path, customization.type, customization.price ?: 0)
}
override fun unlockPath(set: CustomizationSet): Flowable<UnlockResponse> {
var path = ""
for (customization in set.customizations) {

View file

@ -5,6 +5,7 @@ import android.content.res.Resources
import com.google.gson.annotations.SerializedName
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.models.BaseObject
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.inventory.ItemEvent
import com.habitrpg.android.habitica.models.user.User
import io.realm.RealmObject
@ -150,5 +151,18 @@ open class ShopItem : RealmObject(), BaseObject {
item.purchaseType = "fortify"
return item
}
fun fromCustomization(customization: Customization, userSize: String?, hairColor: String?): ShopItem {
val item = ShopItem()
item.key = customization.identifier ?: ""
item.text = customization.text
item.currency = "gems"
item.notes = customization.notes
item.value = customization.price ?: 0
item.path = customization.path
item.purchaseType = if (customization.type == "background") "background" else "customization"
item.imageName = customization.getImageName(userSize, hairColor)
return item
}
}
}

View file

@ -4,12 +4,12 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import coil.load
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.CustomizationGridItemBinding
import com.habitrpg.android.habitica.databinding.CustomizationSectionFooterBinding
@ -17,9 +17,10 @@ import com.habitrpg.android.habitica.databinding.CustomizationSectionHeaderBindi
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.inventory.CustomizationSet
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
@ -198,31 +199,10 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler
dialog.addButton(R.string.reward_dialog_dismiss, false)
dialog.show()
} else {
val dialogContent = LayoutInflater.from(itemView.context).inflate(R.layout.dialog_purchase_customization, null) as LinearLayout
val imageView = dialogContent.findViewById<ImageView>(R.id.imageView)
DataBindingUtils.loadImage(imageView, customization?.getImageName(userSize, hairColor))
val priceLabel = dialogContent.findViewById<TextView>(R.id.priceLabel)
priceLabel.text = customization?.price.toString()
(dialogContent.findViewById<View>(R.id.gem_icon) as? ImageView)?.setImageBitmap(HabiticaIconsHelper.imageOfGem())
val dialog = HabiticaAlertDialog(itemView.context)
dialog.addButton(R.string.purchase_button, true) { _, _ ->
if (customization?.price ?: 0 > gemBalance) {
MainNavigationController.navigate(R.id.gemPurchaseActivity, bundleOf(Pair("openSubscription", false)))
return@addButton
}
customization?.let {
unlockCustomizationEvents.onNext(it)
}
customization?.let {
val dialog = PurchaseDialog(itemView.context, HabiticaBaseApplication.userComponent, ShopItem.fromCustomization(it, userSize, hairColor))
dialog.show()
}
dialog.setTitle(R.string.purchase_customization)
dialog.setAdditionalContentView(dialogContent)
dialog.addButton(R.string.reward_dialog_dismiss, false)
dialog.show()
}
return
}

View file

@ -47,7 +47,7 @@ open class HabiticaAlertDialog(context: Context) : AlertDialog(context, R.style.
internal var contentView: FrameLayout
private var scrollingSeparator: View
internal var scrollView: LockableScrollView
private var buttonsWrapper: LinearLayout
protected var buttonsWrapper: LinearLayout
private var noticeTextView: TextView
private var closeButton: Button

View file

@ -27,7 +27,6 @@ import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.ArmoireActivityArgs
import com.habitrpg.android.habitica.ui.activities.ArmoireActivityDirections
import com.habitrpg.android.habitica.ui.views.CurrencyView
import com.habitrpg.android.habitica.ui.views.CurrencyViews
@ -42,16 +41,16 @@ import com.habitrpg.android.habitica.ui.views.tasks.form.StepperValueFormView
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.disposables.CompositeDisposable
import java.util.*
import javax.inject.Inject
import kotlin.math.max
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.Date
import javax.inject.Inject
import kotlin.math.max
import kotlin.time.DurationUnit
import kotlin.time.toDuration
class PurchaseDialog(context: Context, component: UserComponent?, val item: ShopItem) : HabiticaAlertDialog(context) {
@ -106,6 +105,8 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
checkGearClass()
}
"gems" == shopItem.purchaseType -> contentView = PurchaseDialogGemsContent(context)
"background" == shopItem.purchaseType -> contentView = PurchaseDialogBackgroundContent(context)
"customization" == shopItem.purchaseType -> contentView = PurchaseDialogCustomizationContent(context)
else -> contentView = PurchaseDialogBaseContent(context)
}
@ -168,7 +169,6 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
private var limitedTextViewJob: Job? = null
@OptIn(ExperimentalTime::class)
private fun setLimitedTextView() {
if (user == null) return
if (shopItem.habitClass != null && shopItem.habitClass != "special" && shopItem.habitClass != "armoire" && user?.stats?.habitClass != shopItem.habitClass) {
@ -182,7 +182,7 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
while (shopItem.event?.end?.after(Date()) == true) {
limitedTextView.text = context.getString(R.string.available_for, shopItem.event?.end?.getShortRemainingString())
val diff = (shopItem.event?.end?.time ?: 0) - Date().time
delay(if (diff < (60 * 60 * 1000)) Duration.seconds(1) else Duration.minutes(1))
delay(1.toDuration(if (diff < (60 * 60 * 1000)) DurationUnit.SECONDS else DurationUnit.MINUTES))
}
if (shopItem.event?.end?.before(Date()) == true) {
limitedTextView.text = context.getString(R.string.no_longer_available)
@ -239,10 +239,11 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
pinTextView = customHeader.findViewById(R.id.pin_text)
addCloseButton()
buyButton = addButton(layoutInflater.inflate(R.layout.dialog_purchase_shopitem_button, null), autoDismiss = false) { _, _ ->
buyButton = addButton(layoutInflater.inflate(R.layout.dialog_purchase_shopitem_button, buttonsWrapper, false), autoDismiss = false) { _, _ ->
onBuyButtonClicked()
}
priceLabel = buyButton.findViewById(R.id.priceLabel)
priceLabel.animationDuration = 0L
buyLabel = buyButton.findViewById(R.id.buy_label)
pinButton.setOnClickListener { inventoryRepository.togglePinnedItem(shopItem).subscribe({ isPinned = !this.isPinned }, RxErrorHandler.handleEmptyError()) }
@ -353,6 +354,12 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
observable = inventoryRepository.purchaseQuest(shopItem.key).cast(Any::class.java)
} else if (shopItem.purchaseType == "debuffPotion") {
observable = userRepository.useSkill(shopItem.key, null).cast(Any::class.java)
} else if (shopItem.purchaseType == "customization" || shopItem.purchaseType == "background") {
observable = userRepository.unlockPath(user, item.path, item.purchaseType,
item.value
).cast(Any::class.java)
} else if (shopItem.purchaseType == "debuffPotion") {
observable = userRepository.useSkill(shopItem.key, null).cast(Any::class.java)
} else if (shopItem.purchaseType == "card") {
purchaseCardAction?.invoke(shopItem)
dismiss()

View file

@ -0,0 +1,21 @@
package com.habitrpg.android.habitica.ui.views.shops
import android.content.Context
import android.widget.ImageView
import android.widget.TextView
import com.habitrpg.android.habitica.databinding.PurchaseDialogBackgroundBinding
import com.habitrpg.android.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.models.shops.ShopItem
class PurchaseDialogBackgroundContent(context: Context) : PurchaseDialogContent(context) {
val binding = PurchaseDialogBackgroundBinding.inflate(context.layoutInflater, this)
override val imageView: ImageView
get() = binding.imageView
override val titleTextView: TextView
get() = binding.titleTextView
override fun setItem(item: ShopItem) {
super.setItem(item)
binding.notesTextView.text = item.notes
}
}

View file

@ -0,0 +1,15 @@
package com.habitrpg.android.habitica.ui.views.shops
import android.content.Context
import android.widget.ImageView
import android.widget.TextView
import com.habitrpg.android.habitica.databinding.DialogPurchaseCustomizationBinding
import com.habitrpg.android.habitica.extensions.layoutInflater
class PurchaseDialogCustomizationContent(context: Context) : PurchaseDialogContent(context) {
val binding = DialogPurchaseCustomizationBinding.inflate(context.layoutInflater, this)
override val imageView: ImageView
get() = binding.imageView
override val titleTextView: TextView
get() = binding.titleTextView
}