Potentially fix library IndexOutOfBound crash (mihonapp/mihon#2341)
(cherry picked from commit c4407eda0eed5a7faed47d4470d79e6b1512b5c2)
This commit is contained in:
parent
3485297434
commit
3ccf255d4e
3 changed files with 44 additions and 26 deletions
|
|
@ -31,7 +31,7 @@ fun LibraryContent(
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
selection: Set<Long>,
|
selection: Set<Long>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
currentPage: () -> Int,
|
currentPage: Int,
|
||||||
hasActiveFilters: Boolean,
|
hasActiveFilters: Boolean,
|
||||||
showPageTabs: Boolean,
|
showPageTabs: Boolean,
|
||||||
onChangeCurrentPage: (Int) -> Unit,
|
onChangeCurrentPage: (Int) -> Unit,
|
||||||
|
|
@ -53,10 +53,7 @@ fun LibraryContent(
|
||||||
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
// SY -->
|
val pagerState = rememberPagerState(currentPage) { categories.size }
|
||||||
val coercedCurrentPage = remember(categories) { currentPage().coerceIn(0, categories.lastIndex) }
|
|
||||||
// SY <--
|
|
||||||
val pagerState = rememberPagerState(coercedCurrentPage) { categories.size }
|
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
|
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ package eu.kanade.tachiyomi.ui.library
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.util.fastAll
|
import androidx.compose.ui.util.fastAll
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
import androidx.compose.ui.util.fastFilter
|
import androidx.compose.ui.util.fastFilter
|
||||||
|
|
@ -81,6 +79,7 @@ import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.flow.updateAndGet
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import mihon.core.common.utils.mutate
|
import mihon.core.common.utils.mutate
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
|
|
@ -162,9 +161,6 @@ class LibraryScreenModel(
|
||||||
// KMK <--
|
// KMK <--
|
||||||
) : StateScreenModel<LibraryScreenModel.State>(State()) {
|
) : StateScreenModel<LibraryScreenModel.State>(State()) {
|
||||||
|
|
||||||
var activeCategoryIndex: Int by libraryPreferences.lastUsedCategory().asState(screenModelScope)
|
|
||||||
val activeCategory: Category get() = state.value.displayedCategories[activeCategoryIndex]
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
val favoritesSync = FavoritesSyncHelper(preferences.context)
|
val favoritesSync = FavoritesSyncHelper(preferences.context)
|
||||||
val recommendationSearch = RecommendationSearchHelper(preferences.context)
|
val recommendationSearch = RecommendationSearchHelper(preferences.context)
|
||||||
|
|
@ -173,6 +169,9 @@ class LibraryScreenModel(
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
mutableState.update { state ->
|
||||||
|
state.copy(activeCategoryIndex = libraryPreferences.lastUsedCategory().get())
|
||||||
|
}
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
combine(
|
combine(
|
||||||
combine(
|
combine(
|
||||||
|
|
@ -191,6 +190,7 @@ class LibraryScreenModel(
|
||||||
// KMK <--
|
// KMK <--
|
||||||
getLibraryItemPreferencesFlow(),
|
getLibraryItemPreferencesFlow(),
|
||||||
) { (searchQuery, categories, favorites), (tracksMap, trackingFilters), (includedCategories, excludedCategories), itemPreferences ->
|
) { (searchQuery, categories, favorites), (tracksMap, trackingFilters), (includedCategories, excludedCategories), itemPreferences ->
|
||||||
|
val showSystemCategory = favorites.any { it.libraryManga.categories.contains(0) }
|
||||||
val filteredFavorites = favorites
|
val filteredFavorites = favorites
|
||||||
.applyFilters(
|
.applyFilters(
|
||||||
tracksMap,
|
tracksMap,
|
||||||
|
|
@ -217,6 +217,7 @@ class LibraryScreenModel(
|
||||||
|
|
||||||
LibraryData(
|
LibraryData(
|
||||||
isInitialized = true,
|
isInitialized = true,
|
||||||
|
showSystemCategory = showSystemCategory,
|
||||||
categories = categories,
|
categories = categories,
|
||||||
favorites = filteredFavorites,
|
favorites = filteredFavorites,
|
||||||
tracksMap = tracksMap,
|
tracksMap = tracksMap,
|
||||||
|
|
@ -263,6 +264,7 @@ class LibraryScreenModel(
|
||||||
data.favorites
|
data.favorites
|
||||||
.applyGrouping(
|
.applyGrouping(
|
||||||
data.categories,
|
data.categories,
|
||||||
|
data.showSystemCategory,
|
||||||
// KMK -->
|
// KMK -->
|
||||||
if (filterCategory && includedCategories.isNotEmpty()) {
|
if (filterCategory && includedCategories.isNotEmpty()) {
|
||||||
LibraryGroup.UNGROUPED
|
LibraryGroup.UNGROUPED
|
||||||
|
|
@ -532,6 +534,7 @@ class LibraryScreenModel(
|
||||||
|
|
||||||
private fun List<LibraryItem>.applyGrouping(
|
private fun List<LibraryItem>.applyGrouping(
|
||||||
categories: List<Category>,
|
categories: List<Category>,
|
||||||
|
showSystemCategory: Boolean,
|
||||||
// KMK -->
|
// KMK -->
|
||||||
groupType: Int,
|
groupType: Int,
|
||||||
showHiddenCategories: Boolean,
|
showHiddenCategories: Boolean,
|
||||||
|
|
@ -547,7 +550,6 @@ class LibraryScreenModel(
|
||||||
groupCache.getOrPut(categoryId) { mutableListOf() }.add(item.id)
|
groupCache.getOrPut(categoryId) { mutableListOf() }.add(item.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val showSystemCategory = groupCache.containsKey(0L)
|
|
||||||
return categories.filter { showSystemCategory || !it.isSystemCategory }
|
return categories.filter { showSystemCategory || !it.isSystemCategory }
|
||||||
// KMK -->
|
// KMK -->
|
||||||
.filterNot { !showHiddenCategories && it.hidden }
|
.filterNot { !showHiddenCategories && it.hidden }
|
||||||
|
|
@ -1091,7 +1093,8 @@ class LibraryScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRandomLibraryItemForCurrentCategory(): LibraryItem? {
|
fun getRandomLibraryItemForCurrentCategory(): LibraryItem? {
|
||||||
return state.value.getItemsForCategoryId(activeCategory.id).randomOrNull()
|
val state = state.value
|
||||||
|
return state.getItemsForCategoryId(state.activeCategory.id).randomOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showSettingsDialog() {
|
fun showSettingsDialog() {
|
||||||
|
|
@ -1322,7 +1325,7 @@ class LibraryScreenModel(
|
||||||
lastSelectionCategory = null
|
lastSelectionCategory = null
|
||||||
mutableState.update { state ->
|
mutableState.update { state ->
|
||||||
val newSelection = state.selection.mutate { list ->
|
val newSelection = state.selection.mutate { list ->
|
||||||
state.getItemsForCategoryId(activeCategory.id).map { it.id }.let(list::addAll)
|
state.getItemsForCategoryId(state.activeCategory.id).map { it.id }.let(list::addAll)
|
||||||
}
|
}
|
||||||
state.copy(selection = newSelection)
|
state.copy(selection = newSelection)
|
||||||
}
|
}
|
||||||
|
|
@ -1332,7 +1335,7 @@ class LibraryScreenModel(
|
||||||
lastSelectionCategory = null
|
lastSelectionCategory = null
|
||||||
mutableState.update { state ->
|
mutableState.update { state ->
|
||||||
val newSelection = state.selection.mutate { list ->
|
val newSelection = state.selection.mutate { list ->
|
||||||
val itemIds = state.getItemsForCategoryId(activeCategory.id).fastMap { it.id }
|
val itemIds = state.getItemsForCategoryId(state.activeCategory.id).fastMap { it.id }
|
||||||
val (toRemove, toAdd) = itemIds.partition { it in list }
|
val (toRemove, toAdd) = itemIds.partition { it in list }
|
||||||
list.removeAll(toRemove.toSet())
|
list.removeAll(toRemove.toSet())
|
||||||
list.addAll(toAdd)
|
list.addAll(toAdd)
|
||||||
|
|
@ -1345,6 +1348,15 @@ class LibraryScreenModel(
|
||||||
mutableState.update { it.copy(searchQuery = query) }
|
mutableState.update { it.copy(searchQuery = query) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateActiveCategoryIndex(index: Int) {
|
||||||
|
val newIndex = mutableState.updateAndGet { state ->
|
||||||
|
state.copy(activeCategoryIndex = index)
|
||||||
|
}
|
||||||
|
.coercedActiveCategoryIndex
|
||||||
|
|
||||||
|
libraryPreferences.lastUsedCategory().set(newIndex)
|
||||||
|
}
|
||||||
|
|
||||||
fun openChangeCategoryDialog() {
|
fun openChangeCategoryDialog() {
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
// Create a copy of selected manga
|
// Create a copy of selected manga
|
||||||
|
|
@ -1583,6 +1595,7 @@ class LibraryScreenModel(
|
||||||
@Immutable
|
@Immutable
|
||||||
data class LibraryData(
|
data class LibraryData(
|
||||||
val isInitialized: Boolean = false,
|
val isInitialized: Boolean = false,
|
||||||
|
val showSystemCategory: Boolean = false,
|
||||||
val categories: List<Category> = emptyList(),
|
val categories: List<Category> = emptyList(),
|
||||||
val favorites: List<LibraryItem> = emptyList(),
|
val favorites: List<LibraryItem> = emptyList(),
|
||||||
val tracksMap: Map</* Manga */ Long, List<Track>> = emptyMap(),
|
val tracksMap: Map</* Manga */ Long, List<Track>> = emptyMap(),
|
||||||
|
|
@ -1603,6 +1616,7 @@ class LibraryScreenModel(
|
||||||
val showMangaContinueButton: Boolean = false,
|
val showMangaContinueButton: Boolean = false,
|
||||||
val dialog: Dialog? = null,
|
val dialog: Dialog? = null,
|
||||||
val libraryData: LibraryData = LibraryData(),
|
val libraryData: LibraryData = LibraryData(),
|
||||||
|
private val activeCategoryIndex: Int = 0,
|
||||||
private val groupedFavorites: Map<Category, List</* LibraryItem */ Long>> = emptyMap(),
|
private val groupedFavorites: Map<Category, List</* LibraryItem */ Long>> = emptyMap(),
|
||||||
// SY -->
|
// SY -->
|
||||||
val showSyncExh: Boolean = false,
|
val showSyncExh: Boolean = false,
|
||||||
|
|
@ -1615,18 +1629,25 @@ class LibraryScreenModel(
|
||||||
val excludedCategories: ImmutableSet<Long> = persistentSetOf(),
|
val excludedCategories: ImmutableSet<Long> = persistentSetOf(),
|
||||||
// KMK <--
|
// KMK <--
|
||||||
) {
|
) {
|
||||||
|
/**
|
||||||
|
* The grouped tabs which is displayed above the library screen.
|
||||||
|
* They can be actual [Category] or [Source], [Track]...
|
||||||
|
*/
|
||||||
|
val displayedCategories: List<Category> = groupedFavorites.keys.toList()
|
||||||
|
|
||||||
|
val coercedActiveCategoryIndex = activeCategoryIndex.coerceIn(
|
||||||
|
minimumValue = 0,
|
||||||
|
maximumValue = displayedCategories.lastIndex.coerceAtLeast(0),
|
||||||
|
)
|
||||||
|
|
||||||
|
val activeCategory: Category by lazy { displayedCategories[coercedActiveCategoryIndex] }
|
||||||
|
|
||||||
val isLibraryEmpty = libraryData.favorites.isEmpty()
|
val isLibraryEmpty = libraryData.favorites.isEmpty()
|
||||||
|
|
||||||
val selectionMode = selection.isNotEmpty()
|
val selectionMode = selection.isNotEmpty()
|
||||||
|
|
||||||
val selectedManga by lazy { selection.mapNotNull { libraryData.favoritesById[it]?.libraryManga?.manga } }
|
val selectedManga by lazy { selection.mapNotNull { libraryData.favoritesById[it]?.libraryManga?.manga } }
|
||||||
|
|
||||||
/**
|
|
||||||
* The grouped tabs which is displayed above the library screen.
|
|
||||||
* They can be actual [Category] or [Source], [Track]...
|
|
||||||
*/
|
|
||||||
val displayedCategories = groupedFavorites.keys.toList()
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
val showCleanTitles: Boolean by lazy {
|
val showCleanTitles: Boolean by lazy {
|
||||||
selectedManga.fastAny {
|
selectedManga.fastAny {
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ data object LibraryTab : Tab {
|
||||||
val title = state.getToolbarTitle(
|
val title = state.getToolbarTitle(
|
||||||
defaultTitle = stringResource(MR.strings.label_library),
|
defaultTitle = stringResource(MR.strings.label_library),
|
||||||
defaultCategoryTitle = stringResource(MR.strings.label_default),
|
defaultCategoryTitle = stringResource(MR.strings.label_default),
|
||||||
page = screenModel.activeCategoryIndex,
|
page = state.coercedActiveCategoryIndex,
|
||||||
)
|
)
|
||||||
LibraryToolbar(
|
LibraryToolbar(
|
||||||
hasActiveFilters = state.hasActiveFilters,
|
hasActiveFilters = state.hasActiveFilters,
|
||||||
|
|
@ -156,7 +156,7 @@ data object LibraryTab : Tab {
|
||||||
onClickSelectAll = screenModel::selectAll,
|
onClickSelectAll = screenModel::selectAll,
|
||||||
onClickInvertSelection = screenModel::invertSelection,
|
onClickInvertSelection = screenModel::invertSelection,
|
||||||
onClickFilter = screenModel::showSettingsDialog,
|
onClickFilter = screenModel::showSettingsDialog,
|
||||||
onClickRefresh = { onClickRefresh(screenModel.activeCategory) },
|
onClickRefresh = { onClickRefresh(state.activeCategory) },
|
||||||
onClickGlobalUpdate = { onClickRefresh(null) },
|
onClickGlobalUpdate = { onClickRefresh(null) },
|
||||||
onClickOpenRandomManga = {
|
onClickOpenRandomManga = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|
@ -303,10 +303,10 @@ data object LibraryTab : Tab {
|
||||||
searchQuery = state.searchQuery,
|
searchQuery = state.searchQuery,
|
||||||
selection = state.selection,
|
selection = state.selection,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
currentPage = { screenModel.activeCategoryIndex },
|
currentPage = state.coercedActiveCategoryIndex,
|
||||||
hasActiveFilters = state.hasActiveFilters,
|
hasActiveFilters = state.hasActiveFilters,
|
||||||
showPageTabs = state.showCategoryTabs || !state.searchQuery.isNullOrEmpty(),
|
showPageTabs = state.showCategoryTabs || !state.searchQuery.isNullOrEmpty(),
|
||||||
onChangeCurrentPage = { screenModel.activeCategoryIndex = it },
|
onChangeCurrentPage = screenModel::updateActiveCategoryIndex,
|
||||||
onClickManga = { navigator.push(MangaScreen(it)) },
|
onClickManga = { navigator.push(MangaScreen(it)) },
|
||||||
onContinueReadingClicked = { it: LibraryManga ->
|
onContinueReadingClicked = { it: LibraryManga ->
|
||||||
scope.launchIO {
|
scope.launchIO {
|
||||||
|
|
@ -326,7 +326,7 @@ data object LibraryTab : Tab {
|
||||||
screenModel.toggleRangeSelection(category, manga)
|
screenModel.toggleRangeSelection(category, manga)
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
},
|
},
|
||||||
onRefresh = { onClickRefresh(screenModel.activeCategory) },
|
onRefresh = { onClickRefresh(state.activeCategory) },
|
||||||
onGlobalSearchClicked = {
|
onGlobalSearchClicked = {
|
||||||
navigator.push(GlobalSearchScreen(screenModel.state.value.searchQuery ?: ""))
|
navigator.push(GlobalSearchScreen(screenModel.state.value.searchQuery ?: ""))
|
||||||
},
|
},
|
||||||
|
|
@ -345,7 +345,7 @@ data object LibraryTab : Tab {
|
||||||
LibrarySettingsDialog(
|
LibrarySettingsDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
screenModel = settingsScreenModel,
|
screenModel = settingsScreenModel,
|
||||||
category = screenModel.activeCategory,
|
category = state.activeCategory,
|
||||||
// SY -->
|
// SY -->
|
||||||
hasCategories = state.libraryData.categories.fastAny { !it.isSystemCategory },
|
hasCategories = state.libraryData.categories.fastAny { !it.isSystemCategory },
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue