From 19a3f1419052fb18cb74c6494bb359143b498ee9 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 21 Apr 2024 14:38:51 +0300 Subject: [PATCH] Fix chapters sheet interaction --- .../kotatsu/core/ui/BaseListAdapter.kt | 15 +++++++++ .../core/ui/list/ListSelectionController.kt | 10 +++--- .../org/koitharu/kotatsu/core/util/Event.kt | 2 +- .../kotatsu/core/util/ext/FlowObserver.kt | 3 +- .../kotatsu/details/ui/DetailsActivity.kt | 14 ++++++-- .../kotatsu/details/ui/DetailsMenuProvider.kt | 2 ++ .../details/ui/pager/ChaptersPagesSheet.kt | 32 ++++++++++++------- .../ui/pager/chapters/ChaptersFragment.kt | 8 +++-- .../reader/ui/ReaderBottomMenuProvider.kt | 2 +- .../ScrobblerMangaSelectionDecoration.kt | 10 ------ 10 files changed, 64 insertions(+), 34 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseListAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseListAdapter.kt index 4f385d0af..58eca0175 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseListAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseListAdapter.kt @@ -6,7 +6,12 @@ import com.hannesdorfmann.adapterdelegates4.AdapterDelegate import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.onStart import org.koitharu.kotatsu.core.util.ContinuationResumeRunnable import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.adapter.ListItemType @@ -48,4 +53,14 @@ open class BaseListAdapter : AsyncListDifferDelegationAdapter( } return null } + + fun observeItems(): Flow> = callbackFlow { + val listListener = ListListener { _, list -> + trySendBlocking(list) + } + addListListener(listListener) + awaitClose { removeListListener(listListener) } + }.onStart { + emit(items) + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ListSelectionController.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ListSelectionController.kt index e66857c99..f2f5d7e4b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ListSelectionController.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ListSelectionController.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.core.ui.list +import android.app.Notification.Action import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -80,8 +81,7 @@ class ListSelectionController( } fun onItemLongClick(id: Long): Boolean { - startActionMode() - return actionMode?.also { + return startActionMode()?.also { decoration.setItemIsChecked(id, true) notifySelectionChanged() } != null @@ -105,9 +105,9 @@ class ListSelectionController( actionMode = null } - private fun startActionMode() { - if (actionMode == null) { - actionMode = appCompatDelegate.startSupportActionMode(this) + private fun startActionMode(): ActionMode? { + return actionMode ?: appCompatDelegate.startSupportActionMode(this).also { + actionMode = it } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Event.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Event.kt index 1fd768af1..d211e9e6d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Event.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/Event.kt @@ -9,8 +9,8 @@ class Event( suspend fun consume(collector: FlowCollector) { if (!isConsumed) { - collector.emit(data) isConsumed = true + collector.emit(data) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/FlowObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/FlowObserver.kt index f5e5b68a1..1f4aa2499 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/FlowObserver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/FlowObserver.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.core.util.ext +import android.util.Log import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope @@ -28,7 +29,7 @@ fun Flow.observe(owner: LifecycleOwner, minState: Lifecycle.State, collec fun Flow?>.observeEvent(owner: LifecycleOwner, collector: FlowCollector) { owner.lifecycleScope.launch { - owner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + owner.repeatOnLifecycle(Lifecycle.State.STARTED) { collect { it?.consume(collector) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index 7ca5e7465..bd74df54c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -36,6 +36,7 @@ import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNotNull import org.koitharu.kotatsu.R import org.koitharu.kotatsu.bookmarks.domain.Bookmark @@ -54,6 +55,7 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.widgets.ChipsView +import org.koitharu.kotatsu.core.util.Event import org.koitharu.kotatsu.core.util.FileSize import org.koitharu.kotatsu.core.util.ViewBadge import org.koitharu.kotatsu.core.util.ext.crossfade @@ -156,8 +158,12 @@ class DetailsActivity : viewModel.details.filterNotNull().observe(this, ::onMangaUpdated) viewModel.onMangaRemoved.observeEvent(this, ::onMangaRemoved) viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged) - viewModel.onError.observeEvent(this, DetailsErrorObserver(this, viewModel, exceptionResolver)) - viewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.scrollView, null)) + viewModel.onError + .filterNot { ChaptersPagesSheet.isShown(supportFragmentManager) } + .observeEvent(this, DetailsErrorObserver(this, viewModel, exceptionResolver)) + viewModel.onActionDone + .filterNot { ChaptersPagesSheet.isShown(supportFragmentManager) } + .observeEvent(this, ReversibleActionObserver(viewBinding.scrollView, null)) combine(viewModel.historyInfo, viewModel.isLoading, ::Pair).observe(this) { onHistoryChanged(it.first, it.second) } @@ -177,7 +183,9 @@ class DetailsActivity : viewBinding.infoLayout.chipBranch.isVisible = it.size > 1 } viewModel.chapters.observe(this, PrefetchObserver(this)) - viewModel.onDownloadStarted.observeEvent(this, DownloadStartedObserver(viewBinding.scrollView)) + viewModel.onDownloadStarted + .filterNot { ChaptersPagesSheet.isShown(supportFragmentManager) } + .observeEvent(this, DownloadStartedObserver(viewBinding.scrollView)) addMenuProvider( DetailsMenuProvider( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt index 87ef10aa0..3f31ae0eb 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt @@ -19,6 +19,7 @@ import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.util.ShareHelper +import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet import org.koitharu.kotatsu.download.ui.dialog.DownloadOption import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet @@ -134,6 +135,7 @@ class DetailsMenuProvider( is DownloadOption.WholeManga -> null is DownloadOption.SelectionHint -> { viewModel.startChaptersSelection() + ChaptersPagesSheet.show(activity.supportFragmentManager, ChaptersPagesSheet.TAB_CHAPTERS) return } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt index 5177a8370..d9c483b44 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt @@ -7,19 +7,24 @@ import androidx.appcompat.view.ActionMode import androidx.core.view.isVisible import androidx.fragment.app.FragmentManager import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.util.ActionModeListener +import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.util.ext.doOnPageChanged import org.koitharu.kotatsu.core.util.ext.menuView +import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.recyclerView import org.koitharu.kotatsu.core.util.ext.setTabsEnabled import org.koitharu.kotatsu.core.util.ext.showDistinct import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.databinding.SheetChaptersPagesBinding import org.koitharu.kotatsu.details.ui.DetailsViewModel +import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver import javax.inject.Inject @AndroidEntryPoint @@ -39,13 +44,14 @@ class ChaptersPagesSheet : BaseAdaptiveSheet(), Actio disableFitToContents() val args = arguments ?: Bundle.EMPTY - val adapter = ChaptersPagesAdapter(this, args.getBoolean(ARG_SHOW_PAGES, settings.isPagesTabEnabled)) + val defaultTab = args.getInt(ARG_TAB, settings.defaultDetailsTab) + val adapter = ChaptersPagesAdapter(this, settings.isPagesTabEnabled || defaultTab == TAB_PAGES) binding.pager.recyclerView?.isNestedScrollingEnabled = false binding.pager.offscreenPageLimit = adapter.itemCount binding.pager.adapter = adapter binding.pager.doOnPageChanged(::onPageChanged) TabLayoutMediator(binding.tabs, binding.pager, adapter).attach() - binding.pager.setCurrentItem(args.getInt(ARG_TAB, settings.defaultDetailsTab), false) + binding.pager.setCurrentItem(defaultTab, false) binding.tabs.isVisible = adapter.itemCount > 1 val menuProvider = ChapterPagesMenuProvider(viewModel, this, binding.pager, settings) @@ -53,16 +59,20 @@ class ChaptersPagesSheet : BaseAdaptiveSheet(), Actio binding.toolbar.addMenuProvider(menuProvider) actionModeDelegate.addListener(this, viewLifecycleOwner) + + viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.pager, this)) + viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.pager, null)) + viewModel.onDownloadStarted.observeEvent(viewLifecycleOwner, DownloadStartedObserver(binding.pager)) } override fun onActionModeStarted(mode: ActionMode) { expandAndLock() - viewBinding?.toolbar?.menuView?.isEnabled = false + viewBinding?.toolbar?.menuView?.isVisible = false } override fun onActionModeFinished(mode: ActionMode) { unlock() - viewBinding?.toolbar?.menuView?.isEnabled = true + viewBinding?.toolbar?.menuView?.isVisible = true } override fun expandAndLock() { @@ -93,6 +103,8 @@ class ChaptersPagesSheet : BaseAdaptiveSheet(), Actio const val TAB_PAGES = 1 const val TAB_BOOKMARKS = 2 private const val ARG_TAB = "tag" + + @Deprecated("") private const val ARG_SHOW_PAGES = "pages" private const val TAG = "ChaptersPagesSheet" @@ -100,17 +112,15 @@ class ChaptersPagesSheet : BaseAdaptiveSheet(), Actio ChaptersPagesSheet().showDistinct(fm, TAG) } - fun show(fm: FragmentManager, showPagesTab: Boolean) { + fun show(fm: FragmentManager, defaultTab: Int) { ChaptersPagesSheet().withArgs(1) { - putBoolean(ARG_SHOW_PAGES, showPagesTab) + putInt(ARG_TAB, defaultTab) }.showDistinct(fm, TAG) } - fun show(fm: FragmentManager, showPagesTab: Boolean, defaultTab: Int) { - ChaptersPagesSheet().withArgs(2) { - putBoolean(ARG_SHOW_PAGES, showPagesTab) - putInt(ARG_TAB, defaultTab) - }.showDistinct(fm, TAG) + fun isShown(fm: FragmentManager): Boolean { + val sheet = fm.findFragmentByTag(TAG) as? ChaptersPagesSheet + return sheet != null && sheet.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt index e2204722f..e88c5329b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt @@ -14,6 +14,7 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import org.koitharu.kotatsu.R @@ -90,8 +91,11 @@ class ChaptersFragment : viewModel.isChaptersEmpty.observe(viewLifecycleOwner) { binding.textViewHolder.isVisible = it } - viewModel.onSelectChapter.observeEvent(viewLifecycleOwner) { - selectionController?.onItemLongClick(it) + viewModel.onSelectChapter.observeEvent(viewLifecycleOwner) { chapterId -> + chaptersAdapter?.observeItems()?.firstOrNull { items -> + items.any { x -> x is ChapterListItem && x.chapter.id == chapterId } + } + selectionController?.onItemLongClick(chapterId) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderBottomMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderBottomMenuProvider.kt index 462c256e1..5cdee3e18 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderBottomMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderBottomMenuProvider.kt @@ -59,7 +59,7 @@ class ReaderBottomMenuProvider( } R.id.action_pages_thumbs -> { - ChaptersPagesSheet.show(activity.supportFragmentManager, true, ChaptersPagesSheet.TAB_PAGES) + ChaptersPagesSheet.show(activity.supportFragmentManager, ChaptersPagesSheet.TAB_PAGES) true } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblerMangaSelectionDecoration.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblerMangaSelectionDecoration.kt index 4fa7f1a6c..46715d84c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblerMangaSelectionDecoration.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblerMangaSelectionDecoration.kt @@ -38,15 +38,5 @@ class ScrobblerMangaSelectionDecoration(context: Context) : MangaSelectionDecora paint.color = strokeColor paint.style = Paint.Style.STROKE canvas.drawRoundRect(bounds, defaultRadius, defaultRadius, paint) - checkIcon?.run { - val offset = (bounds.height() - intrinsicHeight) / 2 - setBounds( - (bounds.right - offset - intrinsicWidth).toInt(), - (bounds.top + offset).toInt(), - (bounds.right - offset).toInt(), - (bounds.top + offset + intrinsicHeight).toInt(), - ) - draw(canvas) - } } }