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>
|
||||
@ -1,39 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
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:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
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>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView_filter"
|
||||
android:layout_width="240dp"
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:background="?android:windowBackground"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:scrollbars="vertical"
|
||||
android:padding="@dimen/grid_spacing_outer"
|
||||
app:fastScrollEnabled="true"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_category_checkable" />
|
||||
tools:listitem="@layout/item_manga_list" />
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
@ -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