Implement background/customization filtering

This commit is contained in:
Phillip Thelen 2022-04-22 13:43:40 +02:00
parent ddb1650b01
commit fffae439a4
5 changed files with 367 additions and 8 deletions

View file

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_marginBottom="16dp"
android:paddingStart="@dimen/bottom_sheet_inset"
android:paddingEnd="@dimen/bottom_sheet_inset">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_large">
<TextView
style="@style/Headline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/background_filters"
android:textColor="@color/text_primary"
android:layout_gravity="center_vertical"/>
<Space
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<Button
android:id="@+id/clear_button"
style="@style/Body1_Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="@string/clear"
android:textColor="?colorPrimary"
android:textSize="16sp"
android:gravity="end|center_vertical"
android:paddingEnd="0dp"
android:paddingStart="0dp"/>
</LinearLayout>
<TextView
android:id="@+id/task_type_title"
style="@style/Caption3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/show_me"
android:textAllCaps="true"/>
<RadioGroup
android:id="@+id/show_me_wrapper"
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal"
android:layout_marginTop="12dp"
android:layout_marginBottom="24dp">
<RadioButton
android:id="@+id/show_all_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/all"
style="@style/TaskFilterRadioButton"
android:checked="true"
/>
<RadioButton
android:id="@+id/show_purchased_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/purchased"
style="@style/TaskFilterRadioButton"
android:layout_marginStart="8dp" />
</RadioGroup>
<TextView
android:id="@+id/sort_by_title"
style="@style/Caption3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/sort_by"
android:textAllCaps="true"/>
<RadioGroup
android:id="@+id/sort_by_wrapper"
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal"
android:layout_marginTop="12dp"
android:layout_marginBottom="24dp">
<RadioButton
android:id="@+id/newest_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/newest"
style="@style/TaskFilterRadioButton"
android:checked="true"
/>
<RadioButton
android:id="@+id/oldest_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/oldest"
style="@style/TaskFilterRadioButton"
android:layout_marginStart="8dp" />
</RadioGroup>
<TextView
android:id="@+id/month_released_title"
style="@style/Caption3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/sort_by"
android:textAllCaps="true"/>
<LinearLayout
android:id="@+id/month_released_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="12dp"
android:layout_marginBottom="24dp">
<CheckBox
android:id="@+id/january_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/january"
android:checked="true"
/>
<CheckBox
android:id="@+id/febuary_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/febuary"
android:checked="true"
/>
<CheckBox
android:id="@+id/march_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/march"
android:checked="true"
/>
<CheckBox
android:id="@+id/april_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/april"
android:checked="true"
/>
<CheckBox
android:id="@+id/may_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/may"
android:checked="true"
/>
<CheckBox
android:id="@+id/june_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/june"
android:checked="true"
/>
<CheckBox
android:id="@+id/july_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/july"
android:checked="true"
/>
<CheckBox
android:id="@+id/august_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/august"
android:checked="true"
/>
<CheckBox
android:id="@+id/september_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/september"
android:checked="true"
/>
<CheckBox
android:id="@+id/october_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/october"
android:checked="true"
/>
<CheckBox
android:id="@+id/november_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/november"
android:checked="true"
/>
<CheckBox
android:id="@+id/december_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/december"
android:checked="true"
/>
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.habitrpg.android.habitica.ui.activities.MainActivity">
<item android:id="@+id/action_filter"
android:icon="@drawable/ic_action_filter_list"
android:title="@string/filter"
app:showAsAction="ifRoom" />
</menu>

View file

@ -1241,4 +1241,22 @@
<string name="armoire_rate_experience_title">20% Experience points</string>
<string name="armoire_rate_experience_description">The amount gained varies randomly from 10 to 50</string>
<string name="day_start_adjustment">Day Start Adjustment</string>
<string name="purchased">Purchased</string>
<string name="show_me">Show Me</string>
<string name="background_filters">Background Filters</string>
<string name="newest">Newest</string>
<string name="oldest">Oldest</string>
<string name="sort_by">Sort By</string>
<string name="january">January</string>
<string name="febuary">Febuary</string>
<string name="march">March</string>
<string name="april">April</string>
<string name="may">May</string>
<string name="june">June</string>
<string name="july">July</string>
<string name="august">August</string>
<string name="september">September</string>
<string name="october">October</string>
<string name="november">November</string>
<string name="december">December</string>
</resources>

View file

@ -0,0 +1,12 @@
package com.habitrpg.android.habitica.models
data class CustomizationFilter(
var onlyPurchased: Boolean = false,
var ascending: Boolean = false,
var months: MutableList<String> = mutableListOf()
) {
val isFiltering: Boolean
get() {
return onlyPurchased || months.isNotEmpty()
}
}

View file

@ -2,22 +2,35 @@ package com.habitrpg.android.habitica.ui.fragments.inventory.customization
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import androidx.recyclerview.widget.GridLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.CustomizationRepository
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.BottomSheetBackgroundsFilterBinding
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.CustomizationFilter
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.user.OwnedCustomization
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.adapter.CustomizationRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.MarginDecoration
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.kotlin.combineLatest
import io.reactivex.rxjava3.subjects.BehaviorSubject
import io.reactivex.rxjava3.subjects.PublishSubject
import javax.inject.Inject
class AvatarCustomizationFragment :
@ -44,6 +57,9 @@ class AvatarCustomizationFragment :
internal var adapter: CustomizationRecyclerViewAdapter = CustomizationRecyclerViewAdapter()
internal var layoutManager: GridLayoutManager = GridLayoutManager(activity, 2)
private val currentFilter = BehaviorSubject.create<CustomizationFilter>()
private val ownedCustomizations = PublishSubject.create<List<OwnedCustomization>>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -115,6 +131,7 @@ class AvatarCustomizationFragment :
this.loadCustomizations()
userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) }
currentFilter.onNext(CustomizationFilter())
}
override fun onDestroy() {
@ -122,6 +139,22 @@ class AvatarCustomizationFragment :
super.onDestroy()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_list_customizations, menu)
}
@Suppress("ReturnCount")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_filter -> {
showFilterDialog()
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun injectFragment(component: UserComponent) {
component.inject(this)
}
@ -129,10 +162,44 @@ class AvatarCustomizationFragment :
private fun loadCustomizations() {
val type = this.type ?: return
compositeSubscription.add(
customizationRepository.getCustomizations(type, category, false).subscribe(
{
adapter.setCustomizations(if (type == "background") { it.reversed() } else { it })
},
customizationRepository.getCustomizations(type, category, false)
.combineLatest(currentFilter.toFlowable(BackpressureStrategy.DROP),
ownedCustomizations.toFlowable(BackpressureStrategy.DROP))
.subscribe(
{ (customizations, filter, ownedCustomizations) ->
if (filter.isFiltering) {
val displayedCustomizations = mutableListOf<Customization>()
for (customization in customizations) {
if (filter.onlyPurchased) {
if (ownedCustomizations.find { it.key == customization.identifier } == null) {
continue
}
}
if (filter.months.isNotEmpty()) {
if (!filter.months.contains(customization.customizationSetName?.substringAfter('.'))) {
continue
}
}
displayedCustomizations.add(customization)
}
adapter.setCustomizations(
if (!filter.ascending) {
displayedCustomizations.reversed()
} else {
displayedCustomizations
}
)
} else {
adapter.setCustomizations(
if (!filter.ascending) {
customizations.reversed()
} else {
customizations
}
)
}
adapter.ownedCustomizations = ownedCustomizations.map { it.key + "_" + it.type + "_" + it.category }
},
RxErrorHandler.handleEmptyError()
)
)
@ -154,9 +221,7 @@ class AvatarCustomizationFragment :
fun updateUser(user: User?) {
if (user == null) return
this.updateActiveCustomization(user)
val ownedCustomizations = ArrayList<String>()
user.purchased?.customizations?.filter { it.type == this.type && it.purchased }?.mapTo(ownedCustomizations) { it.key + "_" + it.type + "_" + it.category }
adapter.updateOwnership(ownedCustomizations)
ownedCustomizations.onNext(user.purchased?.customizations?.filter { it.type == this.type && it.purchased })
this.adapter.userSize = user.preferences?.size
this.adapter.hairColor = user.preferences?.hair?.color
this.adapter.gemBalance = user.gemCount
@ -192,7 +257,7 @@ class AvatarCustomizationFragment :
override fun onRefresh() {
compositeSubscription.add(
userRepository.retrieveUser(false, true).subscribe(
userRepository.retrieveUser(withTasks = false, forced = true).subscribe(
{
binding?.refreshLayout?.isRefreshing = false
},
@ -200,4 +265,59 @@ class AvatarCustomizationFragment :
)
)
}
fun showFilterDialog() {
val filter = currentFilter.value ?: CustomizationFilter()
val context = context ?: return
val dialog = HabiticaBottomSheetDialog(context)
val binding = BottomSheetBackgroundsFilterBinding.inflate(layoutInflater)
binding.showMeWrapper.check(if (filter.onlyPurchased) R.id.show_purchased_button else R.id.show_all_button)
binding.showMeWrapper.setOnCheckedChangeListener { _, checkedId ->
filter.onlyPurchased = checkedId == R.id.show_purchased_button
currentFilter.onNext(filter)
}
binding.clearButton.setOnClickListener {
currentFilter.onNext(CustomizationFilter())
dialog.dismiss()
}
if (type == "background") {
binding.sortByWrapper.check(if (filter.ascending) R.id.oldest_button else R.id.newest_button)
binding.sortByWrapper.setOnCheckedChangeListener { _, checkedId ->
filter.ascending = checkedId == R.id.oldest_button
currentFilter.onNext(filter)
}
configureMonthFilterButton(binding.januaryButton, 1, filter)
configureMonthFilterButton(binding.febuaryButton, 2, filter)
configureMonthFilterButton(binding.marchButton, 3, filter)
configureMonthFilterButton(binding.aprilButton, 4, filter)
configureMonthFilterButton(binding.mayButton, 5, filter)
configureMonthFilterButton(binding.juneButton, 6, filter)
configureMonthFilterButton(binding.julyButton, 7, filter)
configureMonthFilterButton(binding.augustButton, 8, filter)
configureMonthFilterButton(binding.septemberButton, 9, filter)
configureMonthFilterButton(binding.octoberButton, 10, filter)
configureMonthFilterButton(binding.novemberButton, 11, filter)
configureMonthFilterButton(binding.decemberButton, 12, filter)
} else {
binding.sortByTitle.visibility = View.GONE
binding.sortByWrapper.visibility = View.GONE
binding.monthReleasedTitle.visibility = View.GONE
binding.monthReleasedWrapper.visibility = View.GONE
}
dialog.setContentView(binding.root)
dialog.show()
}
private fun configureMonthFilterButton(button: CheckBox, value: Int, filter: CustomizationFilter) {
val identifier = value.toString().padStart(2, '0')
button.isChecked = filter.months.contains(identifier)
button.setOnCheckedChangeListener { _, isChecked ->
if (!isChecked && filter.months.contains(identifier)) {
filter.months.remove(identifier)
} else if (isChecked && !filter.months.contains(identifier)) {
filter.months.add(identifier)
}
currentFilter.onNext(filter)
}
}
}