Merge feature/cache-ui: RemoteLibrary cache UI

This commit is contained in:
Atte149 2026-06-16 12:05:45 +03:00
commit 7205e35d9d

View file

@ -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)
}