Save state of selection in lists

pull/192/head
Koitharu 4 years ago
parent 6e324fd5ab
commit d9985d03ab
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -12,6 +12,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.ActionBarContextView import androidx.appcompat.widget.ActionBarContextView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
@ -82,9 +83,8 @@ abstract class BaseActivity<B : ViewBinding> :
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) { // TODO remove if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) { // TODO remove
// ActivityCompat.recreate(this) ActivityCompat.recreate(this)
throw RuntimeException("Test crash") return true
// return true
} }
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)
} }

@ -0,0 +1,156 @@
package org.koitharu.kotatsu.base.ui.list
import android.app.Activity
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
private const val KEY_SELECTION = "selection"
private const val PROVIDER_NAME = "selection_decoration"
class ListSelectionController(
private val activity: Activity,
private val decoration: AbstractSelectionItemDecoration,
private val registryOwner: SavedStateRegistryOwner,
private val callback: Callback,
) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider {
private var actionMode: ActionMode? = null
private val stateEventObserver = StateEventObserver()
val count: Int
get() = decoration.checkedItemsCount
fun snapshot(): Set<Long> {
return peekCheckedIds().toSet()
}
fun peekCheckedIds(): Set<Long> {
return decoration.checkedItemsIds
}
fun clear() {
decoration.clearSelection()
notifySelectionChanged()
}
fun addAll(ids: Collection<Long>) {
if (ids.isEmpty()) {
return
}
decoration.checkAll(ids)
notifySelectionChanged()
}
fun attachToRecyclerView(recyclerView: RecyclerView) {
recyclerView.addItemDecoration(decoration)
registryOwner.lifecycle.addObserver(stateEventObserver)
}
override fun saveState(): Bundle {
val bundle = Bundle(1)
bundle.putLongArray(KEY_SELECTION, peekCheckedIds().toLongArray())
return bundle
}
fun onItemClick(id: Long): Boolean {
if (decoration.checkedItemsCount != 0) {
decoration.toggleItemChecked(id)
if (decoration.checkedItemsCount == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
}
notifySelectionChanged()
return true
}
return false
}
fun onItemLongClick(id: Long): Boolean {
startActionMode()
return actionMode?.also {
decoration.setItemIsChecked(id, true)
notifySelectionChanged()
} != null
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
return callback.onCreateActionMode(mode, menu)
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
return callback.onPrepareActionMode(mode, menu)
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return callback.onActionItemClicked(mode, item)
}
override fun onDestroyActionMode(mode: ActionMode) {
callback.onDestroyActionMode(mode)
clear()
actionMode = null
}
private fun startActionMode() {
if (actionMode == null) {
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
}
}
private fun notifySelectionChanged() {
val count = decoration.checkedItemsCount
callback.onSelectionChanged(count)
if (count == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
}
}
private fun restoreState(ids: Collection<Long>) {
if (ids.isEmpty() || decoration.checkedItemsCount != 0) {
return
}
decoration.checkAll(ids)
startActionMode()
notifySelectionChanged()
}
interface Callback : ActionMode.Callback {
fun onSelectionChanged(count: Int)
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean
override fun onDestroyActionMode(mode: ActionMode) = Unit
}
private inner class StateEventObserver : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_CREATE) {
val registry = registryOwner.savedStateRegistry
registry.registerSavedStateProvider(PROVIDER_NAME, this@ListSelectionController)
val state = registry.consumeRestoredStateForKey(PROVIDER_NAME)
if (state != null) {
restoreState(state.getLongArray(KEY_SELECTION)?.toList().orEmpty())
}
}
}
}
}

@ -5,7 +5,6 @@ import android.os.Bundle
import android.view.* import android.view.*
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.Spinner import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
@ -16,6 +15,7 @@ import com.google.android.material.snackbar.Snackbar
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.FragmentChaptersBinding import org.koitharu.kotatsu.databinding.FragmentChaptersBinding
import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter
@ -34,16 +34,15 @@ import kotlin.math.roundToInt
class ChaptersFragment : class ChaptersFragment :
BaseFragment<FragmentChaptersBinding>(), BaseFragment<FragmentChaptersBinding>(),
OnListItemClickListener<ChapterListItem>, OnListItemClickListener<ChapterListItem>,
ActionMode.Callback,
AdapterView.OnItemSelectedListener, AdapterView.OnItemSelectedListener,
MenuItem.OnActionExpandListener, MenuItem.OnActionExpandListener,
SearchView.OnQueryTextListener { SearchView.OnQueryTextListener,
ListSelectionController.Callback {
private val viewModel by sharedViewModel<DetailsViewModel>() private val viewModel by sharedViewModel<DetailsViewModel>()
private var chaptersAdapter: ChaptersAdapter? = null private var chaptersAdapter: ChaptersAdapter? = null
private var actionMode: ActionMode? = null private var selectionController: ListSelectionController? = null
private var selectionDecoration: ChaptersSelectionDecoration? = null
override fun onInflateView( override fun onInflateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -53,9 +52,14 @@ class ChaptersFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
chaptersAdapter = ChaptersAdapter(this) chaptersAdapter = ChaptersAdapter(this)
selectionDecoration = ChaptersSelectionDecoration(view.context) selectionController = ListSelectionController(
activity = requireActivity(),
decoration = ChaptersSelectionDecoration(view.context),
registryOwner = this,
callback = this,
)
with(binding.recyclerViewChapters) { with(binding.recyclerViewChapters) {
addItemDecoration(selectionDecoration!!) checkNotNull(selectionController).attachToRecyclerView(this)
setHasFixedSize(true) setHasFixedSize(true)
adapter = chaptersAdapter adapter = chaptersAdapter
} }
@ -74,20 +78,13 @@ class ChaptersFragment :
override fun onDestroyView() { override fun onDestroyView() {
chaptersAdapter = null chaptersAdapter = null
selectionDecoration = null selectionController = null
binding.spinnerBranches?.adapter = null binding.spinnerBranches?.adapter = null
super.onDestroyView() super.onDestroyView()
} }
override fun onItemClick(item: ChapterListItem, view: View) { override fun onItemClick(item: ChapterListItem, view: View) {
if (selectionDecoration?.checkedItemsCount != 0) { if (selectionController?.onItemClick(item.chapter.id) == true) {
selectionDecoration?.toggleItemChecked(item.chapter.id)
if (selectionDecoration?.checkedItemsCount == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
binding.recyclerViewChapters.invalidateItemDecorations()
}
return return
} }
if (item.hasFlag(ChapterListItem.FLAG_MISSING)) { if (item.hasFlag(ChapterListItem.FLAG_MISSING)) {
@ -106,14 +103,7 @@ class ChaptersFragment :
} }
override fun onItemLongClick(item: ChapterListItem, view: View): Boolean { override fun onItemLongClick(item: ChapterListItem, view: View): Boolean {
if (actionMode == null) { return selectionController?.onItemLongClick(item.chapter.id) ?: false
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
}
return actionMode?.also {
selectionDecoration?.setItemIsChecked(item.chapter.id, true)
binding.recyclerViewChapters.invalidateItemDecorations()
it.invalidate()
} != null
} }
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
@ -122,13 +112,13 @@ class ChaptersFragment :
DownloadService.start( DownloadService.start(
context ?: return false, context ?: return false,
viewModel.getRemoteManga() ?: viewModel.manga.value ?: return false, viewModel.getRemoteManga() ?: viewModel.manga.value ?: return false,
selectionDecoration?.checkedItemsIds?.toSet() selectionController?.snapshot(),
) )
mode.finish() mode.finish()
true true
} }
R.id.action_delete -> { R.id.action_delete -> {
val ids = selectionDecoration?.checkedItemsIds val ids = selectionController?.peekCheckedIds()
val manga = viewModel.manga.value val manga = viewModel.manga.value
when { when {
ids.isNullOrEmpty() || manga == null -> Unit ids.isNullOrEmpty() || manga == null -> Unit
@ -147,9 +137,7 @@ class ChaptersFragment :
} }
R.id.action_select_all -> { R.id.action_select_all -> {
val ids = chaptersAdapter?.items?.map { it.chapter.id } ?: return false val ids = chaptersAdapter?.items?.map { it.chapter.id } ?: return false
selectionDecoration?.checkAll(ids) selectionController?.addAll(ids)
binding.recyclerViewChapters.invalidateItemDecorations()
mode.invalidate()
true true
} }
else -> false else -> false
@ -169,7 +157,7 @@ class ChaptersFragment :
} }
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
val selectedIds = selectionDecoration?.checkedItemsIds ?: return false val selectedIds = selectionController?.peekCheckedIds() ?: return false
val items = chaptersAdapter?.items?.filter { x -> x.chapter.id in selectedIds }.orEmpty() val items = chaptersAdapter?.items?.filter { x -> x.chapter.id in selectedIds }.orEmpty()
menu.findItem(R.id.action_save).isVisible = items.none { x -> menu.findItem(R.id.action_save).isVisible = items.none { x ->
x.chapter.source == MangaSource.LOCAL x.chapter.source == MangaSource.LOCAL
@ -181,10 +169,8 @@ class ChaptersFragment :
return true return true
} }
override fun onDestroyActionMode(mode: ActionMode?) { override fun onSelectionChanged(count: Int) {
selectionDecoration?.clearSelection()
binding.recyclerViewChapters.invalidateItemDecorations() binding.recyclerViewChapters.invalidateItemDecorations()
actionMode = null
} }
override fun onMenuItemActionExpand(item: MenuItem?): Boolean = true override fun onMenuItemActionExpand(item: MenuItem?): Boolean = true

@ -3,7 +3,6 @@ package org.koitharu.kotatsu.list.ui
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.collection.ArraySet import androidx.collection.ArraySet
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
@ -18,6 +17,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.FitHeightGridLayoutManager import org.koitharu.kotatsu.base.ui.list.FitHeightGridLayoutManager
import org.koitharu.kotatsu.base.ui.list.FitHeightLinearLayoutManager import org.koitharu.kotatsu.base.ui.list.FitHeightLinearLayoutManager
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.base.ui.list.decor.TypedSpacingItemDecoration import org.koitharu.kotatsu.base.ui.list.decor.TypedSpacingItemDecoration
@ -46,12 +46,11 @@ abstract class MangaListFragment :
PaginationScrollListener.Callback, PaginationScrollListener.Callback,
MangaListListener, MangaListListener,
SwipeRefreshLayout.OnRefreshListener, SwipeRefreshLayout.OnRefreshListener,
ActionMode.Callback { ListSelectionController.Callback {
private var listAdapter: MangaListAdapter? = null private var listAdapter: MangaListAdapter? = null
private var paginationListener: PaginationScrollListener? = null private var paginationListener: PaginationScrollListener? = null
private var selectionDecoration: MangaSelectionDecoration? = null private var selectionController: ListSelectionController? = null
private var actionMode: ActionMode? = null
private val spanResolver = MangaListSpanResolver() private val spanResolver = MangaListSpanResolver()
private val spanSizeLookup = SpanSizeLookup() private val spanSizeLookup = SpanSizeLookup()
private val listCommitCallback = Runnable { private val listCommitCallback = Runnable {
@ -62,7 +61,7 @@ abstract class MangaListFragment :
protected abstract val viewModel: MangaListViewModel protected abstract val viewModel: MangaListViewModel
protected val selectedItemsIds: Set<Long> protected val selectedItemsIds: Set<Long>
get() = selectionDecoration?.checkedItemsIds?.toSet().orEmpty() get() = selectionController?.snapshot().orEmpty()
protected val selectedItems: Set<Manga> protected val selectedItems: Set<Manga>
get() = collectSelectedItems() get() = collectSelectedItems()
@ -79,12 +78,17 @@ abstract class MangaListFragment :
lifecycleOwner = viewLifecycleOwner, lifecycleOwner = viewLifecycleOwner,
listener = this, listener = this,
) )
selectionDecoration = MangaSelectionDecoration(view.context) selectionController = ListSelectionController(
activity = requireActivity(),
decoration = MangaSelectionDecoration(view.context),
registryOwner = this,
callback = this,
)
paginationListener = PaginationScrollListener(4, this) paginationListener = PaginationScrollListener(4, this)
with(binding.recyclerView) { with(binding.recyclerView) {
setHasFixedSize(true) setHasFixedSize(true)
adapter = listAdapter adapter = listAdapter
addItemDecoration(selectionDecoration!!) checkNotNull(selectionController).attachToRecyclerView(binding.recyclerView)
addOnScrollListener(paginationListener!!) addOnScrollListener(paginationListener!!)
} }
with(binding.swipeRefreshLayout) { with(binding.swipeRefreshLayout) {
@ -105,34 +109,19 @@ abstract class MangaListFragment :
override fun onDestroyView() { override fun onDestroyView() {
listAdapter = null listAdapter = null
paginationListener = null paginationListener = null
selectionDecoration = null selectionController = null
spanSizeLookup.invalidateCache() spanSizeLookup.invalidateCache()
super.onDestroyView() super.onDestroyView()
} }
override fun onItemClick(item: Manga, view: View) { override fun onItemClick(item: Manga, view: View) {
if (selectionDecoration?.checkedItemsCount != 0) { if (selectionController?.onItemClick(item.id) != true) {
selectionDecoration?.toggleItemChecked(item.id) startActivity(DetailsActivity.newIntent(context ?: return, item))
if (selectionDecoration?.checkedItemsCount == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
binding.recyclerView.invalidateItemDecorations()
}
return
} }
startActivity(DetailsActivity.newIntent(context ?: return, item))
} }
override fun onItemLongClick(item: Manga, view: View): Boolean { override fun onItemLongClick(item: Manga, view: View): Boolean {
if (actionMode == null) { return selectionController?.onItemLongClick(item.id) ?: false
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
}
return actionMode?.also {
selectionDecoration?.setItemIsChecked(item.id, true)
binding.recyclerView.invalidateItemDecorations()
it.invalidate()
} != null
} }
@CallSuper @CallSuper
@ -249,7 +238,7 @@ abstract class MangaListFragment :
addOnLayoutChangeListener(spanResolver) addOnLayoutChangeListener(spanResolver)
} }
} }
selectionDecoration?.let { addItemDecoration(it) } selectionController?.attachToRecyclerView(binding.recyclerView)
} }
} }
@ -259,7 +248,7 @@ abstract class MangaListFragment :
@CallSuper @CallSuper
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.title = selectionDecoration?.checkedItemsCount?.toString() mode.title = selectionController?.count?.toString()
return true return true
} }
@ -269,9 +258,7 @@ abstract class MangaListFragment :
val ids = listAdapter?.items?.mapNotNull { val ids = listAdapter?.items?.mapNotNull {
(it as? MangaItemModel)?.id (it as? MangaItemModel)?.id
} ?: return false } ?: return false
selectionDecoration?.checkAll(ids) selectionController?.addAll(ids)
binding.recyclerView.invalidateItemDecorations()
mode.invalidate()
true true
} }
R.id.action_share -> { R.id.action_share -> {
@ -293,14 +280,12 @@ abstract class MangaListFragment :
} }
} }
override fun onDestroyActionMode(mode: ActionMode) { override fun onSelectionChanged(count: Int) {
selectionDecoration?.clearSelection()
binding.recyclerView.invalidateItemDecorations() binding.recyclerView.invalidateItemDecorations()
actionMode = null
} }
private fun collectSelectedItems(): Set<Manga> { private fun collectSelectedItems(): Set<Manga> {
val checkedIds = selectionDecoration?.checkedItemsIds ?: return emptySet() val checkedIds = selectionController?.peekCheckedIds() ?: return emptySet()
val items = listAdapter?.items ?: return emptySet() val items = listAdapter?.items ?: return emptySet()
val result = ArraySet<Manga>(checkedIds.size) val result = ArraySet<Manga>(checkedIds.size)
for (item in items) { for (item in items) {

@ -145,8 +145,15 @@ class MainActivity :
override fun onRestoreInstanceState(savedInstanceState: Bundle) { override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState)
drawerToggle?.isDrawerIndicatorEnabled = val isSearchOpened = isSearchOpened()
drawer?.getDrawerLockMode(GravityCompat.START) == DrawerLayout.LOCK_MODE_UNLOCKED adjustDrawerLock(isSearchOpened)
if (isSearchOpened) {
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
scrollFlags = SCROLL_FLAG_NO_SCROLL
}
binding.appbar.setBackgroundColor(getThemeColor(materialR.attr.colorSurfaceVariant))
binding.appbar.updatePadding(left = 0, right = 0)
}
} }
override fun onPostCreate(savedInstanceState: Bundle?) { override fun onPostCreate(savedInstanceState: Bundle?) {
@ -396,29 +403,31 @@ class MainActivity :
private fun onSearchOpened() { private fun onSearchOpened() {
TransitionManager.beginDelayedTransition(binding.appbar) TransitionManager.beginDelayedTransition(binding.appbar)
drawerToggle?.isDrawerIndicatorEnabled = false
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> { binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
scrollFlags = SCROLL_FLAG_NO_SCROLL scrollFlags = SCROLL_FLAG_NO_SCROLL
} }
binding.appbar.setBackgroundColor(getThemeColor(materialR.attr.colorSurfaceVariant)) binding.appbar.setBackgroundColor(getThemeColor(materialR.attr.colorSurfaceVariant))
binding.appbar.updatePadding(left = 0, right = 0) binding.appbar.updatePadding(left = 0, right = 0)
adjustDrawerLock() adjustDrawerLock(isSearchOpened = true)
adjustFabVisibility(isSearchOpened = true) adjustFabVisibility(isSearchOpened = true)
} }
private fun onSearchClosed() { private fun onSearchClosed() {
TransitionManager.beginDelayedTransition(binding.appbar) TransitionManager.beginDelayedTransition(binding.appbar)
drawerToggle?.isDrawerIndicatorEnabled = true
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> { binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS
} }
binding.appbar.background = null binding.appbar.background = null
val padding = resources.getDimensionPixelOffset(R.dimen.margin_normal) val padding = resources.getDimensionPixelOffset(R.dimen.margin_normal)
binding.appbar.updatePadding(left = padding, right = padding) binding.appbar.updatePadding(left = padding, right = padding)
adjustDrawerLock() adjustDrawerLock(isSearchOpened = false)
adjustFabVisibility(isSearchOpened = false) adjustFabVisibility(isSearchOpened = false)
} }
private fun isSearchOpened(): Boolean {
return supportFragmentManager.findFragmentByTag(TAG_SEARCH)?.isVisible == true
}
private fun onFirstStart() { private fun onFirstStart() {
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
val isUpdateSupported = withContext(Dispatchers.Default) { val isUpdateSupported = withContext(Dispatchers.Default) {
@ -440,7 +449,7 @@ class MainActivity :
private fun adjustFabVisibility( private fun adjustFabVisibility(
isResumeEnabled: Boolean = viewModel.isResumeEnabled.value == true, isResumeEnabled: Boolean = viewModel.isResumeEnabled.value == true,
topFragment: Fragment? = supportFragmentManager.findFragmentByTag(TAG_PRIMARY), topFragment: Fragment? = supportFragmentManager.findFragmentByTag(TAG_PRIMARY),
isSearchOpened: Boolean = supportFragmentManager.findFragmentByTag(TAG_SEARCH)?.isVisible == true, isSearchOpened: Boolean = isSearchOpened(),
) { ) {
val fab = binding.fab val fab = binding.fab
if (isResumeEnabled && !isSearchOpened && topFragment is HistoryListFragment) { if (isResumeEnabled && !isSearchOpened && topFragment is HistoryListFragment) {
@ -454,12 +463,15 @@ class MainActivity :
} }
} }
private fun adjustDrawerLock() { private fun adjustDrawerLock(
isSearchOpened: Boolean = isSearchOpened(),
) {
val drawer = drawer ?: return val drawer = drawer ?: return
val isLocked = actionModeDelegate.isActionModeStarted || (drawerToggle?.isDrawerIndicatorEnabled == false) val isLocked = actionModeDelegate.isActionModeStarted || isSearchOpened
drawer.setDrawerLockMode( drawer.setDrawerLockMode(
if (isLocked) DrawerLayout.LOCK_MODE_LOCKED_CLOSED else DrawerLayout.LOCK_MODE_UNLOCKED if (isLocked) DrawerLayout.LOCK_MODE_LOCKED_CLOSED else DrawerLayout.LOCK_MODE_UNLOCKED
) )
drawerToggle?.isDrawerIndicatorEnabled = !isLocked
} }
private inner class VoiceInputCallback : ActivityResultCallback<String?> { private inner class VoiceInputCallback : ActivityResultCallback<String?> {

@ -17,6 +17,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.ActivitySearchMultiBinding import org.koitharu.kotatsu.databinding.ActivitySearchMultiBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
@ -32,14 +33,14 @@ import org.koitharu.kotatsu.search.ui.multi.adapter.MultiSearchAdapter
import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.findViewsByType import org.koitharu.kotatsu.utils.ext.findViewsByType
class MultiSearchActivity : BaseActivity<ActivitySearchMultiBinding>(), MangaListListener, ActionMode.Callback { class MultiSearchActivity : BaseActivity<ActivitySearchMultiBinding>(), MangaListListener,
ListSelectionController.Callback {
private val viewModel by viewModel<MultiSearchViewModel> { private val viewModel by viewModel<MultiSearchViewModel> {
parametersOf(intent.getStringExtra(EXTRA_QUERY).orEmpty()) parametersOf(intent.getStringExtra(EXTRA_QUERY).orEmpty())
} }
private lateinit var adapter: MultiSearchAdapter private lateinit var adapter: MultiSearchAdapter
private lateinit var selectionDecoration: MangaSelectionDecoration private lateinit var selectionController: ListSelectionController
private var actionMode: ActionMode? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -51,7 +52,13 @@ class MultiSearchActivity : BaseActivity<ActivitySearchMultiBinding>(), MangaLis
} }
} }
val sizeResolver = ItemSizeResolver(resources, get()) val sizeResolver = ItemSizeResolver(resources, get())
selectionDecoration = MangaSelectionDecoration(this) val selectionDecoration = MangaSelectionDecoration(this)
selectionController = ListSelectionController(
activity = this,
decoration = selectionDecoration,
registryOwner = this,
callback = this,
)
adapter = MultiSearchAdapter( adapter = MultiSearchAdapter(
lifecycleOwner = this, lifecycleOwner = this,
coil = get(), coil = get(),
@ -90,29 +97,14 @@ class MultiSearchActivity : BaseActivity<ActivitySearchMultiBinding>(), MangaLis
} }
override fun onItemClick(item: Manga, view: View) { override fun onItemClick(item: Manga, view: View) {
if (selectionDecoration.checkedItemsCount != 0) { if (!selectionController.onItemClick(item.id)) {
selectionDecoration.toggleItemChecked(item.id) val intent = DetailsActivity.newIntent(this, item)
if (selectionDecoration.checkedItemsCount == 0) { startActivity(intent)
actionMode?.finish()
} else {
actionMode?.invalidate()
invalidateItemDecorations()
}
return
} }
val intent = DetailsActivity.newIntent(this, item)
startActivity(intent)
} }
override fun onItemLongClick(item: Manga, view: View): Boolean { override fun onItemLongClick(item: Manga, view: View): Boolean {
if (actionMode == null) { return selectionController.onItemLongClick(item.id)
actionMode = startSupportActionMode(this)
}
return actionMode?.also {
selectionDecoration.setItemIsChecked(item.id, true)
invalidateItemDecorations()
it.invalidate()
} != null
} }
override fun onRetryClick(error: Throwable) { override fun onRetryClick(error: Throwable) {
@ -131,7 +123,7 @@ class MultiSearchActivity : BaseActivity<ActivitySearchMultiBinding>(), MangaLis
} }
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.title = selectionDecoration.checkedItemsCount.toString() mode.title = selectionController.count.toString()
return true return true
} }
@ -156,22 +148,16 @@ class MultiSearchActivity : BaseActivity<ActivitySearchMultiBinding>(), MangaLis
} }
} }
override fun onDestroyActionMode(mode: ActionMode) { override fun onSelectionChanged(count: Int) {
selectionDecoration.clearSelection()
invalidateItemDecorations()
actionMode = null
}
private fun collectSelectedItems(): Set<Manga> {
return viewModel.getItems(selectionDecoration.checkedItemsIds)
}
private fun invalidateItemDecorations() {
binding.recyclerView.findViewsByType(RecyclerView::class.java).forEach { binding.recyclerView.findViewsByType(RecyclerView::class.java).forEach {
it.invalidateItemDecorations() it.invalidateItemDecorations()
} }
} }
private fun collectSelectedItems(): Set<Manga> {
return viewModel.getItems(selectionController.peekCheckedIds())
}
companion object { companion object {
private const val EXTRA_QUERY = "query" private const val EXTRA_QUERY = "query"

Loading…
Cancel
Save