diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt index 8dec9d11..8463cd87 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt @@ -16,7 +16,7 @@ class BookMapperImpl @Inject constructor() : BookMapper { scrollIndex = book.scrollIndex, scrollOffset = book.scrollOffset, progress = book.progress, - author = book.author.string, + author = book.author.getAsString(), textPath = book.textPath, description = book.description, image = if (book.coverImage != null) book.coverImage.toString() else null, diff --git a/app/src/main/java/ua/acclorite/book_story/domain/util/UIText.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/UIText.kt index 0c396d5f..9acc0778 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/util/UIText.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/util/UIText.kt @@ -11,9 +11,9 @@ import java.io.Serializable @Parcelize @Immutable -sealed class UIText(val string: String?) : Parcelable { - data class StringValue(val value: String) : UIText(value) - class StringResource(@StringRes val resId: Int, vararg val args: Serializable) : UIText(null) +sealed class UIText : Parcelable { + data class StringValue(val value: String) : UIText() + class StringResource(@StringRes val resId: Int, vararg val args: Serializable) : UIText() @Composable fun asString(): String { @@ -29,4 +29,11 @@ sealed class UIText(val string: String?) : Parcelable { is StringResource -> context.getString(resId, *args) } } + + fun getAsString(): String? { + return when (this) { + is StringValue -> value + is StringResource -> null + } + } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt index 44fcfb51..b56a4c59 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt @@ -1,8 +1,6 @@ package ua.acclorite.book_story.presentation.screens.book_info -import android.widget.Toast import androidx.activity.compose.BackHandler -import androidx.compose.animation.core.tween import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -15,6 +13,7 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -24,10 +23,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Done import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState @@ -44,32 +40,25 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.util.OnNavigate -import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility -import ua.acclorite.book_story.presentation.components.CustomIconButton import ua.acclorite.book_story.presentation.components.CustomSnackbar -import ua.acclorite.book_story.presentation.components.GoBackButton import ua.acclorite.book_story.presentation.data.LocalNavigator import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoBackground import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoDescriptionSection import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoInfoSection -import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoMoreDropDown import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoStatisticSection +import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoTopBar import ua.acclorite.book_story.presentation.screens.book_info.components.change_cover_bottom_sheet.BookInfoChangeCoverBottomSheet import ua.acclorite.book_story.presentation.screens.book_info.components.confirm_update_dialog.BookInfoConfirmUpdateDialog import ua.acclorite.book_story.presentation.screens.book_info.components.details_bottom_sheet.BookInfoDetailsBottomSheet @@ -84,8 +73,6 @@ import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel -import ua.acclorite.book_story.presentation.ui.DefaultTransition -import ua.acclorite.book_story.presentation.ui.Transitions @Composable fun BookInfoScreenRoot(screen: Screen.BookInfo) { @@ -140,7 +127,6 @@ private fun BookInfoScreen( ) } ) - val firstVisibleItemIndex by remember { derivedStateOf { listState.firstVisibleItemIndex } } if (state.value.showChangeCoverBottomSheet) { BookInfoChangeCoverBottomSheet( @@ -185,101 +171,21 @@ private fun BookInfoScreen( Scaffold( Modifier .fillMaxSize() + .imePadding() .windowInsetsPadding(WindowInsets.navigationBars) .pullRefresh(refreshState) .nestedScroll(scrollBehavior.nestedScrollConnection), containerColor = MaterialTheme.colorScheme.surface, topBar = { - AnimatedTopAppBar( - containerColor = Color.Transparent, + BookInfoTopBar( + state = state, + onEvent = onEvent, + onLibraryEvent = onLibraryEvent, + onHistoryEvent = onHistoryEvent, + onNavigate = onNavigate, scrollBehavior = scrollBehavior, - isTopBarScrolled = null, - - content1NavigationIcon = { - if (state.value.editTitle) { - CustomIconButton( - icon = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = R.string.hide_edit_title_content_desc, - disableOnClick = true - ) { - onEvent(BookInfoEvent.OnShowHideEditTitle) - } - } else { - GoBackButton(onNavigate = onNavigate, enabled = !state.value.isRefreshing) { - onEvent(BookInfoEvent.OnCancelUpdate) - } - } - }, - content1Title = { - DefaultTransition(visible = firstVisibleItemIndex > 0) { - Text( - state.value.book.title, - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface, - overflow = TextOverflow.Ellipsis, - maxLines = 1 - ) - } - }, - content1Actions = { - CustomIconButton( - icon = Icons.Default.Refresh, - contentDescription = R.string.refresh_book_content_desc, - disableOnClick = false, - enabled = !state.value.isRefreshing && !state.value.isLoadingUpdate - ) { - onEvent( - BookInfoEvent.OnLoadUpdate( - snackbarState, - context - ) - ) - } - - Box { - DefaultTransition( - visible = !state.value.editTitle - ) { - BookInfoMoreDropDown( - state = state, - onEvent = onEvent, - snackbarState = snackbarState - ) - } - CustomAnimatedVisibility( - visible = state.value.editTitle, - enter = Transitions.DefaultTransitionIn, - exit = fadeOut(tween(200)) - ) { - CustomIconButton( - icon = Icons.Default.Done, - contentDescription = R.string.apply_changes_content_desc, - disableOnClick = true, - enabled = !state.value.isRefreshing && - state.value.titleValue.isNotBlank() && - state.value.titleValue.trim() != - state.value.book.title.trim(), - color = if (state.value.titleValue.isNotBlank() - && state.value.titleValue != state.value.book.title - ) MaterialTheme.colorScheme.onSurface - else MaterialTheme.colorScheme.onSurfaceVariant - ) { - onEvent( - BookInfoEvent.OnUpdateTitle( - refreshList = { - onLibraryEvent(LibraryEvent.OnUpdateBook(it)) - onHistoryEvent(HistoryEvent.OnUpdateBook(it)) - } - )) - Toast.makeText( - context, - context.getString(R.string.title_changed), - Toast.LENGTH_LONG - ).show() - } - } - } - } + listState = listState, + snackbarState = snackbarState ) }, bottomBar = { @@ -324,7 +230,10 @@ private fun BookInfoScreen( item { Spacer(modifier = Modifier.height(24.dp)) // Description - BookInfoDescriptionSection(state = state) + BookInfoDescriptionSection( + state = state, + onEvent = onEvent + ) } } @@ -382,8 +291,24 @@ private fun BookInfoScreen( } BackHandler { + var exitedEditMode = false + if (state.value.editTitle) { onEvent(BookInfoEvent.OnShowHideEditTitle) + exitedEditMode = true + } + + if (state.value.editAuthor) { + onEvent(BookInfoEvent.OnShowHideEditAuthor) + exitedEditMode = true + } + + if (state.value.editDescription) { + onEvent(BookInfoEvent.OnShowHideEditDescription) + exitedEditMode = true + } + + if (exitedEditMode) { return@BackHandler } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDescriptionSection.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDescriptionSection.kt index 1ff0e56b..6279b310 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDescriptionSection.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDescriptionSection.kt @@ -1,34 +1,93 @@ package ua.acclorite.book_story.presentation.screens.book_info.components +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box 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.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.State +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.res.stringResource +import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R +import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState /** * Description section. + * + * @param state [BookInfoState]. + * @param onEvent [BookInfoEvent] callback. */ +@OptIn(ExperimentalFoundationApi::class) @Composable -fun BookInfoDescriptionSection(state: State) { - SelectionContainer { +fun BookInfoDescriptionSection( + state: State, + onEvent: (BookInfoEvent) -> Unit +) { + val descriptionFocusRequester = remember { FocusRequester() } + + if (!state.value.editDescription) { Text( if (state.value.book.description?.isNotBlank() == true) state.value.book.description!! else stringResource(id = R.string.error_no_description), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurface, textAlign = TextAlign.Start, - modifier = Modifier.padding(horizontal = 24.dp) + modifier = Modifier + .padding(horizontal = 24.dp) + .combinedClickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = {}, + onLongClick = { + onEvent(BookInfoEvent.OnShowHideEditDescription) + } + ), ) + } else { + BasicTextField( + value = state.value.descriptionValue, + textStyle = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onSurface + ), + onValueChange = { + if (it.length < 5000 || it.length < state.value.authorValue.length) { + onEvent(BookInfoEvent.OnDescriptionValueChange(it)) + } + }, + modifier = Modifier + .padding(horizontal = 24.dp) + .focusRequester(descriptionFocusRequester) + .onGloballyPositioned { + onEvent(BookInfoEvent.OnDescriptionRequestFocus(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)) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt index 082fa423..e9bcb05a 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt @@ -18,7 +18,6 @@ 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.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Image import androidx.compose.material.icons.outlined.Person @@ -56,7 +55,8 @@ fun BookInfoInfoSection( state: State, onEvent: (BookInfoEvent) -> Unit ) { - val focusRequester = remember { FocusRequester() } + val titleFocusRequester = remember { FocusRequester() } + val authorFocusRequester = remember { FocusRequester() } Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier @@ -135,9 +135,9 @@ fun BookInfoInfoSection( } }, modifier = Modifier - .focusRequester(focusRequester) + .focusRequester(titleFocusRequester) .onGloballyPositioned { - onEvent(BookInfoEvent.OnRequestFocus(focusRequester)) + onEvent(BookInfoEvent.OnTitleRequestFocus(titleFocusRequester)) }, keyboardOptions = KeyboardOptions(KeyboardCapitalization.Words), cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant) @@ -160,19 +160,54 @@ fun BookInfoInfoSection( tint = MaterialTheme.colorScheme.onSurfaceVariant ) Spacer(modifier = Modifier.width(4.dp)) - SelectionContainer { + + if (!state.value.editAuthor) { Text( state.value.book.author.asString(), color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyLarge, modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .combinedClickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = {}, + onLongClick = { + onEvent(BookInfoEvent.OnShowHideEditAuthor) + } + ), maxLines = 1, overflow = TextOverflow.Ellipsis ) + } else { + BasicTextField( + singleLine = true, + value = state.value.authorValue, + textStyle = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant + ), + onValueChange = { + if (it.length < 50 || it.length < state.value.authorValue.length) { + onEvent(BookInfoEvent.OnAuthorValueChange(it)) + } + }, + modifier = Modifier + .focusRequester(authorFocusRequester) + .onGloballyPositioned { + onEvent(BookInfoEvent.OnAuthorRequestFocus(authorFocusRequester)) + }, + keyboardOptions = KeyboardOptions(KeyboardCapitalization.Words), + cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant) + ) { innerText -> + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.CenterStart + ) { + innerText() + } + } } } - } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoTopBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoTopBar.kt new file mode 100644 index 00000000..300a531d --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoTopBar.kt @@ -0,0 +1,188 @@ +package ua.acclorite.book_story.presentation.screens.book_info.components + +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.filled.ArrowBack +import androidx.compose.material.icons.filled.Done +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextOverflow +import ua.acclorite.book_story.R +import ua.acclorite.book_story.domain.util.OnNavigate +import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar +import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility +import ua.acclorite.book_story.presentation.components.CustomIconButton +import ua.acclorite.book_story.presentation.components.GoBackButton +import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent +import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState +import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent +import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent +import ua.acclorite.book_story.presentation.ui.DefaultTransition +import ua.acclorite.book_story.presentation.ui.Transitions + +/** + * Book Info Top Bar. + * + * @param state [BookInfoState]. + * @param onEvent [BookInfoEvent] callback. + * @param onLibraryEvent [LibraryEvent] callback. + * @param onHistoryEvent [HistoryEvent] callback. + * @param onNavigate Navigator callback. + * @param scrollBehavior [TopAppBarScrollBehavior]. + * @param listState [LazyListState]. + * @param snackbarState [SnackbarHostState]. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BookInfoTopBar( + state: State, + onEvent: (BookInfoEvent) -> Unit, + onLibraryEvent: (LibraryEvent) -> Unit, + onHistoryEvent: (HistoryEvent) -> Unit, + onNavigate: OnNavigate, + scrollBehavior: TopAppBarScrollBehavior, + listState: LazyListState, + snackbarState: SnackbarHostState +) { + val context = LocalContext.current + val firstVisibleItemIndex by remember { derivedStateOf { listState.firstVisibleItemIndex } } + + AnimatedTopAppBar( + containerColor = Color.Transparent, + scrollBehavior = scrollBehavior, + isTopBarScrolled = null, + + content1NavigationIcon = { + if ( + state.value.editTitle || + state.value.editAuthor || + state.value.editDescription + ) { + CustomIconButton( + icon = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = R.string.exit_editing_content_desc, + disableOnClick = true + ) { + if (state.value.editTitle) { + onEvent(BookInfoEvent.OnShowHideEditTitle) + } + + if (state.value.editAuthor) { + onEvent(BookInfoEvent.OnShowHideEditAuthor) + } + + if (state.value.editDescription) { + onEvent(BookInfoEvent.OnShowHideEditDescription) + } + } + } else { + GoBackButton(onNavigate = onNavigate, enabled = !state.value.isRefreshing) { + onEvent(BookInfoEvent.OnCancelUpdate) + } + } + }, + content1Title = { + DefaultTransition( + firstVisibleItemIndex > 0 && !state.value.editTitle + ) { + Text( + state.value.book.title, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } + }, + content1Actions = { + CustomIconButton( + icon = Icons.Default.Refresh, + contentDescription = R.string.refresh_book_content_desc, + disableOnClick = false, + enabled = !state.value.isRefreshing && !state.value.isLoadingUpdate + ) { + onEvent( + BookInfoEvent.OnLoadUpdate( + snackbarState, + context + ) + ) + } + + Box { + DefaultTransition( + visible = !state.value.editTitle && + !state.value.editAuthor && + !state.value.editDescription + ) { + BookInfoMoreDropDown( + state = state, + onEvent = onEvent, + snackbarState = snackbarState + ) + } + CustomAnimatedVisibility( + visible = state.value.editTitle || + state.value.editAuthor || + state.value.editDescription, + enter = Transitions.DefaultTransitionIn, + exit = fadeOut(tween(200)) + ) { + CustomIconButton( + icon = Icons.Default.Done, + contentDescription = R.string.apply_changes_content_desc, + disableOnClick = true, + enabled = !state.value.isRefreshing && + (state.value.titleValue.isNotBlank() && + state.value.titleValue.trim() != + state.value.book.title.trim()) || + + (state.value.authorValue.isNotBlank() && + state.value.authorValue.trim() != + state.value.book.author.getAsString()?.trim()) || + + (state.value.descriptionValue.isNotBlank() && + state.value.descriptionValue.trim() != + state.value.book.description?.trim()), + + color = if ((state.value.titleValue.isNotBlank() && + state.value.titleValue.trim() != + state.value.book.title.trim()) || + + (state.value.authorValue.isNotBlank() && + state.value.authorValue.trim() != + state.value.book.author.getAsString()?.trim()) || + + (state.value.descriptionValue.isNotBlank() && + state.value.descriptionValue.trim() != + state.value.book.description?.trim()) + ) MaterialTheme.colorScheme.onSurface + else MaterialTheme.colorScheme.onSurfaceVariant + ) { + onEvent( + BookInfoEvent.OnUpdateData( + refreshList = { + onLibraryEvent(LibraryEvent.OnUpdateBook(it)) + onHistoryEvent(HistoryEvent.OnUpdateBook(it)) + } + )) + } + } + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt index 8363ff54..4833b12e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt @@ -23,10 +23,21 @@ sealed class BookInfoEvent { val refreshList: (Book) -> Unit, val showResult: (UIText) -> Unit ) : BookInfoEvent() + data object OnShowHideEditTitle : BookInfoEvent() - data class OnRequestFocus(val focusRequester: FocusRequester) : BookInfoEvent() + data class OnTitleRequestFocus(val focusRequester: FocusRequester) : BookInfoEvent() data class OnTitleValueChange(val value: String) : BookInfoEvent() - data class OnUpdateTitle(val refreshList: (Book) -> Unit) : BookInfoEvent() + + data object OnShowHideEditAuthor : BookInfoEvent() + data class OnAuthorRequestFocus(val focusRequester: FocusRequester) : BookInfoEvent() + data class OnAuthorValueChange(val value: String) : BookInfoEvent() + + data object OnShowHideEditDescription : BookInfoEvent() + data class OnDescriptionRequestFocus(val focusRequester: FocusRequester) : BookInfoEvent() + data class OnDescriptionValueChange(val value: String) : BookInfoEvent() + + data class OnUpdateData(val refreshList: (Book) -> Unit) : BookInfoEvent() + data object OnShowHideDeleteDialog : BookInfoEvent() data object OnShowHideMoveDialog : BookInfoEvent() data class OnDeleteBook(val refreshList: () -> Unit, val onNavigate: OnNavigate) : diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt index 1bba16b1..8544c4b0 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt @@ -18,9 +18,17 @@ data class BookInfoState( val textChanged: Boolean = false, val editTitle: Boolean = false, - val hasFocused: 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 showChangeCoverBottomSheet: Boolean = false, val canResetCover: Boolean = false, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt index 753c7523..a45b6eb2 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt @@ -165,7 +165,7 @@ class BookInfoViewModel @Inject constructor( _state.update { it.copy( titleValue = it.book.title, - hasFocused = false + hasTitleFocused = false ) } } @@ -178,12 +178,12 @@ class BookInfoViewModel @Inject constructor( } } - is BookInfoEvent.OnRequestFocus -> { - if (!_state.value.hasFocused) { + is BookInfoEvent.OnTitleRequestFocus -> { + if (!_state.value.hasTitleFocused) { event.focusRequester.requestFocus() _state.update { it.copy( - hasFocused = true + hasTitleFocused = true ) } } @@ -197,25 +197,129 @@ class BookInfoViewModel @Inject constructor( } } - is BookInfoEvent.OnUpdateTitle -> { - viewModelScope.launch { - val title = _state.value.titleValue.trim().replace("\n", "") + is BookInfoEvent.OnShowHideEditAuthor -> { + viewModelScope.launch(Dispatchers.IO) { + val shouldHide = _state.value.editAuthor + + if (!shouldHide) { + _state.update { + it.copy( + authorValue = it.book.author.getAsString() ?: "", + hasAuthorFocused = false + ) + } + } - updateBooks.execute( - listOf( - _state.value.book.copy(title = title) - ) - ) _state.update { it.copy( - book = it.book.copy( - title = title + editAuthor = !shouldHide + ) + } + } + } + + is BookInfoEvent.OnAuthorRequestFocus -> { + if (!_state.value.hasAuthorFocused) { + event.focusRequester.requestFocus() + _state.update { + it.copy( + hasAuthorFocused = true + ) + } + } + } + + is BookInfoEvent.OnAuthorValueChange -> { + _state.update { + it.copy( + authorValue = event.value + ) + } + } + + is BookInfoEvent.OnShowHideEditDescription -> { + viewModelScope.launch(Dispatchers.IO) { + val shouldHide = _state.value.editDescription + + if (!shouldHide) { + _state.update { + it.copy( + descriptionValue = it.book.description ?: "", + hasDescriptionFocused = false ) + } + } + + _state.update { + it.copy( + editDescription = !shouldHide + ) + } + } + } + + is BookInfoEvent.OnDescriptionRequestFocus -> { + if (!_state.value.hasDescriptionFocused) { + event.focusRequester.requestFocus() + _state.update { + it.copy( + hasDescriptionFocused = true + ) + } + } + } + + is BookInfoEvent.OnDescriptionValueChange -> { + _state.update { + it.copy( + descriptionValue = event.value + ) + } + } + + is BookInfoEvent.OnUpdateData -> { + viewModelScope.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 + && description.isNotBlank() + + 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 + ) + + updateBooks.execute(listOf(book)) + _state.update { + it.copy( + book = book ) } event.refreshList(_state.value.book) - onEvent(BookInfoEvent.OnShowHideEditTitle) + if (state.value.editTitle) { + onEvent(BookInfoEvent.OnShowHideEditTitle) + } + + if (state.value.editAuthor) { + onEvent(BookInfoEvent.OnShowHideEditAuthor) + } + + if (state.value.editDescription) { + onEvent(BookInfoEvent.OnShowHideEditDescription) + } } } @@ -346,7 +450,9 @@ class BookInfoViewModel @Inject constructor( _state.update { it.copy( isLoadingUpdate = true, - editTitle = false + editTitle = false, + editAuthor = false, + editDescription = false ) } @@ -708,10 +814,10 @@ class BookInfoViewModel @Inject constructor( _state.update { BookInfoState( - book = book, - canResetCover = canResetCover.execute(screen.bookId) + book = book ) } + onEvent(BookInfoEvent.OnCheckCoverReset) } } } \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index b046aacd..ce52c7cd 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -575,7 +575,7 @@ Анімація стрілки Необхідно Скинути початковий посібник - Вийти з редагування назви + Вийти з режиму редагування Відкрити в браузері Перекладач Поміняти мови місцями diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b6b6e9d7..b17d3f20 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -596,7 +596,7 @@ Arrow animation Required Reset start guide - Exit title editing + Exit editing mode Open in web Translator Change language places