Implement new empty states

This commit is contained in:
Phillip Thelen 2023-08-11 11:50:31 +02:00
parent 9e3578762f
commit 0be77ca124
34 changed files with 193 additions and 73 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -26,19 +26,4 @@
android:scrollbarThumbVertical="@color/scrollbarThumb"
android:scrollbars="vertical" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/footerTextView"
tools:text="Need more items. Check the Market"
android:gravity="center"
android:padding="8dp"/>
<Button
android:id="@+id/openMarketButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="@string/open_market"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"/>
</LinearLayout>

View file

@ -0,0 +1,33 @@
<?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:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:gravity="center_horizontal"
android:paddingHorizontal="50dp"
android:layout_marginVertical="30dp">
<ImageView
android:id="@+id/image_view"
android:src="@drawable/icon_shops"
android:layout_width="54dp"
android:layout_height="52dp"
/>
<TextView
android:id="@+id/title_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Not getting the right eggs from tasks?"
android:layout_marginTop="@dimen/spacing_medium"
android:gravity="center_horizontal"
style="@style/SubHeader1"/>
<TextView
android:id="@+id/description_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Check out the Market to buy just the things you need!"
style="@style/Body2"
android:gravity="center_horizontal"
android:textColor="@color/text_ternary"
android:layout_marginTop="2dp"/>
</LinearLayout>

View file

@ -1399,6 +1399,19 @@
<string name="contributor_tiers">Contributor Tiers</string>
<string name="contrib_tier_description">If you see an account with a colored display name and an icon, thats a contributor tier! Tiers are given to people who help around Habitica, whether it be for translation, coding, or just being helpful. The higher the tier, the more the player has contributed.</string>
<string name="visit_faq">Visit FAQ</string>
<string name="no_x">No %s</string>
<string name="empty_quests_description">Get Quests from leveling up, log in bonuses, or the [Quest Shop](/shops/quests)!</string>
<string name="empty_special_description_subscribed">Get transformation items during Seasonal Galas and mystery boxes of subscriber gear are delivered at the start of each month!</string>
<string name="empty_food_description">Complete tasks, buy an Armoire, or head over to the [Market](/shops/market) to stock up!</string>
<string name="empty_items_description">Complete tasks, Potion Quests, or head over to the [Market](/shops/market) to stock up!</string>
<string name="item_footer_title">Not getting the right %s from tasks?</string>
<string name="item_footer_description">Check out the [Market](/shops/market) to buy just the things you need!</string>
<string name="quests_footer_description">Check out the [Quest Shop](/shops/quests) to see whats available!</string>
<string name="quests_footer_title">Want more Quests?</string>
<string name="potions">potions</string>
<string name="eggs_footer_description">Check out the [Market](/shops/market) to buy just the eggs you need!</string>
<string name="food_footer_description">Check out the [Market](/shops/market) to buy food or a Saddle to instantly raise your Pet!</string>
<string name="hatchingPotions_footer_description">Check out the [Market](/shops/market) to buy standard and seasonal Hatching Potions!</string>
<plurals name="you_x_others">
<item quantity="zero">You</item>

View file

@ -6,8 +6,11 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ItemItemBinding
import com.habitrpg.android.habitica.databinding.ShopAdBinding
import com.habitrpg.android.habitica.models.BaseMainObject
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Food
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
@ -25,17 +28,20 @@ import com.habitrpg.android.habitica.ui.views.dialogs.DetailDialog
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.extensions.localizedCapitalizeWithSpaces
import com.habitrpg.common.habitica.helpers.setMarkdown
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedItem, ItemRecyclerAdapter.ItemViewHolder>() {
class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<BaseMainObject, ViewHolder>() {
var user: User? = null
var isHatching: Boolean = false
var isFeeding: Boolean = false
var hatchingItem: Item? = null
var feedingPet: Pet? = null
var fragment: DialogFragment? = null
var itemType = ""
var itemText = ""
private var existingPets: List<Pet>? = null
private var ownedPets: Map<String, OwnedPet>? = null
var items: Map<String, Item>? = null
@ -52,14 +58,53 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedI
var onFeedPet: ((Food) -> Unit)? = null
var onCreateNewParty: (() -> Unit)? = null
var onUseSpecialItem: ((SpecialItem) -> Unit)? = null
var onOpenShop: (() -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(ItemItemBinding.inflate(context.layoutInflater, parent, false))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (viewType == 0) {
ItemViewHolder(ItemItemBinding.inflate(context.layoutInflater, parent, false))
} else {
ShopAdViewHolder(ShopAdBinding.inflate(context.layoutInflater, parent, false))
}
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val ownedItem = data[position]
holder.bind(ownedItem, items?.get(ownedItem.key))
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (position < data.size) {
val ownedItem = data[position] as OwnedItem
(holder as? ItemViewHolder)?.bind(ownedItem, items?.get(ownedItem.key))
} else {
val typedHolder = (holder as? ShopAdViewHolder) ?: return
if (itemType == "quests") {
typedHolder.binding.imageView.setImageResource(R.drawable.icon_quests)
typedHolder.binding.titleView.text = context.getString(R.string.quests_footer_title)
typedHolder.binding.descriptionView.setMarkdown(context.getString(R.string.quests_footer_description))
} else {
typedHolder.binding.imageView.setImageResource(R.drawable.icon_shops)
typedHolder.binding.titleView.text = context.getString(R.string.item_footer_title, itemText)
typedHolder.binding.descriptionView.setMarkdown(when (itemType) {
"eggs" -> context.getString(R.string.eggs_footer_description)
"food" -> context.getString(R.string.food_footer_description)
"hatchingPotions" -> context.getString(R.string.hatchingPotions_footer_description)
else -> ""
})
}
typedHolder.itemView.setOnClickListener {
onOpenShop?.invoke()
}
}
}
override fun getItemCount(): Int {
val actualCount = super.getItemCount()
return actualCount + if (itemType == "special" || actualCount == 0) 0 else 1
}
override fun getItemViewType(position: Int): Int {
return if (position < data.size) {
0
} else {
-1
}
}
fun setExistingPets(pets: List<Pet>) {
@ -72,6 +117,8 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedI
notifyDataSetChanged()
}
inner class ShopAdViewHolder(val binding: ShopAdBinding): RecyclerView.ViewHolder(binding.root)
inner class ItemViewHolder(val binding: ItemItemBinding) : RecyclerView.ViewHolder(binding.root), View.OnClickListener {
private var ownedItem: OwnedItem? = null
var item: Item? = null

View file

@ -71,7 +71,6 @@ class EquipmentDetailFragment :
getString(R.string.empty_title),
getString(R.string.empty_equipment_description),
null,
getString(R.string.open_market)
) {
MainNavigationController.navigate(R.id.marketFragment)
}

View file

@ -13,6 +13,9 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentItemsDialogBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.EventCategory
import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
@ -108,12 +111,35 @@ class ItemDialogFragment : BaseDialogFragment<FragmentItemsDialogBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.recyclerView?.emptyItem = EmptyItem(
getString(R.string.empty_items, itemTypeText ?: itemType),
getString(R.string.open_market)
) {
openMarket()
val buttonMethod = {
Analytics.sendEvent("Items CTA tap", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf(
"area" to "empty",
"type" to (itemType ?: "")
))
if (itemType == "quests") {
MainNavigationController.navigate(R.id.questShopFragment)
} else {
openMarket()
}
}
binding?.recyclerView?.emptyItem = EmptyItem(
getString(R.string.no_x, itemTypeText ?: itemType),
when (itemType) {
"food" -> getString(R.string.empty_food_description)
"quests" -> getString(R.string.empty_quests_description)
"special" -> getString(R.string.empty_special_description_subscribed)
else -> getString(R.string.empty_items_description)
},
when (itemType) {
"eggs" -> R.drawable.icon_eggs
"hatchingPotions" -> R.drawable.icon_hatchingpotions
"food" -> R.drawable.icon_food
"quests" -> R.drawable.icon_quests
"special" -> R.drawable.icon_special
else -> null
},
false,
if (itemType == "special") null else buttonMethod)
layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
binding?.recyclerView?.layoutManager = layoutManager

View file

@ -17,6 +17,9 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentItemsBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.EventCategory
import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
import com.habitrpg.android.habitica.models.inventory.Egg
@ -90,18 +93,35 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
super.onViewCreated(view, savedInstanceState)
binding?.refreshLayout?.setOnRefreshListener(this)
binding?.recyclerView?.emptyItem = EmptyItem(
getString(R.string.empty_items, itemTypeText ?: itemType),
null,
null,
if (itemType == "special") null else getString(R.string.open_shop)
) {
val buttonMethod = {
Analytics.sendEvent("Items CTA tap", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf(
"area" to "empty",
"type" to (itemType ?: "")
))
if (itemType == "quests") {
MainNavigationController.navigate(R.id.questShopFragment)
} else {
openMarket()
}
}
binding?.recyclerView?.emptyItem = EmptyItem(
getString(R.string.no_x, itemTypeText ?: itemType),
when (itemType) {
"food" -> getString(R.string.empty_food_description)
"quests" -> getString(R.string.empty_quests_description)
"special" -> getString(R.string.empty_special_description_subscribed)
else -> getString(R.string.empty_items_description)
},
when (itemType) {
"eggs" -> R.drawable.icon_eggs
"hatchingPotions" -> R.drawable.icon_hatchingpotions
"food" -> R.drawable.icon_food
"quests" -> R.drawable.icon_quests
"special" -> R.drawable.icon_special
else -> null
},
false,
if (itemType == "special") null else buttonMethod)
layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
binding?.recyclerView?.layoutManager = layoutManager
@ -122,12 +142,6 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
}
binding?.titleTextView?.visibility = View.GONE
binding?.footerTextView?.visibility = View.GONE
binding?.openMarketButton?.visibility = View.GONE
binding?.openMarketButton?.setOnClickListener {
openMarket()
}
setAdapter()
this.loadItems()
}
@ -177,6 +191,19 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
adapter?.onStartHatching = { showHatchingDialog(it) }
adapter?.onHatchPet = { pet, egg -> hatchPet(pet, egg) }
adapter?.onCreateNewParty = { createNewParty() }
adapter?.itemType = itemType ?: ""
adapter?.itemText = (if (itemType == "hatchingPotions") context?.getString(R.string.potions) else itemTypeText) ?: ""
adapter?.onOpenShop = {
Analytics.sendEvent("Items CTA tap", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf(
"area" to "bottom",
"type" to (itemType ?: "")
))
if (itemType == "quests") {
MainNavigationController.navigate(R.id.questShopFragment)
} else {
openMarket()
}
}
}
}

View file

@ -15,7 +15,7 @@ data class EmptyItem(
var title: String,
var text: String? = null,
var iconResource: Int? = null,
var buttonLabel: String? = null,
var tintedIcon: Boolean = true,
var onButtonTap: (() -> Unit)? = null
)
@ -46,24 +46,20 @@ class EmptyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val binding = EmptyItemBinding.bind(itemView)
fun bind(emptyItem: EmptyItem?) {
binding.emptyIconView.setColorFilter(
ContextCompat.getColor(
itemView.context,
R.color.text_dimmed
),
android.graphics.PorterDuff.Mode.MULTIPLY
)
if (emptyItem?.tintedIcon == true) {
binding.emptyIconView.setColorFilter(
ContextCompat.getColor(
itemView.context,
R.color.text_dimmed
),
android.graphics.PorterDuff.Mode.MULTIPLY
)
}
emptyItem?.iconResource?.let { binding.emptyIconView.setImageResource(it) }
binding.emptyViewTitle.text = emptyItem?.title
binding.emptyViewDescription.text = emptyItem?.text
val buttonLabel = emptyItem?.buttonLabel
if (buttonLabel != null) {
binding.button.visibility = View.VISIBLE
binding.button.text = buttonLabel
binding.button.setOnClickListener { emptyItem.onButtonTap?.invoke() }
} else {
binding.button.visibility = View.GONE
binding.emptyViewDescription.setMarkdown(emptyItem?.text)
if (emptyItem?.onButtonTap != null) {
binding.emptyView.setOnClickListener { emptyItem.onButtonTap?.invoke() }
}
}
}

View file

@ -5,23 +5,25 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="30dp">
android:gravity="top|center"
android:paddingTop="56dp"
android:paddingHorizontal="24dp">
<ImageView
android:id="@+id/empty_icon_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp" />
android:layout_marginBottom="16dp" />
<TextView
android:id="@+id/emptyViewTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:layout_marginBottom="2dp"
android:gravity="center"
android:textColor="@color/text_ternary"
android:textSize="@dimen/card_medium_text"
android:textColor="@color/text_secondary"
android:textSize="16sp"
android:textStyle="bold"
tools:text="No Items" />
<TextView
@ -29,15 +31,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="14sp"
android:textSize="16sp"
android:textColor="@color/text_ternary"
tools:text="No Items" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
android:layout_marginTop="@dimen/spacing_large"/>
</LinearLayout>
</LinearLayout>

View file

@ -1,2 +1,2 @@
NAME=4.2.5
CODE=6321
CODE=6331