|
|
@@ -62,6 +62,7 @@ import io.legado.app.model.ImageProvider
|
|
|
import io.legado.app.model.ReadAloud
|
|
|
import io.legado.app.model.ReadAloudSessionStore
|
|
|
import io.legado.app.model.ReadBook
|
|
|
+import io.legado.app.model.ReaderBookmarkState
|
|
|
import io.legado.app.model.ReaderSession
|
|
|
import io.legado.app.model.ReaderSessionEvent
|
|
|
import io.legado.app.model.SourceCallBack
|
|
|
@@ -85,6 +86,7 @@ import io.legado.app.utils.toStringArray
|
|
|
import kotlinx.collections.immutable.toImmutableList
|
|
|
import kotlinx.collections.immutable.toImmutableMap
|
|
|
import kotlinx.coroutines.Dispatchers.IO
|
|
|
+import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
|
import kotlinx.coroutines.Job
|
|
|
import kotlinx.coroutines.channels.awaitClose
|
|
|
import kotlinx.coroutines.delay
|
|
|
@@ -94,7 +96,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
import kotlinx.coroutines.flow.asSharedFlow
|
|
|
import kotlinx.coroutines.flow.asStateFlow
|
|
|
import kotlinx.coroutines.flow.callbackFlow
|
|
|
+import kotlinx.coroutines.flow.distinctUntilChanged
|
|
|
import kotlinx.coroutines.flow.first
|
|
|
+import kotlinx.coroutines.flow.flatMapLatest
|
|
|
+import kotlinx.coroutines.flow.flowOf
|
|
|
+import kotlinx.coroutines.flow.map
|
|
|
import kotlinx.coroutines.flow.takeWhile
|
|
|
import kotlinx.coroutines.flow.update
|
|
|
import kotlinx.coroutines.launch
|
|
|
@@ -340,6 +346,10 @@ class ReadBookViewModel(
|
|
|
override fun setActiveSheet(sheet: ReadBookSheet?) {
|
|
|
_uiState.update { it.copy(menuState = ReadBookMenuState(), activeSheet = sheet) }
|
|
|
}
|
|
|
+
|
|
|
+ override fun emitEffect(effect: ReadBookEffect) {
|
|
|
+ _effects.tryEmit(effect)
|
|
|
+ }
|
|
|
},
|
|
|
bookmarkRepository = bookmarkRepository,
|
|
|
)
|
|
|
@@ -565,6 +575,7 @@ class ReadBookViewModel(
|
|
|
collectEventBus()
|
|
|
collectReaderSession()
|
|
|
collectReadStyle()
|
|
|
+ collectBookmarks()
|
|
|
replaceRuleDelegate.start()
|
|
|
execute { readAloudDelegate.syncConfiguredTtsVoices() }
|
|
|
}
|
|
|
@@ -600,6 +611,32 @@ class ReadBookViewModel(
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 维护 [ReaderBookmarkState]:渲染层要在主线程热路径上同步判定「本页是否有书签」画角标,
|
|
|
+ * 不能起协程查库,所以把当前书的书签位置整份缓存进会话快照。
|
|
|
+ *
|
|
|
+ * 书名/作者是 `bookmarks` 表的关联键(无 bookUrl 列),故以它们为 flow 的切换键。
|
|
|
+ */
|
|
|
+ @OptIn(ExperimentalCoroutinesApi::class)
|
|
|
+ private fun collectBookmarks() {
|
|
|
+ viewModelScope.launch {
|
|
|
+ uiState
|
|
|
+ .map { it.book?.let { book -> book.name to book.author } }
|
|
|
+ .distinctUntilChanged()
|
|
|
+ .flatMapLatest { key ->
|
|
|
+ if (key == null) {
|
|
|
+ flowOf(emptyList())
|
|
|
+ } else {
|
|
|
+ bookmarkRepository.flowByBook(key.first, key.second)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .collect { bookmarks ->
|
|
|
+ ReaderBookmarkState.update(bookmarks)
|
|
|
+ _effects.tryEmit(ReadBookEffect.UpBookmarkBadge)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 消费 [ReaderSession.state](Track A A5):会话快照在任意受控 mutator 完成后发射,
|
|
|
* 据此驱动 UiState 刷新。与遗留 CallBack 刷新路径叠加、幂等(相同结果 StateFlow 不再发),
|
|
|
@@ -861,6 +898,7 @@ class ReadBookViewModel(
|
|
|
is ReadBookIntent.SureNewProgress -> ReadBook.setProgress(intent.progress)
|
|
|
is ReadBookIntent.SureSyncProgress -> ReadBook.setProgress(intent.progress)
|
|
|
is ReadBookIntent.AddBookmark -> bookmarkDelegate.addForCurrentPage()
|
|
|
+ is ReadBookIntent.ToggleBookmark -> bookmarkDelegate.toggleForCurrentPage()
|
|
|
is ReadBookIntent.SaveBookmark -> bookmarkDelegate.save(intent.bookmark)
|
|
|
is ReadBookIntent.DeleteBookmark -> bookmarkDelegate.delete(intent.bookmark)
|
|
|
is ReadBookIntent.CancelSelect -> _effects.tryEmit(ReadBookEffect.CancelSelect)
|
|
|
@@ -2479,6 +2517,7 @@ class ReadBookViewModel(
|
|
|
|
|
|
override fun onCleared() {
|
|
|
translationStatusJob?.cancel()
|
|
|
+ ReaderBookmarkState.clear()
|
|
|
super.onCleared()
|
|
|
if (BaseReadAloudService.isRun && BaseReadAloudService.pause) {
|
|
|
ReadAloud.stop(context)
|