Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 33 additions & 19 deletions app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package eu.kanade.presentation.updates

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
Expand Down Expand Up @@ -47,7 +49,6 @@ import kotlin.time.Duration.Companion.seconds
fun UpdateScreen(
state: UpdatesViewModel.State,
snackbarHostState: SnackbarHostState,
lastUpdated: Long,
onClickCover: (UpdatesItem) -> Unit,
onSelectAll: (Boolean) -> Unit,
onInvertSelection: () -> Unit,
Expand All @@ -60,7 +61,6 @@ fun UpdateScreen(
onUpdateSelected: (UpdatesItem, Boolean, Boolean) -> Unit,
onOpenChapter: (UpdatesItem) -> Unit,
onFilterClicked: () -> Unit,
hasActiveFilters: Boolean,
) {
BackHandler(enabled = state.selectionMode) {
onSelectAll(false)
Expand All @@ -72,7 +72,7 @@ fun UpdateScreen(
onCalendarClicked = { onCalendarClicked() },
onUpdateLibrary = { onUpdateLibrary() },
onFilterClicked = { onFilterClicked() },
hasFilters = hasActiveFilters,
hasFilters = state.hasActiveFilters,
actionModeCounter = state.selected.size,
onSelectAll = { onSelectAll(true) },
onInvertSelection = { onInvertSelection() },
Expand All @@ -93,10 +93,6 @@ fun UpdateScreen(
) { contentPadding ->
when {
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
state.items.isEmpty() -> EmptyScreen(
stringRes = MR.strings.information_no_recent,
modifier = Modifier.padding(contentPadding),
)
else -> {
val scope = rememberCoroutineScope()
var isRefreshing by remember { mutableStateOf(false) }
Expand All @@ -116,19 +112,37 @@ fun UpdateScreen(
enabled = !state.selectionMode,
indicatorPadding = contentPadding,
) {
FastScrollLazyColumn(
contentPadding = contentPadding,
) {
updatesLastUpdatedItem(lastUpdated)
if (state.items.isEmpty()) {
Column(
modifier = Modifier
.padding(contentPadding)
.fillMaxSize(),
) {
UpdatesLastUpdatedHeader(lastUpdated = state.lastUpdated)
EmptyScreen(
stringRes = when {
state.hasError -> MR.strings.internal_error
state.hasActiveFilters -> MR.strings.error_no_match
else -> MR.strings.information_no_recent
},
modifier = Modifier.weight(1f),
)
}
} else {
FastScrollLazyColumn(
contentPadding = contentPadding,
) {
updatesLastUpdatedItem(state.lastUpdated)

updatesUiItems(
uiModels = state.getUiModel(),
selectionMode = state.selectionMode,
onUpdateSelected = onUpdateSelected,
onClickCover = onClickCover,
onClickUpdate = onOpenChapter,
onDownloadChapter = onDownloadChapter,
)
updatesUiItems(
uiModels = state.getUiModel(),
selectionMode = state.selectionMode,
onUpdateSelected = onUpdateSelected,
onClickCover = onClickCover,
onClickUpdate = onOpenChapter,
onDownloadChapter = onDownloadChapter,
)
}
}
}
}
Expand Down
30 changes: 20 additions & 10 deletions app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,26 @@ internal fun LazyListScope.updatesLastUpdatedItem(
lastUpdated: Long,
) {
item(key = "updates-lastUpdated") {
Box(
modifier = Modifier
.animateItem(fadeInSpec = null, fadeOutSpec = null)
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
) {
Text(
text = stringResource(MR.strings.updates_last_update_info, relativeTimeSpanString(lastUpdated)),
fontStyle = FontStyle.Italic,
)
}
UpdatesLastUpdatedHeader(
lastUpdated = lastUpdated,
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
)
}
}

@Composable
internal fun UpdatesLastUpdatedHeader(
lastUpdated: Long,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
) {
Text(
text = stringResource(MR.strings.updates_last_update_info, relativeTimeSpanString(lastUpdated)),
fontStyle = FontStyle.Italic,
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ data object UpdatesTab : Tab {
UpdateScreen(
state = state,
snackbarHostState = viewModel.snackbarHostState,
lastUpdated = viewModel.lastUpdated,
onClickCover = { item -> navigator.push(MangaScreen(item.update.mangaId)) },
onSelectAll = viewModel::toggleAllSelection,
onInvertSelection = viewModel::invertSelection,
Expand All @@ -77,7 +76,6 @@ data object UpdatesTab : Tab {
},
onCalendarClicked = { navigator.push(UpcomingScreen()) },
onFilterClicked = viewModel::showFilterDialog,
hasActiveFilters = state.hasActiveFilters,
)

val onDismissDialog = { viewModel.setDialog(null) }
Expand Down
113 changes: 81 additions & 32 deletions app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ package eu.kanade.tachiyomi.ui.updates
import android.app.Application
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.ui.util.fastFilter
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import eu.kanade.core.preference.asState
import eu.kanade.core.util.addOrRemove
import eu.kanade.core.util.insertSeparators
import eu.kanade.domain.chapter.interactor.SetReadStatus
Expand All @@ -18,6 +16,7 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.util.lang.toLocalDate
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -76,8 +75,6 @@ class UpdatesViewModel(
private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
val events: Flow<Event> = _events.receiveAsFlow()

val lastUpdated by libraryPreferences.lastUpdatedTimestamp.asState(viewModelScope)

// First and last selected index in list
private val selectedPositions: Array<Int> = arrayOf(-1, -1)
private val selectedChapterIds = MutableStateFlow(emptySet<Long>())
Expand Down Expand Up @@ -138,7 +135,14 @@ class UpdatesViewModel(
hideExcludedScanlators = it.filterExcludedScanlators,
includedCategories = it.filterIncludedCategories,
excludedCategories = it.filterExcludedCategories,
).distinctUntilChanged()
)
.distinctUntilChanged()
.map { Result.success(it) }
.catch { error ->
logcat(LogPriority.ERROR, error)
_events.send(Event.InternalError)
emit(Result.failure(error))
}
},
downloadCache.changes,
downloadManager.queueState,
Expand All @@ -147,41 +151,78 @@ class UpdatesViewModel(
old.filterDownloaded == new.filterDownloaded
},
) { updates, _, _, itemPreferences ->
updates
.toUpdateItems()
.applyFilters(itemPreferences)
val loaded = updates.getOrElse {
return@combine UpdatesLoad.Error
}
try {
UpdatesLoad.Data(
loaded
.toUpdateItems()
.applyFilters(itemPreferences),
)
} catch (e: Exception) {
if (e is CancellationException) throw e
logcat(LogPriority.ERROR, e)
_events.send(Event.InternalError)
UpdatesLoad.Error
}
}
.catch { error ->
logcat(LogPriority.ERROR, error)
_events.send(Event.InternalError)
emit(UpdatesLoad.Error)
}
.flowOn(Dispatchers.IO)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), null)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), UpdatesLoad.Loading)

val state: StateFlow<State> = combine(
updateItems,
selectedChapterIds,
downloadStates,
dialog,
hasActiveFilters,
) { items, selectedIds, downloads, dialog, hasActiveFilters ->
State(
isLoading = items == null,
hasActiveFilters = hasActiveFilters,
items = items.orEmpty().map { item ->
val download = downloads[item.update.chapterId]
item.copy(
selected = item.update.chapterId in selectedIds,
downloadStateProvider = if (download != null) {
{ download.status }
} else {
item.downloadStateProvider
},
downloadProgressProvider = if (download != null) {
{ download.progress }
} else {
item.downloadProgressProvider
},
)
},
dialog = dialog,
)
combine(
hasActiveFilters,
libraryPreferences.lastUpdatedTimestamp.changes(),
::Pair,
),
) { items, selectedIds, downloads, dialog, (hasActiveFilters, lastUpdated) ->
when (items) {
UpdatesLoad.Loading -> State(
isLoading = true,
lastUpdated = lastUpdated,
hasActiveFilters = hasActiveFilters,
dialog = dialog,
)
UpdatesLoad.Error -> State(
isLoading = false,
lastUpdated = lastUpdated,
hasActiveFilters = hasActiveFilters,
hasError = true,
dialog = dialog,
)
is UpdatesLoad.Data -> State(
isLoading = false,
lastUpdated = lastUpdated,
hasActiveFilters = hasActiveFilters,
items = items.items.map { item ->
val download = downloads[item.update.chapterId]
item.copy(
selected = item.update.chapterId in selectedIds,
downloadStateProvider = if (download != null) {
{ download.status }
} else {
item.downloadStateProvider
},
downloadProgressProvider = if (download != null) {
{ download.progress }
} else {
item.downloadProgressProvider
},
)
},
dialog = dialog,
)
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), State())

Expand Down Expand Up @@ -467,10 +508,18 @@ class UpdatesViewModel(

private data class DownloadProgress(val status: Download.State, val progress: Int)

private sealed interface UpdatesLoad {
data object Loading : UpdatesLoad
data class Data(val items: List<UpdatesItem>) : UpdatesLoad
data object Error : UpdatesLoad
}

@Immutable
data class State(
val isLoading: Boolean = true,
val lastUpdated: Long = 0L,
val hasActiveFilters: Boolean = false,
val hasError: Boolean = false,
val items: List<UpdatesItem> = listOf(),
val dialog: Dialog? = null,
) {
Expand Down