Add drawer menu.

This commit is contained in:
Thomas Andres Gomez 2021-05-17 17:24:50 +02:00
parent 01d635ca1a
commit bb9e1b36f7
3 changed files with 204 additions and 61 deletions

View file

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

View file

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

View file

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