Refactor FilterOwner

This commit is contained in:
Koitharu
2023-06-17 16:05:08 +03:00
parent 183a61272e
commit 71b14a3aa8
9 changed files with 52 additions and 94 deletions

View File

@@ -22,6 +22,7 @@ import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.ext.lifecycleScope import org.koitharu.kotatsu.core.util.ext.lifecycleScope
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.require import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel
import org.koitharu.kotatsu.filter.ui.model.FilterItem import org.koitharu.kotatsu.filter.ui.model.FilterItem
@@ -35,7 +36,6 @@ import org.koitharu.kotatsu.parsers.util.SuspendLazy
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import org.koitharu.kotatsu.search.domain.MangaSearchRepository import org.koitharu.kotatsu.search.domain.MangaSearchRepository
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import java.text.Collator import java.text.Collator
import java.util.LinkedList import java.util.LinkedList
import java.util.Locale import java.util.Locale
@@ -49,7 +49,7 @@ class FilterCoordinator @Inject constructor(
dataRepository: MangaDataRepository, dataRepository: MangaDataRepository,
private val searchRepository: MangaSearchRepository, private val searchRepository: MangaSearchRepository,
lifecycle: ViewModelLifecycle, lifecycle: ViewModelLifecycle,
) : FilterOwner { ) : MangaFilter {
private val coroutineScope = lifecycle.lifecycleScope private val coroutineScope = lifecycle.lifecycleScope
private val repository = mangaRepositoryFactory.create(savedStateHandle.require(RemoteListFragment.ARG_SOURCE)) private val repository = mangaRepositoryFactory.create(savedStateHandle.require(RemoteListFragment.ARG_SOURCE))

View File

@@ -19,9 +19,8 @@ import com.google.android.material.R as materialR
class FilterHeaderFragment : BaseFragment<FragmentFilterHeaderBinding>(), ChipsView.OnChipClickListener { class FilterHeaderFragment : BaseFragment<FragmentFilterHeaderBinding>(), ChipsView.OnChipClickListener {
private val owner by lazy(LazyThreadSafetyMode.NONE) { private val filter: MangaFilter
FilterOwner.from(requireActivity()) get() = (requireActivity() as FilterOwner).filter
}
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFilterHeaderBinding { override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFilterHeaderBinding {
return FragmentFilterHeaderBinding.inflate(inflater, container, false) return FragmentFilterHeaderBinding.inflate(inflater, container, false)
@@ -30,7 +29,7 @@ class FilterHeaderFragment : BaseFragment<FragmentFilterHeaderBinding>(), ChipsV
override fun onViewBindingCreated(binding: FragmentFilterHeaderBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: FragmentFilterHeaderBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
binding.chipsTags.onChipClickListener = this binding.chipsTags.onChipClickListener = this
owner.header.observe(viewLifecycleOwner, ::onDataChanged) filter.header.observe(viewLifecycleOwner, ::onDataChanged)
} }
override fun onWindowInsetsChanged(insets: Insets) = Unit override fun onWindowInsetsChanged(insets: Insets) = Unit
@@ -40,7 +39,7 @@ class FilterHeaderFragment : BaseFragment<FragmentFilterHeaderBinding>(), ChipsV
if (tag == null) { if (tag == null) {
FilterSheetFragment.show(parentFragmentManager) FilterSheetFragment.show(parentFragmentManager)
} else { } else {
owner.onTagItemClick(FilterItem.Tag(tag, !chip.isChecked)) filter.onTagItemClick(FilterItem.Tag(tag, !chip.isChecked))
} }
} }

View File

@@ -1,32 +1,6 @@
package org.koitharu.kotatsu.filter.ui package org.koitharu.kotatsu.filter.ui
import androidx.fragment.app.Fragment interface FilterOwner {
import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.flow.StateFlow
import org.koitharu.kotatsu.core.util.ext.values
import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.MangaTag
interface FilterOwner : OnFilterChangedListener { val filter: MangaFilter
val filterItems: StateFlow<List<ListModel>>
val header: StateFlow<FilterHeaderModel>
fun applyFilter(tags: Set<MangaTag>)
companion object {
fun from(activity: FragmentActivity): FilterOwner {
for (f in activity.supportFragmentManager.fragments) {
return find(f) ?: continue
}
error("Cannot find FilterOwner")
}
fun find(fragment: Fragment): FilterOwner? {
return fragment.viewModelStore.values.firstNotNullOfOrNull { it as? FilterOwner }
}
}
} }

View File

@@ -21,20 +21,17 @@ class FilterSheetFragment :
AdaptiveSheetCallback, AdaptiveSheetCallback,
AsyncListDiffer.ListListener<ListModel> { AsyncListDiffer.ListListener<ListModel> {
private val owner by lazy(LazyThreadSafetyMode.NONE) {
FilterOwner.from(requireActivity())
}
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding { override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding {
return SheetFilterBinding.inflate(inflater, container, false) return SheetFilterBinding.inflate(inflater, container, false)
} }
override fun onViewBindingCreated(binding: SheetFilterBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: SheetFilterBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
val filter = (requireActivity() as FilterOwner).filter
addSheetCallback(this) addSheetCallback(this)
val adapter = FilterAdapter(owner, this) val adapter = FilterAdapter(filter, this)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
owner.filterItems.observe(viewLifecycleOwner, adapter::setItems) filter.filterItems.observe(viewLifecycleOwner, adapter::setItems)
if (dialog == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (dialog == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
binding.recyclerView.scrollIndicators = 0 binding.recyclerView.scrollIndicators = 0

View File

@@ -0,0 +1,15 @@
package org.koitharu.kotatsu.filter.ui
import kotlinx.coroutines.flow.StateFlow
import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.MangaTag
interface MangaFilter : OnFilterChangedListener {
val filterItems: StateFlow<List<ListModel>>
val header: StateFlow<FilterHeaderModel>
fun applyFilter(tags: Set<MangaTag>)
}

View File

@@ -10,7 +10,6 @@ import androidx.core.net.toUri
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.flow.StateFlow
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.core.util.ShareHelper
@@ -20,18 +19,18 @@ import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.filter.ui.FilterOwner import org.koitharu.kotatsu.filter.ui.FilterOwner
import org.koitharu.kotatsu.filter.ui.FilterSheetFragment import org.koitharu.kotatsu.filter.ui.FilterSheetFragment
import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel import org.koitharu.kotatsu.filter.ui.MangaFilter
import org.koitharu.kotatsu.filter.ui.model.FilterItem
import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
class LocalListFragment : MangaListFragment(), FilterOwner { class LocalListFragment : MangaListFragment(), FilterOwner {
override val viewModel by viewModels<LocalListViewModel>() override val viewModel by viewModels<LocalListViewModel>()
override val filter: MangaFilter
get() = viewModel
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
addMenuProvider(LocalListMenuProvider(this::onEmptyActionClick)) addMenuProvider(LocalListMenuProvider(this::onEmptyActionClick))
@@ -71,24 +70,6 @@ class LocalListFragment : MangaListFragment(), FilterOwner {
} }
} }
override val filterItems: StateFlow<List<ListModel>>
get() = viewModel.filterItems
override val header: StateFlow<FilterHeaderModel>
get() = viewModel.header
override fun applyFilter(tags: Set<MangaTag>) {
viewModel.applyFilter(tags)
}
override fun onSortItemClick(item: FilterItem.Sort) {
viewModel.onSortItemClick(item)
}
override fun onTagItemClick(item: FilterItem.Tag) {
viewModel.onTagItemClick(item)
}
private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode) { private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode) {
MaterialAlertDialogBuilder(context ?: return) MaterialAlertDialogBuilder(context ?: return)
.setTitle(R.string.delete_manga) .setTitle(R.string.delete_manga)

View File

@@ -10,7 +10,6 @@ import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.StateFlow
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.addMenuProvider
@@ -18,13 +17,10 @@ import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.filter.ui.FilterOwner import org.koitharu.kotatsu.filter.ui.FilterOwner
import org.koitharu.kotatsu.filter.ui.FilterSheetFragment import org.koitharu.kotatsu.filter.ui.FilterSheetFragment
import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel import org.koitharu.kotatsu.filter.ui.MangaFilter
import org.koitharu.kotatsu.filter.ui.model.FilterItem
import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.settings.SettingsActivity
@@ -33,6 +29,9 @@ class RemoteListFragment : MangaListFragment(), FilterOwner {
override val viewModel by viewModels<RemoteListViewModel>() override val viewModel by viewModels<RemoteListViewModel>()
override val filter: MangaFilter
get() = viewModel
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
addMenuProvider(RemoteListMenuProvider()) addMenuProvider(RemoteListMenuProvider())
@@ -55,24 +54,6 @@ class RemoteListFragment : MangaListFragment(), FilterOwner {
viewModel.resetFilter() viewModel.resetFilter()
} }
override val filterItems: StateFlow<List<ListModel>>
get() = viewModel.filterItems
override val header: StateFlow<FilterHeaderModel>
get() = viewModel.header
override fun applyFilter(tags: Set<MangaTag>) {
viewModel.applyFilter(tags)
}
override fun onSortItemClick(item: FilterItem.Sort) {
viewModel.onSortItemClick(item)
}
override fun onTagItemClick(item: FilterItem.Tag) {
viewModel.onTagItemClick(item)
}
private inner class RemoteListMenuProvider : private inner class RemoteListMenuProvider :
MenuProvider, MenuProvider,
SearchView.OnQueryTextListener, SearchView.OnQueryTextListener,

View File

@@ -20,10 +20,11 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.require import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.filter.ui.FilterOwner import org.koitharu.kotatsu.filter.ui.MangaFilter
import org.koitharu.kotatsu.filter.ui.model.FilterState import org.koitharu.kotatsu.filter.ui.model.FilterState
import org.koitharu.kotatsu.list.domain.ListExtraProvider import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
@@ -36,7 +37,6 @@ import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import javax.inject.Inject import javax.inject.Inject
private const val FILTER_MIN_INTERVAL = 250L private const val FILTER_MIN_INTERVAL = 250L
@@ -49,7 +49,7 @@ open class RemoteListViewModel @Inject constructor(
settings: AppSettings, settings: AppSettings,
listExtraProvider: ListExtraProvider, listExtraProvider: ListExtraProvider,
downloadScheduler: DownloadWorker.Scheduler, downloadScheduler: DownloadWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler), FilterOwner by filter { ) : MangaListViewModel(settings, downloadScheduler), MangaFilter by filter {
val source = savedStateHandle.require<MangaSource>(RemoteListFragment.ARG_SOURCE) val source = savedStateHandle.require<MangaSource>(RemoteListFragment.ARG_SOURCE)
private val repository = mangaRepositoryFactory.create(source) private val repository = mangaRepositoryFactory.create(source)

View File

@@ -26,6 +26,7 @@ import org.koitharu.kotatsu.databinding.ActivityMangaListBinding
import org.koitharu.kotatsu.filter.ui.FilterHeaderFragment import org.koitharu.kotatsu.filter.ui.FilterHeaderFragment
import org.koitharu.kotatsu.filter.ui.FilterOwner import org.koitharu.kotatsu.filter.ui.FilterOwner
import org.koitharu.kotatsu.filter.ui.FilterSheetFragment import org.koitharu.kotatsu.filter.ui.FilterSheetFragment
import org.koitharu.kotatsu.filter.ui.MangaFilter
import org.koitharu.kotatsu.local.ui.LocalListFragment import org.koitharu.kotatsu.local.ui.LocalListFragment
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
@@ -35,11 +36,16 @@ import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
@AndroidEntryPoint @AndroidEntryPoint
class MangaListActivity : class MangaListActivity :
BaseActivity<ActivityMangaListBinding>(), BaseActivity<ActivityMangaListBinding>(),
AppBarOwner, View.OnClickListener { AppBarOwner, View.OnClickListener, FilterOwner {
override val appBar: AppBarLayout override val appBar: AppBarLayout
get() = viewBinding.appbar get() = viewBinding.appbar
override val filter: MangaFilter
get() = checkNotNull(findFilterOwner()) {
"Cannot find FilterOwner fragment in ${supportFragmentManager.fragments}"
}.filter
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityMangaListBinding.inflate(layoutInflater)) setContentView(ActivityMangaListBinding.inflate(layoutInflater))
@@ -109,13 +115,14 @@ class MangaListActivity :
} }
} }
} }
val filter = filterOwner.filter
val chipSort = viewBinding.chipSort val chipSort = viewBinding.chipSort
if (chipSort != null) { if (chipSort != null) {
filterOwner.header.observe(this) { filter.header.observe(this) {
chipSort.setTextAndVisible(it.sortOrder?.titleRes ?: 0) chipSort.setTextAndVisible(it.sortOrder?.titleRes ?: 0)
} }
} else { } else {
filterOwner.header.map { filter.header.map {
it.textSummary it.textSummary
}.flowOn(Dispatchers.Default) }.flowOn(Dispatchers.Default)
.observe(this) { .observe(this) {
@@ -124,13 +131,17 @@ class MangaListActivity :
} }
} }
private fun findFilterOwner(): FilterOwner? {
return supportFragmentManager.findFragmentById(R.id.container) as? FilterOwner
}
private class ApplyFilterRunnable( private class ApplyFilterRunnable(
private val filterOwner: FilterOwner, private val filterOwner: FilterOwner,
private val tags: Set<MangaTag>, private val tags: Set<MangaTag>,
) : Runnable { ) : Runnable {
override fun run() { override fun run() {
filterOwner.applyFilter(tags) filterOwner.filter.applyFilter(tags)
} }
} }