BookInfoScreen: Editing Author and Description + Fixed slow loading (caused by BookInfoEvent.OnCheckCoverReset).

This commit is contained in:
acclorite 2024-07-06 13:42:16 +03:00
parent 45c1bd2b80
commit 936627e742
11 changed files with 483 additions and 144 deletions

View file

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

View file

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

View file

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

View file

@ -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<BookInfoState>) {
SelectionContainer {
fun BookInfoDescriptionSection(
state: State<BookInfoState>,
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))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -575,7 +575,7 @@
<string name="arrow_anim_content_desc">Анімація стрілки</string>
<string name="required_content_desc">Необхідно</string>
<string name="reset_start_content_desc">Скинути початковий посібник</string>
<string name="hide_edit_title_content_desc">Вийти з редагування назви</string>
<string name="exit_editing_content_desc">Вийти з режиму редагування</string>
<string name="open_in_web_content_desc">Відкрити в браузері</string>
<string name="translator_content_desc">Перекладач</string>
<string name="translator_language_content_desc">Поміняти мови місцями</string>

View file

@ -596,7 +596,7 @@
<string name="arrow_anim_content_desc">Arrow animation</string>
<string name="required_content_desc">Required</string>
<string name="reset_start_content_desc">Reset start guide</string>
<string name="hide_edit_title_content_desc">Exit title editing</string>
<string name="exit_editing_content_desc">Exit editing mode</string>
<string name="open_in_web_content_desc">Open in web</string>
<string name="translator_content_desc">Translator</string>
<string name="translator_language_content_desc">Change language places</string>