mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-16 13:48:09 -07:00
Show download progress and open the manga screen with less work
This commit is contained in:
@@ -63,7 +63,6 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import mihon.icons.materialsymbols.MaterialSymbols
|
||||
import mihon.icons.materialsymbols.roundedfilled.PlayArrow
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.service.missingChaptersCount
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.source.model.StubSource
|
||||
@@ -88,6 +87,7 @@ fun MangaScreen(
|
||||
navigateUp: () -> Unit,
|
||||
onChapterClicked: (Chapter) -> Unit,
|
||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||
downloadProgressProvider: (chapterId: Long) -> Int,
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onWebViewLongClicked: (() -> Unit)?,
|
||||
@@ -143,6 +143,7 @@ fun MangaScreen(
|
||||
navigateUp = navigateUp,
|
||||
onChapterClicked = onChapterClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
downloadProgressProvider = downloadProgressProvider,
|
||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||
onWebViewClicked = onWebViewClicked,
|
||||
onWebViewLongClicked = onWebViewLongClicked,
|
||||
@@ -179,6 +180,7 @@ fun MangaScreen(
|
||||
navigateUp = navigateUp,
|
||||
onChapterClicked = onChapterClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
downloadProgressProvider = downloadProgressProvider,
|
||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||
onWebViewClicked = onWebViewClicked,
|
||||
onWebViewLongClicked = onWebViewLongClicked,
|
||||
@@ -218,6 +220,7 @@ private fun MangaScreenSmallImpl(
|
||||
navigateUp: () -> Unit,
|
||||
onChapterClicked: (Chapter) -> Unit,
|
||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||
downloadProgressProvider: (chapterId: Long) -> Int,
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onWebViewLongClicked: (() -> Unit)?,
|
||||
@@ -273,9 +276,6 @@ private fun MangaScreenSmallImpl(
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
val selectedChapterCount: Int = remember(chapters) {
|
||||
chapters.count { it.selected }
|
||||
}
|
||||
val isFirstItemVisible by remember {
|
||||
derivedStateOf { chapterListState.firstVisibleItemIndex == 0 }
|
||||
}
|
||||
@@ -301,7 +301,7 @@ private fun MangaScreenSmallImpl(
|
||||
onClickRefresh = onRefresh,
|
||||
onClickMigrate = onMigrateClicked,
|
||||
onClickEditNotes = onEditNotesClicked,
|
||||
actionModeCounter = selectedChapterCount,
|
||||
actionModeCounter = state.selectedChapters.size,
|
||||
onCancelActionMode = { onAllChapterSelected(false) },
|
||||
onSelectAll = { onAllChapterSelected(true) },
|
||||
onInvertSelection = { onInvertSelection() },
|
||||
@@ -310,11 +310,8 @@ private fun MangaScreenSmallImpl(
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
val selectedChapters = remember(chapters) {
|
||||
chapters.filter { it.selected }
|
||||
}
|
||||
SharedMangaBottomActionMenu(
|
||||
selected = selectedChapters,
|
||||
selected = state.selectedChapters,
|
||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||
@@ -325,16 +322,13 @@ private fun MangaScreenSmallImpl(
|
||||
},
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
floatingActionButton = {
|
||||
val isFABVisible = remember(chapters) {
|
||||
chapters.fastAny { !it.chapter.read } && !isAnySelected
|
||||
}
|
||||
val isFABVisible = state.hasUnreadChapters && !isAnySelected
|
||||
SmallExtendedFloatingActionButton(
|
||||
text = {
|
||||
val isReading = remember(state.chapters) {
|
||||
state.chapters.fastAny { it.chapter.read }
|
||||
}
|
||||
Text(
|
||||
text = stringResource(if (isReading) MR.strings.action_resume else MR.strings.action_start),
|
||||
text = stringResource(
|
||||
if (state.hasReadChapters) MR.strings.action_resume else MR.strings.action_start,
|
||||
),
|
||||
)
|
||||
},
|
||||
icon = { Icon(imageVector = MaterialSymbols.RoundedFilled.PlayArrow, contentDescription = null) },
|
||||
@@ -422,17 +416,10 @@ private fun MangaScreenSmallImpl(
|
||||
key = MangaScreenItem.CHAPTER_HEADER,
|
||||
contentType = MangaScreenItem.CHAPTER_HEADER,
|
||||
) {
|
||||
val missingChapterCount = remember(chapters, state.hideMissingChapters) {
|
||||
if (state.hideMissingChapters) {
|
||||
return@remember 0
|
||||
}
|
||||
|
||||
chapters.map { it.chapter.chapterNumber }.missingChaptersCount()
|
||||
}
|
||||
ChapterHeader(
|
||||
enabled = !isAnySelected,
|
||||
chapterCount = chapters.size,
|
||||
missingChapterCount = missingChapterCount,
|
||||
missingChapterCount = state.missingChapterCount,
|
||||
onClick = onFilterClicked,
|
||||
)
|
||||
}
|
||||
@@ -440,11 +427,12 @@ private fun MangaScreenSmallImpl(
|
||||
sharedChapterItems(
|
||||
manga = state.manga,
|
||||
chapters = listItem,
|
||||
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||
isAnyChapterSelected = state.selectedChapters.isNotEmpty(),
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
onChapterClicked = onChapterClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
downloadProgressProvider = downloadProgressProvider,
|
||||
onChapterSelected = onChapterSelected,
|
||||
onChapterSwipe = onChapterSwipe,
|
||||
)
|
||||
@@ -464,6 +452,7 @@ fun MangaScreenLargeImpl(
|
||||
navigateUp: () -> Unit,
|
||||
onChapterClicked: (Chapter) -> Unit,
|
||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||
downloadProgressProvider: (chapterId: Long) -> Int,
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onWebViewLongClicked: (() -> Unit)?,
|
||||
@@ -525,9 +514,6 @@ fun MangaScreenLargeImpl(
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
val selectedChapterCount = remember(chapters) {
|
||||
chapters.count { it.selected }
|
||||
}
|
||||
MangaToolbar(
|
||||
modifier = Modifier.onSizeChanged { topBarHeight = it.height },
|
||||
title = state.manga.title,
|
||||
@@ -541,7 +527,7 @@ fun MangaScreenLargeImpl(
|
||||
onClickMigrate = onMigrateClicked,
|
||||
onClickEditNotes = onEditNotesClicked,
|
||||
onCancelActionMode = { onAllChapterSelected(false) },
|
||||
actionModeCounter = selectedChapterCount,
|
||||
actionModeCounter = state.selectedChapters.size,
|
||||
onSelectAll = { onAllChapterSelected(true) },
|
||||
onInvertSelection = { onInvertSelection() },
|
||||
titleAlphaProvider = { 1f },
|
||||
@@ -553,11 +539,8 @@ fun MangaScreenLargeImpl(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.BottomEnd,
|
||||
) {
|
||||
val selectedChapters = remember(chapters) {
|
||||
chapters.filter { it.selected }
|
||||
}
|
||||
SharedMangaBottomActionMenu(
|
||||
selected = selectedChapters,
|
||||
selected = state.selectedChapters,
|
||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||
@@ -569,17 +552,12 @@ fun MangaScreenLargeImpl(
|
||||
},
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
floatingActionButton = {
|
||||
val isFABVisible = remember(chapters) {
|
||||
chapters.fastAny { !it.chapter.read } && !isAnySelected
|
||||
}
|
||||
val isFABVisible = state.hasUnreadChapters && !isAnySelected
|
||||
SmallExtendedFloatingActionButton(
|
||||
text = {
|
||||
val isReading = remember(state.chapters) {
|
||||
state.chapters.fastAny { it.chapter.read }
|
||||
}
|
||||
Text(
|
||||
text = stringResource(
|
||||
if (isReading) MR.strings.action_resume else MR.strings.action_start,
|
||||
if (state.hasReadChapters) MR.strings.action_resume else MR.strings.action_start,
|
||||
),
|
||||
)
|
||||
},
|
||||
@@ -663,17 +641,10 @@ fun MangaScreenLargeImpl(
|
||||
key = MangaScreenItem.CHAPTER_HEADER,
|
||||
contentType = MangaScreenItem.CHAPTER_HEADER,
|
||||
) {
|
||||
val missingChapterCount = remember(chapters, state.hideMissingChapters) {
|
||||
if (state.hideMissingChapters) {
|
||||
return@remember 0
|
||||
}
|
||||
|
||||
chapters.map { it.chapter.chapterNumber }.missingChaptersCount()
|
||||
}
|
||||
ChapterHeader(
|
||||
enabled = !isAnySelected,
|
||||
chapterCount = chapters.size,
|
||||
missingChapterCount = missingChapterCount,
|
||||
missingChapterCount = state.missingChapterCount,
|
||||
onClick = onFilterButtonClicked,
|
||||
)
|
||||
}
|
||||
@@ -681,11 +652,12 @@ fun MangaScreenLargeImpl(
|
||||
sharedChapterItems(
|
||||
manga = state.manga,
|
||||
chapters = listItem,
|
||||
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||
isAnyChapterSelected = state.selectedChapters.isNotEmpty(),
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
onChapterClicked = onChapterClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
downloadProgressProvider = downloadProgressProvider,
|
||||
onChapterSelected = onChapterSelected,
|
||||
onChapterSwipe = onChapterSwipe,
|
||||
)
|
||||
@@ -747,6 +719,7 @@ private fun LazyListScope.sharedChapterItems(
|
||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||
onChapterClicked: (Chapter) -> Unit,
|
||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||
downloadProgressProvider: (chapterId: Long) -> Int,
|
||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
|
||||
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
) {
|
||||
@@ -791,7 +764,7 @@ private fun LazyListScope.sharedChapterItems(
|
||||
selected = item.selected,
|
||||
downloadIndicatorEnabled = !isAnyChapterSelected && !manga.isLocal(),
|
||||
downloadStateProvider = { item.downloadState },
|
||||
downloadProgressProvider = { item.downloadProgress },
|
||||
downloadProgressProvider = { downloadProgressProvider(item.id) },
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
onLongClick = {
|
||||
|
||||
@@ -45,7 +45,7 @@ import com.mikepenz.markdown.model.MarkdownTypography
|
||||
import com.mikepenz.markdown.model.NoOpImageTransformerImpl
|
||||
import com.mikepenz.markdown.model.markdownAlertPadding
|
||||
import com.mikepenz.markdown.model.markdownAnnotator
|
||||
import com.mikepenz.markdown.model.rememberMarkdownState
|
||||
import com.mikepenz.markdown.model.parseMarkdown
|
||||
import mihon.icons.materialsymbols.MaterialSymbols
|
||||
import mihon.icons.materialsymbols.rounded.Image
|
||||
import org.intellij.markdown.MarkdownTokenTypes.Companion.HTML_TAG
|
||||
@@ -79,11 +79,9 @@ fun MarkdownRender(
|
||||
loadImages: Boolean = true,
|
||||
) {
|
||||
Markdown(
|
||||
markdownState = rememberMarkdownState(
|
||||
content = content,
|
||||
flavour = flavour,
|
||||
immediate = true,
|
||||
),
|
||||
// rememberMarkdownState parses again in the background even when it already parsed immediately, and its
|
||||
// new result recomposes the whole content a second time
|
||||
state = remember(content, flavour) { parseMarkdown(content = content, flavour = flavour) },
|
||||
annotator = annotator,
|
||||
colors = getMarkdownColors(),
|
||||
typography = getMarkdownTypography(),
|
||||
|
||||
@@ -114,6 +114,7 @@ class MangaScreen(
|
||||
navigateUp = navigator::pop,
|
||||
onChapterClicked = { openChapter(context, it) },
|
||||
onDownloadChapter = viewModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() },
|
||||
downloadProgressProvider = viewModel::getDownloadProgress,
|
||||
onAddToLibraryClicked = {
|
||||
viewModel.toggleFavorite()
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
|
||||
@@ -6,7 +6,10 @@ import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.ui.util.fastAny
|
||||
import androidx.compose.ui.util.fastFilter
|
||||
import androidx.compose.ui.util.fastMap
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dev.zacsweers.metro.AppScope
|
||||
@@ -61,10 +64,8 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -91,6 +92,7 @@ import tachiyomi.domain.chapter.model.ChapterUpdate
|
||||
import tachiyomi.domain.chapter.model.NoChaptersException
|
||||
import tachiyomi.domain.chapter.service.calculateChapterGap
|
||||
import tachiyomi.domain.chapter.service.getChapterSort
|
||||
import tachiyomi.domain.chapter.service.missingChaptersCount
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
||||
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
||||
@@ -103,7 +105,6 @@ import tachiyomi.domain.track.interactor.GetTracks
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.source.local.isLocal
|
||||
import kotlin.math.floor
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@AssistedInject
|
||||
@@ -185,7 +186,11 @@ class MangaViewModel(
|
||||
|
||||
private val isRefreshingData = MutableStateFlow(false)
|
||||
|
||||
private val downloadStates = MutableStateFlow(emptyMap<Long, DownloadProgress>())
|
||||
private val downloadStates = MutableStateFlow(emptyMap<Long, Download.State>())
|
||||
|
||||
// Progress ticks often, so only the download indicators that read it recompose instead of every tick rebuilding
|
||||
// the chapter list
|
||||
private val downloadProgress = mutableStateMapOf<Long, Int>()
|
||||
|
||||
// A finished chapter leaves the queue, and its last status change can be lost with it, so its state is left to
|
||||
// the queried item once it's no longer queued
|
||||
@@ -262,12 +267,11 @@ class MangaViewModel(
|
||||
source = mangaAndChapters.source,
|
||||
isFromSource = isFromSource,
|
||||
chapters = mangaAndChapters.chapters.map { item ->
|
||||
val download = downloads[item.id]
|
||||
val downloadState = downloads[item.id]
|
||||
val selected = item.id in selectedIds
|
||||
if (download == null && !selected) return@map item
|
||||
if (downloadState == null && !selected) return@map item
|
||||
item.copy(
|
||||
downloadState = download?.status ?: item.downloadState,
|
||||
downloadProgress = download?.progress ?: item.downloadProgress,
|
||||
downloadState = downloadState ?: item.downloadState,
|
||||
selected = selected,
|
||||
)
|
||||
},
|
||||
@@ -280,27 +284,32 @@ class MangaViewModel(
|
||||
hideMissingChapters = hideMissingChapters,
|
||||
)
|
||||
}
|
||||
// Composition would otherwise build these on first read, on the main thread, for every download progress tick
|
||||
// Composition would otherwise build these on first read, on the main thread, for every new state
|
||||
.onEach {
|
||||
it.processedChapters
|
||||
it.chapterListItems
|
||||
it.isAnySelected
|
||||
it.selectedChapters
|
||||
it.hasUnreadChapters
|
||||
it.hasReadChapters
|
||||
it.missingChapterCount
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
.stateIn(viewModelScope + subscriberGate, subscriberGate.whileSubscribed(5.seconds), State.Loading)
|
||||
|
||||
init {
|
||||
// Progress ticks often, and each one copies the map of download states
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
merge(
|
||||
downloadManager.statusFlow(),
|
||||
// Each tick rebuilds the shown chapter items, so cap how often that happens
|
||||
downloadManager.progressFlow().sample(200.milliseconds),
|
||||
)
|
||||
downloadManager.statusFlow()
|
||||
.filter { it.manga.id == mangaId }
|
||||
.catch { logcat(LogPriority.ERROR, it) }
|
||||
.collect(::updateDownloadState)
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
downloadManager.progressFlow()
|
||||
.filter { it.manga.id == mangaId }
|
||||
.catch { logcat(LogPriority.ERROR, it) }
|
||||
.collect { downloadProgress[it.chapter.id] = it.progress }
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
// The state loads the entry and its chapters anyway, so they aren't queried separately
|
||||
@@ -560,9 +569,18 @@ class MangaViewModel(
|
||||
if (download.status == Download.State.NOT_DOWNLOADED || download.status == Download.State.DOWNLOADED) {
|
||||
it - chapterId
|
||||
} else {
|
||||
it + (chapterId to DownloadProgress(download.status, download.progress))
|
||||
it + (chapterId to download.status)
|
||||
}
|
||||
}
|
||||
if (download.status == Download.State.DOWNLOADING) {
|
||||
downloadProgress[chapterId] = download.progress
|
||||
} else {
|
||||
downloadProgress.remove(chapterId)
|
||||
}
|
||||
}
|
||||
|
||||
fun getDownloadProgress(chapterId: Long): Int {
|
||||
return downloadProgress[chapterId] ?: 0
|
||||
}
|
||||
|
||||
private fun List<Chapter>.toChapterListItems(manga: Manga): List<ChapterList.Item> {
|
||||
@@ -581,7 +599,6 @@ class MangaViewModel(
|
||||
ChapterList.Item(
|
||||
chapter = chapter,
|
||||
downloadState = downloadState,
|
||||
downloadProgress = activeDownload?.progress ?: 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1074,8 +1091,6 @@ class MangaViewModel(
|
||||
|
||||
private data class MangaAndChapters(val manga: Manga, val source: Source, val chapters: List<ChapterList.Item>)
|
||||
|
||||
private data class DownloadProgress(val status: Download.State, val progress: Int)
|
||||
|
||||
sealed interface State {
|
||||
@Immutable
|
||||
data object Loading : State
|
||||
@@ -1102,6 +1117,23 @@ class MangaViewModel(
|
||||
chapters.fastAny { it.selected }
|
||||
}
|
||||
|
||||
val selectedChapters by lazy {
|
||||
processedChapters.fastFilter { it.selected }
|
||||
}
|
||||
|
||||
val hasUnreadChapters by lazy {
|
||||
processedChapters.fastAny { !it.chapter.read }
|
||||
}
|
||||
|
||||
val hasReadChapters by lazy {
|
||||
chapters.fastAny { it.chapter.read }
|
||||
}
|
||||
|
||||
val missingChapterCount by lazy {
|
||||
if (hideMissingChapters) return@lazy 0
|
||||
processedChapters.fastMap { it.chapter.chapterNumber }.missingChaptersCount()
|
||||
}
|
||||
|
||||
val chapterListItems by lazy {
|
||||
if (hideMissingChapters) {
|
||||
return@lazy processedChapters
|
||||
@@ -1171,7 +1203,6 @@ sealed class ChapterList {
|
||||
data class Item(
|
||||
val chapter: Chapter,
|
||||
val downloadState: Download.State,
|
||||
val downloadProgress: Int,
|
||||
val selected: Boolean = false,
|
||||
) : ChapterList() {
|
||||
val id = chapter.id
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.ui.util.fastFilter
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -37,9 +38,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -64,7 +63,6 @@ import tachiyomi.domain.updates.interactor.GetUpdates
|
||||
import tachiyomi.domain.updates.model.UpdatesWithRelations
|
||||
import tachiyomi.domain.updates.service.UpdatesPreferences
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Inject
|
||||
@@ -100,7 +98,11 @@ class UpdatesViewModel(
|
||||
|
||||
private val dialog = MutableStateFlow<Dialog?>(null)
|
||||
|
||||
private val downloadStates = MutableStateFlow(emptyMap<Long, DownloadProgress>())
|
||||
private val downloadStates = MutableStateFlow(emptyMap<Long, Download.State>())
|
||||
|
||||
// Progress ticks often, so only the download indicators that read it recompose instead of every tick rebuilding
|
||||
// the list
|
||||
private val downloadProgress = mutableStateMapOf<Long, Int>()
|
||||
|
||||
// A finished chapter leaves the queue, and its last status change can be lost with it, so its state is left to
|
||||
// the queried item once it's no longer queued
|
||||
@@ -111,11 +113,15 @@ class UpdatesViewModel(
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
// Each progress tick rebuilds every row of the list, so cap how often that happens
|
||||
merge(downloadManager.statusFlow(), downloadManager.progressFlow().sample(200.milliseconds))
|
||||
downloadManager.statusFlow()
|
||||
.catch { logcat(LogPriority.ERROR, it) }
|
||||
.collect(this@UpdatesViewModel::updateDownloadState)
|
||||
}
|
||||
viewModelScope.launch {
|
||||
downloadManager.progressFlow()
|
||||
.catch { logcat(LogPriority.ERROR, it) }
|
||||
.collect { downloadProgress[it.chapter.id] = it.progress }
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDownloadState(download: Download) {
|
||||
@@ -126,9 +132,14 @@ class UpdatesViewModel(
|
||||
if (download.status == Download.State.NOT_DOWNLOADED || download.status == Download.State.DOWNLOADED) {
|
||||
it - chapterId
|
||||
} else {
|
||||
it + (chapterId to DownloadProgress(download.status, download.progress))
|
||||
it + (chapterId to download.status)
|
||||
}
|
||||
}
|
||||
if (download.status == Download.State.DOWNLOADING) {
|
||||
downloadProgress[chapterId] = download.progress
|
||||
} else {
|
||||
downloadProgress.remove(chapterId)
|
||||
}
|
||||
}
|
||||
|
||||
private val hasActiveFilters = getUpdatesItemPreferenceFlow()
|
||||
@@ -189,19 +200,14 @@ class UpdatesViewModel(
|
||||
isLoading = items == null,
|
||||
hasActiveFilters = hasActiveFilters,
|
||||
items = items.orEmpty().map { item ->
|
||||
val download = downloads[item.update.chapterId]
|
||||
val downloadState = downloads[item.update.chapterId]
|
||||
item.copy(
|
||||
selected = item.update.chapterId in selectedIds,
|
||||
downloadStateProvider = if (download != null) {
|
||||
{ download.status }
|
||||
downloadStateProvider = if (downloadState != null) {
|
||||
{ downloadState }
|
||||
} else {
|
||||
item.downloadStateProvider
|
||||
},
|
||||
downloadProgressProvider = if (download != null) {
|
||||
{ download.progress }
|
||||
} else {
|
||||
item.downloadProgressProvider
|
||||
},
|
||||
)
|
||||
},
|
||||
dialog = dialog,
|
||||
@@ -246,7 +252,7 @@ class UpdatesViewModel(
|
||||
UpdatesItem(
|
||||
update = update,
|
||||
downloadStateProvider = { downloadState },
|
||||
downloadProgressProvider = { activeDownload?.progress ?: 0 },
|
||||
downloadProgressProvider = { downloadProgress[update.chapterId] ?: 0 },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -489,8 +495,6 @@ class UpdatesViewModel(
|
||||
val filterExcludedCategories: List<Long>,
|
||||
)
|
||||
|
||||
private data class DownloadProgress(val status: Download.State, val progress: Int)
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val isLoading: Boolean = true,
|
||||
|
||||
Reference in New Issue
Block a user