Improve listener system

This commit is contained in:
Syer10
2025-10-07 12:33:24 -04:00
parent 2a8d937992
commit 1641a0e9f4
21 changed files with 102 additions and 111 deletions

View File

@@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
@@ -34,16 +33,6 @@ class ServerListeners {
) )
val mangaListener = _mangaListener.asSharedFlow() val mangaListener = _mangaListener.asSharedFlow()
private val _chapterIdsListener = MutableSharedFlow<List<Long>>(
extraBufferCapacity = Channel.UNLIMITED,
)
val chapterIdsListener = _chapterIdsListener.asSharedFlow()
private val _mangaChapterIdsListener = MutableSharedFlow<List<Long>>(
extraBufferCapacity = Channel.UNLIMITED,
)
val mangaChapterIdsListener = _mangaChapterIdsListener.asSharedFlow()
private val categoryMangaListener = MutableSharedFlow<Long>( private val categoryMangaListener = MutableSharedFlow<Long>(
extraBufferCapacity = Channel.UNLIMITED, extraBufferCapacity = Channel.UNLIMITED,
) )
@@ -65,9 +54,23 @@ class ServerListeners {
.buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) .buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
.flatMapLatest { flow } .flatMapLatest { flow }
fun updateManga(vararg ids: Long) { fun updateManga(ids: List<Long>) {
val ids = ids.filter { id -> id >= 0 }
if (ids.isEmpty()) {
return
}
scope.launch { scope.launch {
_mangaListener.emit(ids.toList()) _mangaListener.emit(ids)
}
}
fun updateManga(vararg ids: Long) {
val ids = ids.filter { id -> id >= 0 }
if (ids.isEmpty()) {
return
}
scope.launch {
_mangaListener.emit(ids)
} }
} }
@@ -88,36 +91,6 @@ class ServerListeners {
} }
} }
fun <T> combineChapters(
flow: Flow<T>,
chapterIdPredate: (suspend (List<Long>) -> Boolean)? = null,
mangaIdPredate: (suspend (List<Long>) -> Boolean)? = null,
): Flow<T> {
val idsListener = _chapterIdsListener
.filter { chapterIdPredate?.invoke(it) ?: false }
.startWith(Unit)
.combine(
_mangaChapterIdsListener.filter { mangaIdPredate?.invoke(it) ?: false }
.startWith(Unit),
) { _, _ -> }
return idsListener
.buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
.flatMapLatest { flow }
}
fun updateChapters(chapterIds: List<Long>) {
scope.launch {
_chapterIdsListener.emit(chapterIds)
}
}
fun updateChapters(vararg chapterIds: Long) {
scope.launch {
_chapterIdsListener.emit(chapterIds.toList())
}
}
companion object { companion object {
private val log = logging() private val log = logging()
} }

View File

@@ -23,8 +23,9 @@ class DeleteChapterDownload(
) { ) {
suspend fun await( suspend fun await(
chapterId: Long, chapterId: Long,
mangaId: Long?,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapterId) ) = asFlow(chapterId, mangaId)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to delete chapter download for $chapterId" } log.warn(it) { "Failed to delete chapter download for $chapterId" }
@@ -44,8 +45,9 @@ class DeleteChapterDownload(
suspend fun await( suspend fun await(
chapterIds: List<Long>, chapterIds: List<Long>,
mangaIds: List<Long>?,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapterIds) ) = asFlow(chapterIds, mangaIds)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to delete chapter download for $chapterIds" } log.warn(it) { "Failed to delete chapter download for $chapterIds" }
@@ -63,23 +65,23 @@ class DeleteChapterDownload(
} }
.collect() .collect()
fun asFlow(chapterId: Long) = fun asFlow(chapterId: Long, mangaId: Long?) =
chapterRepository.deleteDownloadedChapter(chapterId) chapterRepository.deleteDownloadedChapter(chapterId)
.onEach { serverListeners.updateChapters(chapterId) } .onEach { serverListeners.updateManga(mangaId ?: -1) }
@JvmName("asFlowChapter") @JvmName("asFlowChapter")
fun asFlow(chapter: Chapter) = fun asFlow(chapter: Chapter) =
chapterRepository.deleteDownloadedChapter(chapter.id) chapterRepository.deleteDownloadedChapter(chapter.id)
.onEach { serverListeners.updateChapters(chapter.id) } .onEach { serverListeners.updateManga(chapter.mangaId) }
fun asFlow(chapterIds: List<Long>) = fun asFlow(chapterIds: List<Long>, mangaIds: List<Long>?) =
chapterRepository.deleteDownloadedChapters(chapterIds) chapterRepository.deleteDownloadedChapters(chapterIds)
.onEach { serverListeners.updateChapters(chapterIds) } .onEach { serverListeners.updateManga(mangaIds.orEmpty()) }
@JvmName("asFlowChapters") @JvmName("asFlowChapters")
fun asFlow(chapter: List<Chapter>) = fun asFlow(chapter: List<Chapter>) =
chapterRepository.deleteDownloadedChapters(chapter.map { it.id }) chapterRepository.deleteDownloadedChapters(chapter.map { it.id })
.onEach { serverListeners.updateChapters(chapter.map { it.id }) } .onEach { serverListeners.updateManga(chapter.map { it.mangaId }) }
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -22,8 +22,9 @@ class GetChapter(
) { ) {
suspend fun await( suspend fun await(
chapterId: Long, chapterId: Long,
mangaId: Long?,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapterId) ) = asFlow(chapterId, mangaId)
.take(1) .take(1)
.catch { .catch {
onError(it) onError(it)
@@ -42,16 +43,16 @@ class GetChapter(
} }
.singleOrNull() .singleOrNull()
fun asFlow(chapterId: Long) = fun asFlow(chapterId: Long, mangaId: Long?) =
serverListeners.combineChapters( serverListeners.combineMangaUpdates(
chapterRepository.getChapter(chapterId), chapterRepository.getChapter(chapterId),
chapterIdPredate = { ids -> chapterId in ids }, predate = { ids -> mangaId in ids },
) )
fun asFlow(chapter: Chapter) = fun asFlow(chapter: Chapter) =
serverListeners.combineChapters( serverListeners.combineMangaUpdates(
chapterRepository.getChapter(chapter.id), chapterRepository.getChapter(chapter.id),
chapterIdPredate = { ids -> chapter.id in ids }, predate = { ids -> chapter.mangaId in ids },
) )
companion object { companion object {

View File

@@ -43,15 +43,15 @@ class GetChapters(
.singleOrNull() .singleOrNull()
fun asFlow(mangaId: Long) = fun asFlow(mangaId: Long) =
serverListeners.combineChapters( serverListeners.combineMangaUpdates(
chapterRepository.getChapters(mangaId), chapterRepository.getChapters(mangaId),
chapterIdPredate = { ids -> false }, // todo predate = { ids -> mangaId in ids },
) )
fun asFlow(manga: Manga) = fun asFlow(manga: Manga) =
serverListeners.combineChapters( serverListeners.combineMangaUpdates(
chapterRepository.getChapters(manga.id), chapterRepository.getChapters(manga.id),
chapterIdPredate = { ids -> false }, // todo predate = { ids -> manga.id in ids },
) )
companion object { companion object {

View File

@@ -40,13 +40,13 @@ class RefreshChapters(
} }
.singleOrNull() .singleOrNull()
fun asFlow(mangaId: Long) = fun asFlow(mangaId: Long, ) =
chapterRepository.fetchChapters(mangaId) chapterRepository.fetchChapters(mangaId)
.onEach { serverListeners.updateChapters(mangaId) } .onEach { serverListeners.updateManga(mangaId) }
fun asFlow(manga: Manga) = fun asFlow(manga: Manga) =
chapterRepository.fetchChapters(manga.id) chapterRepository.fetchChapters(manga.id)
.onEach { serverListeners.updateChapters(manga.id) } .onEach { serverListeners.updateManga(manga.id) }
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -23,11 +23,12 @@ class UpdateChapter(
) { ) {
suspend fun await( suspend fun await(
chapterId: Long, chapterId: Long,
mangaId: Long?,
bookmarked: Boolean? = null, bookmarked: Boolean? = null,
read: Boolean? = null, read: Boolean? = null,
lastPageRead: Int? = null, lastPageRead: Int? = null,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapterId, bookmarked, read, lastPageRead) ) = asFlow(chapterId, mangaId, bookmarked, read, lastPageRead)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to update chapter bookmark for chapter $chapterId" } log.warn(it) { "Failed to update chapter bookmark for chapter $chapterId" }
@@ -49,11 +50,12 @@ class UpdateChapter(
suspend fun await( suspend fun await(
chapterIds: List<Long>, chapterIds: List<Long>,
mangaIds: List<Long>?,
bookmarked: Boolean? = null, bookmarked: Boolean? = null,
read: Boolean? = null, read: Boolean? = null,
lastPageRead: Int? = null, lastPageRead: Int? = null,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapterIds, bookmarked, read, lastPageRead) ) = asFlow(chapterIds, mangaIds, bookmarked, read, lastPageRead)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to update chapter bookmark for chapters $chapterIds" } log.warn(it) { "Failed to update chapter bookmark for chapters $chapterIds" }
@@ -76,6 +78,7 @@ class UpdateChapter(
fun asFlow( fun asFlow(
chapterId: Long, chapterId: Long,
mangaId: Long?,
bookmarked: Boolean? = null, bookmarked: Boolean? = null,
read: Boolean? = null, read: Boolean? = null,
lastPageRead: Int? = null, lastPageRead: Int? = null,
@@ -84,7 +87,7 @@ class UpdateChapter(
bookmarked = bookmarked, bookmarked = bookmarked,
read = read, read = read,
lastPageRead = lastPageRead, lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(chapterId) } ).onEach { serverListeners.updateManga(mangaId ?: -1) }
fun asFlow( fun asFlow(
chapter: Chapter, chapter: Chapter,
@@ -96,10 +99,11 @@ class UpdateChapter(
bookmarked = bookmarked, bookmarked = bookmarked,
read = read, read = read,
lastPageRead = lastPageRead, lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(chapter.id) } ).onEach { serverListeners.updateManga(chapter.mangaId) }
fun asFlow( fun asFlow(
chapterIds: List<Long>, chapterIds: List<Long>,
mangaIds: List<Long>?,
bookmarked: Boolean? = null, bookmarked: Boolean? = null,
read: Boolean? = null, read: Boolean? = null,
lastPageRead: Int? = null, lastPageRead: Int? = null,
@@ -108,7 +112,7 @@ class UpdateChapter(
bookmarked = bookmarked, bookmarked = bookmarked,
read = read, read = read,
lastPageRead = lastPageRead, lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(chapterIds) } ).onEach { serverListeners.updateManga(mangaIds.orEmpty()) }
@JvmName("asFlowChapters") @JvmName("asFlowChapters")
fun asFlow( fun asFlow(
@@ -121,7 +125,7 @@ class UpdateChapter(
bookmarked = bookmarked, bookmarked = bookmarked,
read = read, read = read,
lastPageRead = lastPageRead, lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(chapters.map { it.id }) } ).onEach { serverListeners.updateManga(chapters.map { it.mangaId }.distinct()) }
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -22,9 +22,10 @@ class UpdateChapterLastPageRead(
) { ) {
suspend fun await( suspend fun await(
chapterId: Long, chapterId: Long,
mangaId: Long?,
lastPageRead: Int, lastPageRead: Int,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapterId, lastPageRead) ) = asFlow(chapterId, mangaId, lastPageRead)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to update chapter last page read for chapter $chapterId" } log.warn(it) { "Failed to update chapter last page read for chapter $chapterId" }
@@ -44,11 +45,12 @@ class UpdateChapterLastPageRead(
fun asFlow( fun asFlow(
chapterId: Long, chapterId: Long,
mangaId: Long?,
lastPageRead: Int, lastPageRead: Int,
) = chapterRepository.updateChapter( ) = chapterRepository.updateChapter(
chapterId = chapterId, chapterId = chapterId,
lastPageRead = lastPageRead, lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(chapterId) } ).onEach { serverListeners.updateManga(mangaId ?: -1) }
fun asFlow( fun asFlow(
chapter: Chapter, chapter: Chapter,
@@ -56,7 +58,7 @@ class UpdateChapterLastPageRead(
) = chapterRepository.updateChapter( ) = chapterRepository.updateChapter(
chapterId = chapter.id, chapterId = chapter.id,
lastPageRead = lastPageRead, lastPageRead = lastPageRead,
).onEach { serverListeners.updateChapters(chapter.id) } ).onEach { serverListeners.updateManga(chapter.mangaId) }
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -41,7 +41,7 @@ class UpdateChapterMeta(
"juiPageOffset", "juiPageOffset",
pageOffset.toString(), pageOffset.toString(),
).collect() ).collect()
serverListeners.updateChapters(chapter.id) serverListeners.updateManga(chapter.mangaId)
} }
emit(Unit) emit(Unit)
} }

View File

@@ -22,9 +22,10 @@ class UpdateChapterRead(
) { ) {
suspend fun await( suspend fun await(
chapterId: Long, chapterId: Long,
mangaId: Long?,
read: Boolean, read: Boolean,
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(chapterId, read) ) = asFlow(chapterId, mangaId, read)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to update chapter read status for chapter $chapterId" } log.warn(it) { "Failed to update chapter read status for chapter $chapterId" }
@@ -44,19 +45,21 @@ class UpdateChapterRead(
fun asFlow( fun asFlow(
chapterId: Long, chapterId: Long,
mangaId: Long?,
read: Boolean, read: Boolean,
) = chapterRepository.updateChapter( ) = chapterRepository.updateChapter(
chapterId = chapterId, chapterId = chapterId,
read = read, read = read,
).onEach { serverListeners.updateChapters(chapterId) } ).onEach { serverListeners.updateManga(mangaId ?: -1) }
fun asFlow( fun asFlow(
chapterIds: List<Long>, chapterIds: List<Long>,
mangaIds: List<Long>?,
read: Boolean, read: Boolean,
) = chapterRepository.updateChapters( ) = chapterRepository.updateChapters(
chapterIds = chapterIds, chapterIds = chapterIds,
read = read, read = read,
).onEach { serverListeners.updateChapters(chapterIds) } ).onEach { serverListeners.updateManga(mangaIds.orEmpty()) }
fun asFlow( fun asFlow(
chapter: Chapter, chapter: Chapter,
@@ -64,7 +67,7 @@ class UpdateChapterRead(
) = chapterRepository.updateChapter( ) = chapterRepository.updateChapter(
chapterId = chapter.id, chapterId = chapter.id,
read = read, read = read,
).onEach { serverListeners.updateChapters(chapter.id) } ).onEach { serverListeners.updateManga(chapter.mangaId) }
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -23,12 +23,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.runningFold import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
@@ -96,20 +93,20 @@ class UpdatesPager(
private val changedChapters = MutableStateFlow(emptyMap<Long, Chapter>()) private val changedChapters = MutableStateFlow(emptyMap<Long, Chapter>())
init { // init {
serverListeners.chapterIdsListener // serverListeners.chapterIdsListener
.onEach { updatedChapterIds -> // .onEach { updatedChapterIds ->
val chapters = coroutineScope { // val chapters = coroutineScope {
updatedChapterIds.mapNotNull { id -> chapterIds.value.find { it == id } }.map { // updatedChapterIds.mapNotNull { id -> chapterIds.value.find { it == id } }.map {
async { // async {
getChapter.await(it) // getChapter.await(it)
} // }
}.awaitAll().filterNotNull().associateBy { it.id } // }.awaitAll().filterNotNull().associateBy { it.id }
} // }
changedChapters.update { it + chapters } // changedChapters.update { it + chapters }
} // }
.launchIn(this) // .launchIn(this)
} // }
val updates = combine( val updates = combine(
foldedUpdates, foldedUpdates,

View File

@@ -13,8 +13,8 @@ import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.TweenSpec import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.rememberTransition
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.expandVertically import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
@@ -408,7 +408,7 @@ fun ExpandablePreference(
targetState = !expanded targetState = !expanded
} }
} }
val transition = updateTransition(transitionState) val transition = rememberTransition(transitionState)
val elevation by transition.animateDp({ val elevation by transition.animateDp({
tween(durationMillis = EXPAND_ANIMATION_DURATION) tween(durationMillis = EXPAND_ANIMATION_DURATION)
}) { }) {

View File

@@ -7,6 +7,7 @@
package ca.gosyer.jui.ui.base.state package ca.gosyer.jui.ui.base.state
import ca.gosyer.jui.uicore.vm.ViewModel import ca.gosyer.jui.uicore.vm.ViewModel
import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi
import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.internal.SynchronizedObject import kotlinx.coroutines.internal.SynchronizedObject
@@ -39,6 +40,7 @@ class SavedStateHandleDelegate<T>(
} }
} }
@OptIn(ExperimentalForInheritanceCoroutinesApi::class)
class SavedStateHandleStateFlow<T>( class SavedStateHandleStateFlow<T>(
private val key: String, private val key: String,
private val savedStateHandle: SavedStateHandle, private val savedStateHandle: SavedStateHandle,

View File

@@ -6,7 +6,6 @@
package ca.gosyer.jui.ui.library.components package ca.gosyer.jui.ui.library.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.PagerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -119,7 +118,5 @@ private fun LibraryLoadedPage(
showLanguage = showLanguage, showLanguage = showLanguage,
showLocal = showLocal, showLocal = showLocal,
) )
else -> Box {}
} }
} }

View File

@@ -291,7 +291,7 @@ class MangaScreenViewModel(
) { ) {
scope.launch { scope.launch {
manga.value?.let { manga.value?.let {
updateChapter.await(chapterIds, read = read, onError = { toast(it.message.orEmpty()) }) updateChapter.await(chapterIds, listOf(params.mangaId), read = read, onError = { toast(it.message.orEmpty()) })
selectedIds.value = persistentListOf() selectedIds.value = persistentListOf()
loadChapters() loadChapters()
} }
@@ -308,7 +308,7 @@ class MangaScreenViewModel(
) { ) {
scope.launch { scope.launch {
manga.value?.let { manga.value?.let {
updateChapter.await(chapterIds, bookmarked = bookmark, onError = { toast(it.message.orEmpty()) }) updateChapter.await(chapterIds, listOf(params.mangaId), bookmarked = bookmark, onError = { toast(it.message.orEmpty()) })
selectedIds.value = persistentListOf() selectedIds.value = persistentListOf()
loadChapters() loadChapters()
} }
@@ -325,7 +325,7 @@ class MangaScreenViewModel(
val chapters = chapters.value val chapters = chapters.value
.sortedBy { it.chapter.index } .sortedBy { it.chapter.index }
.subList(0, index).map { it.chapter.id } // todo test .subList(0, index).map { it.chapter.id } // todo test
updateChapter.await(chapters, read = true, onError = { toast(it.message.orEmpty()) }) updateChapter.await(chapters, listOf(params.mangaId), read = true, onError = { toast(it.message.orEmpty()) })
selectedIds.value = persistentListOf() selectedIds.value = persistentListOf()
loadChapters() loadChapters()
} }
@@ -340,7 +340,7 @@ class MangaScreenViewModel(
scope.launch { scope.launch {
if (id == null) { if (id == null) {
val chapterIds = selectedIds.value val chapterIds = selectedIds.value
deleteChapterDownload.await(chapterIds, onError = { toast(it.message.orEmpty()) }) deleteChapterDownload.await(chapterIds, listOf(params.mangaId), onError = { toast(it.message.orEmpty()) })
selectedItems.value.forEach { selectedItems.value.forEach {
it.setNotDownloaded() it.setNotDownloaded()
} }

View File

@@ -93,6 +93,7 @@ import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@@ -137,6 +138,7 @@ expect class SettingsServerHostViewModel : ViewModel
@Composable @Composable
expect fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostViewModel): LazyListScope.() -> Unit expect fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostViewModel): LazyListScope.() -> Unit
@OptIn(ExperimentalForInheritanceCoroutinesApi::class)
private class ServerSettingMutableStateFlow<T>( private class ServerSettingMutableStateFlow<T>(
parent: StateFlow<Settings>, parent: StateFlow<Settings>,
getSetting: (Settings) -> T, getSetting: (Settings) -> T,

View File

@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@Suppress("DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_WARNING")
sealed class SourceFiltersView<T : SourceFilter, R : Any?> { sealed class SourceFiltersView<T : SourceFilter, R : Any?> {
abstract val index: Int abstract val index: Int
abstract val name: String abstract val name: String

View File

@@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@Suppress("DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_WARNING")
sealed class SourceSettingsView<T : SourcePreference, R : Any?> { sealed class SourceSettingsView<T : SourcePreference, R : Any?> {
abstract val index: Int abstract val index: Int
abstract val title: String? abstract val title: String?

View File

@@ -118,7 +118,9 @@ class UpdatesScreenViewModel(
read: Boolean, read: Boolean,
) { ) {
scope.launch { scope.launch {
updateChapter.await(chapterIds, read = read, onError = { toast(it.message.orEmpty()) }) val mangaIds = updates.value.filterIsInstance<UpdatesUI.Item>().filter { it.chapterDownloadItem.chapter.id in chapterIds }
.mapNotNull { it.chapterDownloadItem.manga?.id }
updateChapter.await(chapterIds, mangaIds, read = read, onError = { toast(it.message.orEmpty()) })
selectedIds.value = persistentListOf() selectedIds.value = persistentListOf()
} }
} }
@@ -132,7 +134,9 @@ class UpdatesScreenViewModel(
bookmark: Boolean, bookmark: Boolean,
) { ) {
scope.launch { scope.launch {
updateChapter.await(chapterIds, bookmarked = bookmark, onError = { toast(it.message.orEmpty()) }) val mangaIds = updates.value.filterIsInstance<UpdatesUI.Item>().filter { it.chapterDownloadItem.chapter.id in chapterIds }
.mapNotNull { it.chapterDownloadItem.manga?.id }
updateChapter.await(chapterIds, mangaIds, bookmarked = bookmark, onError = { toast(it.message.orEmpty()) })
selectedIds.value = persistentListOf() selectedIds.value = persistentListOf()
} }
} }
@@ -157,7 +161,9 @@ class UpdatesScreenViewModel(
scope.launchDefault { scope.launchDefault {
if (chapter == null) { if (chapter == null) {
val selectedIds = selectedIds.value val selectedIds = selectedIds.value
deleteChapterDownload.await(selectedIds, onError = { toast(it.message.orEmpty()) }) val mangaIds = updates.value.filterIsInstance<UpdatesUI.Item>().filter { it.chapterDownloadItem.chapter.id in selectedIds }
.mapNotNull { it.chapterDownloadItem.manga?.id }
deleteChapterDownload.await(selectedIds, mangaIds, onError = { toast(it.message.orEmpty()) })
selectedItems.value.forEach { selectedItems.value.forEach {
it.setNotDownloaded() it.setNotDownloaded()
} }

View File

@@ -108,10 +108,6 @@ fun ImageLoaderImage(
progress.value = 1.0F progress.value = 1.0F
ImageLoaderImageState.Success ImageLoaderImageState.Success
} }
else -> {
ImageLoaderImageState.Loading
}
} }
} }
Crossfade(state, animationSpec = animationSpec, modifier = modifier) { Crossfade(state, animationSpec = animationSpec, modifier = modifier) {

View File

@@ -8,10 +8,12 @@ package ca.gosyer.jui.uicore.prefs
import ca.gosyer.jui.core.prefs.Preference import ca.gosyer.jui.core.prefs.Preference
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@OptIn(ExperimentalForInheritanceCoroutinesApi::class)
class PreferenceMutableStateFlow<T>( class PreferenceMutableStateFlow<T>(
private val preference: Preference<T>, private val preference: Preference<T>,
scope: CoroutineScope, scope: CoroutineScope,

View File

@@ -10,6 +10,7 @@ import androidx.compose.runtime.Stable
import ca.gosyer.jui.core.lang.launchDefault import ca.gosyer.jui.core.lang.launchDefault
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.format import dev.icerock.moko.resources.format
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
@@ -26,6 +27,7 @@ actual class ContextWrapper {
vararg args: Any, vararg args: Any,
): String = stringResource.format(*args).localized() ): String = stringResource.format(*args).localized()
@OptIn(DelicateCoroutinesApi::class)
actual fun toast( actual fun toast(
string: String, string: String,
length: Length, length: Length,