diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksFragment.kt index 8ad428eaf..7f2f5f762 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksFragment.kt @@ -46,7 +46,7 @@ class AllBookmarksFragment : BaseFragment(), ListStateHolderListener, OnListItemClickListener, - ListSelectionController.Callback2, + ListSelectionController.Callback, FastScroller.FastScrollListener, ListHeaderClickListener { @Inject diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BaseListSelectionCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BaseListSelectionCallback.kt new file mode 100644 index 000000000..94e6b30b7 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BaseListSelectionCallback.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.core.ui.list + +import androidx.recyclerview.widget.RecyclerView + +abstract class BaseListSelectionCallback( + protected val recyclerView: RecyclerView, +) : ListSelectionController.Callback { + + override fun onSelectionChanged(controller: ListSelectionController, count: Int) { + recyclerView.invalidateItemDecorations() + } +} 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 db0cdef05..94ce689dc 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 @@ -25,7 +25,7 @@ class ListSelectionController( private val appCompatDelegate: AppCompatDelegate, private val decoration: AbstractSelectionItemDecoration, private val registryOwner: SavedStateRegistryOwner, - private val callback: Callback2, + private val callback: Callback, ) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider { private var actionMode: ActionMode? = null @@ -130,43 +130,7 @@ class ListSelectionController( notifySelectionChanged() } - @Deprecated("") - interface Callback : Callback2 { - - fun onSelectionChanged(count: Int) - - fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean - - fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean - - fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean - - fun onDestroyActionMode(mode: ActionMode) = Unit - - override fun onSelectionChanged(controller: ListSelectionController, count: Int) { - onSelectionChanged(count) - } - - override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { - return onCreateActionMode(mode, menu) - } - - override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { - return onPrepareActionMode(mode, menu) - } - - override fun onActionItemClicked( - controller: ListSelectionController, - mode: ActionMode, - item: MenuItem, - ): Boolean = onActionItemClicked(mode, item) - - override fun onDestroyActionMode(controller: ListSelectionController, mode: ActionMode) { - onDestroyActionMode(mode) - } - } - - interface Callback2 { + interface Callback { fun onSelectionChanged(controller: ListSelectionController, count: Int) 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 f7ed9000f..9d93ab73b 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 @@ -93,8 +93,8 @@ class ChaptersPagesSheet : BaseAdaptiveSheet(), Actio } override fun onActionModeStarted(mode: ActionMode) { - expandAndLock() viewBinding?.toolbar?.menuView?.isVisible = false + view?.post(::expandAndLock) } override fun onActionModeFinished(mode: ActionMode) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt index ab00346ea..8c68a133f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt @@ -40,7 +40,7 @@ import javax.inject.Inject @AndroidEntryPoint class BookmarksFragment : BaseFragment(), - OnListItemClickListener, ListSelectionController.Callback2 { + OnListItemClickListener, ListSelectionController.Callback { private val activityViewModel by activityViewModels() private val viewModel by viewModels() 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 ed4e5c805..d7c147761 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 @@ -2,11 +2,8 @@ package org.koitharu.kotatsu.details.ui.pager.chapters import android.os.Bundle import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.appcompat.view.ActionMode import androidx.core.graphics.Insets import androidx.core.view.ancestors import androidx.core.view.isVisible @@ -14,14 +11,12 @@ import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.viewpager2.widget.ViewPager2 -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 kotlinx.coroutines.withContext import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.model.LocalMangaSource import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener @@ -32,8 +27,6 @@ import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate import org.koitharu.kotatsu.core.util.ext.findParentCallback import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent -import org.koitharu.kotatsu.core.util.ext.toCollection -import org.koitharu.kotatsu.core.util.ext.toSet import org.koitharu.kotatsu.databinding.FragmentChaptersBinding import org.koitharu.kotatsu.details.ui.DetailsViewModel import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter @@ -42,7 +35,6 @@ import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.withVolumeHeaders import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.model.ListModel -import org.koitharu.kotatsu.local.ui.LocalChaptersRemoveService import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder import org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback import org.koitharu.kotatsu.reader.ui.ReaderState @@ -50,8 +42,7 @@ import kotlin.math.roundToInt class ChaptersFragment : BaseFragment(), - OnListItemClickListener, - ListSelectionController.Callback2 { + OnListItemClickListener { private val viewModel by activityViewModels() @@ -70,7 +61,7 @@ class ChaptersFragment : appCompatDelegate = checkNotNull(findAppCompatDelegate()), decoration = ChaptersSelectionDecoration(binding.root.context), registryOwner = this, - callback = this, + callback = ChaptersSelectionCallback(viewModel, binding.recyclerViewChapters), ) viewModel.isChaptersInGridView.observe(viewLifecycleOwner) { chaptersInGridView -> binding.recyclerViewChapters.layoutManager = if (chaptersInGridView) { @@ -127,126 +118,6 @@ class ChaptersFragment : return selectionController?.onItemLongClick(item.chapter.id) ?: false } - override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.action_save -> { - viewModel.download(selectionController?.snapshot()) - mode.finish() - true - } - - R.id.action_delete -> { - val ids = selectionController?.peekCheckedIds() - val manga = viewModel.manga.value - when { - ids == null || ids.isEmpty() || manga == null -> Unit - ids.size == manga.chapters?.size -> viewModel.deleteLocal() - else -> { - LocalChaptersRemoveService.start(requireContext(), manga, ids.toSet()) - Snackbar.make( - requireViewBinding().recyclerViewChapters, - R.string.chapters_will_removed_background, - Snackbar.LENGTH_LONG, - ).show() - } - } - mode.finish() - true - } - - R.id.action_select_range -> { - val items = chaptersAdapter?.items ?: return false - val ids = controller.peekCheckedIds().toCollection(HashSet()) - val buffer = HashSet() - var isAdding = false - for (x in items) { - if (x !is ChapterListItem) { - continue - } - if (x.chapter.id in ids) { - isAdding = true - if (buffer.isNotEmpty()) { - ids.addAll(buffer) - buffer.clear() - } - } else if (isAdding) { - buffer.add(x.chapter.id) - } - } - controller.addAll(ids) - true - } - - R.id.action_select_all -> { - val ids = chaptersAdapter?.items?.mapNotNull { - if (it is ChapterListItem) { - it.chapter.id - } else { - null - } - } ?: return false - controller.addAll(ids) - true - } - - R.id.action_mark_current -> { - val ids = controller.peekCheckedIds() - if (ids.size == 1) { - viewModel.markChapterAsCurrent(ids.first()) - } else { - return false - } - mode.finish() - true - } - - else -> false - } - } - - override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { - mode.menuInflater.inflate(R.menu.mode_chapters, menu) - return true - } - - override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { - val selectedIds = selectionController?.peekCheckedIds() ?: return false - val allItems = chaptersAdapter?.items.orEmpty() - val items = allItems.withIndex().mapNotNull, IndexedValue> { x -> - val value = x.value - @Suppress("UNCHECKED_CAST") - if (value is ChapterListItem && value.chapter.id in selectedIds) { - x as IndexedValue - } else { - null - } - } - var canSave = true - var canDelete = true - items.forEach { (_, x) -> - val isLocal = x.isDownloaded || x.chapter.source == LocalMangaSource - if (isLocal) canSave = false else canDelete = false - } - menu.findItem(R.id.action_save).isVisible = canSave - menu.findItem(R.id.action_delete).isVisible = canDelete - menu.findItem(R.id.action_select_all).isVisible = items.size < allItems.size - menu.findItem(R.id.action_mark_current).isVisible = items.size == 1 - mode.title = items.size.toString() - var hasGap = false - for (i in 0 until items.size - 1) { - if (items[i].index + 1 != items[i + 1].index) { - hasGap = true - break - } - } - menu.findItem(R.id.action_select_range).isVisible = hasGap - return true - } - - override fun onSelectionChanged(controller: ListSelectionController, count: Int) { - viewBinding?.recyclerViewChapters?.invalidateItemDecorations() - } - override fun onWindowInsetsChanged(insets: Insets) = Unit private fun onChaptersChanged(list: List) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersSelectionCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersSelectionCallback.kt new file mode 100644 index 000000000..37950dcfc --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersSelectionCallback.kt @@ -0,0 +1,122 @@ +package org.koitharu.kotatsu.details.ui.pager.chapters + +import android.view.Menu +import android.view.MenuItem +import androidx.appcompat.view.ActionMode +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.snackbar.Snackbar +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.model.LocalMangaSource +import org.koitharu.kotatsu.core.ui.list.BaseListSelectionCallback +import org.koitharu.kotatsu.core.ui.list.ListSelectionController +import org.koitharu.kotatsu.core.util.ext.toCollection +import org.koitharu.kotatsu.core.util.ext.toSet +import org.koitharu.kotatsu.details.ui.DetailsViewModel +import org.koitharu.kotatsu.local.ui.LocalChaptersRemoveService + +class ChaptersSelectionCallback( + private val viewModel: DetailsViewModel, + recyclerView: RecyclerView, +) : BaseListSelectionCallback(recyclerView) { + + override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { + mode.menuInflater.inflate(R.menu.mode_chapters, menu) + return true + } + + override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { + val selectedIds = controller.peekCheckedIds() + val allItems = viewModel.chapters.value + val items = allItems.withIndex().filter { it.value.chapter.id in selectedIds } + var canSave = true + var canDelete = true + items.forEach { (_, x) -> + val isLocal = x.isDownloaded || x.chapter.source == LocalMangaSource + if (isLocal) canSave = false else canDelete = false + } + menu.findItem(R.id.action_save).isVisible = canSave + menu.findItem(R.id.action_delete).isVisible = canDelete + menu.findItem(R.id.action_select_all).isVisible = items.size < allItems.size + menu.findItem(R.id.action_mark_current).isVisible = items.size == 1 + mode.title = items.size.toString() + var hasGap = false + for (i in 0 until items.size - 1) { + if (items[i].index + 1 != items[i + 1].index) { + hasGap = true + break + } + } + menu.findItem(R.id.action_select_range).isVisible = hasGap + return true + } + + override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_save -> { + viewModel.download(controller.snapshot()) + mode.finish() + true + } + + R.id.action_delete -> { + val ids = controller.peekCheckedIds() + val manga = viewModel.manga.value + when { + ids.isEmpty() || manga == null -> Unit + ids.size == manga.chapters?.size -> viewModel.deleteLocal() + else -> { + LocalChaptersRemoveService.start(recyclerView.context, manga, ids.toSet()) + Snackbar.make( + recyclerView, + R.string.chapters_will_removed_background, + Snackbar.LENGTH_LONG, + ).show() + } + } + mode.finish() + true + } + + R.id.action_select_range -> { + val items = viewModel.chapters.value + val ids = controller.peekCheckedIds().toCollection(HashSet()) + val buffer = HashSet() + var isAdding = false + for (x in items) { + if (x.chapter.id in ids) { + isAdding = true + if (buffer.isNotEmpty()) { + ids.addAll(buffer) + buffer.clear() + } + } else if (isAdding) { + buffer.add(x.chapter.id) + } + } + controller.addAll(ids) + true + } + + R.id.action_select_all -> { + val ids = viewModel.chapters.value.map { + it.chapter.id + } + controller.addAll(ids) + true + } + + R.id.action_mark_current -> { + val ids = controller.peekCheckedIds() + if (ids.size == 1) { + viewModel.markChapterAsCurrent(ids.first()) + } else { + return false + } + mode.finish() + true + } + + else -> false + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt index c9db51f41..f798498c7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt @@ -27,7 +27,7 @@ import javax.inject.Inject @AndroidEntryPoint class DownloadsActivity : BaseActivity(), DownloadItemListener, - ListSelectionController.Callback2 { + ListSelectionController.Callback { @Inject lateinit var coil: ImageLoader diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt index 726e2eab8..743253b59 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt @@ -56,7 +56,7 @@ class ExploreFragment : BaseFragment(), RecyclerViewOwner, ExploreListEventListener, - OnListItemClickListener, ListSelectionController.Callback2 { + OnListItemClickListener, ListSelectionController.Callback { @Inject lateinit var coil: ImageLoader diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/CategoriesSelectionCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/CategoriesSelectionCallback.kt index fc529b643..c0ab6302d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/CategoriesSelectionCallback.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/CategoriesSelectionCallback.kt @@ -12,7 +12,7 @@ import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED class CategoriesSelectionCallback( private val recyclerView: RecyclerView, private val viewModel: FavouritesCategoriesViewModel, -) : ListSelectionController.Callback2 { +) : ListSelectionController.Callback { override fun onSelectionChanged(controller: ListSelectionController, count: Int) { recyclerView.invalidateItemDecorations() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/domain/HistoryListQuickFilter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/domain/HistoryListQuickFilter.kt index bb9545bb7..d099394e4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/domain/HistoryListQuickFilter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/domain/HistoryListQuickFilter.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.history.domain +import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.os.NetworkState import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.history.data.HistoryRepository @@ -24,6 +25,14 @@ class HistoryListQuickFilter @Inject constructor( } add(ListFilterOption.Macro.COMPLETED) add(ListFilterOption.Macro.FAVORITE) + add( + ListFilterOption.Inverted( + option = ListFilterOption.Macro.FAVORITE, + iconResId = R.drawable.ic_heart_off, + titleResId = R.string.not_in_favorites, + titleText = null, + ), + ) if (!settings.isNsfwContentDisabled && !settings.isHistoryExcludeNsfw) { add(ListFilterOption.Macro.NSFW) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt index ceea6e7a6..51842f604 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt @@ -68,7 +68,7 @@ abstract class MangaListFragment : PaginationScrollListener.Callback, MangaListListener, SwipeRefreshLayout.OnRefreshListener, - ListSelectionController.Callback2, + ListSelectionController.Callback, FastScroller.FastScrollListener { @Inject diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt index e121b274c..eed362e67 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt @@ -45,7 +45,7 @@ import javax.inject.Inject class MultiSearchActivity : BaseActivity(), MangaListListener, - ListSelectionController.Callback2 { + ListSelectionController.Callback { @Inject lateinit var coil: ImageLoader diff --git a/app/src/main/res/drawable/ic_heart_off.xml b/app/src/main/res/drawable/ic_heart_off.xml new file mode 100644 index 000000000..4916d1ace --- /dev/null +++ b/app/src/main/res/drawable/ic_heart_off.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/layout/fragment_list.xml b/app/src/main/res/layout/fragment_list.xml index 63de3168e..418645c19 100644 --- a/app/src/main/res/layout/fragment_list.xml +++ b/app/src/main/res/layout/fragment_list.xml @@ -15,10 +15,11 @@ android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" + android:clipChildren="false" android:clipToPadding="false" android:orientation="vertical" - app:bubbleSize="small" android:padding="@dimen/list_spacing_normal" + app:bubbleSize="small" tools:layoutManager="org.koitharu.kotatsu.core.ui.list.FitHeightLinearLayoutManager" tools:listitem="@layout/item_manga_list" /> diff --git a/app/src/main/res/layout/fragment_list_simple.xml b/app/src/main/res/layout/fragment_list_simple.xml index edae2f8f0..c9de21c24 100644 --- a/app/src/main/res/layout/fragment_list_simple.xml +++ b/app/src/main/res/layout/fragment_list_simple.xml @@ -11,6 +11,7 @@ android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" + android:clipChildren="false" android:clipToPadding="false" android:orientation="vertical" android:padding="@dimen/list_spacing_normal" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c20643649..dd905d89c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -685,4 +685,5 @@ SFW Skip all Stuck + Not in favoites