diff --git a/app/src/main/java/org/dueattendant149/bookshelf/ui/bookshelf/RemoteLibraryContent.kt b/app/src/main/java/org/dueattendant149/bookshelf/ui/bookshelf/RemoteLibraryContent.kt index 3b934004..ef374a0b 100644 --- a/app/src/main/java/org/dueattendant149/bookshelf/ui/bookshelf/RemoteLibraryContent.kt +++ b/app/src/main/java/org/dueattendant149/bookshelf/ui/bookshelf/RemoteLibraryContent.kt @@ -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) + }