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:
@@ -23,6 +23,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
|||||||
### Fixed
|
### Fixed
|
||||||
- Fixed extension installation with shizuku installer ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#3676](https://github.com/mihonapp/mihon/pull/3676))
|
- Fixed extension installation with shizuku installer ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#3676](https://github.com/mihonapp/mihon/pull/3676))
|
||||||
- Fixed `X-Requested-With` spoofing leaking to unrelated callers ([@AntsyLich](https://github.com/AntsyLich)) ([#3678](https://github.com/mihonapp/mihon/pull/3678))
|
- Fixed `X-Requested-With` spoofing leaking to unrelated callers ([@AntsyLich](https://github.com/AntsyLich)) ([#3678](https://github.com/mihonapp/mihon/pull/3678))
|
||||||
|
- Fixed reader loading indefinitely in some scenarios ([@AntsyLich](https://github.com/AntsyLich)) ([#3686](https://github.com/mihonapp/mihon/pull/3686))
|
||||||
|
|
||||||
## [v0.20.2] - 2026-08-01
|
## [v0.20.2] - 2026-08-01
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ import tachiyomi.core.common.Constants
|
|||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||||
import tachiyomi.core.common.util.lang.withUIContext
|
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
@@ -171,25 +170,16 @@ class ReaderActivity : BaseActivity() {
|
|||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
binding.setComposeOverlay()
|
binding.setComposeOverlay()
|
||||||
|
|
||||||
if (viewModel.needsInit()) {
|
if (!viewModel.hasValidArgs) {
|
||||||
val manga = intent.extras?.getLong("manga", -1) ?: -1L
|
|
||||||
val chapter = intent.extras?.getLong("chapter", -1) ?: -1L
|
|
||||||
if (manga == -1L || chapter == -1L) {
|
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
NotificationReceiver.dismissNotification(this, manga.hashCode(), Notifications.ID_NEW_CHAPTERS)
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
NotificationReceiver.dismissNotification(
|
||||||
val initResult = viewModel.init(manga, chapter)
|
this,
|
||||||
if (!initResult.getOrDefault(false)) {
|
viewModel.mangaId.hashCode(),
|
||||||
val exception = initResult.exceptionOrNull() ?: IllegalStateException("Unknown err")
|
Notifications.ID_NEW_CHAPTERS,
|
||||||
withUIContext {
|
)
|
||||||
setInitialChapterError(exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config = ReaderConfig()
|
config = ReaderConfig()
|
||||||
setMenuVisibility(viewModel.state.value.menuVisible)
|
setMenuVisibility(viewModel.state.value.menuVisible)
|
||||||
@@ -200,6 +190,13 @@ class ReaderActivity : BaseActivity() {
|
|||||||
.onEach { if (!it) finish() }
|
.onEach { if (!it) finish() }
|
||||||
.launchIn(lifecycleScope)
|
.launchIn(lifecycleScope)
|
||||||
|
|
||||||
|
viewModel.state
|
||||||
|
.map { it.initError }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.filterNotNull()
|
||||||
|
.onEach(::setInitialChapterError)
|
||||||
|
.launchIn(lifecycleScope)
|
||||||
|
|
||||||
viewModel.state
|
viewModel.state
|
||||||
.map { it.isLoadingAdjacentChapter }
|
.map { it.isLoadingAdjacentChapter }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.common.preference.toggle
|
import tachiyomi.core.common.preference.toggle
|
||||||
@@ -107,6 +108,14 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
private val mutableState = MutableStateFlow(State())
|
private val mutableState = MutableStateFlow(State())
|
||||||
val state = mutableState.asStateFlow()
|
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>()
|
private val eventChannel = Channel<Event>()
|
||||||
val eventFlow = eventChannel.receiveAsFlow()
|
val eventFlow = eventChannel.receiveAsFlow()
|
||||||
|
|
||||||
@@ -244,6 +253,10 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
chapterId = currentChapter.chapter.id!!
|
chapterId = currentChapter.chapter.id!!
|
||||||
}
|
}
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
if (hasValidArgs) {
|
||||||
|
viewModelScope.launch { init() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
@@ -265,22 +278,14 @@ 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 {
|
private suspend fun init() {
|
||||||
return manga == null
|
withIOContext {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {
|
|
||||||
try {
|
try {
|
||||||
val manga = getManga.await(mangaId)
|
val manga = getManga.await(mangaId) ?: error("Requested manga of id $mangaId not found")
|
||||||
if (manga != null) {
|
|
||||||
sourceManager.isInitialized.first { it }
|
sourceManager.isInitialized.first { it }
|
||||||
mutableState.update { it.copy(manga = manga) }
|
mutableState.update { it.copy(manga = manga) }
|
||||||
if (chapterId == -1L) chapterId = initialChapterId
|
if (chapterId == -1L) chapterId = initialChapterId
|
||||||
@@ -290,16 +295,11 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source)
|
loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source)
|
||||||
|
|
||||||
loadChapter(loader!!, chapterList.first { chapterId == it.chapter.id })
|
loadChapter(loader!!, chapterList.first { chapterId == it.chapter.id })
|
||||||
Result.success(true)
|
|
||||||
} else {
|
|
||||||
// Unlikely but okay
|
|
||||||
Result.success(false)
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
if (e is CancellationException) {
|
if (e is CancellationException) {
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
Result.failure(e)
|
mutableState.update { it.copy(initError = e) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -948,6 +948,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
@Immutable
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val manga: Manga? = null,
|
val manga: Manga? = null,
|
||||||
|
val initError: Throwable? = null,
|
||||||
val viewerChapters: ViewerChapters? = null,
|
val viewerChapters: ViewerChapters? = null,
|
||||||
val bookmarked: Boolean = false,
|
val bookmarked: Boolean = false,
|
||||||
val isLoadingAdjacentChapter: Boolean = false,
|
val isLoadingAdjacentChapter: Boolean = false,
|
||||||
|
|||||||
Reference in New Issue
Block a user