🚀 BookInfo UI update

* Updated UI of BookInfo screen
* Dialogs to update fields (title, author and description)
* Simplified updating mechanism
This commit is contained in:
Acclorite 2025-01-27 19:19:02 +02:00
parent f232cd3688
commit cf4cb62061
44 changed files with 968 additions and 1253 deletions

View file

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

View file

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

View file

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

View file

@ -33,7 +33,6 @@ fun BookInfoChangeCoverBottomSheet(
dismissBottomSheet: (BookInfoEvent.OnDismissBottomSheet) -> Unit
) {
val context = LocalContext.current
val photoPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickVisualMedia(),
onResult = { uri ->

View file

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

View file

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

View file

@ -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(
label = stringResource(id = R.string.file_path),
text = book.filePath,
context = context
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
)
)
}
}
}
}
}

View file

@ -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 = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
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)
}
)
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.Start
if (editable) {
IconButton(
icon = Icons.Default.EditNote,
contentDescription = R.string.edit_content_desc,
disableOnClick = false,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(24.dp)
) {
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
)
onEdit()
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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) {
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
)
)
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
.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
)
)
}
) {
Text(
if (book.description?.isNotBlank() == true) book.description
if (!book.description.isNullOrBlank()) 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))
}
),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
} 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))
}
},
modifier = Modifier
.padding(horizontal = 24.dp)
.focusRequester(descriptionFocusRequester)
.onGloballyPositioned {
editDescriptionRequestFocus(
BookInfoEvent.OnEditDescriptionRequestFocus(
descriptionFocusRequester
)
)
},
keyboardOptions = KeyboardOptions(KeyboardCapitalization.Sentences),
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant)
) { innerText ->
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.CenterStart
) {
innerText()
}
}
}
Spacer(modifier = Modifier.height(96.dp))
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
)
}
}
}

View file

@ -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(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 18.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp)
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)
BookInfoLayoutInfoTitle(
book = book,
showTitleDialog = showTitleDialog
)
} 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
)
}
}
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
BookInfoLayoutInfoProgress(
book = book
)
} 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()
}
}
}
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,
showTitleDialog = showTitleDialog,
showAuthorDialog = showAuthorDialog,
showDescriptionDialog = showDescriptionDialog,
showChangeCoverBottomSheet = showChangeCoverBottomSheet,
showMoveDialog = showMoveDialog,
showDeleteDialog = showDeleteDialog,
navigateToReader = navigateToReader
)
}
}
}

View file

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

View file

@ -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()
}
}
},
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)
}
}
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)
}
}
icon = Icons.Outlined.Info,
contentDescription = R.string.file_details,
disableOnClick = false,
onClick = {
showDetailsBottomSheet(BookInfoEvent.OnShowDetailsBottomSheet)
}
)
}
)
)

View file

@ -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<UIText?>(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
)
}
}
)
}

View file

@ -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(

View file

@ -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) {
is BookInfoEvent.OnShowDetailsBottomSheet -> {
_state.update {
it.copy(
titleValue = it.book.title,
hasTitleFocused = false
bottomSheet = BookInfoScreen.DETAILS_BOTTOM_SHEET
)
}
}
_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 -> {
_state.update {
it.copy(
titleValue = event.value
)
}
}
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(

View file

@ -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,27 +57,21 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
}
}
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,
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,
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,
@ -81,13 +80,10 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
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)
@ -106,4 +102,5 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
}
)
}
}
}

View file

@ -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,

View file

@ -43,7 +43,6 @@
<string name="get_help">أحتاج المساعدة</string>
<string name="add">أضف</string>
<string name="add_book">أضف كتابا˝</string>
<string name="start">ابدأ</string>
<string name="continue_read">استمرار</string>
<string name="never">أبدا˝</string>
<string name="go_back">عودة</string>
@ -168,10 +167,6 @@
<string name="credits_updates">إدارة التحديثات الجديدة</string>
<string name="alignment_start">ابدأ</string>
<string name="alignment_center">المركز</string>
<string name="move_this_book">نقل</string>
<string name="delete_this_book">حذف</string>
<string name="details_book">التفاصيل</string>
<string name="file_name">اسم الملف</string>
<string name="file_path">مسار الملف</string>
<string name="app_version_option_desc_2">انقر للتحقق من وجود تحديثات</string>
<string name="report_bug_option">الإبلاغ عن خطأ</string>

View file

@ -22,7 +22,6 @@
<string name="marsh_theme">Pluto</string>
<string name="library_empty">Čtěte vaše oblíbené knihy a třiďte je do kategorií</string>
<string name="updates_notification_action">Stáhnout</string>
<string name="file_name">Název souboru</string>
<string name="language_option">Jazyk aplikace</string>
<string name="pink_theme">Saturn</string>
<string name="yes_go_to_help">Ano, přejít na pomoc</string>
@ -54,7 +53,6 @@
<string name="credits_fonts">Fonty</string>
<string name="theme_contrast_standard">Standardní</string>
<string name="browse_sort_order_name">Abecedně</string>
<string name="details_book">Detaily</string>
<string name="file_path">Cesta k souboru</string>
<string name="books_added">Všechny vybrané knihy byly úspěšně přidány.</string>
<string name="book_moved">Tato kniha byla úspěšně přesunuta.</string>
@ -115,7 +113,6 @@
<string name="delete">Smazat</string>
<string name="green_color">Zelená</string>
<string name="copied">Zkopírováno</string>
<string name="delete_this_book">Smazat</string>
<string name="fullscreen_option">Celá obrazovka</string>
<string name="check_for_updates_option">Hledat aktualizace</string>
<string name="double_click_translation_option">Překlad na dvojité kliknutí</string>
@ -153,7 +150,6 @@
<string name="error_something_went_wrong">Něco se pokazilo.</string>
<string name="grant">Povolit</string>
<string name="add_book">Přidat knihu</string>
<string name="start">Začít</string>
<string name="go_back">Zpět</string>
<string name="copy">Kopírovat</string>
<string name="translate">Přeložit</string>
@ -201,7 +197,6 @@
<string name="pink2_theme">Ganymede</string>
<string name="cover_image_changed">Obálka byla úspěšně změněna.</string>
<string name="history_deleted">Historie byla úspěšně smazána.</string>
<string name="move_this_book">Přesunout</string>
<string name="slava_ukraini">Sláva Ukrajině!</string>
<string name="credits_design">Design</string>
<string name="credits_translation">Překlad</string>

View file

@ -36,7 +36,6 @@
<string name="add">Hinzufügen</string>
<string name="add_book">Buch hinzufügen</string>
<string name="move">Verschieben</string>
<string name="start">Starten</string>
<string name="continue_read">Fortfahren</string>
<string name="never">Niemals</string>
<string name="go_back">Zurück gehen</string>
@ -109,10 +108,7 @@
<string name="no_updates">Die neuste Version der App ist bereits installiert.</string>
<string name="cover_reset">Das Titelbild wurde erfolgreich zurückgesetzt.</string>
<string name="history_element_deleted">Das Verlaufselement wurde erfolgreich gelöscht.</string>
<string name="move_this_book">Verschieben</string>
<string name="delete_this_book">Löschen</string>
<string name="dynamic_theme">Quecksilber</string>
<string name="details_book">Details</string>
<string name="file_path">Dateipfad</string>
<string name="file_last_opened">Zuletzt geöffnete Datei</string>
<string name="file_size">Dateigröße</string>
@ -203,7 +199,6 @@
<string name="theme_contrast_high">Hoch</string>
<string name="font_style_normal">Normal</string>
<string name="font_style_italic">Kursiv</string>
<string name="file_name">Dateiname</string>
<string name="unknown">Unbekannt</string>
<string name="app_version_option_desc_2">Klicken Sie hier, um nach Updates zu suchen</string>
<string name="whats_new_option">Was ist neu</string>

View file

@ -95,7 +95,6 @@
<string name="browse_empty">Expanda la colección de libros de la biblioteca con sus descargas</string>
<string name="library_empty">Lea sus libros favoritos y ordénelos en categorías</string>
<string name="history_empty">Deje que su último libro leído abra las páginas de una nueva gran historia en su viaje de lectura</string>
<string name="start">Iniciar</string>
<string name="translate">Traducir</string>
<string name="update_query">Actualizar %1$s</string>
<string name="grant_permission">Conceder permiso</string>
@ -158,8 +157,6 @@
<string name="color_preset_placeholder">Nombrar un preajuste…</string>
<string name="book_moved">Este libro se movió correctamente.</string>
<string name="history_element_deleted">El elemento del historial se eliminó correctamente.</string>
<string name="details_book">Detalles</string>
<string name="file_name">Nombre del archivo</string>
<string name="file_last_opened">Último archivo abierto</string>
<string name="file_size">Tamaño del archivo</string>
<string name="unknown">Desconocido</string>
@ -180,8 +177,6 @@
<string name="help_desc_how_to_edit_book_2">Biblioteca</string>
<string name="help_desc_how_to_edit_book_3">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.</string>
<string name="help_title_how_to_customize_reader">¿Cómo personalizo el lector?</string>
<string name="move_this_book">Mover</string>
<string name="delete_this_book">Borrar</string>
<string name="file_path">Ruta del archivo</string>
<string name="help_desc_how_to_add_books_2">Navegar</string>
<string name="whats_new_option">Novedades</string>

View file

@ -34,7 +34,6 @@
<string name="color_preset_selected_query">Sélectionné « %1$s »</string>
<string name="move">Déplacer</string>
<string name="delete">Supprimer</string>
<string name="start">Commencer</string>
<string name="continue_read">Continuer</string>
<string name="never">Jamais</string>
<string name="go_back">Retour</string>
@ -268,10 +267,6 @@
<string name="pure_dark_off">Désactivé</string>
<string name="browse_sort_order_file_format">Format de fichier</string>
<string name="browse_sort_order_name">Alphabétiquement</string>
<string name="move_this_book">Déplacer</string>
<string name="delete_this_book">Supprimer</string>
<string name="details_book">Détails</string>
<string name="file_name">Nom du fichier</string>
<string name="app_version_option">Version de l\'application</string>
<string name="credits_design">Design</string>
<string name="credits_translation">Traduction</string>

View file

@ -108,7 +108,6 @@
<string name="color_preset_query">प्रीसेट %1$s</string>
<string name="move">हटाना</string>
<string name="delete">मिटाना</string>
<string name="start">शुरू</string>
<string name="reset_cover">कवर छवि रीसेट करें</string>
<string name="change_cover">कवर छवि बदलें</string>
<string name="dark_theme_option">डार्क थीम</string>
@ -205,9 +204,6 @@
<string name="red_color">लाल</string>
<string name="no_updates">ऐप का नवीनतम संस्करण पहले से इंस्टॉल है।</string>
<string name="cover_reset">कवर छवि सफलतापूर्वक रीसेट कर दी गई।</string>
<string name="move_this_book">कदम</string>
<string name="delete_this_book">मिटाना</string>
<string name="details_book">विवरण</string>
<string name="file_path">दस्तावेज पथ</string>
<string name="file_last_opened">फ़ाइल अंतिम बार खोली गई</string>
<string name="file_size">फ़ाइल का साइज़</string>
@ -235,7 +231,6 @@
<string name="help_desc_how_to_add_books_2">ब्राउज़</string>
<string name="help_desc_how_to_add_books_4">पुस्तकालय</string>
<string name="history_element_deleted">इतिहास तत्व सफलतापूर्वक हटा दिया गया।</string>
<string name="file_name">फ़ाइल का नाम</string>
<string name="unknown">अज्ञात</string>
<string name="report_bug_option">एक बग रिपोर्ट करो</string>
<string name="credits_updates">नए अपडेट का प्रबंधन</string>

View file

@ -21,7 +21,6 @@
<string name="fullscreen_option">Schermo intero</string>
<string name="dark_theme_follow_system">Sistema</string>
<string name="browse_layout_grid">Griglia</string>
<string name="move_this_book">Sposta</string>
<string name="no_chapters">Nessun capitolo</string>
<string name="chapters">Capitoli</string>
<string name="licenses_option">Licenze open source</string>
@ -47,7 +46,6 @@
<string name="red_theme">Marte</string>
<string name="next">Prossimo</string>
<string name="delete">Elimina</string>
<string name="start">Inizia</string>
<string name="continue_read">Continua</string>
<string name="go_back">Indietro</string>
<string name="enable">Abilita</string>
@ -101,8 +99,6 @@
<string name="browse_sort_order_file_format">Formato file</string>
<string name="books_deleted">Tutti i libri selezionati sono stati eliminati correttamente.</string>
<string name="screen_orientation_portrait">Ritratto</string>
<string name="delete_this_book">Elimina</string>
<string name="details_book">Dettagli</string>
<string name="books_added">Tutti i libri selezionati sono stati aggiunti correttamente.</string>
<string name="delete_book_description">Eliminerà questo libro dal database. Non eliminerà questo libro dal tuo dispositivo.</string>
<string name="add_books_description">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.</string>
@ -208,7 +204,6 @@
<string name="browse_grid_size_per_row">per riga</string>
<string name="history_deleted">La cronologia è stata eliminata correttamente.</string>
<string name="press_again_toast">Premere di nuovo Indietro per uscire</string>
<string name="file_name">Nome del file</string>
<string name="file_size">Dimensione del file</string>
<string name="help_desc_how_to_add_books_2">Sfoglia</string>
<string name="already_read_tab">Già letto</string>

View file

@ -35,7 +35,6 @@
<string name="cancel">Anuluj</string>
<string name="grant">Udziel</string>
<string name="delete">Usuń</string>
<string name="start">Rozpocznij</string>
<string name="add">Dodaj</string>
<string name="sort_browse_settings">Sortuj</string>
<string name="red_color">Czerwony</string>
@ -179,7 +178,6 @@
<string name="help_desc_how_to_read_book_1">Idź do</string>
<string name="help_desc_how_to_use_tooltip_1">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.</string>
<string name="copied">Skopiowano</string>
<string name="file_name">Nazwa pliku</string>
<string name="file_path">Ścieżka pliku</string>
<string name="credits_updates">Zarządzanie nowymi aktualizacjami</string>
<string name="credits_fonts">Czcionki</string>
@ -189,7 +187,6 @@
<string name="help_desc_how_to_customize_app_3">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!</string>
<string name="continue_reading_content_desc">Kontynuuj czytanie</string>
<string name="github_profile_content_desc">Profil GitHub</string>
<string name="move_this_book">Przenieś</string>
<string name="help_desc_how_to_customize_reader_1">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</string>
<string name="color_preset_placeholder">Nazwij ustawienie…</string>
<string name="browse_grid_size_option">Rozmiar siatki</string>
@ -265,12 +262,10 @@
<string name="app_version_option_desc_2">Kliknij, aby sprawdzić aktualizacje</string>
<string name="help_desc_how_to_add_books_3">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</string>
<string name="history_content_desc">Historia czytania</string>
<string name="delete_this_book">Usuń</string>
<string name="exit_search_content_desc">Opuść wyszukiwanie</string>
<string name="books_moved">Wszystkie wybrane książki zostały pomyślnie przeniesione.</string>
<string name="delete_whole_history_content_desc">Usuń całą historię</string>
<string name="help_desc_how_to_add_books_2">Przeglądaj</string>
<string name="details_book">Szczegóły</string>
<string name="help_desc_how_to_add_books_4">Biblioteka</string>
<string name="help_desc_how_to_customize_app_1">Idź do</string>
<string name="help_desc_how_to_customize_app_2">Ustawienia</string>

View file

@ -60,7 +60,6 @@
<string name="updates_notification_title">Novo update do Book\'s Story!</string>
<string name="add_books">Adicionar livros?</string>
<string name="storage_permission_description">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.</string>
<string name="start">Começar</string>
<string name="delete">Apagar</string>
<string name="undo">Desfazer</string>
<string name="sort_tab">Organizar</string>
@ -92,8 +91,6 @@
<string name="marsh_theme">Plutão</string>
<string name="yellow_theme">Vênus</string>
<string name="aqua_theme">Urano</string>
<string name="move_this_book">Mover</string>
<string name="delete_this_book">Apague</string>
<string name="unknown">Desconhecido</string>
<string name="credits_option">Créditos</string>
<string name="chapters">Capítulos</string>
@ -119,7 +116,6 @@
<string name="move_books_description">Escolha a categoria da qual você quer mover todos os livros selecionados(%1$s). Todos os livros selecionados serão movidos para essa categoria.</string>
<string name="delete_books_description">Isto irá apagar todos os livros selecionados(%1$s) da base de dados. Isso não irá apagar os livros do seu dispositivo.</string>
<string name="alignment_center">Centro</string>
<string name="details_book">Detalhes</string>
<string name="help_desc_how_to_customize_reader_2">Configurações</string>
<string name="revert_content_desc">Reverter</string>
<string name="start_permissions_granted">Permitir</string>
@ -142,7 +138,6 @@
<string name="delete_history_description">Isto irá deletar todo o histórico da base de dados. Após deletá-lo, a ordem dos livros será restaurada.</string>
<string name="paragraph_indentation_option">Recuo de parágrafo</string>
<string name="letter_spacing_option">Espaçamento das letras</string>
<string name="file_name">Nome do arquivo</string>
<string name="color_preset_query">Padrão %1$s</string>
<string name="go_back">Voltar</string>
<string name="proceed">Proceder</string>

View file

@ -50,7 +50,6 @@
<string name="book_deleted">இந்த நூல் வெற்றிகரமாக நீக்கப்பட்டது.</string>
<string name="book_moved">இந்த நூல் வெற்றிகரமாக நகர்த்தப்பட்டது.</string>
<string name="copied">நகலெடுக்கப்பட்டது</string>
<string name="move_this_book">நகர்த்தவும்</string>
<string name="history_element_deleted">வரலாற்று உறுப்பு வெற்றிகரமாக நீக்கப்பட்டது.</string>
<string name="credits_translation">மொழிபெயர்ப்பு</string>
<string name="credits_contribution">பங்களிப்பு</string>
@ -101,12 +100,10 @@
<string name="continue_read">தொடரவும்</string>
<string name="history_empty">நீங்கள் படித்த உங்கள் கடைசி நூல் உங்கள் வாசிப்பு பயணத்தின் புதிய சிறந்த கதையின் பக்கங்களைத் திறக்கட்டும்</string>
<string name="share">பங்கு</string>
<string name="start">தொடங்கு</string>
<string name="start_permissions_preferences">அனுமதிகள்</string>
<string name="never">ஒருபோதும்</string>
<string name="browse_layout_option">காட்சி முறை</string>
<string name="browse_sort_order_file_format">கோப்பு வடிவம்</string>
<string name="delete_this_book">நீக்கு</string>
<string name="default_string">இயல்புநிலை</string>
<string name="delete_history">வாசிப்பு வரலாற்றை நீக்கவா?</string>
<string name="enable_check_for_updates_description">செயல்படுத்துவதன் மூலம் «புதுப்பிப்புகளைச் சரிபார்க்கவும்» பயன்பாடு தொடங்கும் ஒவ்வொரு முறையும் «api.github.com இருந்து இலிருந்து அண்மைக் கால வெளியீட்டு தகவல்களை மீட்டெடுக்கும். நீங்கள் தொடர விரும்புகிறீர்களா «புதுப்பிப்புகளை சரிபார்க்கவும்»?</string>
@ -273,8 +270,6 @@
<string name="history_deleted">வரலாறு வெற்றிகரமாக நீக்கப்பட்டது.</string>
<string name="no_updates">பயன்பாட்டின் அண்மைக் கால பதிப்பு ஏற்கனவே நிறுவப்பட்டுள்ளது.</string>
<string name="cover_reset">கவர் படம் வெற்றிகரமாக மீட்டமைக்கப்பட்டது.</string>
<string name="details_book">விவரங்கள்</string>
<string name="file_name">கோப்பு பெயர்</string>
<string name="file_path">கோப்பு பாதை</string>
<string name="file_last_opened">கோப்பு கடைசியாக திறக்கப்பட்டது</string>
<string name="file_size">கோப்பு அளவு</string>

View file

@ -53,7 +53,6 @@
<string name="add_book">Kitap ekle</string>
<string name="move">Taşı</string>
<string name="delete">Sil</string>
<string name="start">Başla</string>
<string name="continue_read">Devam Et</string>
<string name="never">Asla</string>
<string name="go_back">Geri Dön</string>
@ -180,10 +179,6 @@
<string name="no_updates">Uygulamanın en son sürümü zaten yüklü.</string>
<string name="cover_reset">Kapak resmi başarıyla sıfırlandı.</string>
<string name="history_element_deleted">Geçmiş öğesi başarıyla silindi.</string>
<string name="move_this_book">Taşı</string>
<string name="delete_this_book">Sil</string>
<string name="details_book">Ayrıntılar</string>
<string name="file_name">Dosya adı</string>
<string name="file_path">Dosya yolu</string>
<string name="file_last_opened">Son açılma tarihi</string>
<string name="file_size">Dosya boyutu</string>

View file

@ -50,6 +50,7 @@
<string name="error_check_internet">Щось пішло не так. Спробуйте пізніше.</string>
<string name="error_could_not_reset_cover">Не вдалося скинути обкладинку.</string>
<string name="error_closed_source">Закритий код</string>
<string name="error_no_file">Файла не існує</string>
<!-- Is Empty messages -->
<string name="search_field_empty">Пошук…</string>
@ -72,7 +73,6 @@
<string name="add_book">Додати книгу</string>
<string name="move">Перемістити</string>
<string name="delete">Видалити</string>
<string name="start">Почати</string>
<string name="continue_read">Продовжити</string>
<string name="never">Ніколи</string>
<string name="go_back">Назад</string>
@ -88,6 +88,7 @@
<string name="web_search">Веб-пошук</string>
<string name="share">Поділитися</string>
<string name="proceed">Продовжити</string>
<string name="ok">ОК</string>
<!-- Book -->
<string name="unknown_author">Невідомий</string>
@ -320,20 +321,21 @@
<string name="history_deleted">Історія була успішно видалена.</string>
<string name="no_updates">Остання версія застосунка вже встановлена.</string>
<string name="cover_reset">Обкладинка була успішно скинута.</string>
<string name="title_changed">Назва була успішно змінена.</string>
<string name="author_changed">Автор був успішно змінений.</string>
<string name="description_changed">Опис був успішно змінений.</string>
<!-- Snackbar messages -->
<string name="history_element_deleted">Елемент історії був успішно видалений.</string>
<!-- DropDown properties -->
<string name="move_this_book">Перемістити</string>
<string name="delete_this_book">Видалити</string>
<string name="details_book">Подробиці</string>
<!-- BookInfo details -->
<string name="file_name">Ім\'я файлу</string>
<string name="file_path">Шлях до файлу</string>
<string name="file_last_opened">Останнє відкриття файлу</string>
<string name="file_size">Розмір файлу</string>
<!-- BookInfo -->
<string name="start_reading">Почати</string>
<string name="continue_reading_query">Продовжити з %1$s</string>
<string name="file_details">Подробиці файла</string>
<string name="file_path">Шлях</string>
<string name="file_last_opened">Останнє відкриття</string>
<string name="file_size">Розмір</string>
<string name="unknown">Невідомо</string>
<!-- Browse -->
@ -475,5 +477,8 @@
<string name="chapters_content_desc">Розділи</string>
<string name="more_content_desc">Більше</string>
<string name="pin_content_desc">Закріпити</string>
<string name="show_less_content_desc">Показати менше</string>
<string name="show_more_content_desc">Показати більше</string>
<string name="edit_content_desc">Редагувати поле</string>
</resources>

View file

@ -74,7 +74,6 @@
<string name="translate">翻译</string>
<string name="copy">复制</string>
<string name="selected_items_count_query">已选择 %1$s</string>
<string name="start">开始</string>
<string name="enable_check_for_updates">启用检查更新?</string>
<string name="delete_books">删除书籍?</string>
<string name="updates_notification_channel">新更新</string>
@ -204,8 +203,6 @@
<string name="cover_image_changed">成功更改了封面图片。</string>
<string name="history_element_deleted">成功删除了历史元素。</string>
<string name="books_moved">成功移动所有已选择的书。</string>
<string name="move_this_book">移动</string>
<string name="file_name">文件名</string>
<string name="no_chapters">无章节</string>
<string name="chapters">章节</string>
<string name="app_version_option">应用版本</string>
@ -231,10 +228,8 @@
<string name="credits_translation">翻译</string>
<string name="help_desc_how_to_add_books_3">如果你没有看见已下载的书可以尝试下拉刷新列表。确保你的书拥有所支持的文件格式。点击书来选择它或者按住来显示它的位置。当你选择了所有想选择的书之后,点击右上角的对勾图标,等待所有的书加载好后点击“添加”。所有你已经添加的书都应该显示在</string>
<string name="file_size">文件大小</string>
<string name="details_book">详细</string>
<string name="whats_new_option">新内容</string>
<string name="licenses_option">开源许可证</string>
<string name="delete_this_book">删除</string>
<string name="pure_dark_power_saver">保存</string>
<string name="custom_screen_brightness_option">自定义亮度</string>
<string name="screen_brightness_option">亮度</string>

View file

@ -98,6 +98,7 @@
</string>
<string name="error_could_not_reset_cover">Could not reset cover image.</string>
<string name="error_closed_source">Closed source</string>
<string name="error_no_file">File does not exist</string>
<!-- Is Empty messages -->
<string name="search_field_empty">
@ -128,7 +129,6 @@
<string name="add_book">Add a book</string>
<string name="move">Move</string>
<string name="delete">Delete</string>
<string name="start">Start</string>
<string name="continue_read">Continue</string>
<string name="never">Never</string>
<string name="go_back">Go back</string>
@ -379,20 +379,20 @@
<string name="history_deleted">History was successfully deleted.</string>
<string name="no_updates">The latest version of the app is already installed.</string>
<string name="cover_reset">Cover image was successfully reset.</string>
<string name="title_changed">Title was successfully changed.</string>
<string name="author_changed">Author was successfully changed.</string>
<string name="description_changed">Description was successfully changed.</string>
<!-- Snackbar messages -->
<string name="history_element_deleted">History element was successfully deleted.</string>
<!-- DropDown properties -->
<string name="move_this_book">Move</string>
<string name="delete_this_book">Delete</string>
<string name="details_book">Details</string>
<!-- BookInfo details -->
<string name="file_name">File name</string>
<string name="file_path">File path</string>
<string name="file_last_opened">File last opened</string>
<string name="file_size">File size</string>
<!-- BookInfo -->
<string name="start_reading">Start</string>
<string name="continue_reading_query">Continue from %1$s</string>
<string name="file_details">File details</string>
<string name="file_path">Path</string>
<string name="file_last_opened">Last opened</string>
<string name="file_size">Size</string>
<string name="unknown">Unknown</string>
<!-- Reader -->
@ -637,5 +637,8 @@
<string name="chapters_content_desc">Chapters</string>
<string name="more_content_desc">More</string>
<string name="pin_content_desc">Pin</string>
<string name="show_less_content_desc">Show less</string>
<string name="show_more_content_desc">Show more</string>
<string name="edit_content_desc">Edit field</string>
</resources>