Przeglądaj źródła

fix: stabilize manga reader navigation state

HapeLee 3 miesięcy temu
rodzic
commit
3eb5d1329f

+ 15 - 4
app/src/main/java/io/legado/app/ui/book/manga/MangaReaderContract.kt

@@ -1,9 +1,10 @@
 package io.legado.app.ui.book.manga
 package io.legado.app.ui.book.manga
 
 
+import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color
-import io.legado.app.data.entities.BookProgress
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
+import io.legado.app.data.entities.BookProgress
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.ui.book.manga.config.MangaScrollMode
 import io.legado.app.ui.book.manga.config.MangaScrollMode
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.ImmutableList
@@ -19,7 +20,7 @@ data class MangaReaderUiState(
     val sourceName: String = "",
     val sourceName: String = "",
     val sourceUrl: String? = null,
     val sourceUrl: String? = null,
     val sourceType: Int? = null,
     val sourceType: Int? = null,
-    val changeSourceBook: Book? = null,
+    val changeSourceBook: MangaBookSnapshot? = null,
     val pages: ImmutableList<MangaReaderItemUi> = persistentListOf(),
     val pages: ImmutableList<MangaReaderItemUi> = persistentListOf(),
     val currentItemIndex: Int = 0,
     val currentItemIndex: Int = 0,
     val currentPage: Int = 0,
     val currentPage: Int = 0,
@@ -100,6 +101,10 @@ data class MangaReaderSettings(
 @Stable
 @Stable
 data class MangaScrollRequest(val id: Long, val itemIndex: Int, val animated: Boolean)
 data class MangaScrollRequest(val id: Long, val itemIndex: Int, val animated: Boolean)
 
 
+@Immutable
+@JvmInline
+value class MangaBookSnapshot(val json: String)
+
 sealed interface MangaReaderIntent {
 sealed interface MangaReaderIntent {
     data class Initialize(
     data class Initialize(
         val bookUrl: String?,
         val bookUrl: String?,
@@ -113,8 +118,14 @@ sealed interface MangaReaderIntent {
     data object ReloadContent : MangaReaderIntent
     data object ReloadContent : MangaReaderIntent
     data class ApplyReadingProgress(val progress: BookProgress) : MangaReaderIntent
     data class ApplyReadingProgress(val progress: BookProgress) : MangaReaderIntent
     data class OpenChapter(val chapterIndex: Int, val pageIndex: Int) : MangaReaderIntent
     data class OpenChapter(val chapterIndex: Int, val pageIndex: Int) : MangaReaderIntent
-    data class ChangeSourceBook(val book: Book, val toc: List<BookChapter>) : MangaReaderIntent
-    data class AddExternalBookToShelf(val book: Book, val toc: List<BookChapter>) : MangaReaderIntent
+    data class ChangeSourceBook(
+        val book: Book,
+        val toc: List<BookChapter>,
+    ) : MangaReaderIntent
+    data class AddExternalBookToShelf(
+        val book: Book,
+        val toc: List<BookChapter>,
+    ) : MangaReaderIntent
     data object AddCurrentBookToShelf : MangaReaderIntent
     data object AddCurrentBookToShelf : MangaReaderIntent
     data object DiscardCurrentBookAndExit : MangaReaderIntent
     data object DiscardCurrentBookAndExit : MangaReaderIntent
     data object DismissDialog : MangaReaderIntent
     data object DismissDialog : MangaReaderIntent

+ 9 - 2
app/src/main/java/io/legado/app/ui/book/manga/MangaReaderScreen.kt

@@ -58,9 +58,12 @@ import coil3.request.crossfade
 import coil3.request.transformations
 import coil3.request.transformations
 import io.legado.app.ui.book.manga.config.MangaScrollMode
 import io.legado.app.ui.book.manga.config.MangaScrollMode
 import io.legado.app.R
 import io.legado.app.R
+import io.legado.app.data.entities.Book
 import io.legado.app.help.coil.CoverExtras
 import io.legado.app.help.coil.CoverExtras
 import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.changeSource.ChangeSourceSheet
 import io.legado.app.ui.widget.components.changeSource.ChangeSourceSheet
+import io.legado.app.utils.GSON
+import io.legado.app.utils.fromJsonObject
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.distinctUntilChanged
 import org.koin.compose.koinInject
 import org.koin.compose.koinInject
@@ -140,10 +143,14 @@ fun MangaReaderScreen(
         MangaReaderSettingsSheet(sheet, state, onIntent)
         MangaReaderSettingsSheet(sheet, state, onIntent)
     }
     }
     if (state.activeSheet == MangaReaderSheet.ChangeSource) {
     if (state.activeSheet == MangaReaderSheet.ChangeSource) {
-        state.changeSourceBook?.let { oldBook ->
+        val oldBook = remember(state.changeSourceBook) {
+            state.changeSourceBook
+                ?.let { GSON.fromJsonObject<Book>(it.json).getOrNull() }
+        }
+        oldBook?.let {
             ChangeSourceSheet(
             ChangeSourceSheet(
                 show = true,
                 show = true,
-                oldBook = oldBook,
+                oldBook = it,
                 fromReadBookActivity = true,
                 fromReadBookActivity = true,
                 allowAddAsNew = true,
                 allowAddAsNew = true,
                 dismissOnReplaceStart = true,
                 dismissOnReplaceStart = true,

+ 3 - 1
app/src/main/java/io/legado/app/ui/book/manga/MangaReaderViewModel.kt

@@ -179,7 +179,9 @@ class MangaReaderViewModel(
                 _uiState.update {
                 _uiState.update {
                     it.copy(
                     it.copy(
                         activeSheet = MangaReaderSheet.ChangeSource,
                         activeSheet = MangaReaderSheet.ChangeSource,
-                        changeSourceBook = sessionRepository.currentBook(),
+                        changeSourceBook = sessionRepository.currentBook()?.let {
+                            MangaBookSnapshot(GSON.toJson(it))
+                        },
                     )
                     )
                 }
                 }
             }
             }

+ 11 - 0
app/src/main/java/io/legado/app/ui/main/MainNavigator.kt

@@ -24,6 +24,17 @@ object MainNavigator {
         val currentRoute = backStack.lastOrNull()
         val currentRoute = backStack.lastOrNull()
         if (currentRoute == route) return
         if (currentRoute == route) return
 
 
+        if (route is MainRouteReadManga) {
+            val existingReaderIndex = backStack.indexOfLast { it is MainRouteReadManga }
+            if (existingReaderIndex >= 0) {
+                while (backStack.lastIndex > existingReaderIndex) {
+                    backStack.removeAt(backStack.lastIndex)
+                }
+                backStack[existingReaderIndex] = route
+                return
+            }
+        }
+
         // 导航动画和阅读页组合要花几百毫秒, 这段时间足够把正文读出来并排版好
         // 导航动画和阅读页组合要花几百毫秒, 这段时间足够把正文读出来并排版好
         if (route is MainRouteReadBook && !route.chapterChanged) {
         if (route is MainRouteReadBook && !route.chapterChanged) {
             route.bookUrl?.let { ReadBook.prefetchForOpen(it) }
             route.bookUrl?.let { ReadBook.prefetchForOpen(it) }

+ 36 - 0
app/src/test/java/io/legado/app/ui/main/MainNavigatorMangaTest.kt

@@ -0,0 +1,36 @@
+package io.legado.app.ui.main
+
+import androidx.navigation3.runtime.NavKey
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class MainNavigatorMangaTest {
+
+    @Test
+    fun `opening manga from its book info reuses existing reader entry`() {
+        val originalReader = MainRouteReadManga(bookUrl = "book-a")
+        val backStack = mutableListOf<NavKey>(
+            MainRouteHome,
+            originalReader,
+            MainRouteBookInfo("Book A", "Author", "book-a"),
+        )
+
+        MainNavigator.navigateToRoute(backStack, originalReader)
+
+        assertEquals(listOf(MainRouteHome, originalReader), backStack)
+    }
+
+    @Test
+    fun `opening another manga replaces existing reader entry`() {
+        val replacement = MainRouteReadManga(bookUrl = "book-b")
+        val backStack = mutableListOf<NavKey>(
+            MainRouteHome,
+            MainRouteReadManga(bookUrl = "book-a"),
+            MainRouteBookInfo("Book B", "Author", "book-b"),
+        )
+
+        MainNavigator.navigateToRoute(backStack, replacement)
+
+        assertEquals(listOf(MainRouteHome, replacement), backStack)
+    }
+}