From cf4cb6206125b46721c67d18ff2dd398dba8e670 Mon Sep 17 00:00:00 2001 From: Acclorite <140836141+Acclorite@users.noreply.github.com> Date: Mon, 27 Jan 2025 19:19:02 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20BookInfo=20UI=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated UI of BookInfo screen * Dialogs to update fields (title, author and description) * Simplified updating mechanism --- .../book_info/BookInfoAuthorDialog.kt | 34 +++ .../book_info/BookInfoBackHandler.kt | 28 -- .../book_info/BookInfoBottomSheet.kt | 14 - .../BookInfoChangeCoverBottomSheet.kt | 1 - .../presentation/book_info/BookInfoContent.kt | 60 +--- .../book_info/BookInfoDescriptionDialog.kt | 32 ++ .../book_info/BookInfoDetailsBottomSheet.kt | 115 +++----- .../BookInfoDetailsBottomSheetItem.kt | 82 +++-- .../presentation/book_info/BookInfoDialog.kt | 27 ++ .../book_info/BookInfoFloatingActionButton.kt | 74 ----- .../presentation/book_info/BookInfoLayout.kt | 81 +++-- .../book_info/BookInfoLayoutActions.kt | 53 ++++ .../book_info/BookInfoLayoutActionsItem.kt | 64 ++++ .../book_info/BookInfoLayoutButton.kt | 40 +++ .../book_info/BookInfoLayoutDescription.kt | 151 ++++++---- .../book_info/BookInfoLayoutInfo.kt | 212 ++----------- .../book_info/BookInfoLayoutInfoAuthor.kt | 55 ++++ .../book_info/BookInfoLayoutInfoCover.kt | 67 +++++ .../book_info/BookInfoLayoutInfoProgress.kt | 46 +++ .../book_info/BookInfoLayoutInfoTitle.kt | 33 +++ .../book_info/BookInfoLayoutStatistic.kt | 69 ----- .../book_info/BookInfoMoreBottomSheet.kt | 71 ----- .../book_info/BookInfoScaffold.kt | 77 ++--- .../book_info/BookInfoTitleDialog.kt | 32 ++ .../presentation/book_info/BookInfoTopBar.kt | 98 +----- .../components/dialog/DialogWithTextField.kt | 31 +- .../book_story/ui/book_info/BookInfoEvent.kt | 70 ++--- .../book_story/ui/book_info/BookInfoModel.kt | 279 ++++++------------ .../book_story/ui/book_info/BookInfoScreen.kt | 103 ++++--- .../book_story/ui/book_info/BookInfoState.kt | 12 - app/src/main/res/values-ar/strings.xml | 5 - app/src/main/res/values-cs/strings.xml | 5 - app/src/main/res/values-de/strings.xml | 5 - app/src/main/res/values-es/strings.xml | 5 - app/src/main/res/values-fr/strings.xml | 5 - app/src/main/res/values-hi/strings.xml | 5 - app/src/main/res/values-it/strings.xml | 5 - app/src/main/res/values-pl/strings.xml | 5 - app/src/main/res/values-pt-rBR/strings.xml | 5 - app/src/main/res/values-ta/strings.xml | 5 - app/src/main/res/values-tr/strings.xml | 5 - app/src/main/res/values-uk/strings.xml | 25 +- app/src/main/res/values-zh-rCN/strings.xml | 5 - app/src/main/res/values/strings.xml | 25 +- 44 files changed, 968 insertions(+), 1253 deletions(-) create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoAuthorDialog.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDescriptionDialog.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoFloatingActionButton.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutActions.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutActionsItem.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutButton.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoAuthor.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoCover.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoProgress.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoTitle.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutStatistic.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoMoreBottomSheet.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoTitleDialog.kt diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoAuthorDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoAuthorDialog.kt new file mode 100644 index 00000000..f0293bc9 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoAuthorDialog.kt @@ -0,0 +1,34 @@ +package ua.acclorite.book_story.presentation.book_info + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import ua.acclorite.book_story.R +import ua.acclorite.book_story.domain.library.book.Book +import ua.acclorite.book_story.domain.ui.UIText +import ua.acclorite.book_story.presentation.core.components.dialog.DialogWithTextField +import ua.acclorite.book_story.ui.book_info.BookInfoEvent + +@Composable +fun BookInfoAuthorDialog( + book: Book, + actionAuthorDialog: (BookInfoEvent.OnActionAuthorDialog) -> Unit, + dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit +) { + val context = LocalContext.current + DialogWithTextField( + initialValue = book.author.getAsString() ?: "", + lengthLimit = 100, + onDismiss = { + dismissDialog(BookInfoEvent.OnDismissDialog) + }, + onAction = { + actionAuthorDialog( + BookInfoEvent.OnActionAuthorDialog( + author = if (it.isBlank()) UIText.StringResource(R.string.unknown_author) + else UIText.StringValue(it.trim().replace("\n", "")), + context = context + ) + ) + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoBackHandler.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoBackHandler.kt index c8f8714e..591f0856 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoBackHandler.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoBackHandler.kt @@ -2,40 +2,12 @@ package ua.acclorite.book_story.presentation.book_info import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable -import ua.acclorite.book_story.ui.book_info.BookInfoEvent @Composable fun BookInfoBackHandler( - editTitle: Boolean, - editAuthor: Boolean, - editDescription: Boolean, - editTitleMode: (BookInfoEvent.OnEditTitleMode) -> Unit, - editAuthorMode: (BookInfoEvent.OnEditAuthorMode) -> Unit, - editDescriptionMode: (BookInfoEvent.OnEditDescriptionMode) -> Unit, navigateBack: () -> Unit ) { BackHandler { - var exitedEditMode = false - - if (editTitle) { - editTitleMode(BookInfoEvent.OnEditTitleMode(false)) - exitedEditMode = true - } - - if (editAuthor) { - editAuthorMode(BookInfoEvent.OnEditAuthorMode(false)) - exitedEditMode = true - } - - if (editDescription) { - editDescriptionMode(BookInfoEvent.OnEditDescriptionMode(false)) - exitedEditMode = true - } - - if (exitedEditMode) { - return@BackHandler - } - navigateBack() } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoBottomSheet.kt index 6b72dc32..41c60d6e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoBottomSheet.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoBottomSheet.kt @@ -15,10 +15,6 @@ fun BookInfoBottomSheet( resetCover: (BookInfoEvent.OnResetCover) -> Unit, deleteCover: (BookInfoEvent.OnDeleteCover) -> Unit, checkCoverReset: (BookInfoEvent.OnCheckCoverReset) -> Unit, - copyToClipboard: (BookInfoEvent.OnCopyToClipboard) -> Unit, - showDetailsBottomSheet: (BookInfoEvent.OnShowDetailsBottomSheet) -> Unit, - showMoveDialog: (BookInfoEvent.OnShowMoveDialog) -> Unit, - showDeleteDialog: (BookInfoEvent.OnShowDeleteDialog) -> Unit, dismissBottomSheet: (BookInfoEvent.OnDismissBottomSheet) -> Unit ) { when (bottomSheet) { @@ -34,19 +30,9 @@ fun BookInfoBottomSheet( ) } - BookInfoScreen.MORE_BOTTOM_SHEET -> { - BookInfoMoreBottomSheet( - showDetailsBottomSheet = showDetailsBottomSheet, - showDeleteDialog = showDeleteDialog, - showMoveDialog = showMoveDialog, - dismissBottomSheet = dismissBottomSheet - ) - } - BookInfoScreen.DETAILS_BOTTOM_SHEET -> { BookInfoDetailsBottomSheet( book = book, - copyToClipboard = copyToClipboard, dismissBottomSheet = dismissBottomSheet ) } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoChangeCoverBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoChangeCoverBottomSheet.kt index d817b6ab..1a8a34d9 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoChangeCoverBottomSheet.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoChangeCoverBottomSheet.kt @@ -33,7 +33,6 @@ fun BookInfoChangeCoverBottomSheet( dismissBottomSheet: (BookInfoEvent.OnDismissBottomSheet) -> Unit ) { val context = LocalContext.current - val photoPicker = rememberLauncherForActivityResult( contract = ActivityResultContracts.PickVisualMedia(), onResult = { uri -> diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoContent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoContent.kt index e1fd92d1..2103777e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoContent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoContent.kt @@ -16,24 +16,14 @@ fun BookInfoContent( dialog: Dialog?, listState: LazyListState, canResetCover: Boolean, - editTitle: Boolean, - titleValue: String, - editAuthor: Boolean, - authorValue: String, - editDescription: Boolean, - descriptionValue: String, - editTitleMode: (BookInfoEvent.OnEditTitleMode) -> Unit, - editTitleValueChange: (BookInfoEvent.OnEditTitleValueChange) -> Unit, - editTitleRequestFocus: (BookInfoEvent.OnEditTitleRequestFocus) -> Unit, - editAuthorMode: (BookInfoEvent.OnEditAuthorMode) -> Unit, - editAuthorValueChange: (BookInfoEvent.OnEditAuthorValueChange) -> Unit, - editAuthorRequestFocus: (BookInfoEvent.OnEditAuthorRequestFocus) -> Unit, - editDescriptionMode: (BookInfoEvent.OnEditDescriptionMode) -> Unit, - editDescriptionValueChange: (BookInfoEvent.OnEditDescriptionValueChange) -> Unit, - editDescriptionRequestFocus: (BookInfoEvent.OnEditDescriptionRequestFocus) -> Unit, showChangeCoverBottomSheet: (BookInfoEvent.OnShowChangeCoverBottomSheet) -> Unit, showDetailsBottomSheet: (BookInfoEvent.OnShowDetailsBottomSheet) -> Unit, - showMoreBottomSheet: (BookInfoEvent.OnShowMoreBottomSheet) -> Unit, + showTitleDialog: (BookInfoEvent.OnShowTitleDialog) -> Unit, + actionTitleDialog: (BookInfoEvent.OnActionTitleDialog) -> Unit, + showAuthorDialog: (BookInfoEvent.OnShowAuthorDialog) -> Unit, + actionAuthorDialog: (BookInfoEvent.OnActionAuthorDialog) -> Unit, + showDescriptionDialog: (BookInfoEvent.OnShowDescriptionDialog) -> Unit, + actionDescriptionDialog: (BookInfoEvent.OnActionDescriptionDialog) -> Unit, showMoveDialog: (BookInfoEvent.OnShowMoveDialog) -> Unit, showDeleteDialog: (BookInfoEvent.OnShowDeleteDialog) -> Unit, actionDeleteDialog: (BookInfoEvent.OnActionDeleteDialog) -> Unit, @@ -44,8 +34,6 @@ fun BookInfoContent( checkCoverReset: (BookInfoEvent.OnCheckCoverReset) -> Unit, dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit, dismissBottomSheet: (BookInfoEvent.OnDismissBottomSheet) -> Unit, - updateData: (BookInfoEvent.OnUpdateData) -> Unit, - copyToClipboard: (BookInfoEvent.OnCopyToClipboard) -> Unit, navigateToReader: () -> Unit, navigateToLibrary: () -> Unit, navigateBack: () -> Unit @@ -53,6 +41,9 @@ fun BookInfoContent( BookInfoDialog( dialog = dialog, book = book, + actionTitleDialog = actionTitleDialog, + actionAuthorDialog = actionAuthorDialog, + actionDescriptionDialog = actionDescriptionDialog, actionDeleteDialog = actionDeleteDialog, actionMoveDialog = actionMoveDialog, dismissDialog = dismissDialog, @@ -68,45 +59,24 @@ fun BookInfoContent( resetCover = resetCover, deleteCover = deleteCover, checkCoverReset = checkCoverReset, - copyToClipboard = copyToClipboard, - showDetailsBottomSheet = showDetailsBottomSheet, - showMoveDialog = showMoveDialog, - showDeleteDialog = showDeleteDialog, dismissBottomSheet = dismissBottomSheet ) BookInfoScaffold( book = book, listState = listState, - editTitle = editTitle, - titleValue = titleValue, - editAuthor = editAuthor, - authorValue = authorValue, - editDescription = editDescription, - descriptionValue = descriptionValue, - editTitleMode = editTitleMode, - editTitleValueChange = editTitleValueChange, - editTitleRequestFocus = editTitleRequestFocus, - editAuthorMode = editAuthorMode, - editAuthorValueChange = editAuthorValueChange, - editAuthorRequestFocus = editAuthorRequestFocus, - editDescriptionMode = editDescriptionMode, - editDescriptionValueChange = editDescriptionValueChange, - editDescriptionRequestFocus = editDescriptionRequestFocus, + showTitleDialog = showTitleDialog, + showAuthorDialog = showAuthorDialog, + showDescriptionDialog = showDescriptionDialog, showChangeCoverBottomSheet = showChangeCoverBottomSheet, - showMoreBottomSheet = showMoreBottomSheet, - updateData = updateData, + showDetailsBottomSheet = showDetailsBottomSheet, + showMoveDialog = showMoveDialog, + showDeleteDialog = showDeleteDialog, navigateToReader = navigateToReader, navigateBack = navigateBack ) BookInfoBackHandler( - editTitle = editTitle, - editAuthor = editAuthor, - editDescription = editDescription, - editTitleMode = editTitleMode, - editAuthorMode = editAuthorMode, - editDescriptionMode = editDescriptionMode, navigateBack = navigateBack ) } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDescriptionDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDescriptionDialog.kt new file mode 100644 index 00000000..8e1c7233 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDescriptionDialog.kt @@ -0,0 +1,32 @@ +package ua.acclorite.book_story.presentation.book_info + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import ua.acclorite.book_story.domain.library.book.Book +import ua.acclorite.book_story.presentation.core.components.dialog.DialogWithTextField +import ua.acclorite.book_story.ui.book_info.BookInfoEvent + +@Composable +fun BookInfoDescriptionDialog( + book: Book, + actionDescriptionDialog: (BookInfoEvent.OnActionDescriptionDialog) -> Unit, + dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit +) { + val context = LocalContext.current + DialogWithTextField( + initialValue = book.description ?: "", + lengthLimit = 5000, + onDismiss = { + dismissDialog(BookInfoEvent.OnDismissDialog) + }, + onAction = { + actionDescriptionDialog( + BookInfoEvent.OnActionDescriptionDialog( + description = if (it.isBlank()) null + else it.trim().replace("\n", ""), + context = context + ) + ) + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDetailsBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDetailsBottomSheet.kt index 4f86d973..70a1dedc 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDetailsBottomSheet.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDetailsBottomSheet.kt @@ -1,17 +1,20 @@ package ua.acclorite.book_story.presentation.book_info import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.library.book.Book import ua.acclorite.book_story.presentation.core.components.common.LazyColumnWithScrollbar import ua.acclorite.book_story.presentation.core.components.modal_bottom_sheet.ModalBottomSheet +import ua.acclorite.book_story.presentation.settings.components.SettingsSubcategoryTitle import ua.acclorite.book_story.ui.book_info.BookInfoEvent import java.io.File import java.text.SimpleDateFormat @@ -21,40 +24,29 @@ import java.util.Locale @Composable fun BookInfoDetailsBottomSheet( book: Book, - copyToClipboard: (BookInfoEvent.OnCopyToClipboard) -> Unit, dismissBottomSheet: (BookInfoEvent.OnDismissBottomSheet) -> Unit ) { - val context = LocalContext.current + val pattern = remember { SimpleDateFormat("HH:mm dd MMM yyyy", Locale.getDefault()) } + val lastOpened = remember(book.lastOpened) { pattern.format(Date(book.lastOpened ?: 0)) } - val pattern = remember { - SimpleDateFormat("HH:mm dd MMM yyyy", Locale.getDefault()) - } - val lastOpened = remember { - pattern.format(Date(book.lastOpened ?: 0)) - } - - val sizeBytes = remember { + val fileSize = remember(book.filePath) { val file = File(book.filePath) if (file.exists()) { - file.length() - } else 0 + val sizeBytes = file.length() + val sizeKB = sizeBytes / 1024f + val sizeMB = sizeKB / 1024f + when { + sizeMB >= 1f -> "%.2f MB".format(sizeMB) + sizeKB > 0f -> "%.2f KB".format(sizeKB) + else -> "" + } + } else { + "" + } } - val fileSizeKB = remember { - if (sizeBytes > 0) sizeBytes.toDouble() / 1024.0 else 0.0 - } - val fileSizeMB = remember { - if (sizeBytes > 0) fileSizeKB / 1024.0 else 0.0 - } - - val fileSize = remember { - if (fileSizeMB >= 1.0) "%.2f MB".format(fileSizeMB) - else if (fileSizeMB > 0.0) "%.2f KB".format(fileSizeKB) - else "" - } - - val fileName = remember { - book.filePath.substringAfterLast(File.separatorChar) + val fileExists = remember(book.filePath) { + File(book.filePath).exists() } ModalBottomSheet( @@ -69,64 +61,39 @@ fun BookInfoDetailsBottomSheet( contentPadding = PaddingValues(bottom = 8.dp) ) { item { - BookInfoDetailsBottomSheetItem( - title = stringResource(id = R.string.file_name), - description = fileName - ) { - copyToClipboard( - BookInfoEvent.OnCopyToClipboard( - text = fileName, - context = context - ) - ) - } + SettingsSubcategoryTitle( + title = stringResource(id = R.string.file_details), + padding = 16.dp, + color = MaterialTheme.colorScheme.primary + ) + Spacer(Modifier.height(8.dp)) } item { BookInfoDetailsBottomSheetItem( - title = stringResource(id = R.string.file_path), - description = book.filePath - ) { - copyToClipboard( - BookInfoEvent.OnCopyToClipboard( - text = book.filePath, - context = context - ) - ) - } + label = stringResource(id = R.string.file_path), + text = book.filePath, + editable = false, + showError = !fileExists, + errorMessage = stringResource(id = R.string.error_no_file) + ) } item { BookInfoDetailsBottomSheetItem( - title = stringResource(id = R.string.file_last_opened), - description = if (book.lastOpened != null) lastOpened - else stringResource(id = R.string.never) - ) { - if (book.lastOpened != null) { - copyToClipboard( - BookInfoEvent.OnCopyToClipboard( - text = lastOpened, - context = context - ) - ) - } - } + label = stringResource(id = R.string.file_last_opened), + text = if (book.lastOpened != null) lastOpened + else stringResource(id = R.string.never), + editable = false + ) } item { BookInfoDetailsBottomSheetItem( - title = stringResource(id = R.string.file_size), - description = fileSize.ifBlank { stringResource(id = R.string.unknown) } - ) { - if (fileSize.isNotBlank()) { - copyToClipboard( - BookInfoEvent.OnCopyToClipboard( - text = fileSize, - context = context - ) - ) - } - } + label = stringResource(id = R.string.file_size), + text = fileSize.ifBlank { stringResource(id = R.string.unknown) }, + editable = false + ) } } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDetailsBottomSheetItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDetailsBottomSheetItem.kt index 910dce11..9eee34aa 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDetailsBottomSheetItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDetailsBottomSheetItem.kt @@ -1,52 +1,70 @@ package ua.acclorite.book_story.presentation.book_info -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.EditNote import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import ua.acclorite.book_story.R +import ua.acclorite.book_story.presentation.core.components.common.IconButton -@OptIn(ExperimentalFoundationApi::class) @Composable fun BookInfoDetailsBottomSheetItem( - title: String, - description: String, - onClick: () -> Unit + label: String, + text: String, + editable: Boolean, + showError: Boolean = false, + errorMessage: String? = null, + onEdit: () -> Unit = {} ) { - Column( - Modifier + Row( + modifier = Modifier .fillMaxWidth() - .combinedClickable( - onClick = {}, - onLongClick = { - onClick() - } - ) - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.Start + .padding(horizontal = 16.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically ) { - Text( - title, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, - fontSize = 18.sp - ) - Spacer(modifier = Modifier.height(2.dp)) - Text( - description, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + OutlinedTextField( + modifier = Modifier + .weight(1f) + .focusable(false), + value = text, + onValueChange = {}, + readOnly = true, + isError = showError, + supportingText = if (!showError || errorMessage.isNullOrBlank()) null else { + { + Text( + text = errorMessage, + color = MaterialTheme.colorScheme.error + ) + } + }, + label = { + Text(label) + } ) + + if (editable) { + IconButton( + icon = Icons.Default.EditNote, + contentDescription = R.string.edit_content_desc, + disableOnClick = false, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(24.dp) + ) { + onEdit() + } + } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDialog.kt index baf74edc..082979e2 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDialog.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoDialog.kt @@ -10,6 +10,9 @@ import ua.acclorite.book_story.ui.book_info.BookInfoScreen fun BookInfoDialog( dialog: Dialog?, book: Book, + actionTitleDialog: (BookInfoEvent.OnActionTitleDialog) -> Unit, + actionAuthorDialog: (BookInfoEvent.OnActionAuthorDialog) -> Unit, + actionDescriptionDialog: (BookInfoEvent.OnActionDescriptionDialog) -> Unit, actionDeleteDialog: (BookInfoEvent.OnActionDeleteDialog) -> Unit, actionMoveDialog: (BookInfoEvent.OnActionMoveDialog) -> Unit, dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit, @@ -33,5 +36,29 @@ fun BookInfoDialog( navigateToLibrary = navigateToLibrary ) } + + BookInfoScreen.TITLE_DIALOG -> { + BookInfoTitleDialog( + book = book, + actionTitleDialog = actionTitleDialog, + dismissDialog = dismissDialog + ) + } + + BookInfoScreen.AUTHOR_DIALOG -> { + BookInfoAuthorDialog( + book = book, + actionAuthorDialog = actionAuthorDialog, + dismissDialog = dismissDialog + ) + } + + BookInfoScreen.DESCRIPTION_DIALOG -> { + BookInfoDescriptionDialog( + book = book, + actionDescriptionDialog = actionDescriptionDialog, + dismissDialog = dismissDialog + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoFloatingActionButton.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoFloatingActionButton.kt deleted file mode 100644 index 482b91e0..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoFloatingActionButton.kt +++ /dev/null @@ -1,74 +0,0 @@ -package ua.acclorite.book_story.presentation.book_info - -import androidx.compose.animation.expandHorizontally -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkHorizontally -import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.FloatingActionButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.library.book.Book -import ua.acclorite.book_story.presentation.core.components.common.AnimatedVisibility - -@Composable -fun BoxScope.BookInfoFloatingActionButton( - book: Book, - listState: LazyListState, - navigateToReader: () -> Unit -) { - FloatingActionButton( - onClick = { - navigateToReader() - }, - shape = MaterialTheme.shapes.large, - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(16.dp), - elevation = FloatingActionButtonDefaults.elevation(), - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - content = { - Row(verticalAlignment = Alignment.CenterVertically) { - Spacer(modifier = Modifier.width(13.dp)) - Icon( - imageVector = Icons.Filled.PlayArrow, - contentDescription = stringResource(id = R.string.continue_reading_content_desc), - Modifier.size(24.dp) - ) - AnimatedVisibility( - visible = !listState.canScrollBackward, - enter = fadeIn() + expandHorizontally(), - exit = fadeOut() + shrinkHorizontally() - ) { - Text( - text = stringResource( - if (book.progress == 0f) R.string.start - else R.string.continue_read - ), - style = MaterialTheme.typography.labelLarge, - maxLines = 1, - modifier = Modifier.padding(start = 8.dp) - ) - } - Spacer(modifier = Modifier.width(16.dp)) - } - } - ) -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayout.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayout.kt index dcb41c22..0d356b85 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayout.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayout.kt @@ -7,14 +7,14 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import ua.acclorite.book_story.domain.library.book.Book +import ua.acclorite.book_story.presentation.core.components.common.LazyColumnWithScrollbar +import ua.acclorite.book_story.presentation.core.constants.Constants +import ua.acclorite.book_story.presentation.core.constants.providePrimaryScrollbar import ua.acclorite.book_story.ui.book_info.BookInfoEvent @Composable @@ -22,39 +22,25 @@ fun BookInfoLayout( book: Book, listState: LazyListState, paddingValues: PaddingValues, - editTitle: Boolean, - titleValue: String, - editAuthor: Boolean, - authorValue: String, - editDescription: Boolean, - descriptionValue: String, - editTitleMode: (BookInfoEvent.OnEditTitleMode) -> Unit, - editTitleValueChange: (BookInfoEvent.OnEditTitleValueChange) -> Unit, - editTitleRequestFocus: (BookInfoEvent.OnEditTitleRequestFocus) -> Unit, - editAuthorMode: (BookInfoEvent.OnEditAuthorMode) -> Unit, - editAuthorValueChange: (BookInfoEvent.OnEditAuthorValueChange) -> Unit, - editAuthorRequestFocus: (BookInfoEvent.OnEditAuthorRequestFocus) -> Unit, - editDescriptionMode: (BookInfoEvent.OnEditDescriptionMode) -> Unit, - editDescriptionValueChange: (BookInfoEvent.OnEditDescriptionValueChange) -> Unit, - editDescriptionRequestFocus: (BookInfoEvent.OnEditDescriptionRequestFocus) -> Unit, - showChangeCoverBottomSheet: (BookInfoEvent.OnShowChangeCoverBottomSheet) -> Unit + showChangeCoverBottomSheet: (BookInfoEvent.OnShowChangeCoverBottomSheet) -> Unit, + showTitleDialog: (BookInfoEvent.OnShowTitleDialog) -> Unit, + showAuthorDialog: (BookInfoEvent.OnShowAuthorDialog) -> Unit, + showDescriptionDialog: (BookInfoEvent.OnShowDescriptionDialog) -> Unit, + showMoveDialog: (BookInfoEvent.OnShowMoveDialog) -> Unit, + showDeleteDialog: (BookInfoEvent.OnShowDeleteDialog) -> Unit, + navigateToReader: () -> Unit ) { - val backgroundCoverImageHeight = remember { - derivedStateOf { - paddingValues.calculateTopPadding() + 12.dp + 195.dp - } - } - - LazyColumn( - Modifier - .fillMaxSize(), - state = listState + LazyColumnWithScrollbar( + modifier = Modifier.fillMaxSize(), + state = listState, + scrollbarSettings = Constants.providePrimaryScrollbar(false), + contentPadding = PaddingValues(bottom = 18.dp) ) { item { Box(modifier = Modifier.fillMaxWidth()) { if (book.coverImage != null) { BookInfoLayoutBackground( - height = backgroundCoverImageHeight.value, + height = paddingValues.calculateTopPadding() + 232.dp, image = book.coverImage ) } @@ -63,16 +49,8 @@ fun BookInfoLayout( Spacer(modifier = Modifier.height(paddingValues.calculateTopPadding() + 12.dp)) BookInfoLayoutInfo( book = book, - editTitle = editTitle, - titleValue = titleValue, - editAuthor = editAuthor, - authorValue = authorValue, - editTitleMode = editTitleMode, - editTitleValueChange = editTitleValueChange, - editTitleRequestFocus = editTitleRequestFocus, - editAuthorMode = editAuthorMode, - editAuthorValueChange = editAuthorValueChange, - editAuthorRequestFocus = editAuthorRequestFocus, + showTitleDialog = showTitleDialog, + showAuthorDialog = showAuthorDialog, showChangeCoverBottomSheet = showChangeCoverBottomSheet ) } @@ -80,19 +58,26 @@ fun BookInfoLayout( } item { - Spacer(modifier = Modifier.height(24.dp)) - BookInfoLayoutStatistic(book = book) + Spacer(Modifier.height(18.dp)) + BookInfoLayoutActions( + showMoveDialog = showMoveDialog, + showDeleteDialog = showDeleteDialog + ) } item { - Spacer(modifier = Modifier.height(24.dp)) + Spacer(Modifier.height(18.dp)) BookInfoLayoutDescription( book = book, - editDescription = editDescription, - descriptionValue = descriptionValue, - editDescriptionMode = editDescriptionMode, - editDescriptionValueChange = editDescriptionValueChange, - editDescriptionRequestFocus = editDescriptionRequestFocus + showDescriptionDialog = showDescriptionDialog + ) + } + + item { + Spacer(Modifier.height(18.dp)) + BookInfoLayoutButton( + book = book, + navigateToReader = navigateToReader ) } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutActions.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutActions.kt new file mode 100644 index 00000000..1ed32792 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutActions.kt @@ -0,0 +1,53 @@ +package ua.acclorite.book_story.presentation.book_info + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.MoveUp +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import ua.acclorite.book_story.R +import ua.acclorite.book_story.ui.book_info.BookInfoEvent + +@Composable +fun BookInfoLayoutActions( + showMoveDialog: (BookInfoEvent.OnShowMoveDialog) -> Unit, + showDeleteDialog: (BookInfoEvent.OnShowDeleteDialog) -> Unit +) { + Row( + Modifier + .fillMaxWidth() + .padding(horizontal = 18.dp) + .clip(MaterialTheme.shapes.large), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(2.dp) + ) { + BookInfoLayoutActionsItem( + modifier = Modifier.weight(1f), + alignmentStart = true, + title = stringResource(id = R.string.move), + icon = Icons.Default.MoveUp, + onClick = { + showMoveDialog(BookInfoEvent.OnShowMoveDialog) + } + ) + + BookInfoLayoutActionsItem( + modifier = Modifier.weight(1f), + alignmentStart = false, + title = stringResource(id = R.string.delete), + icon = Icons.Default.Delete, + onClick = { + showDeleteDialog(BookInfoEvent.OnShowDeleteDialog) + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutActionsItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutActionsItem.kt new file mode 100644 index 00000000..5c5c8ef4 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutActionsItem.kt @@ -0,0 +1,64 @@ +package ua.acclorite.book_story.presentation.book_info + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp + +@Composable +fun BookInfoLayoutActionsItem( + modifier: Modifier = Modifier, + alignmentStart: Boolean, + title: String, + icon: ImageVector, + onClick: () -> Unit +) { + val shape = remember(alignmentStart) { + when (alignmentStart) { + true -> RoundedCornerShape( + topEnd = 4.dp, + bottomEnd = 4.dp + ) + + false -> RoundedCornerShape( + topStart = 4.dp, + bottomStart = 4.dp + ) + } + } + + Column( + modifier + .clip(shape) + .background(MaterialTheme.colorScheme.surfaceContainerLow) + .clickable { onClick() } + .padding(vertical = 18.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(18.dp) + ) + Text( + text = title, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutButton.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutButton.kt new file mode 100644 index 00000000..526adc79 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutButton.kt @@ -0,0 +1,40 @@ +package ua.acclorite.book_story.presentation.book_info + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import ua.acclorite.book_story.R +import ua.acclorite.book_story.domain.library.book.Book +import ua.acclorite.book_story.presentation.core.util.calculateProgress + +@Composable +fun BookInfoLayoutButton( + book: Book, + navigateToReader: () -> Unit +) { + Button( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 18.dp), + shape = CircleShape, + onClick = { + if (book.id != -1) { + navigateToReader() + } + } + ) { + Text( + if (book.progress == 0f) stringResource(id = R.string.start_reading) + else stringResource( + id = R.string.continue_reading_query, + "${book.progress.calculateProgress(1)}%" + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutDescription.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutDescription.kt index 4069ac7c..ad38aeb2 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutDescription.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutDescription.kt @@ -1,25 +1,37 @@ package ua.acclorite.book_story.presentation.book_info +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardCapitalization -import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.library.book.Book @@ -29,62 +41,83 @@ import ua.acclorite.book_story.ui.book_info.BookInfoEvent @Composable fun BookInfoLayoutDescription( book: Book, - editDescription: Boolean, - descriptionValue: String, - editDescriptionMode: (BookInfoEvent.OnEditDescriptionMode) -> Unit, - editDescriptionValueChange: (BookInfoEvent.OnEditDescriptionValueChange) -> Unit, - editDescriptionRequestFocus: (BookInfoEvent.OnEditDescriptionRequestFocus) -> Unit, + showDescriptionDialog: (BookInfoEvent.OnShowDescriptionDialog) -> Unit ) { - val descriptionFocusRequester = remember { FocusRequester() } + val expand = remember { mutableStateOf(false) } + val expandable = remember(book.description) { (book.description?.length ?: 0) > 50 } - if (!editDescription) { - Text( - if (book.description?.isNotBlank() == true) book.description - else stringResource(id = R.string.error_no_description), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Start, - modifier = Modifier - .padding(horizontal = 24.dp) - .noRippleClickable( - onClick = {}, - onLongClick = { - editDescriptionMode(BookInfoEvent.OnEditDescriptionMode(true)) - } - ), + val backgroundColor = MaterialTheme.colorScheme.surface + val gradientAnimation = animateColorAsState( + targetValue = if (expand.value || !expandable) { + backgroundColor.copy(alpha = 0f) + } else backgroundColor, + animationSpec = spring(stiffness = Spring.StiffnessMedium) + ) + val arrowAnimation = animateFloatAsState( + if (expand.value) -1f + else 1f, + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessMediumLow ) - } else { - BasicTextField( - value = descriptionValue, - textStyle = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onSurface - ), - onValueChange = { - if (it.length < 5000 || it.length < descriptionValue.length) { - editDescriptionValueChange(BookInfoEvent.OnEditDescriptionValueChange(it)) + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .noRippleClickable( + onClick = { + if (expandable) expand.value = !expand.value + }, + onLongClick = { + showDescriptionDialog(BookInfoEvent.OnShowDescriptionDialog) } - }, + ) + .padding(horizontal = 18.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( modifier = Modifier - .padding(horizontal = 24.dp) - .focusRequester(descriptionFocusRequester) - .onGloballyPositioned { - editDescriptionRequestFocus( - BookInfoEvent.OnEditDescriptionRequestFocus( - descriptionFocusRequester + .fillMaxWidth() + .animateContentSize() + .heightIn(0.dp, if (!expand.value && expandable) 60.dp else Dp.Unspecified) + .drawWithContent { + drawContent() + drawRect( + brush = Brush.verticalGradient( + 0f to Color.Transparent, + 0.95f to gradientAnimation.value ) ) - }, - keyboardOptions = KeyboardOptions(KeyboardCapitalization.Sentences), - cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant) - ) { innerText -> - Box( - modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.CenterStart - ) { - innerText() - } + } + ) { + Text( + if (!book.description.isNullOrBlank()) book.description + else stringResource(id = R.string.error_no_description), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + AnimatedVisibility( + visible = expandable, + enter = expandVertically() + fadeIn(), + exit = shrinkVertically() + fadeOut() + ) { + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = stringResource( + id = if (expand.value) R.string.show_less_content_desc + else R.string.show_more_content_desc + ), + modifier = Modifier + .size(24.dp) + .scale( + scaleX = 1f, + scaleY = arrowAnimation.value + ), + tint = MaterialTheme.colorScheme.onSurface + ) } } - - Spacer(modifier = Modifier.height(96.dp)) } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfo.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfo.kt index 510022a9..46d3f74a 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfo.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfo.kt @@ -1,216 +1,54 @@ package ua.acclorite.book_story.presentation.book_info -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Image -import androidx.compose.material.icons.outlined.Person -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardCapitalization -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.library.book.Book -import ua.acclorite.book_story.presentation.core.components.common.AsyncCoverImage -import ua.acclorite.book_story.presentation.core.util.noRippleClickable import ua.acclorite.book_story.ui.book_info.BookInfoEvent +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun BookInfoLayoutInfo( book: Book, - editTitle: Boolean, - titleValue: String, - editAuthor: Boolean, - authorValue: String, - editTitleMode: (BookInfoEvent.OnEditTitleMode) -> Unit, - editTitleValueChange: (BookInfoEvent.OnEditTitleValueChange) -> Unit, - editTitleRequestFocus: (BookInfoEvent.OnEditTitleRequestFocus) -> Unit, - editAuthorMode: (BookInfoEvent.OnEditAuthorMode) -> Unit, - editAuthorValueChange: (BookInfoEvent.OnEditAuthorValueChange) -> Unit, - editAuthorRequestFocus: (BookInfoEvent.OnEditAuthorRequestFocus) -> Unit, + showTitleDialog: (BookInfoEvent.OnShowTitleDialog) -> Unit, + showAuthorDialog: (BookInfoEvent.OnShowAuthorDialog) -> Unit, showChangeCoverBottomSheet: (BookInfoEvent.OnShowChangeCoverBottomSheet) -> Unit ) { - val titleFocusRequester = remember { FocusRequester() } - val authorFocusRequester = remember { FocusRequester() } - Row( - verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 24.dp) + .padding(horizontal = 18.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) ) { - Box( - modifier = Modifier - .height(195.dp) - .width(130.dp) - .clip(MaterialTheme.shapes.medium) - .background( - MaterialTheme.colorScheme.surfaceContainerLow, - MaterialTheme.shapes.medium - ) - .noRippleClickable( - onClick = {}, - onLongClick = { - showChangeCoverBottomSheet(BookInfoEvent.OnShowChangeCoverBottomSheet) - } - ) + BookInfoLayoutInfoCover( + book = book, + showChangeCoverBottomSheet = showChangeCoverBottomSheet + ) + + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) ) { - if (book.coverImage != null) { - AsyncCoverImage( - uri = book.coverImage, - modifier = Modifier - .fillMaxSize() - .clip(MaterialTheme.shapes.medium) - ) - } else { - Icon( - imageVector = Icons.Default.Image, - contentDescription = stringResource(id = R.string.cover_image_not_found_content_desc), - modifier = Modifier - .align(Alignment.Center) - .fillMaxWidth(0.7f) - .aspectRatio(1f), - tint = MaterialTheme.colorScheme.surfaceContainerHigh - ) - } - } + BookInfoLayoutInfoTitle( + book = book, + showTitleDialog = showTitleDialog + ) - Spacer(modifier = Modifier.width(12.dp)) + BookInfoLayoutInfoAuthor( + book = book, + showAuthorDialog = showAuthorDialog + ) - Column(verticalArrangement = Arrangement.Center) { - if (!editTitle) { - Text( - book.title, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier - .fillMaxWidth() - .noRippleClickable( - onClick = {}, - onLongClick = { - editTitleMode(BookInfoEvent.OnEditTitleMode(true)) - } - ), - maxLines = 4, - overflow = TextOverflow.Ellipsis - ) - } else { - BasicTextField( - maxLines = 4, - value = titleValue, - textStyle = MaterialTheme.typography.headlineSmall.copy( - color = MaterialTheme.colorScheme.onSurface - ), - onValueChange = { - if (it.length < 100 || it.length < titleValue.length) { - editTitleValueChange(BookInfoEvent.OnEditTitleValueChange(it)) - } - }, - modifier = Modifier - .focusRequester(titleFocusRequester) - .onGloballyPositioned { - editTitleRequestFocus( - BookInfoEvent.OnEditTitleRequestFocus( - titleFocusRequester - ) - ) - }, - keyboardOptions = KeyboardOptions(KeyboardCapitalization.Words), - cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant) - ) { innerText -> - Box( - modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.CenterStart - ) { - innerText() - } - } - } - Spacer(modifier = Modifier.height(4.dp)) - - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = Icons.Outlined.Person, - contentDescription = stringResource(id = R.string.author), - modifier = Modifier.size(18.dp), - tint = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(modifier = Modifier.width(4.dp)) - - if (!editAuthor) { - Text( - book.author.asString(), - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier - .fillMaxWidth() - .noRippleClickable( - onClick = {}, - onLongClick = { - editAuthorMode(BookInfoEvent.OnEditAuthorMode(true)) - } - ), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } else { - BasicTextField( - singleLine = true, - value = authorValue, - textStyle = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant - ), - onValueChange = { - if (it.length < 50 || it.length < authorValue.length) { - editAuthorValueChange(BookInfoEvent.OnEditAuthorValueChange(it)) - } - }, - modifier = Modifier - .focusRequester(authorFocusRequester) - .onGloballyPositioned { - editAuthorRequestFocus( - BookInfoEvent.OnEditAuthorRequestFocus( - authorFocusRequester - ) - ) - }, - keyboardOptions = KeyboardOptions(KeyboardCapitalization.Words), - cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant) - ) { innerText -> - Box( - modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.CenterStart - ) { - innerText() - } - } - } - } + BookInfoLayoutInfoProgress( + book = book + ) } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoAuthor.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoAuthor.kt new file mode 100644 index 00000000..9aaeb678 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoAuthor.kt @@ -0,0 +1,55 @@ +package ua.acclorite.book_story.presentation.book_info + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Person +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import ua.acclorite.book_story.R +import ua.acclorite.book_story.domain.library.book.Book +import ua.acclorite.book_story.presentation.core.util.noRippleClickable +import ua.acclorite.book_story.ui.book_info.BookInfoEvent + +@Composable +fun BookInfoLayoutInfoAuthor( + book: Book, + showAuthorDialog: (BookInfoEvent.OnShowAuthorDialog) -> Unit +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Icon( + imageVector = Icons.Outlined.Person, + contentDescription = stringResource(id = R.string.author), + modifier = Modifier.size(18.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Text( + book.author.asString(), + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier + .fillMaxWidth() + .noRippleClickable( + onClick = {}, + onLongClick = { + showAuthorDialog(BookInfoEvent.OnShowAuthorDialog) + } + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoCover.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoCover.kt new file mode 100644 index 00000000..025bea74 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoCover.kt @@ -0,0 +1,67 @@ +package ua.acclorite.book_story.presentation.book_info + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Image +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import ua.acclorite.book_story.R +import ua.acclorite.book_story.domain.library.book.Book +import ua.acclorite.book_story.presentation.core.components.common.AsyncCoverImage +import ua.acclorite.book_story.presentation.core.util.noRippleClickable +import ua.acclorite.book_story.ui.book_info.BookInfoEvent + +@Composable +fun BookInfoLayoutInfoCover( + book: Book, + showChangeCoverBottomSheet: (BookInfoEvent.OnShowChangeCoverBottomSheet) -> Unit +) { + val height = remember { 220.dp } + val width = remember(height) { height * 0.66f } + + Box( + modifier = Modifier + .height(height) + .width(width) + .clip(MaterialTheme.shapes.large) + .background(MaterialTheme.colorScheme.surfaceContainerLow) + .noRippleClickable( + onClick = {}, + onLongClick = { + showChangeCoverBottomSheet(BookInfoEvent.OnShowChangeCoverBottomSheet) + } + ) + ) { + if (book.coverImage != null) { + AsyncCoverImage( + uri = book.coverImage, + modifier = Modifier + .fillMaxSize() + .clip(MaterialTheme.shapes.medium) + ) + } else { + Icon( + imageVector = Icons.Default.Image, + contentDescription = stringResource(id = R.string.cover_image_not_found_content_desc), + modifier = Modifier + .align(Alignment.Center) + .fillMaxWidth(0.7f) + .aspectRatio(1f), + tint = MaterialTheme.colorScheme.surfaceContainerHigh + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoProgress.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoProgress.kt new file mode 100644 index 00000000..1b32ab66 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoProgress.kt @@ -0,0 +1,46 @@ +package ua.acclorite.book_story.presentation.book_info + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.LinearWavyProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import ua.acclorite.book_story.domain.library.book.Book +import ua.acclorite.book_story.presentation.core.util.calculateProgress + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun BookInfoLayoutInfoProgress( + book: Book +) { + val progress = remember(book.progress) { + "${book.progress.calculateProgress(1)}%" + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + LinearWavyProgressIndicator( + progress = { book.progress.coerceIn(0f, 1f) }, + trackColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.7f), + modifier = Modifier.weight(1f), + amplitude = { 0.5f }, + wavelength = 80.dp, + waveSpeed = 15.dp + ) + Text( + text = progress, + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoTitle.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoTitle.kt new file mode 100644 index 00000000..beeee53c --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutInfoTitle.kt @@ -0,0 +1,33 @@ +package ua.acclorite.book_story.presentation.book_info + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import ua.acclorite.book_story.domain.library.book.Book +import ua.acclorite.book_story.presentation.core.util.noRippleClickable +import ua.acclorite.book_story.ui.book_info.BookInfoEvent + +@Composable +fun BookInfoLayoutInfoTitle( + book: Book, + showTitleDialog: (BookInfoEvent.OnShowTitleDialog) -> Unit +) { + Text( + book.title, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier + .fillMaxWidth() + .noRippleClickable( + onClick = {}, + onLongClick = { + showTitleDialog(BookInfoEvent.OnShowTitleDialog) + } + ), + maxLines = 4, + overflow = TextOverflow.Ellipsis + ) +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutStatistic.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutStatistic.kt deleted file mode 100644 index 5d42b26d..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoLayoutStatistic.kt +++ /dev/null @@ -1,69 +0,0 @@ -package ua.acclorite.book_story.presentation.book_info - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.LinearWavyProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.library.book.Book -import ua.acclorite.book_story.presentation.core.util.calculateProgress - -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun BookInfoLayoutStatistic( - book: Book -) { - val progress = remember(book.progress) { - derivedStateOf { - "${book.progress.calculateProgress(1)}%" - } - } - val description = stringResource( - if (book.progress == 1f) R.string.read_done - else if (book.progress > 0.2f) R.string.read_keep - else R.string.read_more - ) - - Column( - Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp) - ) { - Text( - text = progress.value, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge - ) - Spacer(modifier = Modifier.height(6.dp)) - - LinearWavyProgressIndicator( - progress = { book.progress.coerceIn(0f, 1f) }, - trackColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.7f), - modifier = Modifier.fillMaxWidth(), - amplitude = { 0.5f }, - wavelength = 80.dp, - waveSpeed = 15.dp - ) - - Spacer(modifier = Modifier.height(6.dp)) - Text( - text = description, - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.bodySmall, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoMoreBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoMoreBottomSheet.kt deleted file mode 100644 index 0d65285e..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoMoreBottomSheet.kt +++ /dev/null @@ -1,71 +0,0 @@ -package ua.acclorite.book_story.presentation.book_info - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.util.Position -import ua.acclorite.book_story.presentation.core.components.modal_bottom_sheet.ModalBottomSheet -import ua.acclorite.book_story.presentation.navigator.NavigatorBottomSheetItem -import ua.acclorite.book_story.ui.book_info.BookInfoEvent - -@Composable -fun BookInfoMoreBottomSheet( - showDetailsBottomSheet: (BookInfoEvent.OnShowDetailsBottomSheet) -> Unit, - showMoveDialog: (BookInfoEvent.OnShowMoveDialog) -> Unit, - showDeleteDialog: (BookInfoEvent.OnShowDeleteDialog) -> Unit, - dismissBottomSheet: (BookInfoEvent.OnDismissBottomSheet) -> Unit, -) { - ModalBottomSheet( - modifier = Modifier.fillMaxWidth(), - onDismissRequest = { - dismissBottomSheet(BookInfoEvent.OnDismissBottomSheet) - }, - sheetGesturesEnabled = true - ) { - LazyColumn(modifier = Modifier.fillMaxWidth()) { - item { - NavigatorBottomSheetItem( - title = stringResource(id = R.string.details_book), - primary = false, - position = Position.SOLO - ) { - showDetailsBottomSheet(BookInfoEvent.OnShowDetailsBottomSheet) - } - } - - item { - Spacer(Modifier.height(18.dp)) - } - - item { - NavigatorBottomSheetItem( - title = stringResource(id = R.string.delete_this_book), - primary = true, - position = Position.TOP - ) { - showDeleteDialog(BookInfoEvent.OnShowDeleteDialog) - } - } - - item { - NavigatorBottomSheetItem( - title = stringResource(id = R.string.move_this_book), - primary = true, - position = Position.BOTTOM - ) { - showMoveDialog(BookInfoEvent.OnShowMoveDialog) - } - } - - item { - Spacer(modifier = Modifier.height(8.dp)) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoScaffold.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoScaffold.kt index 698b9d07..2e6fd2d3 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoScaffold.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoScaffold.kt @@ -1,6 +1,5 @@ package ua.acclorite.book_story.presentation.book_info -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.imePadding @@ -20,24 +19,13 @@ import ua.acclorite.book_story.ui.book_info.BookInfoEvent fun BookInfoScaffold( book: Book, listState: LazyListState, - editTitle: Boolean, - titleValue: String, - editAuthor: Boolean, - authorValue: String, - editDescription: Boolean, - descriptionValue: String, - editTitleMode: (BookInfoEvent.OnEditTitleMode) -> Unit, - editTitleValueChange: (BookInfoEvent.OnEditTitleValueChange) -> Unit, - editTitleRequestFocus: (BookInfoEvent.OnEditTitleRequestFocus) -> Unit, - editAuthorMode: (BookInfoEvent.OnEditAuthorMode) -> Unit, - editAuthorValueChange: (BookInfoEvent.OnEditAuthorValueChange) -> Unit, - editAuthorRequestFocus: (BookInfoEvent.OnEditAuthorRequestFocus) -> Unit, - editDescriptionMode: (BookInfoEvent.OnEditDescriptionMode) -> Unit, - editDescriptionValueChange: (BookInfoEvent.OnEditDescriptionValueChange) -> Unit, - editDescriptionRequestFocus: (BookInfoEvent.OnEditDescriptionRequestFocus) -> Unit, showChangeCoverBottomSheet: (BookInfoEvent.OnShowChangeCoverBottomSheet) -> Unit, - showMoreBottomSheet: (BookInfoEvent.OnShowMoreBottomSheet) -> Unit, - updateData: (BookInfoEvent.OnUpdateData) -> Unit, + showDetailsBottomSheet: (BookInfoEvent.OnShowDetailsBottomSheet) -> Unit, + showTitleDialog: (BookInfoEvent.OnShowTitleDialog) -> Unit, + showAuthorDialog: (BookInfoEvent.OnShowAuthorDialog) -> Unit, + showDescriptionDialog: (BookInfoEvent.OnShowDescriptionDialog) -> Unit, + showMoveDialog: (BookInfoEvent.OnShowMoveDialog) -> Unit, + showDeleteDialog: (BookInfoEvent.OnShowDeleteDialog) -> Unit, navigateToReader: () -> Unit, navigateBack: () -> Unit ) { @@ -51,49 +39,22 @@ fun BookInfoScaffold( BookInfoTopBar( book = book, listState = listState, - editTitle = editTitle, - titleValue = titleValue, - editAuthor = editAuthor, - authorValue = authorValue, - editDescription = editDescription, - descriptionValue = descriptionValue, - editTitleMode = editTitleMode, - editAuthorMode = editAuthorMode, - editDescriptionMode = editDescriptionMode, - updateData = updateData, - showMoreBottomSheet = showMoreBottomSheet, + showDetailsBottomSheet = showDetailsBottomSheet, navigateBack = navigateBack ) } ) { paddingValues -> - Box(Modifier.fillMaxSize()) { - BookInfoLayout( - book = book, - listState = listState, - paddingValues = paddingValues, - editTitle = editTitle, - titleValue = titleValue, - editAuthor = editAuthor, - authorValue = authorValue, - editDescription = editDescription, - descriptionValue = descriptionValue, - editTitleMode = editTitleMode, - editTitleValueChange = editTitleValueChange, - editTitleRequestFocus = editTitleRequestFocus, - editAuthorMode = editAuthorMode, - editAuthorValueChange = editAuthorValueChange, - editAuthorRequestFocus = editAuthorRequestFocus, - editDescriptionMode = editDescriptionMode, - editDescriptionValueChange = editDescriptionValueChange, - editDescriptionRequestFocus = editDescriptionRequestFocus, - showChangeCoverBottomSheet = showChangeCoverBottomSheet - ) - - BookInfoFloatingActionButton( - book = book, - listState = listState, - navigateToReader = navigateToReader - ) - } + BookInfoLayout( + book = book, + listState = listState, + paddingValues = paddingValues, + showTitleDialog = showTitleDialog, + showAuthorDialog = showAuthorDialog, + showDescriptionDialog = showDescriptionDialog, + showChangeCoverBottomSheet = showChangeCoverBottomSheet, + showMoveDialog = showMoveDialog, + showDeleteDialog = showDeleteDialog, + navigateToReader = navigateToReader + ) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoTitleDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoTitleDialog.kt new file mode 100644 index 00000000..644dd645 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoTitleDialog.kt @@ -0,0 +1,32 @@ +package ua.acclorite.book_story.presentation.book_info + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import ua.acclorite.book_story.domain.library.book.Book +import ua.acclorite.book_story.presentation.core.components.dialog.DialogWithTextField +import ua.acclorite.book_story.ui.book_info.BookInfoEvent + +@Composable +fun BookInfoTitleDialog( + book: Book, + actionTitleDialog: (BookInfoEvent.OnActionTitleDialog) -> Unit, + dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit +) { + val context = LocalContext.current + DialogWithTextField( + initialValue = book.title, + lengthLimit = 100, + onDismiss = { + dismissDialog(BookInfoEvent.OnDismissDialog) + }, + onAction = { + if (it.isBlank()) return@DialogWithTextField + actionTitleDialog( + BookInfoEvent.OnActionTitleDialog( + title = it.trim().replace("\n", ""), + context = context + ) + ) + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoTopBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoTopBar.kt index a55e9a51..9dc575bd 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoTopBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoTopBar.kt @@ -1,12 +1,8 @@ package ua.acclorite.book_story.presentation.book_info -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.ArrowBack -import androidx.compose.material.icons.outlined.Done +import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -16,32 +12,19 @@ import androidx.compose.runtime.remember import androidx.compose.ui.text.style.TextOverflow import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.library.book.Book -import ua.acclorite.book_story.presentation.core.components.common.AnimatedVisibility import ua.acclorite.book_story.presentation.core.components.common.IconButton import ua.acclorite.book_story.presentation.core.components.top_bar.TopAppBar import ua.acclorite.book_story.presentation.core.components.top_bar.TopAppBarData import ua.acclorite.book_story.presentation.navigator.NavigatorBackIconButton -import ua.acclorite.book_story.presentation.navigator.NavigatorIconButton import ua.acclorite.book_story.ui.book_info.BookInfoEvent import ua.acclorite.book_story.ui.theme.DefaultTransition -import ua.acclorite.book_story.ui.theme.Transitions @OptIn(ExperimentalMaterial3Api::class) @Composable fun BookInfoTopBar( book: Book, listState: LazyListState, - editTitle: Boolean, - titleValue: String, - editAuthor: Boolean, - authorValue: String, - editDescription: Boolean, - descriptionValue: String, - editTitleMode: (BookInfoEvent.OnEditTitleMode) -> Unit, - editAuthorMode: (BookInfoEvent.OnEditAuthorMode) -> Unit, - editDescriptionMode: (BookInfoEvent.OnEditDescriptionMode) -> Unit, - updateData: (BookInfoEvent.OnUpdateData) -> Unit, - showMoreBottomSheet: (BookInfoEvent.OnShowMoreBottomSheet) -> Unit, + showDetailsBottomSheet: (BookInfoEvent.OnShowDetailsBottomSheet) -> Unit, navigateBack: () -> Unit ) { val firstVisibleItemIndex = remember { @@ -50,12 +33,6 @@ fun BookInfoTopBar( } } - val titleChanged = titleValue.isNotBlank() - && titleValue.trim() != book.title.trim() - val authorChanged = authorValue.isNotBlank() - && authorValue.trim() != book.author.getAsString()?.trim() - val descriptionChanged = descriptionValue.trim() != (book.description?.trim() ?: "") - TopAppBar( containerColor = MaterialTheme.colorScheme.surface.copy(0f), scrollBehavior = null, @@ -66,36 +43,12 @@ fun BookInfoTopBar( TopAppBarData( contentID = 0, contentNavigationIcon = { - if ( - editTitle || - editAuthor || - editDescription - ) { - IconButton( - icon = Icons.AutoMirrored.Outlined.ArrowBack, - contentDescription = R.string.exit_editing_content_desc, - disableOnClick = true - ) { - if (editTitle) { - editTitleMode(BookInfoEvent.OnEditTitleMode(false)) - } - - if (editAuthor) { - editAuthorMode(BookInfoEvent.OnEditAuthorMode(false)) - } - - if (editDescription) { - editDescriptionMode(BookInfoEvent.OnEditDescriptionMode(false)) - } - } - } else { - NavigatorBackIconButton { - navigateBack() - } + NavigatorBackIconButton { + navigateBack() } }, contentTitle = { - DefaultTransition(firstVisibleItemIndex.value > 0 && !editTitle) { + DefaultTransition(firstVisibleItemIndex.value > 0) { Text( book.title, style = MaterialTheme.typography.titleLarge, @@ -106,41 +59,14 @@ fun BookInfoTopBar( } }, contentActions = { - Box { - DefaultTransition( - visible = !editTitle && - !editAuthor && - !editDescription - ) { - NavigatorIconButton { - showMoreBottomSheet(BookInfoEvent.OnShowMoreBottomSheet) - } + IconButton( + icon = Icons.Outlined.Info, + contentDescription = R.string.file_details, + disableOnClick = false, + onClick = { + showDetailsBottomSheet(BookInfoEvent.OnShowDetailsBottomSheet) } - AnimatedVisibility( - visible = editTitle || - editAuthor || - editDescription, - enter = Transitions.DefaultTransitionIn, - exit = fadeOut(tween(200)) - ) { - IconButton( - icon = Icons.Outlined.Done, - contentDescription = R.string.apply_changes_content_desc, - disableOnClick = true, - enabled = titleChanged || - authorChanged || - descriptionChanged, - color = if ( - titleChanged || - authorChanged || - descriptionChanged - ) MaterialTheme.colorScheme.onSurface - else MaterialTheme.colorScheme.onSurfaceVariant - ) { - updateData(BookInfoEvent.OnUpdateData) - } - } - } + ) } ) ) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/core/components/dialog/DialogWithTextField.kt b/app/src/main/java/ua/acclorite/book_story/presentation/core/components/dialog/DialogWithTextField.kt index cd853bf7..524fccf9 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/core/components/dialog/DialogWithTextField.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/core/components/dialog/DialogWithTextField.kt @@ -2,7 +2,7 @@ package ua.acclorite.book_story.presentation.core.components.dialog import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults @@ -16,19 +16,18 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.ui.UIText -import ua.acclorite.book_story.ui.theme.ExpandingTransition @Composable fun DialogWithTextField( initialValue: String, lengthLimit: Int = Int.MAX_VALUE, onDismiss: () -> Unit, - onAction: (String) -> UIText? + onAction: (String) -> Unit ) { val state = remember(initialValue) { mutableStateOf( @@ -41,9 +40,6 @@ fun DialogWithTextField( val focusRequester = remember { FocusRequester() } val focused = remember { mutableStateOf(false) } - val showError = remember { mutableStateOf(false) } - val error = remember { mutableStateOf(null) } - Dialog( title = stringResource(id = R.string.edit), description = null, @@ -51,14 +47,13 @@ fun DialogWithTextField( actionEnabled = true, disableOnClick = false, onAction = { - if (state.value.text.isBlank()) { + if (state.value.text == initialValue) { onDismiss() return@Dialog } - error.value = onAction(state.value.text) - showError.value = error.value != null - if (!showError.value) onDismiss() + onAction(state.value.text) + onDismiss() }, withContent = true, items = { @@ -68,10 +63,9 @@ fun DialogWithTextField( onValueChange = { if (it.text.length < lengthLimit || it.text.length < state.value.text.length) { state.value = it - showError.value = false } }, - isError = showError.value, + keyboardOptions = KeyboardOptions(KeyboardCapitalization.Sentences), modifier = Modifier .padding(horizontal = 24.dp) .fillMaxWidth() @@ -95,17 +89,6 @@ fun DialogWithTextField( maxLines = 1, overflow = TextOverflow.Ellipsis ) - }, - supportingText = { - ExpandingTransition( - visible = showError.value && error.value != null - ) { - Text( - error.value!!.asString(), - color = MaterialTheme.colorScheme.error - ) - } - } ) } diff --git a/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoEvent.kt b/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoEvent.kt index 663cc1aa..8a333647 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoEvent.kt @@ -3,54 +3,13 @@ package ua.acclorite.book_story.ui.book_info import android.content.Context import android.net.Uri import androidx.compose.runtime.Immutable -import androidx.compose.ui.focus.FocusRequester import ua.acclorite.book_story.domain.library.category.Category +import ua.acclorite.book_story.domain.ui.UIText @Immutable sealed class BookInfoEvent { - data class OnCopyToClipboard( - val text: String, - val context: Context, - ) : BookInfoEvent() - - data class OnEditTitleMode( - val edit: Boolean - ) : BookInfoEvent() - - data class OnEditTitleRequestFocus( - val focusRequester: FocusRequester - ) : BookInfoEvent() - - data class OnEditTitleValueChange( - val value: String - ) : BookInfoEvent() - - data class OnEditAuthorMode( - val edit: Boolean - ) : BookInfoEvent() - - data class OnEditAuthorRequestFocus( - val focusRequester: FocusRequester - ) : BookInfoEvent() - - data class OnEditAuthorValueChange( - val value: String - ) : BookInfoEvent() - - data class OnEditDescriptionMode( - val edit: Boolean - ) : BookInfoEvent() - - data class OnEditDescriptionRequestFocus( - val focusRequester: FocusRequester - ) : BookInfoEvent() - - data class OnEditDescriptionValueChange( - val value: String - ) : BookInfoEvent() - - data object OnUpdateData : BookInfoEvent() + data object OnShowDetailsBottomSheet : BookInfoEvent() data object OnShowChangeCoverBottomSheet : BookInfoEvent() @@ -69,12 +28,29 @@ sealed class BookInfoEvent { data object OnCheckCoverReset : BookInfoEvent() - data object OnShowMoreBottomSheet : BookInfoEvent() - - data object OnShowDetailsBottomSheet : BookInfoEvent() - data object OnDismissBottomSheet : BookInfoEvent() + data object OnShowTitleDialog : BookInfoEvent() + + data class OnActionTitleDialog( + val title: String, + val context: Context + ) : BookInfoEvent() + + data object OnShowAuthorDialog : BookInfoEvent() + + data class OnActionAuthorDialog( + val author: UIText, + val context: Context + ) : BookInfoEvent() + + data object OnShowDescriptionDialog : BookInfoEvent() + + data class OnActionDescriptionDialog( + val description: String?, + val context: Context + ) : BookInfoEvent() + data object OnShowDeleteDialog : BookInfoEvent() data class OnActionDeleteDialog( diff --git a/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoModel.kt index 5b1f1ff7..b309bf6e 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoModel.kt @@ -1,10 +1,6 @@ package ua.acclorite.book_story.ui.book_info -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context.CLIPBOARD_SERVICE import android.graphics.BitmapFactory -import android.os.Build import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -21,7 +17,6 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.library.category.Category -import ua.acclorite.book_story.domain.ui.UIText import ua.acclorite.book_story.domain.use_case.book.CanResetCover import ua.acclorite.book_story.domain.use_case.book.DeleteBooks import ua.acclorite.book_story.domain.use_case.book.GetBookById @@ -56,181 +51,14 @@ class BookInfoModel @Inject constructor( fun onEvent(event: BookInfoEvent) { viewModelScope.launch(eventJob + Dispatchers.Main) { when (event) { - is BookInfoEvent.OnCopyToClipboard -> { - val clipboardManager = - event.context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager - - clipboardManager.setPrimaryClip(ClipData.newPlainText(null, event.text)) - - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { - withContext(Dispatchers.Main) { - event.context.getString(R.string.copied) - .showToast(context = event.context, longToast = false) - } - } - } - - is BookInfoEvent.OnEditTitleMode -> { - launch(Dispatchers.IO) { - if (event.edit) { - _state.update { - it.copy( - titleValue = it.book.title, - hasTitleFocused = false - ) - } - } - - _state.update { - it.copy( - editTitle = event.edit - ) - } - } - } - - is BookInfoEvent.OnEditTitleRequestFocus -> { - if (!_state.value.hasTitleFocused) { - event.focusRequester.requestFocus() - _state.update { - it.copy( - hasTitleFocused = true - ) - } - } - } - - is BookInfoEvent.OnEditTitleValueChange -> { + is BookInfoEvent.OnShowDetailsBottomSheet -> { _state.update { it.copy( - titleValue = event.value + bottomSheet = BookInfoScreen.DETAILS_BOTTOM_SHEET ) } } - is BookInfoEvent.OnEditAuthorMode -> { - launch(Dispatchers.IO) { - if (event.edit) { - _state.update { - it.copy( - authorValue = it.book.author.getAsString() ?: "", - hasAuthorFocused = false - ) - } - } - - _state.update { - it.copy( - editAuthor = event.edit - ) - } - } - } - - is BookInfoEvent.OnEditAuthorRequestFocus -> { - if (!_state.value.hasAuthorFocused) { - event.focusRequester.requestFocus() - _state.update { - it.copy( - hasAuthorFocused = true - ) - } - } - } - - is BookInfoEvent.OnEditAuthorValueChange -> { - _state.update { - it.copy( - authorValue = event.value - ) - } - } - - is BookInfoEvent.OnEditDescriptionMode -> { - launch(Dispatchers.IO) { - if (event.edit) { - _state.update { - it.copy( - descriptionValue = it.book.description ?: "", - hasDescriptionFocused = false - ) - } - } - - _state.update { - it.copy( - editDescription = event.edit - ) - } - } - } - - is BookInfoEvent.OnEditDescriptionRequestFocus -> { - if (!_state.value.hasDescriptionFocused) { - event.focusRequester.requestFocus() - _state.update { - it.copy( - hasDescriptionFocused = true - ) - } - } - } - - is BookInfoEvent.OnEditDescriptionValueChange -> { - _state.update { - it.copy( - descriptionValue = event.value - ) - } - } - - is BookInfoEvent.OnUpdateData -> { - launch { - val title = _state.value.titleValue.trim().replace("\n", "") - val author = _state.value.authorValue.trim().replace("\n", "") - val description = _state.value.descriptionValue.trim().replace("\n", "") - - val titleChanged = title != _state.value.book.title - && _state.value.editTitle - && title.isNotBlank() - val authorChanged = author != _state.value.book.author.getAsString() - && _state.value.editAuthor - && author.isNotBlank() - val descriptionChanged = description != _state.value.book.description - && _state.value.editDescription - - val book = _state.value.book.copy( - title = if (titleChanged) title else _state.value.book.title, - author = if (authorChanged) UIText.StringValue(author) - else _state.value.book.author, - description = if (descriptionChanged) description - else _state.value.book.description - ) - - updateBook.execute(book) - _state.update { - it.copy( - book = book - ) - } - - LibraryScreen.refreshListChannel.trySend(0) - HistoryScreen.refreshListChannel.trySend(0) - - if (_state.value.editTitle) { - onEvent(BookInfoEvent.OnEditTitleMode(false)) - } - - if (_state.value.editAuthor) { - onEvent(BookInfoEvent.OnEditAuthorMode(false)) - } - - if (_state.value.editDescription) { - onEvent(BookInfoEvent.OnEditDescriptionMode(false)) - } - } - } - is BookInfoEvent.OnShowChangeCoverBottomSheet -> { _state.update { it.copy( @@ -357,22 +185,6 @@ class BookInfoModel @Inject constructor( } } - is BookInfoEvent.OnShowMoreBottomSheet -> { - _state.update { - it.copy( - bottomSheet = BookInfoScreen.MORE_BOTTOM_SHEET - ) - } - } - - is BookInfoEvent.OnShowDetailsBottomSheet -> { - _state.update { - it.copy( - bottomSheet = BookInfoScreen.DETAILS_BOTTOM_SHEET - ) - } - } - is BookInfoEvent.OnDismissBottomSheet -> { _state.update { it.copy( @@ -381,6 +193,93 @@ class BookInfoModel @Inject constructor( } } + is BookInfoEvent.OnShowTitleDialog -> { + _state.update { + it.copy( + dialog = BookInfoScreen.TITLE_DIALOG + ) + } + } + + is BookInfoEvent.OnActionTitleDialog -> { + launch { + _state.update { + it.copy( + book = it.book.copy( + title = event.title + ) + ) + } + updateBook.execute(_state.value.book) + + LibraryScreen.refreshListChannel.trySend(0) + HistoryScreen.refreshListChannel.trySend(0) + + withContext(Dispatchers.Main) { + event.context.getString(R.string.title_changed) + .showToast(context = event.context) + } + } + } + + is BookInfoEvent.OnShowAuthorDialog -> { + _state.update { + it.copy( + dialog = BookInfoScreen.AUTHOR_DIALOG + ) + } + } + + is BookInfoEvent.OnActionAuthorDialog -> { + launch { + _state.update { + it.copy( + book = it.book.copy( + author = event.author + ) + ) + } + updateBook.execute(_state.value.book) + + LibraryScreen.refreshListChannel.trySend(0) + HistoryScreen.refreshListChannel.trySend(0) + + withContext(Dispatchers.Main) { + event.context.getString(R.string.author_changed) + .showToast(context = event.context) + } + } + } + + is BookInfoEvent.OnShowDescriptionDialog -> { + _state.update { + it.copy( + dialog = BookInfoScreen.DESCRIPTION_DIALOG + ) + } + } + + is BookInfoEvent.OnActionDescriptionDialog -> { + launch { + _state.update { + it.copy( + book = it.book.copy( + description = event.description + ) + ) + } + updateBook.execute(_state.value.book) + + LibraryScreen.refreshListChannel.trySend(0) + HistoryScreen.refreshListChannel.trySend(0) + + withContext(Dispatchers.Main) { + event.context.getString(R.string.description_changed) + .showToast(context = event.context) + } + } + } + is BookInfoEvent.OnShowDeleteDialog -> { _state.update { it.copy( diff --git a/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoScreen.kt b/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoScreen.kt index bea6baed..6282f9b7 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoScreen.kt @@ -1,11 +1,14 @@ package ua.acclorite.book_story.ui.book_info import android.os.Parcelable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.parcelize.Parcelize @@ -22,9 +25,11 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable { companion object { const val DELETE_DIALOG = "delete_dialog" const val MOVE_DIALOG = "move_dialog" + const val TITLE_DIALOG = "title_dialog" + const val AUTHOR_DIALOG = "author_dialog" + const val DESCRIPTION_DIALOG = "description_dialog" const val CHANGE_COVER_BOTTOM_SHEET = "change_cover_bottom_sheet" - const val MORE_BOTTOM_SHEET = "more_bottom_sheet" const val DETAILS_BOTTOM_SHEET = "details_bottom_sheet" } @@ -52,58 +57,50 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable { } } - BookInfoContent( - book = state.value.book, - bottomSheet = state.value.bottomSheet, - dialog = state.value.dialog, - listState = listState, - canResetCover = state.value.canResetCover, - editTitle = state.value.editTitle, - titleValue = state.value.titleValue, - editAuthor = state.value.editAuthor, - authorValue = state.value.authorValue, - editDescription = state.value.editDescription, - descriptionValue = state.value.descriptionValue, - editTitleMode = screenModel::onEvent, - editTitleValueChange = screenModel::onEvent, - editTitleRequestFocus = screenModel::onEvent, - editAuthorMode = screenModel::onEvent, - editAuthorValueChange = screenModel::onEvent, - editAuthorRequestFocus = screenModel::onEvent, - editDescriptionMode = screenModel::onEvent, - editDescriptionValueChange = screenModel::onEvent, - editDescriptionRequestFocus = screenModel::onEvent, - checkCoverReset = screenModel::onEvent, - changeCover = screenModel::onEvent, - resetCover = screenModel::onEvent, - deleteCover = screenModel::onEvent, - dismissBottomSheet = screenModel::onEvent, - dismissDialog = screenModel::onEvent, - showChangeCoverBottomSheet = screenModel::onEvent, - showDetailsBottomSheet = screenModel::onEvent, - showMoreBottomSheet = screenModel::onEvent, - showMoveDialog = screenModel::onEvent, - actionMoveDialog = screenModel::onEvent, - showDeleteDialog = screenModel::onEvent, - actionDeleteDialog = screenModel::onEvent, - updateData = screenModel::onEvent, - copyToClipboard = screenModel::onEvent, - navigateToReader = { - if (state.value.book.id != -1) { - HistoryScreen.insertHistoryChannel.trySend(state.value.book.id) - navigator.push(ReaderScreen(state.value.book.id)) + Box(Modifier.fillMaxSize()) + + if (state.value.book.id == bookId) { + BookInfoContent( + book = state.value.book, + bottomSheet = state.value.bottomSheet, + dialog = state.value.dialog, + listState = listState, + canResetCover = state.value.canResetCover, + showTitleDialog = screenModel::onEvent, + actionTitleDialog = screenModel::onEvent, + showAuthorDialog = screenModel::onEvent, + actionAuthorDialog = screenModel::onEvent, + showDescriptionDialog = screenModel::onEvent, + actionDescriptionDialog = screenModel::onEvent, + checkCoverReset = screenModel::onEvent, + changeCover = screenModel::onEvent, + resetCover = screenModel::onEvent, + deleteCover = screenModel::onEvent, + dismissBottomSheet = screenModel::onEvent, + dismissDialog = screenModel::onEvent, + showChangeCoverBottomSheet = screenModel::onEvent, + showDetailsBottomSheet = screenModel::onEvent, + showMoveDialog = screenModel::onEvent, + actionMoveDialog = screenModel::onEvent, + showDeleteDialog = screenModel::onEvent, + actionDeleteDialog = screenModel::onEvent, + navigateToReader = { + if (state.value.book.id != -1) { + HistoryScreen.insertHistoryChannel.trySend(state.value.book.id) + navigator.push(ReaderScreen(state.value.book.id)) + } + }, + navigateToLibrary = { + navigator.push( + LibraryScreen, + popping = true, + saveInBackStack = false + ) + }, + navigateBack = { + navigator.pop() } - }, - navigateToLibrary = { - navigator.push( - LibraryScreen, - popping = true, - saveInBackStack = false - ) - }, - navigateBack = { - navigator.pop() - } - ) + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoState.kt b/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoState.kt index 7e2a68b0..7ac8b70d 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoState.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoState.kt @@ -11,18 +11,6 @@ import ua.acclorite.book_story.presentation.core.constants.provideEmptyBook data class BookInfoState( val book: Book = Constants.provideEmptyBook(), - val editTitle: Boolean = false, - val hasTitleFocused: Boolean = false, - val titleValue: String = "", - - val editAuthor: Boolean = false, - val hasAuthorFocused: Boolean = false, - val authorValue: String = "", - - val editDescription: Boolean = false, - val hasDescriptionFocused: Boolean = false, - val descriptionValue: String = "", - val canResetCover: Boolean = false, val dialog: Dialog? = null, diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index ed60a7aa..9bd2492b 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -43,7 +43,6 @@ أحتاج المساعدة أضف أضف كتابا˝ - ابدأ استمرار أبدا˝ عودة @@ -168,10 +167,6 @@ إدارة التحديثات الجديدة ابدأ المركز - نقل - حذف - التفاصيل - اسم الملف مسار الملف انقر للتحقق من وجود تحديثات الإبلاغ عن خطأ diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 57783472..7ec24a8e 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -22,7 +22,6 @@ Pluto Čtěte vaše oblíbené knihy a třiďte je do kategorií Stáhnout - Název souboru Jazyk aplikace Saturn Ano, přejít na pomoc @@ -54,7 +53,6 @@ Fonty Standardní Abecedně - Detaily Cesta k souboru Všechny vybrané knihy byly úspěšně přidány. Tato kniha byla úspěšně přesunuta. @@ -115,7 +113,6 @@ Smazat Zelená Zkopírováno - Smazat Celá obrazovka Hledat aktualizace Překlad na dvojité kliknutí @@ -153,7 +150,6 @@ Něco se pokazilo. Povolit Přidat knihu - Začít Zpět Kopírovat Přeložit @@ -201,7 +197,6 @@ Ganymede Obálka byla úspěšně změněna. Historie byla úspěšně smazána. - Přesunout Sláva Ukrajině! Design Překlad diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7a5ecf6d..70092d10 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -36,7 +36,6 @@ Hinzufügen Buch hinzufügen Verschieben - Starten Fortfahren Niemals Zurück gehen @@ -109,10 +108,7 @@ Die neuste Version der App ist bereits installiert. Das Titelbild wurde erfolgreich zurückgesetzt. Das Verlaufselement wurde erfolgreich gelöscht. - Verschieben - Löschen Quecksilber - Details Dateipfad Zuletzt geöffnete Datei Dateigröße @@ -203,7 +199,6 @@ Hoch Normal Kursiv - Dateiname Unbekannt Klicken Sie hier, um nach Updates zu suchen Was ist neu diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index cbcf9d29..d531d3c8 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -95,7 +95,6 @@ Expanda la colección de libros de la biblioteca con sus descargas Lea sus libros favoritos y ordénelos en categorías Deje que su último libro leído abra las páginas de una nueva gran historia en su viaje de lectura - Iniciar Traducir Actualizar %1$s Conceder permiso @@ -158,8 +157,6 @@ Nombrar un preajuste… Este libro se movió correctamente. El elemento del historial se eliminó correctamente. - Detalles - Nombre del archivo Último archivo abierto Tamaño del archivo Desconocido @@ -180,8 +177,6 @@ Biblioteca y haz clic en la imagen de la portada del libro para ver los detalles del libro. Ahora puedes mantener presionado el título, el autor o la descripción del libro para editarlo o la imagen de la portada para cambiarla, restablecerla o eliminarla. ¿Cómo personalizo el lector? - Mover - Borrar Ruta del archivo Navegar Novedades diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index cf1fa81e..66e80c18 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -34,7 +34,6 @@ Sélectionné « %1$s » Déplacer Supprimer - Commencer Continuer Jamais Retour @@ -268,10 +267,6 @@ Désactivé Format de fichier Alphabétiquement - Déplacer - Supprimer - Détails - Nom du fichier Version de l\'application Design Traduction diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 8480dfc7..7ef890fa 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -108,7 +108,6 @@ प्रीसेट %1$s हटाना मिटाना - शुरू कवर छवि रीसेट करें कवर छवि बदलें डार्क थीम @@ -205,9 +204,6 @@ लाल ऐप का नवीनतम संस्करण पहले से इंस्टॉल है। कवर छवि सफलतापूर्वक रीसेट कर दी गई। - कदम - मिटाना - विवरण दस्तावेज पथ फ़ाइल अंतिम बार खोली गई फ़ाइल का साइज़ @@ -235,7 +231,6 @@ ब्राउज़ पुस्तकालय इतिहास तत्व सफलतापूर्वक हटा दिया गया। - फ़ाइल का नाम अज्ञात एक बग रिपोर्ट करो नए अपडेट का प्रबंधन diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c1649096..31c7844f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -21,7 +21,6 @@ Schermo intero Sistema Griglia - Sposta Nessun capitolo Capitoli Licenze open source @@ -47,7 +46,6 @@ Marte Prossimo Elimina - Inizia Continua Indietro Abilita @@ -101,8 +99,6 @@ Formato file Tutti i libri selezionati sono stati eliminati correttamente. Ritratto - Elimina - Dettagli Tutti i libri selezionati sono stati aggiunti correttamente. Eliminerà questo libro dal database. Non eliminerà questo libro dal tuo dispositivo. Puoi scegliere quali libri vuoi aggiungere. È possibile modificare il titolo e l\'immagine di copertina in seguito. Il processo di caricamento può richiedere fino a diversi minuti. @@ -208,7 +204,6 @@ per riga La cronologia è stata eliminata correttamente. Premere di nuovo Indietro per uscire - Nome del file Dimensione del file Sfoglia Già letto diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 3e252c61..1333c852 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -35,7 +35,6 @@ Anuluj Udziel Usuń - Rozpocznij Dodaj Sortuj Czerwony @@ -179,7 +178,6 @@ Idź do Większość przycisków ikon ma podpowiedzi, mogą ci pomóc, jeśli nie wiesz, co robi przycisk ikon. Aby wyświetlić podpowiedź, możesz przytrzymać przycisk ikony. Skopiowano - Nazwa pliku Ścieżka pliku Zarządzanie nowymi aktualizacjami Czcionki @@ -189,7 +187,6 @@ Możesz to zrobić z poziomu ekranu Biblioteka, Historia i Przeglądaj, klikając trzy kropki w prawym górnym rogu i wybierając « Ustawienia » z menu rozwijanego. Następnie zobaczysz wszystkie dostępne ustawienia tej aplikacji. Eksperymentuj z nimi! Kontynuuj czytanie Profil GitHub - Przenieś Zacznij czytać książkę (patrz: Jak mogę przeczytać książkę?), A następnie przynieś górne i dolne paski, klikając w dowolnym miejscu na ekranie. Następnie kliknij ikonę zębatki w prawym górnym rogu. Spowoduje to wyświetlenie wszystkich ustawień Czytnika. Drugim sposobem jest przejście do Nazwij ustawienie… Rozmiar siatki @@ -265,12 +262,10 @@ Kliknij, aby sprawdzić aktualizacje Jeśli nie widzisz pobranych książek, spróbuj odświeżyć listę, przeciągając ją w dół. Upewnij się, że Twoje książki posiadają obsługiwany format pliku. Następnie kliknij książkę, aby ją wybrać lub przytrzymaj, aby pokazać jej lokalizację. Po wybraniu wszystkich książek, które chcesz, kliknij ikonę znacznika wyboru w prawym górnym rogu, poczekaj na załadowanie wszystkich książek i kliknij « Dodaj ». Teraz powinieneś zobaczyć wszystkie książki, które dodałeś do Historia czytania - Usuń Opuść wyszukiwanie Wszystkie wybrane książki zostały pomyślnie przeniesione. Usuń całą historię Przeglądaj - Szczegóły Biblioteka Idź do Ustawienia diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 560d15fd..2e8ac4b7 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -60,7 +60,6 @@ Novo update do Book\'s Story! Adicionar livros? Precisamos de permissão de acesso ao armazenamento para procurar livros no seu dispositivo. Sem essa permissão, você não será capaz de adicionar um livro. - Começar Apagar Desfazer Organizar @@ -92,8 +91,6 @@ Plutão Vênus Urano - Mover - Apague Desconhecido Créditos Capítulos @@ -119,7 +116,6 @@ Escolha a categoria da qual você quer mover todos os livros selecionados(%1$s). Todos os livros selecionados serão movidos para essa categoria. Isto irá apagar todos os livros selecionados(%1$s) da base de dados. Isso não irá apagar os livros do seu dispositivo. Centro - Detalhes Configurações Reverter Permitir @@ -142,7 +138,6 @@ Isto irá deletar todo o histórico da base de dados. Após deletá-lo, a ordem dos livros será restaurada. Recuo de parágrafo Espaçamento das letras - Nome do arquivo Padrão %1$s Voltar Proceder diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 0c49ce1f..64050b13 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -50,7 +50,6 @@ இந்த நூல் வெற்றிகரமாக நீக்கப்பட்டது. இந்த நூல் வெற்றிகரமாக நகர்த்தப்பட்டது. நகலெடுக்கப்பட்டது - நகர்த்தவும் வரலாற்று உறுப்பு வெற்றிகரமாக நீக்கப்பட்டது. மொழிபெயர்ப்பு பங்களிப்பு @@ -101,12 +100,10 @@ தொடரவும் நீங்கள் படித்த உங்கள் கடைசி நூல் உங்கள் வாசிப்பு பயணத்தின் புதிய சிறந்த கதையின் பக்கங்களைத் திறக்கட்டும் பங்கு - தொடங்கு அனுமதிகள் ஒருபோதும் காட்சி முறை கோப்பு வடிவம் - நீக்கு இயல்புநிலை வாசிப்பு வரலாற்றை நீக்கவா? செயல்படுத்துவதன் மூலம் «புதுப்பிப்புகளைச் சரிபார்க்கவும்» பயன்பாடு தொடங்கும் ஒவ்வொரு முறையும் «api.github.com இருந்து இலிருந்து அண்மைக் கால வெளியீட்டு தகவல்களை மீட்டெடுக்கும். நீங்கள் தொடர விரும்புகிறீர்களா «புதுப்பிப்புகளை சரிபார்க்கவும்»? @@ -273,8 +270,6 @@ வரலாறு வெற்றிகரமாக நீக்கப்பட்டது. பயன்பாட்டின் அண்மைக் கால பதிப்பு ஏற்கனவே நிறுவப்பட்டுள்ளது. கவர் படம் வெற்றிகரமாக மீட்டமைக்கப்பட்டது. - விவரங்கள் - கோப்பு பெயர் கோப்பு பாதை கோப்பு கடைசியாக திறக்கப்பட்டது கோப்பு அளவு diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index f05d486e..9066c349 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -53,7 +53,6 @@ Kitap ekle Taşı Sil - Başla Devam Et Asla Geri Dön @@ -180,10 +179,6 @@ Uygulamanın en son sürümü zaten yüklü. Kapak resmi başarıyla sıfırlandı. Geçmiş öğesi başarıyla silindi. - Taşı - Sil - Ayrıntılar - Dosya adı Dosya yolu Son açılma tarihi Dosya boyutu diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 5d3bcae7..df2959e6 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -50,6 +50,7 @@ Щось пішло не так. Спробуйте пізніше. Не вдалося скинути обкладинку. Закритий код + Файла не існує Пошук… @@ -72,7 +73,6 @@ Додати книгу Перемістити Видалити - Почати Продовжити Ніколи Назад @@ -88,6 +88,7 @@ Веб-пошук Поділитися Продовжити + ОК Невідомий @@ -320,20 +321,21 @@ Історія була успішно видалена. Остання версія застосунка вже встановлена. Обкладинка була успішно скинута. + Назва була успішно змінена. + Автор був успішно змінений. + Опис був успішно змінений. Елемент історії був успішно видалений. - Перемістити - Видалити - Подробиці - - - Ім\'я файлу - Шлях до файлу - Останнє відкриття файлу - Розмір файлу + + Почати + Продовжити з %1$s + Подробиці файла + Шлях + Останнє відкриття + Розмір Невідомо @@ -475,5 +477,8 @@ Розділи Більше Закріпити + Показати менше + Показати більше + Редагувати поле \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 5b485dd2..54ef05c2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -74,7 +74,6 @@ 翻译 复制 已选择 %1$s - 开始 启用检查更新? 删除书籍? 新更新 @@ -204,8 +203,6 @@ 成功更改了封面图片。 成功删除了历史元素。 成功移动所有已选择的书。 - 移动 - 文件名 无章节 章节 应用版本 @@ -231,10 +228,8 @@ 翻译 如果你没有看见已下载的书可以尝试下拉刷新列表。确保你的书拥有所支持的文件格式。点击书来选择它或者按住来显示它的位置。当你选择了所有想选择的书之后,点击右上角的对勾图标,等待所有的书加载好后点击“添加”。所有你已经添加的书都应该显示在 文件大小 - 详细 新内容 开源许可证 - 删除 保存 自定义亮度 亮度 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a158be80..ce90429b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -98,6 +98,7 @@ Could not reset cover image. Closed source + File does not exist @@ -128,7 +129,6 @@ Add a book Move Delete - Start Continue Never Go back @@ -379,20 +379,20 @@ History was successfully deleted. The latest version of the app is already installed. Cover image was successfully reset. + Title was successfully changed. + Author was successfully changed. + Description was successfully changed. History element was successfully deleted. - - Move - Delete - Details - - - File name - File path - File last opened - File size + + Start + Continue from %1$s + File details + Path + Last opened + Size Unknown @@ -637,5 +637,8 @@ Chapters More Pin + Show less + Show more + Edit field \ No newline at end of file