BookInfoScreen: Editing Author and Description + Fixed slow loading (caused by BookInfoEvent.OnCheckCoverReset).
This commit is contained in:
parent
45c1bd2b80
commit
936627e742
11 changed files with 483 additions and 144 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,102 +171,22 @@ private fun BookInfoScreen(
|
|||
Scaffold(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.windowInsetsPadding(WindowInsets.navigationBars)
|
||||
.pullRefresh(refreshState)
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
topBar = {
|
||||
AnimatedTopAppBar(
|
||||
containerColor = Color.Transparent,
|
||||
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(
|
||||
BookInfoTopBar(
|
||||
state = state,
|
||||
onEvent = onEvent,
|
||||
onLibraryEvent = onLibraryEvent,
|
||||
onHistoryEvent = onHistoryEvent,
|
||||
onNavigate = onNavigate,
|
||||
scrollBehavior = scrollBehavior,
|
||||
listState = listState,
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
CustomSnackbar(
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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) :
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
|
|
@ -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,26 +197,130 @@ 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
|
||||
|
||||
updateBooks.execute(
|
||||
listOf(
|
||||
_state.value.book.copy(title = title)
|
||||
)
|
||||
)
|
||||
if (!shouldHide) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
book = it.book.copy(
|
||||
title = title
|
||||
authorValue = it.book.author.getAsString() ?: "",
|
||||
hasAuthorFocused = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
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)
|
||||
|
||||
if (state.value.editTitle) {
|
||||
onEvent(BookInfoEvent.OnShowHideEditTitle)
|
||||
}
|
||||
|
||||
if (state.value.editAuthor) {
|
||||
onEvent(BookInfoEvent.OnShowHideEditAuthor)
|
||||
}
|
||||
|
||||
if (state.value.editDescription) {
|
||||
onEvent(BookInfoEvent.OnShowHideEditDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BookInfoEvent.OnShowHideDeleteDialog -> {
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue