mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-29 21:37:56 +01:00 
			
		
		
		
	Potentially fix library IndexOutOfBound crash (#2341)
				
					
				
			This commit is contained in:
		| @@ -31,7 +31,7 @@ fun LibraryContent( | ||||
|     searchQuery: String?, | ||||
|     selection: Set<Long>, | ||||
|     contentPadding: PaddingValues, | ||||
|     currentPage: () -> Int, | ||||
|     currentPage: Int, | ||||
|     hasActiveFilters: Boolean, | ||||
|     showPageTabs: Boolean, | ||||
|     onChangeCurrentPage: (Int) -> Unit, | ||||
| @@ -53,8 +53,7 @@ fun LibraryContent( | ||||
|             end = contentPadding.calculateEndPadding(LocalLayoutDirection.current), | ||||
|         ), | ||||
|     ) { | ||||
|         val coercedCurrentPage = remember { currentPage().coerceAtMost(categories.lastIndex) } | ||||
|         val pagerState = rememberPagerState(coercedCurrentPage) { categories.size } | ||||
|         val pagerState = rememberPagerState(currentPage) { categories.size } | ||||
|  | ||||
|         val scope = rememberCoroutineScope() | ||||
|         var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) } | ||||
|   | ||||
| @@ -1,8 +1,6 @@ | ||||
| package eu.kanade.tachiyomi.ui.library | ||||
|  | ||||
| import androidx.compose.runtime.Immutable | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.util.fastAny | ||||
| import androidx.compose.ui.util.fastFilter | ||||
| import androidx.compose.ui.util.fastMap | ||||
| @@ -39,6 +37,7 @@ import kotlinx.coroutines.flow.launchIn | ||||
| import kotlinx.coroutines.flow.map | ||||
| import kotlinx.coroutines.flow.onEach | ||||
| import kotlinx.coroutines.flow.update | ||||
| import kotlinx.coroutines.flow.updateAndGet | ||||
| import mihon.core.common.utils.mutate | ||||
| import tachiyomi.core.common.preference.CheckboxState | ||||
| import tachiyomi.core.common.preference.TriState | ||||
| @@ -86,10 +85,10 @@ class LibraryScreenModel( | ||||
|     private val trackerManager: TrackerManager = Injekt.get(), | ||||
| ) : StateScreenModel<LibraryScreenModel.State>(State()) { | ||||
|  | ||||
|     var activeCategoryIndex: Int by libraryPreferences.lastUsedCategory().asState(screenModelScope) | ||||
|     val activeCategory: Category get() = state.value.displayedCategories[activeCategoryIndex] | ||||
|  | ||||
|     init { | ||||
|         mutableState.update { state -> | ||||
|             state.copy(activeCategoryIndex = libraryPreferences.lastUsedCategory().get()) | ||||
|         } | ||||
|         screenModelScope.launchIO { | ||||
|             combine( | ||||
|                 state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), | ||||
| @@ -98,12 +97,14 @@ class LibraryScreenModel( | ||||
|                 combine(getTracksPerManga.subscribe(), getTrackingFiltersFlow(), ::Pair), | ||||
|                 getLibraryItemPreferencesFlow(), | ||||
|             ) { searchQuery, categories, favorites, (tracksMap, trackingFilters), itemPreferences -> | ||||
|                 val showSystemCategory = favorites.any { it.libraryManga.categories.contains(0) } | ||||
|                 val filteredFavorites = favorites | ||||
|                     .applyFilters(tracksMap, trackingFilters, itemPreferences) | ||||
|                     .let { if (searchQuery == null) it else it.filter { m -> m.matches(searchQuery) } } | ||||
|  | ||||
|                 LibraryData( | ||||
|                     isInitialized = true, | ||||
|                     showSystemCategory = showSystemCategory, | ||||
|                     categories = categories, | ||||
|                     favorites = filteredFavorites, | ||||
|                     tracksMap = tracksMap, | ||||
| @@ -125,7 +126,7 @@ class LibraryScreenModel( | ||||
|                 .distinctUntilChanged() | ||||
|                 .map { data -> | ||||
|                     data.favorites | ||||
|                         .applyGrouping(data.categories) | ||||
|                         .applyGrouping(data.categories, data.showSystemCategory) | ||||
|                         .applySort(data.favoritesById, data.tracksMap, data.loggedInTrackerIds) | ||||
|                 } | ||||
|                 .collectLatest { | ||||
| @@ -256,6 +257,7 @@ class LibraryScreenModel( | ||||
|  | ||||
|     private fun List<LibraryItem>.applyGrouping( | ||||
|         categories: List<Category>, | ||||
|         showSystemCategory: Boolean, | ||||
|     ): Map<Category, List</* LibraryItem */ Long>> { | ||||
|         val groupCache = mutableMapOf</* Category */ Long, MutableList</* LibraryItem */ Long>>() | ||||
|         forEach { item -> | ||||
| @@ -263,7 +265,6 @@ class LibraryScreenModel( | ||||
|                 groupCache.getOrPut(categoryId) { mutableListOf() }.add(item.id) | ||||
|             } | ||||
|         } | ||||
|         val showSystemCategory = groupCache.containsKey(0L) | ||||
|         return categories.filter { showSystemCategory || !it.isSystemCategory } | ||||
|             .associateWith { groupCache[it.id]?.toList().orEmpty() } | ||||
|     } | ||||
| @@ -571,7 +572,8 @@ class LibraryScreenModel( | ||||
|     } | ||||
|  | ||||
|     fun getRandomLibraryItemForCurrentCategory(): LibraryItem? { | ||||
|         return state.value.getItemsForCategoryId(activeCategory.id).randomOrNull() | ||||
|         val state = state.value | ||||
|         return state.getItemsForCategoryId(state.activeCategory.id).randomOrNull() | ||||
|     } | ||||
|  | ||||
|     fun showSettingsDialog() { | ||||
| @@ -629,7 +631,7 @@ class LibraryScreenModel( | ||||
|         lastSelectionCategory = null | ||||
|         mutableState.update { state -> | ||||
|             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) | ||||
|         } | ||||
| @@ -639,7 +641,7 @@ class LibraryScreenModel( | ||||
|         lastSelectionCategory = null | ||||
|         mutableState.update { state -> | ||||
|             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 } | ||||
|                 list.removeAll(toRemove) | ||||
|                 list.addAll(toAdd) | ||||
| @@ -652,6 +654,15 @@ class LibraryScreenModel( | ||||
|         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() { | ||||
|         screenModelScope.launchIO { | ||||
|             // Create a copy of selected manga | ||||
| @@ -714,6 +725,7 @@ class LibraryScreenModel( | ||||
|     @Immutable | ||||
|     data class LibraryData( | ||||
|         val isInitialized: Boolean = false, | ||||
|         val showSystemCategory: Boolean = false, | ||||
|         val categories: List<Category> = emptyList(), | ||||
|         val favorites: List<LibraryItem> = emptyList(), | ||||
|         val tracksMap: Map</* Manga */ Long, List<Track>> = emptyMap(), | ||||
| @@ -734,16 +746,24 @@ class LibraryScreenModel( | ||||
|         val showMangaContinueButton: Boolean = false, | ||||
|         val dialog: Dialog? = null, | ||||
|         val libraryData: LibraryData = LibraryData(), | ||||
|         private val activeCategoryIndex: Int = 0, | ||||
|         private val groupedFavorites: Map<Category, List</* LibraryItem */ Long>> = emptyMap(), | ||||
|     ) { | ||||
|         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 selectionMode = selection.isNotEmpty() | ||||
|  | ||||
|         val selectedManga by lazy { selection.mapNotNull { libraryData.favoritesById[it]?.libraryManga?.manga } } | ||||
|  | ||||
|         val displayedCategories = groupedFavorites.keys.toList() | ||||
|  | ||||
|         fun getItemsForCategoryId(categoryId: Long): List<LibraryItem> { | ||||
|             val category = displayedCategories.find { it.id == categoryId } ?: return emptyList() | ||||
|             return getItemsForCategory(category) | ||||
|   | ||||
| @@ -111,7 +111,7 @@ data object LibraryTab : Tab { | ||||
|                 val title = state.getToolbarTitle( | ||||
|                     defaultTitle = stringResource(MR.strings.label_library), | ||||
|                     defaultCategoryTitle = stringResource(MR.strings.label_default), | ||||
|                     page = screenModel.activeCategoryIndex, | ||||
|                     page = state.coercedActiveCategoryIndex, | ||||
|                 ) | ||||
|                 LibraryToolbar( | ||||
|                     hasActiveFilters = state.hasActiveFilters, | ||||
| @@ -121,7 +121,7 @@ data object LibraryTab : Tab { | ||||
|                     onClickSelectAll = screenModel::selectAll, | ||||
|                     onClickInvertSelection = screenModel::invertSelection, | ||||
|                     onClickFilter = screenModel::showSettingsDialog, | ||||
|                     onClickRefresh = { onClickRefresh(screenModel.activeCategory) }, | ||||
|                     onClickRefresh = { onClickRefresh(state.activeCategory) }, | ||||
|                     onClickGlobalUpdate = { onClickRefresh(null) }, | ||||
|                     onClickOpenRandomManga = { | ||||
|                         scope.launch { | ||||
| @@ -183,10 +183,10 @@ data object LibraryTab : Tab { | ||||
|                         searchQuery = state.searchQuery, | ||||
|                         selection = state.selection, | ||||
|                         contentPadding = contentPadding, | ||||
|                         currentPage = { screenModel.activeCategoryIndex }, | ||||
|                         currentPage = state.coercedActiveCategoryIndex, | ||||
|                         hasActiveFilters = state.hasActiveFilters, | ||||
|                         showPageTabs = state.showCategoryTabs || !state.searchQuery.isNullOrEmpty(), | ||||
|                         onChangeCurrentPage = { screenModel.activeCategoryIndex = it }, | ||||
|                         onChangeCurrentPage = screenModel::updateActiveCategoryIndex, | ||||
|                         onClickManga = { navigator.push(MangaScreen(it)) }, | ||||
|                         onContinueReadingClicked = { it: LibraryManga -> | ||||
|                             scope.launchIO { | ||||
| @@ -206,7 +206,7 @@ data object LibraryTab : Tab { | ||||
|                             screenModel.toggleRangeSelection(category, manga) | ||||
|                             haptic.performHapticFeedback(HapticFeedbackType.LongPress) | ||||
|                         }, | ||||
|                         onRefresh = { onClickRefresh(screenModel.activeCategory) }, | ||||
|                         onRefresh = { onClickRefresh(state.activeCategory) }, | ||||
|                         onGlobalSearchClicked = { | ||||
|                             navigator.push(GlobalSearchScreen(screenModel.state.value.searchQuery ?: "")) | ||||
|                         }, | ||||
| @@ -225,7 +225,7 @@ data object LibraryTab : Tab { | ||||
|                 LibrarySettingsDialog( | ||||
|                     onDismissRequest = onDismissRequest, | ||||
|                     screenModel = settingsScreenModel, | ||||
|                     category = screenModel.activeCategory, | ||||
|                     category = state.activeCategory, | ||||
|                 ) | ||||
|             } | ||||
|             is LibraryScreenModel.Dialog.ChangeCategory -> { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user