Use Voyager on Updates tab (#8603)

* Use Voyager on Updates tab

* Fix back press

* Fix selection
This commit is contained in:
Ivan Iskandar
2022-11-23 21:22:20 +07:00
committed by GitHub
parent 7d34ff214c
commit acc2312384
9 changed files with 372 additions and 349 deletions

View File

@@ -1,7 +1,6 @@
package eu.kanade.presentation.updates
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
@@ -10,9 +9,11 @@ import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -33,152 +34,103 @@ import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.SwipeRefresh
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
import eu.kanade.tachiyomi.ui.updates.UpdatesPresenter
import eu.kanade.tachiyomi.ui.updates.UpdatesPresenter.Dialog
import eu.kanade.tachiyomi.ui.updates.UpdatesPresenter.Event
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.ui.updates.UpdatesState
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.util.Date
import kotlin.time.Duration.Companion.seconds
@Composable
fun UpdateScreen(
presenter: UpdatesPresenter,
state: UpdatesState,
snackbarHostState: SnackbarHostState,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
lastUpdated: Long,
relativeTime: Int,
onClickCover: (UpdatesItem) -> Unit,
onBackClicked: () -> Unit,
onSelectAll: (Boolean) -> Unit,
onInvertSelection: () -> Unit,
onUpdateLibrary: () -> Boolean,
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit,
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
onOpenChapter: (UpdatesItem) -> Unit,
) {
val internalOnBackPressed = {
if (presenter.selectionMode) {
presenter.toggleAllSelection(false)
} else {
onBackClicked()
}
}
BackHandler(onBack = internalOnBackPressed)
BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) })
val context = LocalContext.current
val onUpdateLibrary = {
val started = LibraryUpdateService.start(context)
context.toast(if (started) R.string.updating_library else R.string.update_already_running)
started
}
Scaffold(
topBar = { scrollBehavior ->
UpdatesAppBar(
incognitoMode = presenter.isIncognitoMode,
downloadedOnlyMode = presenter.isDownloadOnly,
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
onUpdateLibrary = { onUpdateLibrary() },
actionModeCounter = presenter.selected.size,
onSelectAll = { presenter.toggleAllSelection(true) },
onInvertSelection = { presenter.invertSelection() },
onCancelActionMode = { presenter.toggleAllSelection(false) },
actionModeCounter = state.selected.size,
onSelectAll = { onSelectAll(true) },
onInvertSelection = { onInvertSelection() },
onCancelActionMode = { onSelectAll(false) },
scrollBehavior = scrollBehavior,
)
},
bottomBar = {
UpdatesBottomBar(
selected = presenter.selected,
onDownloadChapter = presenter::downloadChapters,
onMultiBookmarkClicked = presenter::bookmarkUpdates,
onMultiMarkAsReadClicked = presenter::markUpdatesRead,
onMultiDeleteClicked = {
presenter.dialog = Dialog.DeleteConfirmation(it)
},
selected = state.selected,
onDownloadChapter = onDownloadChapter,
onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
onMultiDeleteClicked = onMultiDeleteClicked,
)
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
) { contentPadding ->
val contentPaddingWithNavBar = TachiyomiBottomNavigationView.withBottomNavPadding(contentPadding)
when {
presenter.isLoading -> LoadingScreen()
presenter.uiModels.isEmpty() -> EmptyScreen(
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
state.items.isEmpty() -> EmptyScreen(
textResource = R.string.information_no_recent,
modifier = Modifier.padding(contentPaddingWithNavBar),
modifier = Modifier.padding(contentPadding),
)
else -> {
UpdateScreenContent(
presenter = presenter,
contentPadding = contentPaddingWithNavBar,
onUpdateLibrary = onUpdateLibrary,
onClickCover = onClickCover,
)
}
}
}
}
val scope = rememberCoroutineScope()
var isRefreshing by remember { mutableStateOf(false) }
@Composable
private fun UpdateScreenContent(
presenter: UpdatesPresenter,
contentPadding: PaddingValues,
onUpdateLibrary: () -> Boolean,
onClickCover: (UpdatesItem) -> Unit,
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
var isRefreshing by remember { mutableStateOf(false) }
SwipeRefresh(
refreshing = isRefreshing,
onRefresh = {
val started = onUpdateLibrary()
if (!started) return@SwipeRefresh
scope.launch {
// Fake refresh status but hide it after a second as it's a long running task
isRefreshing = true
delay(1.seconds)
isRefreshing = false
}
},
enabled = !state.selectionMode,
indicatorPadding = contentPadding,
) {
FastScrollLazyColumn(
contentPadding = contentPadding,
) {
if (lastUpdated > 0L) {
updatesLastUpdatedItem(lastUpdated)
}
SwipeRefresh(
refreshing = isRefreshing,
onRefresh = {
val started = onUpdateLibrary()
if (!started) return@SwipeRefresh
scope.launch {
// Fake refresh status but hide it after a second as it's a long running task
isRefreshing = true
delay(1.seconds)
isRefreshing = false
}
},
enabled = presenter.selectionMode.not(),
indicatorPadding = contentPadding,
) {
FastScrollLazyColumn(
contentPadding = contentPadding,
) {
if (presenter.lastUpdated > 0L) {
updatesLastUpdatedItem(presenter.lastUpdated)
}
updatesUiItems(
uiModels = presenter.uiModels,
selectionMode = presenter.selectionMode,
onUpdateSelected = presenter::toggleSelection,
onClickCover = onClickCover,
onClickUpdate = {
val intent = ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId)
context.startActivity(intent)
},
onDownloadChapter = presenter::downloadChapters,
relativeTime = presenter.relativeTime,
dateFormat = presenter.dateFormat,
)
}
}
val onDismissDialog = { presenter.dialog = null }
when (val dialog = presenter.dialog) {
is Dialog.DeleteConfirmation -> {
UpdatesDeleteConfirmationDialog(
onDismissRequest = onDismissDialog,
onConfirm = {
presenter.toggleAllSelection(false)
presenter.deleteChapters(dialog.toDelete)
},
)
}
null -> {}
}
LaunchedEffect(Unit) {
presenter.events.collectLatest { event ->
when (event) {
Event.InternalError -> context.toast(R.string.internal_error)
updatesUiItems(
uiModels = state.getUiModel(context, relativeTime),
selectionMode = state.selectionMode,
onUpdateSelected = onUpdateSelected,
onClickCover = onClickCover,
onClickUpdate = onOpenChapter,
onDownloadChapter = onDownloadChapter,
)
}
}
}
}
}
@@ -265,6 +217,6 @@ private fun UpdatesBottomBar(
}
sealed class UpdatesUiModel {
data class Header(val date: Date) : UpdatesUiModel()
data class Header(val date: String) : UpdatesUiModel()
data class Item(val item: UpdatesItem) : UpdatesUiModel()
}

View File

@@ -1,51 +0,0 @@
package eu.kanade.presentation.updates
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.kanade.core.util.insertSeparators
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
import eu.kanade.tachiyomi.ui.updates.UpdatesPresenter
import eu.kanade.tachiyomi.util.lang.toDateKey
import java.util.Date
@Stable
interface UpdatesState {
val isLoading: Boolean
val items: List<UpdatesItem>
val selected: List<UpdatesItem>
val selectionMode: Boolean
val uiModels: List<UpdatesUiModel>
var dialog: UpdatesPresenter.Dialog?
}
fun UpdatesState(): UpdatesState = UpdatesStateImpl()
class UpdatesStateImpl : UpdatesState {
override var isLoading: Boolean by mutableStateOf(true)
override var items: List<UpdatesItem> by mutableStateOf(emptyList())
override val selected: List<UpdatesItem> by derivedStateOf {
items.filter { it.selected }
}
override val selectionMode: Boolean by derivedStateOf { selected.isNotEmpty() }
override val uiModels: List<UpdatesUiModel> by derivedStateOf {
items.toUpdateUiModel()
}
override var dialog: UpdatesPresenter.Dialog? by mutableStateOf(null)
}
fun List<UpdatesItem>.toUpdateUiModel(): List<UpdatesUiModel> {
return this.map {
UpdatesUiModel.Item(it)
}
.insertSeparators { before, after ->
val beforeDate = before?.item?.update?.dateFetch?.toDateKey() ?: Date(0)
val afterDate = after?.item?.update?.dateFetch?.toDateKey() ?: Date(0)
when {
beforeDate.time != afterDate.time && afterDate.time != 0L ->
UpdatesUiModel.Header(afterDate)
// Return null to avoid adding a separator between two items.
else -> null
}
}
}

View File

@@ -16,7 +16,6 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -37,15 +36,14 @@ import androidx.compose.ui.unit.dp
import eu.kanade.domain.updates.model.UpdatesWithRelations
import eu.kanade.presentation.components.ChapterDownloadAction
import eu.kanade.presentation.components.ChapterDownloadIndicator
import eu.kanade.presentation.components.ListGroupHeader
import eu.kanade.presentation.components.MangaCover
import eu.kanade.presentation.components.RelativeDateHeader
import eu.kanade.presentation.util.ReadItemAlpha
import eu.kanade.presentation.util.padding
import eu.kanade.presentation.util.selectedBackground
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
import java.text.DateFormat
import java.util.Date
import kotlin.time.Duration.Companion.minutes
@@ -73,9 +71,7 @@ fun LazyListScope.updatesLastUpdatedItem(
} else {
stringResource(R.string.updates_last_update_info, time)
},
style = LocalTextStyle.current.copy(
fontStyle = FontStyle.Italic,
),
fontStyle = FontStyle.Italic,
)
}
}
@@ -88,8 +84,6 @@ fun LazyListScope.updatesUiItems(
onClickCover: (UpdatesItem) -> Unit,
onClickUpdate: (UpdatesItem) -> Unit,
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
relativeTime: Int,
dateFormat: DateFormat,
) {
items(
items = uiModels,
@@ -108,11 +102,9 @@ fun LazyListScope.updatesUiItems(
) { item ->
when (item) {
is UpdatesUiModel.Header -> {
RelativeDateHeader(
ListGroupHeader(
modifier = Modifier.animateItemPlacement(),
date = item.date,
relativeTime = relativeTime,
dateFormat = dateFormat,
text = item.date,
)
}
is UpdatesUiModel.Item -> {
@@ -130,11 +122,10 @@ fun LazyListScope.updatesUiItems(
else -> onClickUpdate(updatesItem)
}
},
onClickCover = { if (selectionMode.not()) onClickCover(updatesItem) },
onDownloadChapter = {
if (selectionMode.not()) onDownloadChapter(listOf(updatesItem), it)
},
downloadIndicatorEnabled = selectionMode.not(),
onClickCover = { onClickCover(updatesItem) }.takeIf { !selectionMode },
onDownloadChapter = { action: ChapterDownloadAction ->
onDownloadChapter(listOf(updatesItem), action)
}.takeIf { !selectionMode },
downloadStateProvider = updatesItem.downloadStateProvider,
downloadProgressProvider = updatesItem.downloadProgressProvider,
)
@@ -150,10 +141,9 @@ fun UpdatesUiItem(
selected: Boolean,
onClick: () -> Unit,
onLongClick: () -> Unit,
onClickCover: () -> Unit,
onDownloadChapter: (ChapterDownloadAction) -> Unit,
onClickCover: (() -> Unit)?,
onDownloadChapter: ((ChapterDownloadAction) -> Unit)?,
// Download Indicator
downloadIndicatorEnabled: Boolean,
downloadStateProvider: () -> Download.State,
downloadProgressProvider: () -> Int,
) {
@@ -217,8 +207,8 @@ fun UpdatesUiItem(
Text(
text = update.chapterName,
maxLines = 1,
style = MaterialTheme.typography.bodySmall
.copy(color = secondaryTextColor),
color = secondaryTextColor,
style = MaterialTheme.typography.bodySmall,
overflow = TextOverflow.Ellipsis,
onTextLayout = { textHeight = it.size.height },
modifier = Modifier.alpha(textAlpha),
@@ -226,11 +216,11 @@ fun UpdatesUiItem(
}
}
ChapterDownloadIndicator(
enabled = downloadIndicatorEnabled,
enabled = onDownloadChapter != null,
modifier = Modifier.padding(start = 4.dp),
downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider,
onClick = onDownloadChapter,
onClick = { onDownloadChapter?.invoke(it) },
)
}
}