diff --git a/app/build.gradle b/app/build.gradle index bd7498317..f15922393 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,7 +95,7 @@ dependencies { implementation 'androidx.activity:activity-ktx:1.9.0' implementation 'androidx.fragment:fragment-ktx:1.8.1' implementation 'androidx.transition:transition-ktx:1.5.0' - implementation 'androidx.collection:collection-ktx:1.4.0' + implementation 'androidx.collection:collection-ktx:1.4.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3' implementation 'androidx.lifecycle:lifecycle-service:2.8.3' implementation 'androidx.lifecycle:lifecycle-process:2.8.3' 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 f2f5d7e4b..db0cdef05 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,11 +1,11 @@ package org.koitharu.kotatsu.core.ui.list -import android.app.Notification.Action import android.os.Bundle import android.view.Menu import android.view.MenuItem import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.view.ActionMode +import androidx.collection.LongSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner @@ -14,6 +14,8 @@ import androidx.savedstate.SavedStateRegistry import androidx.savedstate.SavedStateRegistryOwner import kotlinx.coroutines.Dispatchers import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration +import org.koitharu.kotatsu.core.util.ext.toLongArray +import org.koitharu.kotatsu.core.util.ext.toSet import kotlin.coroutines.EmptyCoroutineContext private const val KEY_SELECTION = "selection" @@ -35,11 +37,9 @@ class ListSelectionController( registryOwner.lifecycle.addObserver(StateEventObserver()) } - fun snapshot(): Set { - return peekCheckedIds().toSet() - } + fun snapshot(): Set = peekCheckedIds().toSet() - fun peekCheckedIds(): Set { + fun peekCheckedIds(): LongSet { return decoration.checkedItemsIds } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt index 80d7c91d8..1c3be909c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt @@ -4,6 +4,8 @@ import android.graphics.Canvas import android.graphics.Rect import android.graphics.RectF import android.view.View +import androidx.collection.LongSet +import androidx.collection.MutableLongSet import androidx.core.view.children import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.NO_ID @@ -12,7 +14,7 @@ abstract class AbstractSelectionItemDecoration : RecyclerView.ItemDecoration() { private val bounds = Rect() private val boundsF = RectF() - protected val selection = HashSet() // TODO MutableLongSet + protected val selection = MutableLongSet() protected var hasBackground: Boolean = true protected var hasForeground: Boolean = false @@ -21,7 +23,7 @@ abstract class AbstractSelectionItemDecoration : RecyclerView.ItemDecoration() { val checkedItemsCount: Int get() = selection.size - val checkedItemsIds: Set + val checkedItemsIds: LongSet get() = selection fun toggleItemChecked(id: Long) { @@ -39,7 +41,9 @@ abstract class AbstractSelectionItemDecoration : RecyclerView.ItemDecoration() { } fun checkAll(ids: Collection) { - selection.addAll(ids) + for (id in ids) { + selection.add(id) + } } fun clearSelection() { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt index 11400295c..edcb7845a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.core.util.ext import androidx.collection.ArrayMap import androidx.collection.ArraySet +import androidx.collection.LongSet import org.koitharu.kotatsu.BuildConfig import java.util.Collections import java.util.EnumSet @@ -77,3 +78,16 @@ inline fun Collection.mapToArray(transform: (T) -> R): Array result[index] = transform(t) } return result as Array } + +fun LongSet.toLongArray(): LongArray { + val result = LongArray(size) + var i = 0 + forEach { result[i++] = it } + return result +} + +fun LongSet.toSet(): Set = toCollection(ArraySet(size)) + +fun > LongSet.toCollection(out: R): R = out.also { result -> + forEach(result::add) +} 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 1e5b2da24..ed4e5c805 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 @@ -32,6 +32,8 @@ 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 @@ -137,10 +139,10 @@ class ChaptersFragment : val ids = selectionController?.peekCheckedIds() val manga = viewModel.manga.value when { - ids.isNullOrEmpty() || manga == null -> Unit + ids == null || ids.isEmpty() || manga == null -> Unit ids.size == manga.chapters?.size -> viewModel.deleteLocal() else -> { - LocalChaptersRemoveService.start(requireContext(), manga, ids) + LocalChaptersRemoveService.start(requireContext(), manga, ids.toSet()) Snackbar.make( requireViewBinding().recyclerViewChapters, R.string.chapters_will_removed_background, @@ -154,7 +156,7 @@ class ChaptersFragment : R.id.action_select_range -> { val items = chaptersAdapter?.items ?: return false - val ids = HashSet(controller.peekCheckedIds()) + val ids = controller.peekCheckedIds().toCollection(HashSet()) val buffer = HashSet() var isAdding = false for (x in items) { @@ -188,8 +190,12 @@ class ChaptersFragment : } R.id.action_mark_current -> { - val id = controller.peekCheckedIds().singleOrNull() ?: return false - viewModel.markChapterAsCurrent(id) + val ids = controller.peekCheckedIds() + if (ids.size == 1) { + viewModel.markChapterAsCurrent(ids.first()) + } else { + return false + } mode.finish() true } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt index 828f0cef2..ef5a3470c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.download.ui.list import androidx.collection.ArrayMap +import androidx.collection.LongSet import androidx.collection.LongSparseArray import androidx.collection.getOrElse import androidx.collection.set @@ -182,7 +183,7 @@ class DownloadsViewModel @Inject constructor( } } - fun snapshot(ids: Set): Collection { + fun snapshot(ids: LongSet): Collection { return works.value?.filterTo(ArrayList(ids.size)) { x -> x.id.mostSignificantBits in ids }.orEmpty() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt index 6f9f681dc..f92d13103 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.explore.ui +import androidx.collection.LongSet import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -126,7 +127,7 @@ class ExploreViewModel @Inject constructor( settings.closeTip(TIP_SUGGESTIONS) } - fun sourcesSnapshot(ids: Set): List { + fun sourcesSnapshot(ids: LongSet): List { return content.value.mapNotNull { (it as? MangaSourceItem)?.takeIf { x -> x.id in ids }?.source } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt index 3df13093a..b4c653264 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.favourites.ui.categories +import androidx.collection.LongSet import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -76,7 +77,7 @@ class FavouritesCategoriesViewModel @Inject constructor( } } - fun getCategories(ids: Set): ArrayList { + fun getCategories(ids: LongSet): ArrayList { val items = content.requireValue() return items.mapNotNullTo(ArrayList(ids.size)) { item -> (item as? CategoryListModel)?.category?.takeIf { it.id in ids } 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 46715d84c..c5ed4ebb7 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 @@ -14,7 +14,11 @@ import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga class ScrobblerMangaSelectionDecoration(context: Context) : MangaSelectionDecoration(context) { var checkedItemId: Long - get() = selection.singleOrNull() ?: NO_ID + get() = if (selection.size == 1) { + selection.first() + } else { + NO_ID + } set(value) { clearSelection() if (value != NO_ID) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt index 27c2e5001..f26a46092 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.search.ui.multi import androidx.annotation.CheckResult +import androidx.collection.LongSet import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -81,7 +82,7 @@ class MultiSearchViewModel @Inject constructor( } }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState)) - fun getItems(ids: Set): Set { + fun getItems(ids: LongSet): Set { val snapshot = listData.value ?: return emptySet() val result = HashSet(ids.size) snapshot.forEach { x ->