feat(bookshelf): add offline cache UI to RemoteLibrary tab
- Add Offline only switch above the remote books list. - Display cache status (pending/downloading/completed/failed) per book. - Add download and delete-cache action buttons. - Show LinearProgressIndicator while a book is downloading.
This commit is contained in:
parent
ed3afc1f16
commit
f385b9ba8b
1 changed files with 116 additions and 18 deletions
|
|
@ -8,7 +8,9 @@ package org.dueattendant149.bookshelf.ui.bookshelf
|
|||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -19,15 +21,21 @@ import androidx.compose.material.ExperimentalMaterialApi
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CloudOff
|
||||
import androidx.compose.material.icons.filled.CloudQueue
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -39,6 +47,9 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.dueattendant149.bookshelf.R
|
||||
import org.dueattendant149.bookshelf.domain.model.cache.CacheState
|
||||
import org.dueattendant149.bookshelf.domain.model.cache.CacheStatus
|
||||
import org.dueattendant149.bookshelf.domain.model.library.Book
|
||||
import org.dueattendant149.bookshelf.presentation.bookshelf.RemoteLibraryModel
|
||||
import org.dueattendant149.bookshelf.ui.common.components.placeholder.EmptyPlaceholder
|
||||
import org.dueattendant149.bookshelf.ui.common.components.placeholder.ErrorPlaceholder
|
||||
|
|
@ -125,28 +136,34 @@ fun RemoteLibraryContent(model: RemoteLibraryModel) {
|
|||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.bookshelf_offline_only_label),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Switch(
|
||||
checked = state.offlineOnly,
|
||||
onCheckedChange = { model.toggleOfflineOnly() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items(state.books.size) { index ->
|
||||
val book = state.books[index]
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = book.title,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
text = book.author.getAsString()
|
||||
?: stringResource(R.string.unknown_author)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
val cacheStatus = state.cacheStatuses[book.remoteId]
|
||||
BookListItem(
|
||||
book = book,
|
||||
cacheStatus = cacheStatus,
|
||||
onDownload = { model.cacheBook(book) },
|
||||
onDelete = { model.deleteCache(book) }
|
||||
)
|
||||
}
|
||||
|
||||
if (state.isLoading && state.libraries.isEmpty() && state.books.isEmpty()) {
|
||||
|
|
@ -193,3 +210,84 @@ fun RemoteLibraryContent(model: RemoteLibraryModel) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BookListItem(
|
||||
book: Book,
|
||||
cacheStatus: CacheStatus?,
|
||||
onDownload: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
) {
|
||||
val cacheState = cacheStatus?.state ?: CacheState.NONE
|
||||
val showDownload = cacheState == CacheState.NONE || cacheState == CacheState.FAILED
|
||||
val showDelete = cacheState != CacheState.NONE
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = book.title,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
Column {
|
||||
Text(
|
||||
text = book.author.getAsString()
|
||||
?: stringResource(R.string.unknown_author)
|
||||
)
|
||||
if (cacheState != CacheState.NONE) {
|
||||
Text(
|
||||
text = cacheStatusLabel(cacheState),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
trailingContent = {
|
||||
Row {
|
||||
if (showDownload) {
|
||||
IconButton(onClick = onDownload) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Download,
|
||||
contentDescription = stringResource(R.string.bookshelf_download_action)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showDelete) {
|
||||
IconButton(onClick = onDelete) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringResource(R.string.bookshelf_delete_cache_action)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (cacheState == CacheState.DOWNLOADING) {
|
||||
LinearProgressIndicator(
|
||||
progress = { cacheStatus?.progress?.coerceIn(0f, 1f) ?: 0f },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun cacheStatusLabel(state: CacheState): String =
|
||||
when (state) {
|
||||
CacheState.NONE -> ""
|
||||
CacheState.PENDING -> stringResource(R.string.bookshelf_cache_pending)
|
||||
CacheState.DOWNLOADING -> stringResource(R.string.bookshelf_cache_downloading)
|
||||
CacheState.COMPLETED -> stringResource(R.string.bookshelf_cache_completed)
|
||||
CacheState.FAILED -> stringResource(R.string.bookshelf_cache_failed)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue