Move filter into bottom sheet
parent
28a4d4164e
commit
238bc89be9
@ -1,10 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.model
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data class MangaFilter(
|
|
||||||
val sortOrder: SortOrder?,
|
|
||||||
val tags: Set<MangaTag>,
|
|
||||||
) : Parcelable
|
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
package org.koitharu.kotatsu.list.ui.filter
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||||
|
import org.koitharu.kotatsu.core.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.databinding.SheetFilterBinding
|
||||||
|
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||||
|
|
||||||
|
class FilterBottomSheet : BaseBottomSheet<SheetFilterBinding>() {
|
||||||
|
|
||||||
|
private val viewModel by viewModel<FilterViewModel> {
|
||||||
|
parametersOf(
|
||||||
|
requireArguments().getParcelable<MangaSource>(ARG_SOURCE),
|
||||||
|
requireArguments().getParcelable<FilterState>(ARG_STATE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding {
|
||||||
|
return SheetFilterBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.toolbar.setNavigationOnClickListener { dismiss() }
|
||||||
|
if (!resources.getBoolean(R.bool.is_tablet)) {
|
||||||
|
binding.toolbar.navigationIcon = null
|
||||||
|
}
|
||||||
|
val adapter = FilterAdapter(viewModel)
|
||||||
|
binding.recyclerView.adapter = adapter
|
||||||
|
viewModel.filter.observe(viewLifecycleOwner, adapter::setItems)
|
||||||
|
viewModel.result.observe(viewLifecycleOwner) {
|
||||||
|
parentFragmentManager.setFragmentResult(REQUEST_KEY, bundleOf(ARG_STATE to it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?) = super.onCreateDialog(savedInstanceState).also {
|
||||||
|
val behavior = (it as? BottomSheetDialog)?.behavior ?: return@also
|
||||||
|
behavior.addBottomSheetCallback(
|
||||||
|
object : BottomSheetBehavior.BottomSheetCallback() {
|
||||||
|
|
||||||
|
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
|
||||||
|
|
||||||
|
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||||
|
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||||
|
binding.toolbar.setNavigationIcon(R.drawable.ic_cross)
|
||||||
|
} else {
|
||||||
|
binding.toolbar.navigationIcon = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val REQUEST_KEY = "filter"
|
||||||
|
|
||||||
|
const val ARG_STATE = "state"
|
||||||
|
private const val TAG = "FilterBottomSheet"
|
||||||
|
private const val ARG_SOURCE = "source"
|
||||||
|
|
||||||
|
fun show(
|
||||||
|
fm: FragmentManager,
|
||||||
|
source: MangaSource,
|
||||||
|
state: FilterState,
|
||||||
|
) = FilterBottomSheet().withArgs(2) {
|
||||||
|
putParcelable(ARG_SOURCE, source)
|
||||||
|
putParcelable(ARG_STATE, state)
|
||||||
|
}.show(fm, TAG)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package org.koitharu.kotatsu.list.ui.filter
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import org.koitharu.kotatsu.core.model.MangaTag
|
||||||
|
import org.koitharu.kotatsu.core.model.SortOrder
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class FilterState(
|
||||||
|
val sortOrder: SortOrder?,
|
||||||
|
val tags: Set<MangaTag>,
|
||||||
|
) : Parcelable
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
package org.koitharu.kotatsu.list.ui.filter
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||||
|
import org.koitharu.kotatsu.core.model.SortOrder
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class FilterViewModel(
|
||||||
|
private val repository: MangaRepository,
|
||||||
|
state: FilterState,
|
||||||
|
) : BaseViewModel(), OnFilterChangedListener {
|
||||||
|
|
||||||
|
val filter = MutableLiveData<List<FilterItem>>()
|
||||||
|
val result = MutableLiveData<FilterState>()
|
||||||
|
private var job: Job? = null
|
||||||
|
private var selectedSortOrder: SortOrder? = state.sortOrder
|
||||||
|
private val selectedTags = HashSet(state.tags)
|
||||||
|
private val availableTagsDeferred = viewModelScope.async(Dispatchers.Default + createErrorHandler()) {
|
||||||
|
repository.getTags()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
showFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSortItemClick(item: FilterItem.Sort) {
|
||||||
|
selectedSortOrder = item.order
|
||||||
|
updateFilters()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTagItemClick(item: FilterItem.Tag) {
|
||||||
|
val isModified = if (item.isChecked) {
|
||||||
|
selectedTags.remove(item.tag)
|
||||||
|
} else {
|
||||||
|
selectedTags.add(item.tag)
|
||||||
|
}
|
||||||
|
if (isModified) {
|
||||||
|
updateFilters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateFilters() {
|
||||||
|
val previousJob = job
|
||||||
|
job = launchJob(Dispatchers.Default) {
|
||||||
|
previousJob?.cancelAndJoin()
|
||||||
|
val tags = availableTagsDeferred.await()
|
||||||
|
val sortOrders = repository.sortOrders
|
||||||
|
val list = ArrayList<FilterItem>(sortOrders.size + tags.size + 2)
|
||||||
|
list.add(FilterItem.Header(R.string.sort_order))
|
||||||
|
sortOrders.sortedBy { it.ordinal }.mapTo(list) {
|
||||||
|
FilterItem.Sort(it, isSelected = it == selectedSortOrder)
|
||||||
|
}
|
||||||
|
if (tags.isNotEmpty() || selectedTags.isNotEmpty()) {
|
||||||
|
list.add(FilterItem.Header(R.string.genres))
|
||||||
|
val mappedTags = TreeSet<FilterItem.Tag>(compareBy({ !it.isChecked }, { it.tag.title }))
|
||||||
|
tags.mapTo(mappedTags) { FilterItem.Tag(it, isChecked = it in selectedTags) }
|
||||||
|
selectedTags.mapTo(mappedTags) { FilterItem.Tag(it, isChecked = true) }
|
||||||
|
list.addAll(mappedTags)
|
||||||
|
}
|
||||||
|
ensureActive()
|
||||||
|
filter.postValue(list)
|
||||||
|
}
|
||||||
|
result.value = FilterState(selectedSortOrder, selectedTags)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showFilter() {
|
||||||
|
job = launchJob(Dispatchers.Default) {
|
||||||
|
val sortOrders = repository.sortOrders
|
||||||
|
val list = ArrayList<FilterItem>(sortOrders.size + selectedTags.size + 3)
|
||||||
|
list.add(FilterItem.Header(R.string.sort_order))
|
||||||
|
sortOrders.sortedBy { it.ordinal }.mapTo(list) {
|
||||||
|
FilterItem.Sort(it, isSelected = it == selectedSortOrder)
|
||||||
|
}
|
||||||
|
if (selectedTags.isNotEmpty()) {
|
||||||
|
list.add(FilterItem.Header(R.string.genres))
|
||||||
|
selectedTags.sortedBy { it.title }.mapTo(list) {
|
||||||
|
FilterItem.Tag(it, isChecked = it in selectedTags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.add(FilterItem.Loading)
|
||||||
|
filter.postValue(list)
|
||||||
|
updateFilters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,50 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout
|
|
||||||
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"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:animateLayoutChanges="true"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
||||||
android:id="@+id/swipeRefreshLayout"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/recyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/grid_spacing_outer"
|
|
||||||
app:fastScrollEnabled="true"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
tools:listitem="@layout/item_manga_list" />
|
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/divider_filter"
|
|
||||||
android:layout_width="1dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="?attr/colorOutline"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/recyclerView_filter"
|
|
||||||
android:layout_width="240dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:background="?android:windowBackground"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:scrollbars="vertical"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
tools:listitem="@layout/item_category_checkable"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:navigationIcon="@drawable/ic_cross"
|
||||||
|
app:title="@string/filter" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:fastScrollEnabled="true"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/item_category_checkable" />
|
||||||
|
</LinearLayout>
|
||||||
Loading…
Reference in New Issue