LibraryScreen: New LibraryTopBar + Moved LaunchedEffect in LibraryScreenRoot.

This commit is contained in:
acclorite 2024-06-24 18:09:36 +03:00
parent d159b2e902
commit 3967cf787d
2 changed files with 214 additions and 169 deletions

View file

@ -1,3 +1,5 @@
@file:OptIn(ExperimentalMaterialApi::class)
package ua.acclorite.book_story.presentation.screens.library
import android.widget.Toast
@ -5,37 +7,25 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.outlined.DriveFileMove
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.PullRefreshState
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
@ -47,15 +37,10 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
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.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -64,11 +49,7 @@ import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
import ua.acclorite.book_story.domain.util.OnNavigate
import ua.acclorite.book_story.domain.util.Selected
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.CustomSearchTextField
import ua.acclorite.book_story.presentation.components.MoreDropDown
import ua.acclorite.book_story.presentation.components.customItems
import ua.acclorite.book_story.presentation.components.header
import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty
@ -79,7 +60,7 @@ import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
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.components.LibraryBookItem
import ua.acclorite.book_story.presentation.screens.library.components.LibraryTabRow
import ua.acclorite.book_story.presentation.screens.library.components.LibraryTopBar
import ua.acclorite.book_story.presentation.screens.library.components.dialog.LibraryDeleteDialog
import ua.acclorite.book_story.presentation.screens.library.components.dialog.LibraryMoveDialog
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
@ -97,48 +78,49 @@ fun LibraryScreenRoot() {
val state = viewModel.state.collectAsState()
val pagerState = rememberPagerState(
initialPage = state.value.currentPage,
pageCount = { Category.entries.size }
)
val refreshState = rememberPullRefreshState(
refreshing = state.value.isRefreshing,
onRefresh = {
viewModel.onEvent(LibraryEvent.OnRefreshList)
}
)
LaunchedEffect(Unit) {
viewModel.onEvent(LibraryEvent.OnScrollToPage(state.value.currentPage, pagerState))
}
LaunchedEffect(pagerState.currentPage) {
if (pagerState.currentPage != state.value.currentPage) {
viewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(pagerState.currentPage))
}
}
LibraryScreen(
state = state,
onNavigate = { navigator.it() },
pagerState = pagerState,
refreshState = refreshState,
onEvent = viewModel::onEvent,
onBrowseEvent = browseViewModel::onEvent,
onHistoryEvent = historyViewModel::onEvent
)
}
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalMaterialApi::class
)
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun LibraryScreen(
state: State<LibraryState>,
onNavigate: OnNavigate,
pagerState: PagerState,
refreshState: PullRefreshState,
onEvent: (LibraryEvent) -> Unit,
onHistoryEvent: (HistoryEvent) -> Unit,
onBrowseEvent: (BrowseEvent) -> Unit
) {
val pagerState = rememberPagerState(
initialPage = state.value.currentPage,
pageCount = { Category.entries.size }
)
var isScrollInProgress by remember { mutableStateOf(false) }
val refreshState = rememberPullRefreshState(
refreshing = state.value.isRefreshing,
onRefresh = {
onEvent(LibraryEvent.OnRefreshList)
}
)
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
onEvent(LibraryEvent.OnScrollToPage(state.value.currentPage, pagerState))
}
LaunchedEffect(pagerState.currentPage) {
if (pagerState.currentPage != state.value.currentPage) {
onEvent(LibraryEvent.OnUpdateCurrentPage(pagerState.currentPage))
}
}
if (state.value.showMoveDialog) {
LibraryMoveDialog(
@ -163,127 +145,11 @@ private fun LibraryScreen(
.pullRefresh(refreshState),
containerColor = MaterialTheme.colorScheme.surface,
topBar = {
Column(Modifier.fillMaxWidth()) {
AnimatedTopAppBar(
scrollBehavior = null,
isTopBarScrolled = state.value.hasSelectedItems,
content1Visibility = !state.value.hasSelectedItems && !state.value.showSearch,
content1NavigationIcon = {},
content1Title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(id = R.string.library_screen))
Spacer(modifier = Modifier.width(6.dp))
Text(
text = state.value.books.size.toString(),
modifier = Modifier
.background(
MaterialTheme.colorScheme.surfaceContainer,
RoundedCornerShape(14.dp)
)
.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 = {
onEvent(LibraryEvent.OnSearch)
},
placeholder = stringResource(
id = R.string.search_query,
stringResource(id = R.string.books)
)
)
},
content3Actions = {
MoreDropDown()
}
)
LibraryTabRow(
onEvent = onEvent,
books = state.value.categorizedBooks,
pagerState = pagerState
)
}
LibraryTopBar(
state = state,
pagerState = pagerState,
onEvent = onEvent
)
}
) { paddingValues ->
Box(

View file

@ -0,0 +1,179 @@
package ua.acclorite.book_story.presentation.screens.library.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.outlined.DriveFileMove
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.ExperimentalMaterial3Api
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.layout.onGloballyPositioned
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
import ua.acclorite.book_story.presentation.components.CustomIconButton
import ua.acclorite.book_story.presentation.components.CustomSearchTextField
import ua.acclorite.book_story.presentation.components.MoreDropDown
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
import ua.acclorite.book_story.presentation.screens.library.data.LibraryState
/**
* Library Top Bar.
* Contains [AnimatedTopAppBar] and [LibraryTabRow].
*
* @param state [LibraryState].
* @param pagerState [PagerState].
* @param onEvent [LibraryEvent] callback.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LibraryTopBar(
state: State<LibraryState>,
pagerState: PagerState,
onEvent: (LibraryEvent) -> Unit
) {
val focusRequester = remember { FocusRequester() }
Column(Modifier.fillMaxWidth()) {
AnimatedTopAppBar(
scrollBehavior = null,
isTopBarScrolled = state.value.hasSelectedItems,
content1Visibility = !state.value.hasSelectedItems && !state.value.showSearch,
content1NavigationIcon = {},
content1Title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(id = R.string.library_screen))
Spacer(modifier = Modifier.width(6.dp))
Text(
text = state.value.books.size.toString(),
modifier = Modifier
.background(
MaterialTheme.colorScheme.surfaceContainer,
RoundedCornerShape(14.dp)
)
.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 = {
onEvent(LibraryEvent.OnSearch)
},
placeholder = stringResource(
id = R.string.search_query,
stringResource(id = R.string.books)
)
)
},
content3Actions = {
MoreDropDown()
}
)
LibraryTabRow(
onEvent = onEvent,
books = state.value.categorizedBooks,
pagerState = pagerState
)
}
}