show armoire in market if all gear was purchased

This commit is contained in:
Phillip Thelen 2023-03-27 16:09:49 +02:00
parent 06c3bdb2c4
commit c7fc3d70aa
12 changed files with 154 additions and 39 deletions

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="@drawable/layout_rounded_bg_window"
android:layout_marginHorizontal="12dp"
android:clipToOutline="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/spacing_large"
android:paddingHorizontal="@dimen/spacing_large"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/title_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_secondary"
style="@style/Body1"/>
<TextView
android:id="@+id/description_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_ternary"
style="@style/Body2"
android:gravity="center"/>
<com.habitrpg.common.habitica.views.PixelArtView
android:id="@+id/icon_view"
android:layout_width="68dp"
android:layout_height="68dp" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/offset_background"
android:padding="8dp">
<com.habitrpg.android.habitica.ui.views.CurrencyView
android:id="@+id/currency_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:currency="gold" />
</FrameLayout>
</LinearLayout>

View file

@ -1378,6 +1378,8 @@
<string name="leave_party_finder">Leave Party Finder</string>
<string name="equipment_class_locked">This equipment is class-locked</string>
<string name="change_class_to_x">Change class to %s</string>
<string name="shop_armoire_title">You own all %s gear</string>
<string name="shop_armoire_description">New gear is released during the seasonal Galas. Until then, theres %d pieces of gear in the Enchanted Armoire to find!</string>
<plurals name="you_x_others">
<item quantity="zero">You</item>

View file

@ -22,9 +22,11 @@ import kotlinx.coroutines.flow.Flow
interface InventoryRepository : BaseRepository {
fun getArmoireRemainingCount(): Long
fun getArmoireRemainingCount(): Flow<Int>
fun getInAppRewards(): Flow<List<ShopItem>>
fun getInAppReward(key: String): Flow<ShopItem>
fun getOwnedEquipment(): Flow<List<Equipment>>
fun getMounts(): Flow<List<Mount>>

View file

@ -39,7 +39,7 @@ class InventoryRepositoryImpl(
return localRepository.getEquipment(searchedKeys)
}
override fun getArmoireRemainingCount(): Long {
override fun getArmoireRemainingCount(): Flow<Int> {
return localRepository.getArmoireRemainingCount()
}
@ -47,6 +47,10 @@ class InventoryRepositoryImpl(
return localRepository.getInAppRewards()
}
override fun getInAppReward(key : String) : Flow<ShopItem> {
return localRepository.getInAppReward(key)
}
override suspend fun retrieveInAppRewards(): List<ShopItem>? {
val rewards = apiClient.retrieveInAppRewards()
if (rewards != null) {

View file

@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.Flow
interface InventoryLocalRepository : ContentLocalRepository {
fun getArmoireRemainingCount(): Long
fun getArmoireRemainingCount(): Flow<Int>
fun getOwnedEquipment(): Flow<List<Equipment>>
fun getMounts(): Flow<List<Mount>>
@ -27,6 +27,8 @@ interface InventoryLocalRepository : ContentLocalRepository {
fun getOwnedPets(userID: String): Flow<List<OwnedPet>>
fun getInAppRewards(): Flow<List<ShopItem>>
fun getInAppReward(key: String): Flow<ShopItem>
fun getQuestContent(key: String): Flow<QuestContent?>
fun getQuestContent(keys: List<String>): Flow<List<QuestContent>>

View file

@ -57,7 +57,7 @@ class RealmInventoryLocalRepository(realm: Realm) :
.filter { it.isLoaded }
}
override fun getArmoireRemainingCount(): Long {
override fun getArmoireRemainingCount(): Flow<Int> {
return realm.where(Equipment::class.java)
.equalTo("klass", "armoire")
.beginGroup()
@ -65,7 +65,9 @@ class RealmInventoryLocalRepository(realm: Realm) :
.or()
.isNull("owned")
.endGroup()
.count()
.findAll()
.toFlow()
.map { it.count() }
}
override fun getOwnedEquipment(type: String): Flow<out List<Equipment>> {
@ -318,6 +320,16 @@ class RealmInventoryLocalRepository(realm: Realm) :
.filter { it.isLoaded }
}
override fun getInAppReward(key: String): Flow<ShopItem> {
return realm.where(ShopItem::class.java)
.equalTo("key", key)
.findAll()
.toFlow()
.filter { it.isLoaded }
.map { it.firstOrNull() }
.filterNotNull()
}
override fun saveInAppRewards(onlineItems: List<ShopItem>) {
val localItems = realm.where(ShopItem::class.java).findAll().createSnapshot()
executeTransaction {

View file

@ -27,6 +27,7 @@ import com.habitrpg.common.habitica.helpers.launchCatching
import com.plattysoft.leonids.ParticleSystem
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import java.util.Locale
import javax.inject.Inject
@ -66,9 +67,11 @@ class ArmoireActivity : BaseActivity() {
if (gold == null) {
gold = user?.stats?.gp
}
val remaining = inventoryRepository.getArmoireRemainingCount()
binding.equipmentCountView.text = getString(R.string.equipment_remaining, remaining)
binding.noEquipmentView.visibility = if (remaining > 0) View.GONE else View.VISIBLE
lifecycleScope.launchCatching {
val remaining = inventoryRepository.getArmoireRemainingCount().firstOrNull() ?: 0
binding.equipmentCountView.text = getString(R.string.equipment_remaining, remaining)
binding.noEquipmentView.visibility = if (remaining > 0) View.GONE else View.VISIBLE
}
}
if (appConfigManager.enableArmoireAds()) {

View file

@ -35,7 +35,7 @@ class DeathActivity : BaseActivity() {
@Inject
lateinit var userViewModel: MainUserViewModel
override fun getLayoutResId(): Int = R.layout.activity_armoire
override fun getLayoutResId(): Int = R.layout.activity_death
override fun getContentView(layoutResId: Int?): View {
binding = ActivityDeathBinding.inflate(layoutInflater)
@ -66,7 +66,7 @@ class DeathActivity : BaseActivity() {
binding.adButton.visibility = View.INVISIBLE
}
}
binding.adButton.updateForAdType(AdType.ARMOIRE, lifecycleScope)
binding.adButton.updateForAdType(AdType.FAINT, lifecycleScope)
binding.adButton.setOnClickListener {
binding.adButton.state = AdButton.State.LOADING
handler.show()

View file

@ -6,7 +6,9 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ShopArmoireGearBinding
import com.habitrpg.android.habitica.databinding.ShopHeaderBinding
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.helpers.MainNavigationController
@ -19,15 +21,18 @@ import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.ShopItemViewHolder
import com.habitrpg.android.habitica.ui.views.getTranslatedClassName
import com.habitrpg.common.habitica.extensions.fromHtml
import com.habitrpg.common.habitica.extensions.loadImage
class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder>() {
class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<ViewHolder>() {
var armoireCount : Int = 0
var onNeedsRefresh: (() -> Unit)? = null
var onShowPurchaseDialog: ((ShopItem, Boolean) -> Unit)? = null
private val items: MutableList<Any> = ArrayList()
private var shopIdentifier: String? = null
private var ownedItems: Map<String, OwnedItem> = HashMap()
var armoireItem: ShopItem? = null
var changeClassEvents: ((String) -> Unit)? = null
@ -88,11 +93,18 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<an
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder =
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
when (viewType) {
0 -> ShopHeaderViewHolder(parent)
1 -> SectionViewHolder(parent.inflate(R.layout.shop_section_header))
2 -> EmptyStateViewHolder(parent.inflate(emptyViewResource))
3 -> {
val viewHolder = ArmoireGearViewHolder(parent.inflate(R.layout.shop_armoire_gear))
viewHolder.itemView.setOnClickListener {
armoireItem?.let { it1 -> onShowPurchaseDialog?.invoke(it1, true) }
}
viewHolder
}
else -> {
val view = parent.inflate(R.layout.row_shopitem)
val viewHolder = ShopItemViewHolder(view)
@ -105,28 +117,27 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<an
@Suppress("ReturnCount")
override fun onBindViewHolder(
holder: androidx.recyclerview.widget.RecyclerView.ViewHolder,
holder: ViewHolder,
position: Int
) {
val obj = getItem(position)
if (obj != null) {
when (obj.javaClass) {
Shop::class.java -> (obj as? Shop)?.let { (holder as? ShopHeaderViewHolder)?.bind(it, shopSpriteSuffix) }
ShopCategory::class.java -> {
val category = obj as? ShopCategory
when (obj) {
is Shop -> (holder as? ShopHeaderViewHolder)?.bind(obj, shopSpriteSuffix)
is ShopCategory -> {
val sectionHolder = holder as? SectionViewHolder ?: return
sectionHolder.bind(category?.text ?: "")
if (gearCategories.contains(category)) {
sectionHolder.bind(obj.text)
if (gearCategories.contains(obj)) {
context?.let { context ->
val adapter = HabiticaClassArrayAdapter(context, R.layout.class_spinner_dropdown_item, gearCategories.map { it.identifier })
sectionHolder.spinnerAdapter = adapter
sectionHolder.selectedItem = gearCategories.indexOf(category)
sectionHolder.selectedItem = gearCategories.indexOf(obj)
sectionHolder.spinnerSelectionChanged = {
if (selectedGearCategory != gearCategories[holder.selectedItem].identifier) {
selectedGearCategory = gearCategories[holder.selectedItem].identifier
}
}
if (user?.stats?.habitClass != category?.identifier && category?.identifier != "none") {
if (user?.stats?.habitClass != obj.identifier && obj.identifier != "none") {
if (user?.hasClass == true) {
sectionHolder.switchClassButton?.setOnClickListener {
changeClassEvents?.invoke(selectedGearCategory)
@ -146,14 +157,14 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<an
sectionHolder.notesView?.visibility = View.GONE
}
}
ShopItem::class.java -> {
val item = obj as? ShopItem ?: return
is ShopItem -> {
val itemHolder = holder as? ShopItemViewHolder ?: return
val numberOwned = ownedItems[item.key + "-" + item.purchaseType]?.numberOwned ?: 0
itemHolder.bind(item, item.canAfford(user, 1), numberOwned)
itemHolder.isPinned = pinnedItemKeys.contains(item.key)
val numberOwned = ownedItems[obj.key + "-" + obj.purchaseType]?.numberOwned ?: 0
itemHolder.bind(obj, obj.canAfford(user, 1), numberOwned)
itemHolder.isPinned = pinnedItemKeys.contains(obj.key)
}
String::class.java -> (holder as? EmptyStateViewHolder)?.text = obj as? String
is String -> (holder as? EmptyStateViewHolder)?.text = obj
is Pair<*, *> -> (holder as? ArmoireGearViewHolder)?.bind(obj.first as? String ?: "", obj.second as? Int ?: 0)
}
}
}
@ -167,14 +178,15 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<an
return items[0]
}
if (position <= getGearItemCount()) {
val selectedGearCategory = getSelectedShopCategory()
return when {
position == 1 -> {
val category = getSelectedShopCategory()
category?.text = context?.getString(R.string.class_equipment) ?: ""
category
selectedGearCategory?.text = context?.getString(R.string.class_equipment) ?: ""
selectedGearCategory
}
(getSelectedShopCategory()?.items?.size ?: 0) <= position - 2 -> return context?.getString(R.string.equipment_empty)
else -> getSelectedShopCategory()?.items?.get(position - 2)
(selectedGearCategory?.items?.size ?: 0) <= position - 2 -> return Pair(
context?.resources?.let { getTranslatedClassName(it, selectedGearCategory?.identifier) } ?: selectedGearCategory?.identifier, armoireCount)
else -> selectedGearCategory?.items?.get(position - 2)
}
} else {
val itemPosition = position - getGearItemCount()
@ -185,10 +197,11 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<an
}
}
override fun getItemViewType(position: Int): Int = when (getItem(position)?.javaClass) {
Shop::class.java -> 0
ShopCategory::class.java -> 1
ShopItem::class.java -> 3
override fun getItemViewType(position: Int): Int = when (getItem(position)) {
is Shop -> 0
is ShopCategory -> 1
is Pair<*, *> -> 3
is ShopItem -> 4
else -> 2
}
@ -233,7 +246,7 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<an
}
}
internal class ShopHeaderViewHolder(parent: ViewGroup) : androidx.recyclerview.widget.RecyclerView.ViewHolder(parent.inflate(R.layout.shop_header)) {
internal class ShopHeaderViewHolder(parent: ViewGroup) : ViewHolder(parent.inflate(R.layout.shop_header)) {
private val binding = ShopHeaderBinding.bind(itemView)
init {
@ -249,7 +262,7 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<an
}
}
class EmptyStateViewHolder(view: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(view) {
class EmptyStateViewHolder(view: View) : ViewHolder(view) {
private val subscribeButton: Button? = itemView.findViewById(R.id.subscribeButton)
private val textView: TextView? = itemView.findViewById(R.id.textView)
@ -263,4 +276,18 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<an
textView?.text = field
}
}
class ArmoireGearViewHolder(view: View): ViewHolder(view) {
private val binding = ShopArmoireGearBinding.bind(view)
init {
binding.currencyView.value = 100.0
binding.iconView.loadImage("shop_armoire")
}
fun bind(className: String, armoireCount: Int) {
binding.titleView.text = itemView.context.getString(R.string.shop_armoire_title, className)
binding.descriptionView.text = itemView.context.getString(R.string.shop_armoire_description, armoireCount)
}
}
}

View file

@ -121,13 +121,24 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
adapter?.changeClassEvents = {
showClassChangeDialog(it)
}
lifecycleScope.launchCatching {
inventoryRepository.getInAppReward("armoire").collect {
adapter?.armoireItem = it
}
}
lifecycleScope.launchCatching {
inventoryRepository.getArmoireRemainingCount().collect {
adapter?.armoireCount = it
}
}
}
if (binding?.recyclerView?.layoutManager == null) {
layoutManager = GridLayoutManager(context, 2)
layoutManager?.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if ((adapter?.getItemViewType(position) ?: 0) < 3) {
return if ((adapter?.getItemViewType(position) ?: 0) < 4) {
layoutManager?.spanCount ?: 1
} else {
1

View file

@ -113,6 +113,7 @@ class MainActivityViewModel @Inject constructor(
pushNotificationManager.addPushDeviceUsingStoredToken()
}
}
inventoryRepository.retrieveInAppRewards()
contentRepository.retrieveContent()
}
viewModelScope.launchCatching {

View file

@ -255,6 +255,10 @@ class PurchaseDialog(context: Context, private val userRepository : UserReposito
lifecycleScope.launchCatching {
userRepository.getUser().filterNotNull().collect { setUser(it) }
}
if (item.key == "armoire") {
pinButton.visibility = View.GONE
}
}
private fun setUser(user: User) {