🛠️ Unlimited amount of AnimatedTopAppBars

* Changed fixed 3 bars to listOf AnimatedTopAppBarData
This commit is contained in:
Acclorite 2024-09-27 18:21:48 +03:00
parent e7a2d780d7
commit 863e76d905
5 changed files with 457 additions and 483 deletions

View file

@ -23,6 +23,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp import androidx.compose.ui.graphics.lerp
data class AnimatedTopAppBarData(
val contentVisibility: Boolean? = null,
val contentNavigationIcon: @Composable () -> Unit,
val contentTitle: @Composable () -> Unit,
val contentActions: @Composable RowScope.() -> Unit
)
/** /**
* Animated top app bar. * Animated top app bar.
* Has up to 3 different top bars inside it. Follow [TopAppBar] for more info. * Has up to 3 different top bars inside it. Follow [TopAppBar] for more info.
@ -31,9 +38,7 @@ import androidx.compose.ui.graphics.lerp
* @param scrolledContainerColor Scrolled container color of the TopBar. * @param scrolledContainerColor Scrolled container color of the TopBar.
* @param scrollBehavior [TopAppBarScrollBehavior]. * @param scrollBehavior [TopAppBarScrollBehavior].
* @param isTopBarScrolled Whether isScrolled state should be forced or not. * @param isTopBarScrolled Whether isScrolled state should be forced or not.
* @param content1Visibility Whether first top app bar should be shown. * @param animatedTopBars Pass a list of all [AnimatedTopAppBarData] to show.
* @param content2Visibility Whether second top app bar should be shown.
* @param content3Visibility Whether third top app bar should be shown.
* @param customContent Custom content below [TopAppBar]. * @param customContent Custom content below [TopAppBar].
*/ */
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -45,21 +50,7 @@ fun AnimatedTopAppBar(
scrollBehavior: TopAppBarScrollBehavior?, scrollBehavior: TopAppBarScrollBehavior?,
isTopBarScrolled: Boolean?, isTopBarScrolled: Boolean?,
content1Visibility: Boolean? = null, animatedTopBars: List<AnimatedTopAppBarData>,
content1NavigationIcon: @Composable () -> Unit,
content1Title: @Composable () -> Unit,
content1Actions: @Composable RowScope.() -> Unit,
content2Visibility: Boolean? = null,
content2NavigationIcon: @Composable () -> Unit = {},
content2Title: @Composable () -> Unit = {},
content2Actions: @Composable RowScope.() -> Unit = {},
content3Visibility: Boolean? = null,
content3NavigationIcon: @Composable () -> Unit = {},
content3Title: @Composable () -> Unit = {},
content3Actions: @Composable RowScope.() -> Unit = {},
customContent: @Composable ColumnScope.() -> Unit = {} customContent: @Composable ColumnScope.() -> Unit = {}
) { ) {
val animatedContainerColor = lerp( val animatedContainerColor = lerp(
@ -79,59 +70,17 @@ fun AnimatedTopAppBar(
.background(animatedContainerColor) .background(animatedContainerColor)
) { ) {
Box(modifier = Modifier.fillMaxWidth()) { Box(modifier = Modifier.fillMaxWidth()) {
CustomAnimatedVisibility( animatedTopBars.forEach { data ->
visible = content1Visibility ?: true,
enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)),
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
) {
TopAppBar(
windowInsets = WindowInsets.statusBars,
navigationIcon = content1NavigationIcon,
title = content1Title,
actions = content1Actions,
scrollBehavior = scrollBehavior,
modifier = Modifier.fillMaxWidth(),
colors = TopAppBarDefaults.topAppBarColors(
containerColor = if (isTopBarScrolled != null) Color.Transparent
else containerColor,
scrolledContainerColor = scrolledContainerColor
)
)
}
if (content2Visibility != null) {
CustomAnimatedVisibility( CustomAnimatedVisibility(
visible = content2Visibility, visible = data.contentVisibility ?: false,
enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)), enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)),
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow)) exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
) { ) {
TopAppBar( TopAppBar(
windowInsets = WindowInsets.statusBars, windowInsets = WindowInsets.statusBars,
navigationIcon = content2NavigationIcon, navigationIcon = data.contentNavigationIcon,
title = content2Title, title = data.contentTitle,
actions = content2Actions, actions = data.contentActions,
scrollBehavior = scrollBehavior,
modifier = Modifier.fillMaxWidth(),
colors = TopAppBarDefaults.topAppBarColors(
containerColor = if (isTopBarScrolled != null) Color.Transparent
else containerColor,
scrolledContainerColor = scrolledContainerColor
)
)
}
}
if (content3Visibility != null) {
CustomAnimatedVisibility(
visible = content3Visibility,
enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)),
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
) {
TopAppBar(
windowInsets = WindowInsets.statusBars,
navigationIcon = content3NavigationIcon,
title = content3Title,
actions = content3Actions,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
colors = TopAppBarDefaults.topAppBarColors( colors = TopAppBarDefaults.topAppBarColors(

View file

@ -20,6 +20,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBar import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBar
import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBarData
import ua.acclorite.book_story.presentation.core.components.CustomAnimatedVisibility import ua.acclorite.book_story.presentation.core.components.CustomAnimatedVisibility
import ua.acclorite.book_story.presentation.core.components.CustomIconButton import ua.acclorite.book_story.presentation.core.components.CustomIconButton
import ua.acclorite.book_story.presentation.core.components.GoBackButton import ua.acclorite.book_story.presentation.core.components.GoBackButton
@ -59,120 +60,125 @@ fun BookInfoTopBar(
scrollBehavior = null, scrollBehavior = null,
isTopBarScrolled = listState.canScrollBackward, isTopBarScrolled = listState.canScrollBackward,
content1NavigationIcon = { animatedTopBars = listOf(
if ( AnimatedTopAppBarData(
state.value.editTitle || contentVisibility = true,
state.value.editAuthor || contentNavigationIcon = {
state.value.editDescription if (
) { state.value.editTitle ||
CustomIconButton( state.value.editAuthor ||
icon = Icons.AutoMirrored.Outlined.ArrowBack, state.value.editDescription
contentDescription = R.string.exit_editing_content_desc, ) {
disableOnClick = true CustomIconButton(
) { icon = Icons.AutoMirrored.Outlined.ArrowBack,
if (state.value.editTitle) { contentDescription = R.string.exit_editing_content_desc,
onEvent(BookInfoEvent.OnShowHideEditTitle) disableOnClick = true
} ) {
if (state.value.editTitle) {
onEvent(BookInfoEvent.OnShowHideEditTitle)
}
if (state.value.editAuthor) { if (state.value.editAuthor) {
onEvent(BookInfoEvent.OnShowHideEditAuthor) onEvent(BookInfoEvent.OnShowHideEditAuthor)
} }
if (state.value.editDescription) { if (state.value.editDescription) {
onEvent(BookInfoEvent.OnShowHideEditDescription) onEvent(BookInfoEvent.OnShowHideEditDescription)
}
}
} else {
GoBackButton(onNavigate = onNavigate, enabled = !state.value.updating) {
onEvent(BookInfoEvent.OnCancelUpdate)
}
} }
} },
} else { contentTitle = {
GoBackButton(onNavigate = onNavigate, enabled = !state.value.updating) { DefaultTransition(
onEvent(BookInfoEvent.OnCancelUpdate) firstVisibleItemIndex > 0 && !state.value.editTitle
} ) {
} Text(
}, state.value.book.title,
content1Title = { style = MaterialTheme.typography.titleLarge,
DefaultTransition( color = MaterialTheme.colorScheme.onSurface,
firstVisibleItemIndex > 0 && !state.value.editTitle overflow = TextOverflow.Ellipsis,
) { maxLines = 1
Text( )
state.value.book.title, }
style = MaterialTheme.typography.titleLarge, },
color = MaterialTheme.colorScheme.onSurface, contentActions = {
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
}
},
content1Actions = {
CustomIconButton(
icon = Icons.Outlined.Refresh,
contentDescription = R.string.refresh_book_content_desc,
disableOnClick = false,
enabled = !state.value.updating && !state.value.checkingForUpdate
) {
onEvent(
BookInfoEvent.OnCheckForUpdate(
snackbarState,
context
)
)
}
Box {
DefaultTransition(
visible = !state.value.editTitle &&
!state.value.editAuthor &&
!state.value.editDescription
) {
BookInfoMoreDropDown(snackbarState = snackbarState)
}
CustomAnimatedVisibility(
visible = state.value.editTitle ||
state.value.editAuthor ||
state.value.editDescription,
enter = Transitions.DefaultTransitionIn,
exit = fadeOut(tween(200))
) {
CustomIconButton( CustomIconButton(
icon = Icons.Outlined.Done, icon = Icons.Outlined.Refresh,
contentDescription = R.string.apply_changes_content_desc, contentDescription = R.string.refresh_book_content_desc,
disableOnClick = true, disableOnClick = false,
enabled = !state.value.updating && enabled = !state.value.updating && !state.value.checkingForUpdate
(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( onEvent(
BookInfoEvent.OnUpdateData( BookInfoEvent.OnCheckForUpdate(
refreshList = { snackbarState,
onLibraryEvent(LibraryEvent.OnUpdateBook(it)) context
onHistoryEvent(HistoryEvent.OnUpdateBook(it))
}
) )
) )
} }
Box {
DefaultTransition(
visible = !state.value.editTitle &&
!state.value.editAuthor &&
!state.value.editDescription
) {
BookInfoMoreDropDown(snackbarState = snackbarState)
}
CustomAnimatedVisibility(
visible = state.value.editTitle ||
state.value.editAuthor ||
state.value.editDescription,
enter = Transitions.DefaultTransitionIn,
exit = fadeOut(tween(200))
) {
CustomIconButton(
icon = Icons.Outlined.Done,
contentDescription = R.string.apply_changes_content_desc,
disableOnClick = true,
enabled = !state.value.updating &&
(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

@ -1,14 +1,6 @@
package ua.acclorite.book_story.presentation.screens.browse.components.top_bar package ua.acclorite.book_story.presentation.screens.browse.components.top_bar
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Box
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
@ -25,7 +17,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
@ -35,7 +26,7 @@ import androidx.compose.ui.text.style.TextOverflow
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.SelectableFile import ua.acclorite.book_story.domain.model.SelectableFile
import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBar import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBar
import ua.acclorite.book_story.presentation.core.components.CustomAnimatedVisibility import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBarData
import ua.acclorite.book_story.presentation.core.components.CustomIconButton import ua.acclorite.book_story.presentation.core.components.CustomIconButton
import ua.acclorite.book_story.presentation.core.components.CustomSearchTextField import ua.acclorite.book_story.presentation.core.components.CustomSearchTextField
import ua.acclorite.book_story.presentation.core.components.LocalBrowseViewModel import ua.acclorite.book_story.presentation.core.components.LocalBrowseViewModel
@ -44,7 +35,6 @@ import ua.acclorite.book_story.presentation.core.components.MoreDropDown
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
import ua.acclorite.book_story.presentation.screens.settings.nested.browse.data.BrowseFilesStructure import ua.acclorite.book_story.presentation.screens.settings.nested.browse.data.BrowseFilesStructure
import ua.acclorite.book_story.presentation.screens.settings.nested.browse.data.BrowseLayout import ua.acclorite.book_story.presentation.screens.settings.nested.browse.data.BrowseLayout
import ua.acclorite.book_story.presentation.ui.FadeTransitionPreservingSpace
/** /**
* Browse Top Bar. * Browse Top Bar.
@ -88,157 +78,173 @@ fun BrowseTopBar(filteredFiles: List<SelectableFile>) {
AnimatedTopAppBar( AnimatedTopAppBar(
scrollBehavior = null, scrollBehavior = null,
isTopBarScrolled = state.value.hasSelectedItems || isTopBarScrolled = isScrolled.value || inNestedDirectory.value,
isScrolled.value ||
inNestedDirectory.value,
content1Visibility = !state.value.hasSelectedItems && !state.value.showSearch, animatedTopBars = listOf(
content1NavigationIcon = { AnimatedTopAppBarData(
CustomAnimatedVisibility( contentVisibility = !state.value.hasSelectedItems &&
visible = inNestedDirectory.value, !state.value.showSearch &&
enter = expandHorizontally(tween(200)) !inNestedDirectory.value,
+ slideInHorizontally(tween(200)) contentNavigationIcon = {},
+ fadeIn(tween(250)), contentTitle = {
exit = shrinkHorizontally(tween(150))
+ slideOutHorizontally(tween(150))
+ fadeOut(tween(200))
) {
CustomIconButton(
icon = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = R.string.go_back_content_desc,
disableOnClick = false,
enabled = inNestedDirectory.value
) {
onEvent(BrowseEvent.OnGoBackDirectory)
}
}
},
content1Title = {
Box(contentAlignment = Alignment.CenterStart) {
FadeTransitionPreservingSpace(
visible = inNestedDirectory.value,
animationSpec = tween(200)
) {
Text(
selectedDirectoryName.value,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
FadeTransitionPreservingSpace(
visible = !inNestedDirectory.value,
animationSpec = tween(200)
) {
Text( Text(
stringResource(id = R.string.browse_screen), stringResource(id = R.string.browse_screen),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
},
contentActions = {
CustomIconButton(
icon = Icons.Default.Search,
contentDescription = R.string.search_content_desc,
disableOnClick = true
) {
onEvent(BrowseEvent.OnSearchShowHide)
}
CustomIconButton(
icon = Icons.Default.FilterList,
contentDescription = R.string.filter_content_desc,
disableOnClick = false,
color = animateColorAsState(
if (mainState.value.browseIncludedFilterItems.isNotEmpty()) {
MaterialTheme.colorScheme.primary
} else LocalContentColor.current,
label = ""
).value,
enabled = !state.value.showFilterBottomSheet
) {
onEvent(BrowseEvent.OnShowHideFilterBottomSheet)
}
MoreDropDown()
} }
} ),
},
content1Actions = {
CustomIconButton(
icon = Icons.Default.Search,
contentDescription = R.string.search_content_desc,
disableOnClick = true
) {
onEvent(BrowseEvent.OnSearchShowHide)
}
CustomIconButton(
icon = Icons.Default.FilterList,
contentDescription = R.string.filter_content_desc,
disableOnClick = false,
color = animateColorAsState(
if (mainState.value.browseIncludedFilterItems.isNotEmpty()) {
MaterialTheme.colorScheme.primary
} else LocalContentColor.current,
label = ""
).value,
enabled = !state.value.showFilterBottomSheet
) {
onEvent(BrowseEvent.OnShowHideFilterBottomSheet)
}
MoreDropDown()
},
content2Visibility = state.value.hasSelectedItems, AnimatedTopAppBarData(
content2NavigationIcon = { contentVisibility = inNestedDirectory.value,
CustomIconButton( contentNavigationIcon = {
icon = Icons.Default.Clear, CustomIconButton(
contentDescription = R.string.clear_selected_items_content_desc, icon = Icons.AutoMirrored.Default.ArrowBack,
disableOnClick = true contentDescription = R.string.go_back_content_desc,
) { disableOnClick = false,
onEvent(BrowseEvent.OnClearSelectedFiles) enabled = inNestedDirectory.value
} ) {
}, onEvent(BrowseEvent.OnGoBackDirectory)
content2Title = { }
Text( },
stringResource( contentTitle = {
id = R.string.selected_items_count_query, Text(
state.value.selectedItemsCount.coerceAtLeast(1) selectedDirectoryName.value,
), maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis
maxLines = 1
)
},
content2Actions = {
CustomIconButton(
icon = Icons.Default.SelectAll,
contentDescription = R.string.select_all_files_content_desc,
disableOnClick = false,
) {
onEvent(
BrowseEvent.OnSelectFiles(
includedFileFormats = mainState.value.browseIncludedFilterItems,
files = filteredFiles
) )
) },
} contentActions = {
CustomIconButton( CustomIconButton(
icon = Icons.Default.Check, icon = Icons.Default.Search,
contentDescription = R.string.add_files_content_desc, contentDescription = R.string.search_content_desc,
disableOnClick = false, disableOnClick = true
enabled = !state.value.showAddingDialog ) {
) { onEvent(BrowseEvent.OnSearchShowHide)
onEvent(BrowseEvent.OnAddingDialogRequest) }
} CustomIconButton(
}, icon = Icons.Default.FilterList,
contentDescription = R.string.filter_content_desc,
disableOnClick = false,
color = animateColorAsState(
if (mainState.value.browseIncludedFilterItems.isNotEmpty()) {
MaterialTheme.colorScheme.primary
} else LocalContentColor.current,
label = ""
).value,
enabled = !state.value.showFilterBottomSheet
) {
onEvent(BrowseEvent.OnShowHideFilterBottomSheet)
}
MoreDropDown()
}
),
content3Visibility = state.value.showSearch && !state.value.hasSelectedItems, AnimatedTopAppBarData(
content3NavigationIcon = { contentVisibility = state.value.hasSelectedItems,
CustomIconButton( contentNavigationIcon = {
icon = Icons.AutoMirrored.Default.ArrowBack, CustomIconButton(
contentDescription = R.string.exit_search_content_desc, icon = Icons.Default.Clear,
disableOnClick = true contentDescription = R.string.clear_selected_items_content_desc,
) { disableOnClick = true
onEvent(BrowseEvent.OnSearchShowHide) ) {
} onEvent(BrowseEvent.OnClearSelectedFiles)
}, }
content3Title = {
CustomSearchTextField(
modifier = Modifier
.focusRequester(focusRequester)
.onGloballyPositioned {
onEvent(BrowseEvent.OnRequestFocus(focusRequester))
},
query = state.value.searchQuery,
onQueryChange = {
onEvent(BrowseEvent.OnSearchQueryChange(it))
}, },
onSearch = { contentTitle = {
onEvent(BrowseEvent.OnSearch) Text(
stringResource(
id = R.string.selected_items_count_query,
state.value.selectedItemsCount.coerceAtLeast(1)
),
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
}, },
placeholder = stringResource( contentActions = {
id = R.string.search_query, CustomIconButton(
stringResource(id = R.string.files) icon = Icons.Default.SelectAll,
) contentDescription = R.string.select_all_files_content_desc,
disableOnClick = false,
) {
onEvent(
BrowseEvent.OnSelectFiles(
includedFileFormats = mainState.value.browseIncludedFilterItems,
files = filteredFiles
)
)
}
CustomIconButton(
icon = Icons.Default.Check,
contentDescription = R.string.add_files_content_desc,
disableOnClick = false,
enabled = !state.value.showAddingDialog
) {
onEvent(BrowseEvent.OnAddingDialogRequest)
}
}
),
AnimatedTopAppBarData(
contentVisibility = state.value.showSearch && !state.value.hasSelectedItems,
contentNavigationIcon = {
CustomIconButton(
icon = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = R.string.exit_search_content_desc,
disableOnClick = true
) {
onEvent(BrowseEvent.OnSearchShowHide)
}
},
contentTitle = {
CustomSearchTextField(
modifier = Modifier
.focusRequester(focusRequester)
.onGloballyPositioned {
onEvent(BrowseEvent.OnRequestFocus(focusRequester))
},
query = state.value.searchQuery,
onQueryChange = {
onEvent(BrowseEvent.OnSearchQueryChange(it))
},
onSearch = {
onEvent(BrowseEvent.OnSearch)
},
placeholder = stringResource(
id = R.string.search_query,
stringResource(id = R.string.files)
)
)
},
contentActions = {
MoreDropDown()
}
) )
}, ),
content3Actions = {
MoreDropDown()
},
customContent = { customContent = {
BrowseTopBarDirectoryPath() BrowseTopBarDirectoryPath()
} }

View file

@ -36,6 +36,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBar import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBar
import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBarData
import ua.acclorite.book_story.presentation.core.components.CategoryTitle import ua.acclorite.book_story.presentation.core.components.CategoryTitle
import ua.acclorite.book_story.presentation.core.components.CustomAnimatedVisibility import ua.acclorite.book_story.presentation.core.components.CustomAnimatedVisibility
import ua.acclorite.book_story.presentation.core.components.CustomIconButton import ua.acclorite.book_story.presentation.core.components.CustomIconButton
@ -101,69 +102,72 @@ private fun HistoryScreen() {
scrollBehavior = null, scrollBehavior = null,
isTopBarScrolled = state.value.listState.canScrollBackward, isTopBarScrolled = state.value.listState.canScrollBackward,
content1Visibility = !state.value.showSearch, animatedTopBars = listOf(
content1NavigationIcon = {}, AnimatedTopAppBarData(
content1Title = { contentVisibility = !state.value.showSearch,
Text( contentNavigationIcon = {},
stringResource(id = R.string.history_screen) contentTitle = {
) Text(stringResource(id = R.string.history_screen))
}, },
content1Actions = { contentActions = {
CustomIconButton( CustomIconButton(
icon = Icons.Default.Search, icon = Icons.Default.Search,
contentDescription = R.string.search_content_desc, contentDescription = R.string.search_content_desc,
disableOnClick = true, disableOnClick = true,
) { ) {
onEvent(HistoryEvent.OnSearchShowHide) onEvent(HistoryEvent.OnSearchShowHide)
} }
CustomIconButton( CustomIconButton(
icon = Icons.Outlined.DeleteSweep, icon = Icons.Outlined.DeleteSweep,
contentDescription = R.string.delete_whole_history_content_desc, contentDescription = R.string.delete_whole_history_content_desc,
disableOnClick = false, disableOnClick = false,
enabled = !state.value.isLoading enabled = !state.value.isLoading
&& !state.value.isRefreshing && !state.value.isRefreshing
&& state.value.history.isNotEmpty() && state.value.history.isNotEmpty()
&& !state.value.showDeleteWholeHistoryDialog && !state.value.showDeleteWholeHistoryDialog
) { ) {
onEvent(HistoryEvent.OnShowHideDeleteWholeHistoryDialog) onEvent(HistoryEvent.OnShowHideDeleteWholeHistoryDialog)
} }
MoreDropDown() MoreDropDown()
}, }
),
content2Visibility = state.value.showSearch, AnimatedTopAppBarData(
content2NavigationIcon = { contentVisibility = state.value.showSearch,
CustomIconButton( contentNavigationIcon = {
icon = Icons.AutoMirrored.Default.ArrowBack, CustomIconButton(
contentDescription = R.string.exit_search_content_desc, icon = Icons.AutoMirrored.Default.ArrowBack,
disableOnClick = true contentDescription = R.string.exit_search_content_desc,
) { disableOnClick = true
onEvent(HistoryEvent.OnSearchShowHide) ) {
} onEvent(HistoryEvent.OnSearchShowHide)
}, }
content2Title = {
CustomSearchTextField(
modifier = Modifier
.focusRequester(focusRequester)
.onGloballyPositioned {
onEvent(HistoryEvent.OnRequestFocus(focusRequester))
},
query = state.value.searchQuery,
onQueryChange = {
onEvent(HistoryEvent.OnSearchQueryChange(it))
}, },
onSearch = { contentTitle = {
onEvent(HistoryEvent.OnSearch) CustomSearchTextField(
modifier = Modifier
.focusRequester(focusRequester)
.onGloballyPositioned {
onEvent(HistoryEvent.OnRequestFocus(focusRequester))
},
query = state.value.searchQuery,
onQueryChange = {
onEvent(HistoryEvent.OnSearchQueryChange(it))
},
onSearch = {
onEvent(HistoryEvent.OnSearch)
},
placeholder = stringResource(
id = R.string.search_query,
stringResource(id = R.string.history)
)
)
},
contentActions = {
MoreDropDown()
}, },
placeholder = stringResource(
id = R.string.search_query,
stringResource(id = R.string.history)
)
) )
}, )
content2Actions = {
MoreDropDown()
},
content3Visibility = false
) )
}, },
bottomBar = { bottomBar = {

View file

@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBar import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBar
import ua.acclorite.book_story.presentation.core.components.AnimatedTopAppBarData
import ua.acclorite.book_story.presentation.core.components.CustomIconButton import ua.acclorite.book_story.presentation.core.components.CustomIconButton
import ua.acclorite.book_story.presentation.core.components.CustomSearchTextField import ua.acclorite.book_story.presentation.core.components.CustomSearchTextField
import ua.acclorite.book_story.presentation.core.components.LocalLibraryViewModel import ua.acclorite.book_story.presentation.core.components.LocalLibraryViewModel
@ -55,115 +56,123 @@ fun LibraryTopBar(pagerState: PagerState) {
scrollBehavior = null, scrollBehavior = null,
isTopBarScrolled = state.value.hasSelectedItems, isTopBarScrolled = state.value.hasSelectedItems,
content1Visibility = !state.value.hasSelectedItems && !state.value.showSearch, animatedTopBars = listOf(
content1NavigationIcon = {}, AnimatedTopAppBarData(
content1Title = { contentVisibility = !state.value.hasSelectedItems && !state.value.showSearch,
Row(verticalAlignment = Alignment.CenterVertically) { contentNavigationIcon = {},
Text(stringResource(id = R.string.library_screen)) contentTitle = {
Spacer(modifier = Modifier.width(6.dp)) Row(verticalAlignment = Alignment.CenterVertically) {
Text( Text(stringResource(id = R.string.library_screen))
text = state.value.books.size.toString(), Spacer(modifier = Modifier.width(6.dp))
modifier = Modifier Text(
.background( text = state.value.books.size.toString(),
MaterialTheme.colorScheme.surfaceContainer, modifier = Modifier
RoundedCornerShape(14.dp) .background(
MaterialTheme.colorScheme.surfaceContainer,
RoundedCornerShape(14.dp)
)
.padding(horizontal = 8.dp, vertical = 3.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 16.sp
) )
.padding(horizontal = 8.dp, vertical = 3.dp), }
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 16.sp
)
}
},
content1Actions = {
CustomIconButton(
icon = Icons.Default.Search,
contentDescription = R.string.search_content_desc,
disableOnClick = true,
) {
onEvent(LibraryEvent.OnSearchShowHide)
}
MoreDropDown()
},
content2Visibility = state.value.hasSelectedItems,
content2NavigationIcon = {
CustomIconButton(
icon = Icons.Default.Clear,
contentDescription = R.string.clear_selected_items_content_desc,
disableOnClick = true
) {
onEvent(LibraryEvent.OnClearSelectedBooks)
}
},
content2Title = {
Text(
stringResource(
id = R.string.selected_items_count_query,
state.value.selectedItemsCount.coerceAtLeast(1)
),
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
},
content2Actions = {
CustomIconButton(
icon = Icons.AutoMirrored.Outlined.DriveFileMove,
contentDescription = R.string.move_books_content_desc,
enabled = !state.value.isLoading
&& !state.value.isRefreshing
&& !state.value.showMoveDialog,
disableOnClick = false,
) {
onEvent(LibraryEvent.OnShowHideMoveDialog)
}
CustomIconButton(
icon = Icons.Outlined.Delete,
contentDescription = R.string.delete_books_content_desc,
enabled = !state.value.isLoading
&& !state.value.isRefreshing
&& !state.value.showDeleteDialog,
disableOnClick = false
) {
onEvent(LibraryEvent.OnShowHideDeleteDialog)
}
},
content3Visibility = state.value.showSearch && !state.value.hasSelectedItems,
content3NavigationIcon = {
CustomIconButton(
icon = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = R.string.exit_search_content_desc,
disableOnClick = true
) {
onEvent(
LibraryEvent.OnSearchShowHide
)
}
},
content3Title = {
CustomSearchTextField(
modifier = Modifier
.focusRequester(focusRequester)
.onGloballyPositioned {
onEvent(LibraryEvent.OnRequestFocus(focusRequester))
},
query = state.value.searchQuery,
onQueryChange = {
onEvent(LibraryEvent.OnSearchQueryChange(it))
}, },
onSearch = { contentActions = {
onEvent(LibraryEvent.OnSearch) CustomIconButton(
icon = Icons.Default.Search,
contentDescription = R.string.search_content_desc,
disableOnClick = true,
) {
onEvent(LibraryEvent.OnSearchShowHide)
}
MoreDropDown()
}
),
AnimatedTopAppBarData(
contentVisibility = state.value.hasSelectedItems,
contentNavigationIcon = {
CustomIconButton(
icon = Icons.Default.Clear,
contentDescription = R.string.clear_selected_items_content_desc,
disableOnClick = true
) {
onEvent(LibraryEvent.OnClearSelectedBooks)
}
},
contentTitle = {
Text(
stringResource(
id = R.string.selected_items_count_query,
state.value.selectedItemsCount.coerceAtLeast(1)
),
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
},
contentActions = {
CustomIconButton(
icon = Icons.AutoMirrored.Outlined.DriveFileMove,
contentDescription = R.string.move_books_content_desc,
enabled = !state.value.isLoading
&& !state.value.isRefreshing
&& !state.value.showMoveDialog,
disableOnClick = false,
) {
onEvent(LibraryEvent.OnShowHideMoveDialog)
}
CustomIconButton(
icon = Icons.Outlined.Delete,
contentDescription = R.string.delete_books_content_desc,
enabled = !state.value.isLoading
&& !state.value.isRefreshing
&& !state.value.showDeleteDialog,
disableOnClick = false
) {
onEvent(LibraryEvent.OnShowHideDeleteDialog)
}
}
),
AnimatedTopAppBarData(
contentVisibility = state.value.showSearch && !state.value.hasSelectedItems,
contentNavigationIcon = {
CustomIconButton(
icon = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = R.string.exit_search_content_desc,
disableOnClick = true
) {
onEvent(
LibraryEvent.OnSearchShowHide
)
}
},
contentTitle = {
CustomSearchTextField(
modifier = Modifier
.focusRequester(focusRequester)
.onGloballyPositioned {
onEvent(LibraryEvent.OnRequestFocus(focusRequester))
},
query = state.value.searchQuery,
onQueryChange = {
onEvent(LibraryEvent.OnSearchQueryChange(it))
},
onSearch = {
onEvent(LibraryEvent.OnSearch)
},
placeholder = stringResource(
id = R.string.search_query,
stringResource(id = R.string.books)
)
)
},
contentActions = {
MoreDropDown()
}, },
placeholder = stringResource(
id = R.string.search_query,
stringResource(id = R.string.books)
)
) )
}, ),
content3Actions = {
MoreDropDown()
},
customContent = { customContent = {
LibraryTabRow( LibraryTabRow(
onEvent = onEvent, onEvent = onEvent,