mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-05-20 04:39:04 +00:00
show armoire in market if all gear was purchased
This commit is contained in:
parent
06c3bdb2c4
commit
c7fc3d70aa
12 changed files with 154 additions and 39 deletions
47
Habitica/res/layout/shop_armoire_gear.xml
Normal file
47
Habitica/res/layout/shop_armoire_gear.xml
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>>
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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>>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ class MainActivityViewModel @Inject constructor(
|
|||
pushNotificationManager.addPushDeviceUsingStoredToken()
|
||||
}
|
||||
}
|
||||
inventoryRepository.retrieveInAppRewards()
|
||||
contentRepository.retrieveContent()
|
||||
}
|
||||
viewModelScope.launchCatching {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue