correctly display customization purchase dialogs

This commit is contained in:
Phillip Thelen 2022-05-06 15:04:58 +02:00
parent 794c2f0f12
commit 8ac10a469b
15 changed files with 125 additions and 125 deletions

View file

@ -166,7 +166,7 @@ android {
buildConfigField "String", "TESTING_LEVEL", "\"production\""
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 3321
versionCode 3501
versionName "3.6"
targetSdkVersion 32

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:width="8dp"
android:height="8dp" />
</shape>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:parentTag="android.widget.LinearLayout">
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/image_view_wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:flexDirection="row"
app:dividerDrawable="@drawable/purchase_dialog_spacing"
android:layout_marginTop="24dp"
app:showDivider="middle"/>
<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"/>
</merge>

View file

@ -9,8 +9,8 @@
tools:parentTag="LinearLayout"
tools:orientation="vertical">
<ImageView
android:id="@+id/imageView"
<com.habitrpg.android.habitica.ui.AvatarView
android:id="@+id/avatar_view"
android:layout_width="@dimen/avatar_width"
android:layout_height="@dimen/avatar_height"
android:layout_marginTop="24dp"/>

View file

@ -6,7 +6,6 @@ import com.habitrpg.android.habitica.models.QuestAchievement
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.inventory.CustomizationSet
import com.habitrpg.android.habitica.models.responses.SkillResponse
import com.habitrpg.android.habitica.models.responses.UnlockResponse
import com.habitrpg.android.habitica.models.responses.VerifyUsernameResponse
@ -51,9 +50,8 @@ 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?, path: String, price: Int): Flowable<UnlockResponse>
fun unlockPath(user: User?, customization: Customization): Flowable<UnlockResponse>
fun unlockPath(set: CustomizationSet): Flowable<UnlockResponse>
fun runCron(tasks: MutableList<Task>)
fun runCron()

View file

@ -14,7 +14,6 @@ import com.habitrpg.android.habitica.models.QuestAchievement
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.inventory.CustomizationSet
import com.habitrpg.android.habitica.models.responses.SkillResponse
import com.habitrpg.android.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.models.responses.UnlockResponse
@ -162,11 +161,7 @@ class UserRepositoryImpl(
override fun changeClass(selectedClass: String): Flowable<User> = apiClient.changeClass(selectedClass)
.flatMap { retrieveUser(false) }
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
}
override fun unlockPath(user: User?, path: String, price: Int): Flowable<UnlockResponse> {
return zipWithLiveUser(apiClient.unlockPath(path)) { unlockResponse, copiedUser ->
val user = localRepository.getUnmanagedCopy(copiedUser)
user.preferences = unlockResponse.preferences
@ -179,32 +174,11 @@ class UserRepositoryImpl(
}
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) {
path = path + "," + customization.path
var path = customization.path
if (path.last() == '.' && customization.type == "background") {
path += user?.preferences?.background
}
if (path.isEmpty()) {
return Flowable.just(null)
}
path = path.substring(1)
return Flowable.zip(
apiClient.unlockPath(path),
localRepository.getUser(userID).firstElement().toFlowable()
.map { localRepository.getUnmanagedCopy(it) }
.skipNull(),
{ unlockResponse, copiedUser ->
copiedUser.preferences = unlockResponse.preferences
copiedUser.purchased = unlockResponse.purchased
copiedUser.items = unlockResponse.items
copiedUser.balance = copiedUser.balance - set.price / 4.0
localRepository.saveUser(copiedUser, false)
unlockResponse
}
)
return unlockPath(user, path, customization.price ?: 0)
}
override fun runCron() {

View file

@ -6,8 +6,10 @@ 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.CustomizationSet
import com.habitrpg.android.habitica.models.inventory.ItemEvent
import com.habitrpg.android.habitica.models.user.User
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
@ -48,6 +50,8 @@ open class ShopItem : RealmObject(), BaseObject {
var level: Int? = null
var event: ItemEvent? = null
var setImageNames = RealmList<String>()
val isTypeItem: Boolean
get() = "eggs" == purchaseType || "hatchingPotions" == purchaseType || "food" == purchaseType || "armoire" == purchaseType || "potion" == purchaseType || "debuffPotion" == purchaseType || "fortify" == purchaseType
@ -164,5 +168,27 @@ open class ShopItem : RealmObject(), BaseObject {
item.imageName = customization.getImageName(userSize, hairColor)
return item
}
fun fromCustomizationSet(set: CustomizationSet, userSize: String?, hairColor: String?): ShopItem {
val item = ShopItem()
var path = ""
for (customization in set.customizations) {
path = path + "," + customization.path
}
if (path.isEmpty()) {
item.path = path
} else {
item.path = path.substring(1)
}
item.text = set.text
item.key = set.identifier ?: ""
item.currency = "gems"
item.value = set.price
item.purchaseType = "customizationSet"
set.customizations.forEach {
item.setImageNames.add(it.getIconName(userSize, hairColor))
}
return item
}
}
}

View file

@ -37,7 +37,6 @@ class CustomizationEquipmentRecyclerViewAdapter : androidx.recyclerview.widget.R
private val selectCustomizationEvents = PublishSubject.create<Equipment>()
private val unlockCustomizationEvents = PublishSubject.create<Equipment>()
private val unlockSetEvents = PublishSubject.create<CustomizationSet>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder {
val viewID: Int = R.layout.customization_grid_item
@ -81,10 +80,6 @@ class CustomizationEquipmentRecyclerViewAdapter : androidx.recyclerview.widget.R
return unlockCustomizationEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getUnlockSetEvents(): Flowable<CustomizationSet> {
return unlockSetEvents.toFlowable(BackpressureStrategy.DROP)
}
internal inner class EquipmentViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val binding = CustomizationGridItemBinding.bind(itemView)

View file

@ -4,10 +4,7 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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
@ -48,14 +45,6 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler
var ownedCustomizations: List<String> = listOf()
private val selectCustomizationEvents = PublishSubject.create<Customization>()
private val unlockCustomizationEvents = PublishSubject.create<Customization>()
private val unlockSetEvents = PublishSubject.create<CustomizationSet>()
fun updateOwnership(ownedCustomizations: List<String>) {
this.ownedCustomizations = ownedCustomizations
setCustomizations(unsortedCustomizations)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder {
return if (viewType == 0) {
@ -132,6 +121,7 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler
customizationList.add(set)
}
customizationList.add(customization)
lastSet.customizations.add(customization)
if (customization.isUsable(ownedCustomizations.contains(customization.id)) && lastSet.hasPurchasable) {
lastSet.hasPurchasable = false
}
@ -143,14 +133,6 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler
return selectCustomizationEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getUnlockCustomizationEvents(): Flowable<Customization> {
return unlockCustomizationEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getUnlockSetEvents(): Flowable<CustomizationSet> {
return unlockSetEvents.toFlowable(BackpressureStrategy.DROP)
}
internal inner class CustomizationViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val binding = CustomizationGridItemBinding.bind(itemView)
@ -248,36 +230,10 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler
}
override fun onClick(v: View) {
val dialogContent = LayoutInflater.from(context).inflate(R.layout.dialog_purchase_customization, null) as LinearLayout
val priceLabel = dialogContent.findViewById<TextView>(R.id.priceLabel)
priceLabel.text = set?.price.toString()
val dialog = HabiticaAlertDialog(context)
dialog.addButton(R.string.purchase_button, true) { _, _ ->
if (set?.price ?: 0 > gemBalance) {
MainNavigationController.navigate(R.id.gemPurchaseActivity, bundleOf(Pair("openSubscription", false)))
return@addButton
}
set?.customizations = ArrayList()
customizationList
.filter { Customization::class.java.isAssignableFrom(it.javaClass) }
.map { it as Customization }
.filter { it.customizationSet != null && it.customizationSet == set?.identifier }
.forEach { set?.customizations?.add(it) }
if (additionalSetItems.isNotEmpty()) {
additionalSetItems
.filter { !it.isUsable(ownedCustomizations.contains(it.id)) && it.customizationSet == set?.identifier }
.forEach { set?.customizations?.add(it) }
}
set?.let {
unlockSetEvents.onNext(it)
}
set?.let {
val dialog = PurchaseDialog(itemView.context, HabiticaBaseApplication.userComponent, ShopItem.fromCustomizationSet(it, userSize, hairColor))
dialog.show()
}
dialog.setTitle(context.getString(R.string.purchase_set_title, set?.text))
dialog.setAdditionalContentView(dialogContent)
dialog.addButton(R.string.reward_dialog_dismiss, false)
dialog.show()
}
}
}

View file

@ -86,24 +86,6 @@ class AvatarCustomizationFragment :
}
.subscribe({ }, RxErrorHandler.handleEmptyError())
)
compositeSubscription.add(
adapter.getUnlockCustomizationEvents()
.flatMap { customization ->
userRepository.unlockPath(userViewModel.user.value, customization)
}
.flatMap { userRepository.retrieveUser(withTasks = false, forced = true) }
.flatMap { inventoryRepository.retrieveInAppRewards() }
.subscribe({ }, RxErrorHandler.handleEmptyError())
)
compositeSubscription.add(
adapter.getUnlockSetEvents()
.flatMap { set ->
userRepository.unlockPath(set)
}
.flatMap { userRepository.retrieveUser(withTasks = false, forced = true) }
.flatMap { inventoryRepository.retrieveInAppRewards() }
.subscribe({ }, RxErrorHandler.handleEmptyError())
)
return super.onCreateView(inflater, container, savedInstanceState)
}

View file

@ -65,13 +65,6 @@ class AvatarEquipmentFragment :
}
.subscribe({ }, RxErrorHandler.handleEmptyError())
)
compositeSubscription.add(
adapter.getUnlockSetEvents()
.flatMap { set ->
userRepository.unlockPath(set)
}
.subscribe({ }, RxErrorHandler.handleEmptyError())
)
return super.onCreateView(inflater, container, savedInstanceState)
}

View file

@ -11,7 +11,6 @@ import android.view.ViewGroup
import android.view.WindowManager
import android.view.animation.AccelerateInterpolator
import android.widget.Button
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
@ -26,10 +25,10 @@ import com.habitrpg.android.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.views.login.LockableScrollView
import com.plattysoft.leonids.ParticleSystem
import java.lang.ref.WeakReference
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.lang.ref.WeakReference
open class HabiticaAlertDialog(context: Context) : AlertDialog(context, R.style.HabiticaAlertDialogTheme) {
@ -44,7 +43,7 @@ open class HabiticaAlertDialog(context: Context) : AlertDialog(context, R.style.
private val dialogContainer: LinearLayout
private var titleTextView: TextView
private var messageTextView: TextView
internal var contentView: FrameLayout
internal var contentView: ViewGroup
private var scrollingSeparator: View
internal var scrollView: LockableScrollView
protected var buttonsWrapper: LinearLayout

View file

@ -105,8 +105,11 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
checkGearClass()
}
"gems" == shopItem.purchaseType -> contentView = PurchaseDialogGemsContent(context)
"background" == shopItem.purchaseType -> contentView = PurchaseDialogBackgroundContent(context)
"background" == shopItem.purchaseType -> {
contentView = PurchaseDialogBackgroundContent(context)
}
"customization" == shopItem.purchaseType -> contentView = PurchaseDialogCustomizationContent(context)
"customizationSet" == shopItem.purchaseType -> contentView = PurchaseDialogCustomizationSetContent(context)
else -> contentView = PurchaseDialogBaseContent(context)
}
@ -283,6 +286,10 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
checkGearClass()
}
setLimitedTextView()
if (additionalContentView is PurchaseDialogBackgroundContent) {
(additionalContentView as PurchaseDialogBackgroundContent).setAvatar(userRepository.getUnmanagedCopy(user))
}
}
override fun dismiss() {
@ -354,10 +361,8 @@ 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 == "customization" || shopItem.purchaseType == "background" || shopItem.purchaseType == "customizationSet") {
observable = userRepository.unlockPath(user, item.path ?: "", 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") {

View file

@ -5,17 +5,22 @@ 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.Avatar
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
get() = ImageView(context)
override val titleTextView: TextView
get() = binding.titleTextView
override fun setItem(item: ShopItem) {
super.setItem(item)
binding.titleTextView.text = item.text
binding.notesTextView.text = item.notes
}
fun setAvatar(avatar: Avatar) {
binding.avatarView.setAvatar(avatar)
}
}

View file

@ -0,0 +1,32 @@
package com.habitrpg.android.habitica.ui.views.shops
import android.content.Context
import android.widget.ImageView
import android.widget.TextView
import com.google.android.flexbox.FlexboxLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.DialogPurchaseCustomizationsetBinding
import com.habitrpg.android.habitica.extensions.dpToPx
import com.habitrpg.android.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.ui.helpers.loadImage
class PurchaseDialogCustomizationSetContent(context: Context) : PurchaseDialogContent(context) {
val binding = DialogPurchaseCustomizationsetBinding.inflate(context.layoutInflater, this)
override val imageView: ImageView
get() = ImageView(context)
override val titleTextView: TextView
get() = binding.titleTextView
override fun setItem(item: ShopItem) {
titleTextView.text = item.text
binding.imageViewWrapper.removeAllViews()
item.setImageNames.forEach {
val imageView = ImageView(context)
imageView.setBackgroundResource(R.drawable.layout_rounded_bg_window)
imageView.loadImage(it)
imageView.layoutParams = FlexboxLayout.LayoutParams(76.dpToPx(context), 76.dpToPx(context))
binding.imageViewWrapper.addView(imageView)
}
}
}