Fix reader loading indefinitely in some scenarios (#3686)
Init the reader from the view model instead of the activity so it isn't cancelled by a configuration change mid init, which left the retained view model half initialized and needsInit() returning false. Assisted-by: Claude Code:claude-opus-5
This commit is contained in:
@@ -98,7 +98,6 @@ import tachiyomi.core.common.Constants
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||
import tachiyomi.core.common.util.lang.withUIContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
@@ -171,26 +170,17 @@ class ReaderActivity : BaseActivity() {
|
||||
setContentView(binding.root)
|
||||
binding.setComposeOverlay()
|
||||
|
||||
if (viewModel.needsInit()) {
|
||||
val manga = intent.extras?.getLong("manga", -1) ?: -1L
|
||||
val chapter = intent.extras?.getLong("chapter", -1) ?: -1L
|
||||
if (manga == -1L || chapter == -1L) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
NotificationReceiver.dismissNotification(this, manga.hashCode(), Notifications.ID_NEW_CHAPTERS)
|
||||
|
||||
lifecycleScope.launch {
|
||||
val initResult = viewModel.init(manga, chapter)
|
||||
if (!initResult.getOrDefault(false)) {
|
||||
val exception = initResult.exceptionOrNull() ?: IllegalStateException("Unknown err")
|
||||
withUIContext {
|
||||
setInitialChapterError(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!viewModel.hasValidArgs) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
NotificationReceiver.dismissNotification(
|
||||
this,
|
||||
viewModel.mangaId.hashCode(),
|
||||
Notifications.ID_NEW_CHAPTERS,
|
||||
)
|
||||
|
||||
config = ReaderConfig()
|
||||
setMenuVisibility(viewModel.state.value.menuVisible)
|
||||
|
||||
@@ -200,6 +190,13 @@ class ReaderActivity : BaseActivity() {
|
||||
.onEach { if (!it) finish() }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
viewModel.state
|
||||
.map { it.initError }
|
||||
.distinctUntilChanged()
|
||||
.filterNotNull()
|
||||
.onEach(::setInitialChapterError)
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
viewModel.state
|
||||
.map { it.isLoadingAdjacentChapter }
|
||||
.distinctUntilChanged()
|
||||
|
||||
@@ -53,6 +53,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.common.preference.toggle
|
||||
@@ -107,6 +108,14 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
private val mutableState = MutableStateFlow(State())
|
||||
val state = mutableState.asStateFlow()
|
||||
|
||||
/**
|
||||
* Ids of the manga and chapter the reader was launched with, taken from the activity intent.
|
||||
*/
|
||||
val mangaId = savedState.get<Long>("manga") ?: -1L
|
||||
private val initialChapterId = savedState.get<Long>("chapter") ?: -1L
|
||||
|
||||
val hasValidArgs = mangaId != -1L && initialChapterId != -1L
|
||||
|
||||
private val eventChannel = Channel<Event>()
|
||||
val eventFlow = eventChannel.receiveAsFlow()
|
||||
|
||||
@@ -244,6 +253,10 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
chapterId = currentChapter.chapter.id!!
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
if (hasValidArgs) {
|
||||
viewModelScope.launch { init() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
@@ -265,41 +278,28 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this presenter is initialized yet.
|
||||
* Initializes this presenter with the [mangaId] and [initialChapterId] the reader was launched
|
||||
* with. This method will fetch the manga from the database and initialize the initial chapter.
|
||||
* Failures are reported through [State.initError].
|
||||
*/
|
||||
fun needsInit(): Boolean {
|
||||
return manga == null
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this presenter with the given [mangaId] and [initialChapterId]. This method will
|
||||
* fetch the manga from the database and initialize the initial chapter.
|
||||
*/
|
||||
suspend fun init(mangaId: Long, initialChapterId: Long): Result<Boolean> {
|
||||
if (!needsInit()) return Result.success(true)
|
||||
return withIOContext {
|
||||
private suspend fun init() {
|
||||
withIOContext {
|
||||
try {
|
||||
val manga = getManga.await(mangaId)
|
||||
if (manga != null) {
|
||||
sourceManager.isInitialized.first { it }
|
||||
mutableState.update { it.copy(manga = manga) }
|
||||
if (chapterId == -1L) chapterId = initialChapterId
|
||||
val manga = getManga.await(mangaId) ?: error("Requested manga of id $mangaId not found")
|
||||
sourceManager.isInitialized.first { it }
|
||||
mutableState.update { it.copy(manga = manga) }
|
||||
if (chapterId == -1L) chapterId = initialChapterId
|
||||
|
||||
val context = Injekt.get<Application>()
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source)
|
||||
val context = Injekt.get<Application>()
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source)
|
||||
|
||||
loadChapter(loader!!, chapterList.first { chapterId == it.chapter.id })
|
||||
Result.success(true)
|
||||
} else {
|
||||
// Unlikely but okay
|
||||
Result.success(false)
|
||||
}
|
||||
loadChapter(loader!!, chapterList.first { chapterId == it.chapter.id })
|
||||
} catch (e: Throwable) {
|
||||
if (e is CancellationException) {
|
||||
throw e
|
||||
}
|
||||
Result.failure(e)
|
||||
mutableState.update { it.copy(initError = e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -948,6 +948,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
@Immutable
|
||||
data class State(
|
||||
val manga: Manga? = null,
|
||||
val initError: Throwable? = null,
|
||||
val viewerChapters: ViewerChapters? = null,
|
||||
val bookmarked: Boolean = false,
|
||||
val isLoadingAdjacentChapter: Boolean = false,
|
||||
|
||||
Reference in New Issue
Block a user