Add drawer menu.
This commit is contained in:
parent
01d635ca1a
commit
bb9e1b36f7
3 changed files with 204 additions and 61 deletions
|
|
@ -2,19 +2,28 @@ package com.pixelized.biblib.ui.composable.screen
|
|||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CutCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.sharp.ArrowBack
|
||||
import androidx.compose.material.icons.sharp.LocalLibrary
|
||||
import androidx.compose.material.icons.sharp.Menu
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.pixelized.biblib.BuildConfig
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.composable.pages.DetailPageComposable
|
||||
import com.pixelized.biblib.ui.composable.pages.HomePageComposable
|
||||
|
|
@ -26,6 +35,8 @@ import com.pixelized.biblib.ui.viewmodel.book.IBooksViewModel
|
|||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Page
|
||||
import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
|
|
@ -34,96 +45,225 @@ fun MainScreenComposable(
|
|||
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
||||
booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
||||
) {
|
||||
val lazyBooks: LazyPagingItems<BookThumbnailUio> =
|
||||
booksViewModel.books.collectAsLazyPagingItems()
|
||||
val lazyListState = rememberLazyListState()
|
||||
val scrollableState = rememberScrollState()
|
||||
// navigation
|
||||
val page by navigationViewModel.page.observeAsState()
|
||||
// coroutine
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
// scaffold & toolbar
|
||||
val canNavigateBack = page !is Page.HomePage
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
|
||||
LaunchedEffect(key1 = "MainScreen", block = {
|
||||
navigationViewModel.navigateTo(Page.HomePage, true)
|
||||
})
|
||||
|
||||
Scaffold(
|
||||
topBar = { ToolbarComposable(navigationViewModel) },
|
||||
drawerContent = {}
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = page is Page.HomePage,
|
||||
initiallyVisible = true,
|
||||
enter = slideInHorizontally(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
initialOffsetX = { width -> -width }
|
||||
),
|
||||
exit = slideOutHorizontally(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
targetOffsetX = { width -> -width }
|
||||
),
|
||||
) {
|
||||
HomePageComposable(
|
||||
navigationViewModel,
|
||||
lazyListState,
|
||||
scrollableState,
|
||||
lazyBooks
|
||||
scaffoldState = scaffoldState,
|
||||
topBar = {
|
||||
ToolbarComposable(
|
||||
canNavigateBack,
|
||||
onBackPress = {
|
||||
navigationViewModel.navigateBack()
|
||||
},
|
||||
onDrawerPress = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.open() }
|
||||
}
|
||||
)
|
||||
},
|
||||
drawerContent = { DrawerContentComposable() }
|
||||
) {
|
||||
Box {
|
||||
val lazyBooks: LazyPagingItems<BookThumbnailUio> =
|
||||
booksViewModel.books.collectAsLazyPagingItems()
|
||||
val lazyListState = rememberLazyListState()
|
||||
val scrollableState = rememberScrollState()
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = page is Page.HomePage,
|
||||
initiallyVisible = true,
|
||||
enter = slideInHorizontally(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
initialOffsetX = { width -> -width }
|
||||
),
|
||||
exit = slideOutHorizontally(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
targetOffsetX = { width -> -width }
|
||||
),
|
||||
) {
|
||||
HomePageComposable(
|
||||
navigationViewModel,
|
||||
lazyListState,
|
||||
scrollableState,
|
||||
lazyBooks
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = page is Page.Detail,
|
||||
initiallyVisible = false,
|
||||
enter = slideInHorizontally(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
initialOffsetX = { width -> width },
|
||||
),
|
||||
exit = slideOutHorizontally(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
targetOffsetX = { width -> width },
|
||||
),
|
||||
) {
|
||||
// Small trick to display the detail page during animation exit.
|
||||
var currentPage by remember { mutableStateOf<Page.Detail?>(null) }
|
||||
currentPage = page as? Page.Detail ?: currentPage
|
||||
currentPage?.let { DetailPageComposable(booksViewModel, it.bookId) }
|
||||
Box {
|
||||
AnimatedVisibility(
|
||||
visible = page is Page.Detail,
|
||||
initiallyVisible = false,
|
||||
enter = slideInHorizontally(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
initialOffsetX = { width -> width },
|
||||
),
|
||||
exit = slideOutHorizontally(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
targetOffsetX = { width -> width },
|
||||
),
|
||||
) {
|
||||
// Small trick to display the detail page during animation exit.
|
||||
var currentPage by remember { mutableStateOf<Page.Detail?>(null) }
|
||||
currentPage = page as? Page.Detail ?: currentPage
|
||||
currentPage?.let { DetailPageComposable(booksViewModel, it.bookId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToolbarComposable(navigationViewModel: INavigationViewModel) {
|
||||
fun ToolbarComposable(
|
||||
canNavigateBack: Boolean,
|
||||
onBackPress: () -> Unit,
|
||||
onDrawerPress: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(id = R.string.app_name)) },
|
||||
navigationIcon = { NavigationIcon(navigationViewModel) }
|
||||
title = {
|
||||
Text(stringResource(id = R.string.app_name))
|
||||
},
|
||||
navigationIcon = {
|
||||
NavigationIcon(
|
||||
canNavigateBack,
|
||||
onBackPress,
|
||||
onDrawerPress,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavigationIcon(navigationViewModel: INavigationViewModel) {
|
||||
val page: Page? by navigationViewModel.page.observeAsState()
|
||||
|
||||
Crossfade(targetState = page) {
|
||||
when (it) {
|
||||
is Page.HomePage -> IconButton(onClick = {}) {
|
||||
Icon(
|
||||
imageVector = Icons.Sharp.LocalLibrary,
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
else -> IconButton(onClick = {
|
||||
navigationViewModel.navigateBack()
|
||||
}) {
|
||||
fun NavigationIcon(
|
||||
canNavigateBack: Boolean,
|
||||
onBackPress: () -> Unit,
|
||||
onDrawerPress: () -> Unit,
|
||||
) {
|
||||
Crossfade(targetState = canNavigateBack) {
|
||||
if (it) {
|
||||
IconButton(onClick = onBackPress) {
|
||||
Icon(
|
||||
imageVector = Icons.Sharp.ArrowBack,
|
||||
contentDescription = "back"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
IconButton(onClick = onDrawerPress) {
|
||||
Icon(
|
||||
imageVector = Icons.Sharp.Menu,
|
||||
contentDescription = "drawer"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DrawerContentComposable() {
|
||||
val typography = MaterialTheme.typography
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
) {
|
||||
Card(
|
||||
shape = CutCornerShape(bottomEnd = 16.dp),
|
||||
elevation = 8.dp
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
style = typography.body1,
|
||||
text = "R. Daneel Olivaw"
|
||||
)
|
||||
Text(
|
||||
style = typography.caption,
|
||||
text = "r.daneel.olivaw@biblib.com"
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
DrawerLink(
|
||||
text = "Nouveautés",
|
||||
imageVector = Icons.Default.NewReleases,
|
||||
onClick = {},
|
||||
)
|
||||
DrawerLink(
|
||||
text = "Livres",
|
||||
imageVector = Icons.Default.AutoStories,
|
||||
onClick = {},
|
||||
)
|
||||
DrawerLink(
|
||||
text = "Séries",
|
||||
imageVector = Icons.Default.AutoAwesomeMotion,
|
||||
onClick = {},
|
||||
)
|
||||
DrawerLink(
|
||||
text = "Auteurs",
|
||||
imageVector = Icons.Default.SupervisorAccount,
|
||||
onClick = {},
|
||||
)
|
||||
DrawerLink(
|
||||
text = "Étiquettes",
|
||||
imageVector = Icons.Default.LocalOffer,
|
||||
onClick = {},
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.End)
|
||||
.padding(16.dp),
|
||||
style = typography.caption,
|
||||
text = stringResource(
|
||||
R.string.app_version,
|
||||
BuildConfig.BUILD_TYPE.toUpperCase(Locale.getDefault()),
|
||||
BuildConfig.VERSION_NAME,
|
||||
BuildConfig.VERSION_CODE
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DrawerLink(text: String, imageVector: ImageVector, onClick: () -> Unit) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable { onClick() }) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
imageVector = imageVector,
|
||||
contentDescription = ""
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
imageVector = Icons.Default.NavigateNext,
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ToolbarComposableDarkPreview() {
|
||||
BibLibTheme(darkTheme = false) {
|
||||
ToolbarComposable(navigationViewModel = INavigationViewModel.Mock())
|
||||
ToolbarComposable(false, {}, {})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +271,7 @@ fun ToolbarComposableDarkPreview() {
|
|||
@Composable
|
||||
fun ToolbarComposableLightPreview() {
|
||||
BibLibTheme(darkTheme = true) {
|
||||
ToolbarComposable(navigationViewModel = INavigationViewModel.Mock())
|
||||
ToolbarComposable(true, {}, {})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.pixelized.biblib.ui.viewmodel.navigation
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.pixelized.biblib.ui.data.BookUio
|
||||
|
||||
interface INavigationViewModel {
|
||||
val screen: LiveData<Navigable.Screen>
|
||||
|
|
@ -10,6 +9,7 @@ interface INavigationViewModel {
|
|||
|
||||
fun navigateTo(navigable: Navigable, addToBackStack: Boolean = false): Boolean
|
||||
fun navigateBack(): Boolean
|
||||
fun canNavigateBack(): Boolean
|
||||
|
||||
sealed class Navigable {
|
||||
sealed class Screen : Navigable() {
|
||||
|
|
@ -33,5 +33,6 @@ interface INavigationViewModel {
|
|||
|
||||
override fun navigateTo(navigable: Navigable, addToBackStack: Boolean): Boolean = false
|
||||
override fun navigateBack(): Boolean = false
|
||||
override fun canNavigateBack(): Boolean = false
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,8 @@ class NavigationViewModel : ViewModel(), INavigationViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
override fun canNavigateBack(): Boolean = stack.any { it != null }
|
||||
|
||||
private fun popBackStack(): Navigable? {
|
||||
return if (stack.isEmpty()) {
|
||||
null
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue