Composable refactor (prepare for jetpack Navigation)
This commit is contained in:
parent
5053ac7360
commit
0ce392bba7
17 changed files with 597 additions and 542 deletions
|
|
@ -9,9 +9,9 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.biblib.ui.composable.screen.LoginScreenComposable
|
||||
import com.pixelized.biblib.ui.composable.screen.MainScreenComposable
|
||||
import com.pixelized.biblib.ui.composable.screen.SplashScreenComposable
|
||||
import com.pixelized.biblib.ui.composable.screen.LoginScreen
|
||||
import com.pixelized.biblib.ui.composable.screen.HomeScreen
|
||||
import com.pixelized.biblib.ui.composable.screen.SplashScreen
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Screen
|
||||
import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
|
||||
|
|
@ -43,9 +43,9 @@ fun ContentComposable() {
|
|||
|
||||
Crossfade(targetState = main) {
|
||||
when (it) {
|
||||
is Screen.SplashScreen -> SplashScreenComposable()
|
||||
is Screen.LoginScreen -> LoginScreenComposable()
|
||||
is Screen.MainScreen -> MainScreenComposable()
|
||||
is Screen.SplashScreen -> SplashScreen()
|
||||
is Screen.LoginScreen -> LoginScreen()
|
||||
is Screen.MainScreen -> HomeScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
package com.pixelized.biblib.ui.composable.common
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CutCornerShape
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.runtime.Composable
|
||||
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 com.pixelized.biblib.BuildConfig
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import java.util.*
|
||||
|
||||
|
||||
@Composable
|
||||
fun BibLibDrawer(
|
||||
onNewClick: () -> Unit = {},
|
||||
onBookClick: () -> Unit = {},
|
||||
onSeriesClick: () -> Unit = {},
|
||||
onAuthorClick: () -> Unit = {},
|
||||
onTagClick: () -> Unit = {},
|
||||
) {
|
||||
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))
|
||||
DrawerItem(
|
||||
text = "Nouveautés",
|
||||
imageVector = Icons.Default.NewReleases,
|
||||
onClick = onNewClick,
|
||||
)
|
||||
DrawerItem(
|
||||
text = "Livres",
|
||||
imageVector = Icons.Default.AutoStories,
|
||||
onClick = onBookClick,
|
||||
)
|
||||
DrawerItem(
|
||||
text = "Séries",
|
||||
imageVector = Icons.Default.AutoAwesomeMotion,
|
||||
onClick = onSeriesClick,
|
||||
)
|
||||
DrawerItem(
|
||||
text = "Auteurs",
|
||||
imageVector = Icons.Default.SupervisorAccount,
|
||||
onClick = onAuthorClick,
|
||||
)
|
||||
DrawerItem(
|
||||
text = "Étiquettes",
|
||||
imageVector = Icons.Default.LocalOffer,
|
||||
onClick = onTagClick,
|
||||
)
|
||||
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
|
||||
private fun DrawerItem(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 BibLibDrawerLightPreview() {
|
||||
BibLibTheme {
|
||||
BibLibDrawer()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.pixelized.biblib.ui.composable.common
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.sharp.ArrowBack
|
||||
import androidx.compose.material.icons.sharp.Menu
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
|
||||
|
||||
@Composable
|
||||
fun BibLibToolbar(
|
||||
canNavigateBack: Boolean,
|
||||
onBackPress: () -> Unit,
|
||||
onDrawerPress: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(stringResource(id = R.string.app_name))
|
||||
},
|
||||
navigationIcon = {
|
||||
NavigationIcon(
|
||||
canNavigateBack,
|
||||
onBackPress,
|
||||
onDrawerPress,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private 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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun BibLibToolbarDarkPreview() {
|
||||
BibLibTheme(darkTheme = false) {
|
||||
BibLibToolbar(false, {}, {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun BibLibToolbarLightPreview() {
|
||||
BibLibTheme(darkTheme = true) {
|
||||
BibLibToolbar(true, {}, {})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.pixelized.biblib.ui.composable.items.dialog
|
||||
package com.pixelized.biblib.ui.composable.dialog
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.items.dialog
|
||||
package com.pixelized.biblib.ui.composable.dialog
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.items.dialog
|
||||
package com.pixelized.biblib.ui.composable.dialog
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.items.dialog
|
||||
package com.pixelized.biblib.ui.composable.dialog
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
|
@ -19,6 +19,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.data.BookThumbnailUio
|
||||
|
|
@ -28,47 +29,31 @@ import com.pixelized.biblib.utils.BitmapCache
|
|||
import com.pixelized.biblib.utils.injection.Bob
|
||||
import com.pixelized.biblib.utils.mock.BookThumbnailMock
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun BookThumbnailComposablePreview() {
|
||||
Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||
BibLibTheme {
|
||||
val mock = BookThumbnailMock()
|
||||
BookThumbnailComposable(thumbnail = mock.bookThumbnail)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun BookThumbnailComposablePreviewDark() {
|
||||
Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||
BibLibTheme(darkTheme = true) {
|
||||
val mock = BookThumbnailMock()
|
||||
BookThumbnailComposable(thumbnail = mock.bookThumbnail)
|
||||
}
|
||||
}
|
||||
private val THUMBNAIL_WIDTH: Dp = 60.dp
|
||||
private val THUMBNAIL_HEIGHT: Dp = 96.dp
|
||||
|
||||
@Composable
|
||||
fun BookThumbnailComposable(
|
||||
fun BookItem(
|
||||
thumbnail: BookThumbnailUio,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: ((BookThumbnailUio) -> Unit)? = null,
|
||||
) {
|
||||
val typography = MaterialTheme.typography
|
||||
|
||||
Card(
|
||||
modifier = modifier.clickable {
|
||||
onClick?.invoke(thumbnail)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.clickable {
|
||||
onClick?.invoke(thumbnail)
|
||||
},
|
||||
elevation = 4.dp,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.height(96.dp),
|
||||
modifier = Modifier.height(THUMBNAIL_HEIGHT),
|
||||
) {
|
||||
Image(
|
||||
contentModifier = Modifier
|
||||
.width(60.dp)
|
||||
.fillMaxHeight()
|
||||
.size(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
placeHolder = painterResource(id = R.drawable.ic_launcher_foreground),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
|
|
@ -77,7 +62,9 @@ fun BookThumbnailComposable(
|
|||
contentDescription = thumbnail.title
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp).weight(1f)
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
style = typography.h6,
|
||||
|
|
@ -91,17 +78,19 @@ fun BookThumbnailComposable(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
softWrap = false,
|
||||
)
|
||||
Row(modifier = Modifier.weight(1f), verticalAlignment = Alignment.Bottom) {
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Text(
|
||||
style = typography.caption,
|
||||
text = thumbnail.genre,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
softWrap = false,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(0.dp)
|
||||
.widthIn(min = 4.dp)
|
||||
.weight(1f)
|
||||
)
|
||||
Text(
|
||||
|
|
@ -114,8 +103,28 @@ fun BookThumbnailComposable(
|
|||
}
|
||||
Icon(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
imageVector = Icons.Default.NavigateNext, contentDescription = "navigate"
|
||||
imageVector = Icons.Default.NavigateNext, contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun BookItemLightPreview() {
|
||||
Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||
BibLibTheme {
|
||||
val mock = BookThumbnailMock()
|
||||
BookItem(thumbnail = mock.bookThumbnail)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun BookItemDarkPreview() {
|
||||
Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||
BibLibTheme(darkTheme = true) {
|
||||
val mock = BookThumbnailMock()
|
||||
BookItem(thumbnail = mock.bookThumbnail)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,7 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
|
|
@ -19,6 +16,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
|
@ -40,22 +38,25 @@ import com.pixelized.biblib.utils.mock.BookMock
|
|||
|
||||
|
||||
@Composable
|
||||
fun DetailPageComposable(
|
||||
fun DetailPage(
|
||||
booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
||||
bookId: Int
|
||||
) {
|
||||
Box(
|
||||
Modifier.fillMaxWidth().fillMaxHeight()
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(),
|
||||
elevation = 4.dp
|
||||
) {
|
||||
val book by booksViewModel.getBookDetail(bookId).observeAsState()
|
||||
book?.let {
|
||||
DetailPageComposable(book = it)
|
||||
DetailPage(book = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DetailPageComposable(book: BookUio) {
|
||||
fun DetailPage(book: BookUio) {
|
||||
val typography = MaterialTheme.typography
|
||||
|
||||
Column(
|
||||
|
|
@ -81,32 +82,20 @@ fun DetailPageComposable(book: BookUio) {
|
|||
)
|
||||
Row(modifier = Modifier.padding(bottom = 16.dp)) {
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 4.dp),
|
||||
onClick = { }) {
|
||||
Icon(imageVector = Icons.Default.Download, contentDescription = "")
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(text = stringResource(id = R.string.action_epub))
|
||||
}
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
imageVector = Icons.Default.Download,
|
||||
text = stringResource(id = R.string.action_epub)
|
||||
) { }
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 4.dp),
|
||||
onClick = { }) {
|
||||
Icon(imageVector = Icons.Default.Download, contentDescription = "")
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(text = stringResource(id = R.string.action_mobi))
|
||||
}
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
imageVector = Icons.Default.Download,
|
||||
text = stringResource(id = R.string.action_mobi),
|
||||
) { }
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 4.dp),
|
||||
onClick = { }) {
|
||||
Icon(imageVector = Icons.Default.Send, contentDescription = "")
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(text = stringResource(id = R.string.action_send))
|
||||
}
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
imageVector = Icons.Default.Send,
|
||||
text = stringResource(id = R.string.action_send),
|
||||
) { }
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier
|
||||
|
|
@ -124,62 +113,29 @@ fun DetailPageComposable(book: BookUio) {
|
|||
text = book.author,
|
||||
)
|
||||
Row(modifier = Modifier.padding(bottom = 8.dp)) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
style = typography.body2,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = stringResource(id = R.string.detail_rating),
|
||||
)
|
||||
Text(
|
||||
style = typography.body1,
|
||||
text = book.rating.toString(),
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
style = typography.body2,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = stringResource(id = R.string.detail_language),
|
||||
)
|
||||
Text(
|
||||
style = typography.body1,
|
||||
text = book.language,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
style = typography.body2,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = stringResource(id = R.string.detail_release),
|
||||
)
|
||||
Text(
|
||||
style = typography.body1,
|
||||
text = book.date,
|
||||
)
|
||||
}
|
||||
TitleLabel(
|
||||
title = stringResource(id = R.string.detail_rating),
|
||||
label = book.rating.toString(),
|
||||
)
|
||||
TitleLabel(
|
||||
title = stringResource(id = R.string.detail_language),
|
||||
label = book.language,
|
||||
)
|
||||
TitleLabel(
|
||||
title = stringResource(id = R.string.detail_release),
|
||||
label = book.date,
|
||||
)
|
||||
}
|
||||
Row(modifier = Modifier.padding(bottom = 16.dp)) {
|
||||
TitleLabel(
|
||||
title = stringResource(id = R.string.detail_genre),
|
||||
label = book.genre,
|
||||
)
|
||||
TitleLabel(
|
||||
title = stringResource(id = R.string.detail_series),
|
||||
label = book.series ?: "-",
|
||||
)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
style = typography.body2,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = stringResource(id = R.string.detail_series),
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = 16.dp),
|
||||
style = typography.body1,
|
||||
text = book.series ?: "-",
|
||||
)
|
||||
Text(
|
||||
style = typography.body1,
|
||||
text = book.description,
|
||||
|
|
@ -188,12 +144,51 @@ fun DetailPageComposable(book: BookUio) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.TitleLabel(
|
||||
title: String,
|
||||
label: String,
|
||||
) {
|
||||
val typography = MaterialTheme.typography
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
style = typography.body2,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = title,
|
||||
)
|
||||
Text(
|
||||
style = typography.body1,
|
||||
text = label,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.Button(
|
||||
modifier: Modifier = Modifier,
|
||||
imageVector: ImageVector,
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Button(
|
||||
modifier = modifier.weight(1f),
|
||||
onClick = onClick
|
||||
) {
|
||||
Icon(imageVector = imageVector, contentDescription = "")
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(text = text)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun DetailPageComposablePreview() {
|
||||
fun DetailPageLightPreview() {
|
||||
Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||
BibLibTheme {
|
||||
val mock = BookMock()
|
||||
DetailPageComposable(mock.book)
|
||||
DetailPage(mock.book)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,6 @@ import androidx.compose.foundation.gestures.Orientation
|
|||
import androidx.compose.foundation.gestures.scrollable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
|
@ -15,7 +13,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.items
|
||||
import com.pixelized.biblib.ui.composable.items.BookThumbnailComposable
|
||||
import com.pixelized.biblib.ui.composable.items.BookItem
|
||||
import com.pixelized.biblib.ui.data.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.viewmodel.book.IBooksViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel
|
||||
|
|
@ -23,7 +21,7 @@ import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigab
|
|||
|
||||
|
||||
@Composable
|
||||
fun HomePageComposable(
|
||||
fun HomePage(
|
||||
navigationViewModel: INavigationViewModel,
|
||||
booksViewModel: IBooksViewModel,
|
||||
page: Page?,
|
||||
|
|
@ -32,7 +30,7 @@ fun HomePageComposable(
|
|||
currentPage = page as? Page.Home ?: currentPage
|
||||
|
||||
// https://issuetracker.google.com/issues/177245496
|
||||
val data: LazyPagingItems<BookThumbnailUio> = when(currentPage) {
|
||||
val data: LazyPagingItems<BookThumbnailUio> = when (currentPage) {
|
||||
is Page.Home.New -> booksViewModel.books.collectAsLazyPagingItems()
|
||||
else -> booksViewModel.news.collectAsLazyPagingItems()
|
||||
}
|
||||
|
|
@ -47,12 +45,7 @@ fun HomePageComposable(
|
|||
state = lazyListState,
|
||||
) {
|
||||
items(data) { thumbnail ->
|
||||
BookThumbnailComposable(
|
||||
thumbnail = thumbnail!!,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
) { item ->
|
||||
BookItem(thumbnail = thumbnail!!) { item ->
|
||||
navigationViewModel.navigateTo(Page.Detail(item.id))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.pixelized.biblib.ui.composable.screen
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.biblib.ui.composable.common.BibLibDrawer
|
||||
import com.pixelized.biblib.ui.composable.common.BibLibToolbar
|
||||
import com.pixelized.biblib.ui.composable.pages.DetailPage
|
||||
import com.pixelized.biblib.ui.composable.pages.HomePage
|
||||
import com.pixelized.biblib.ui.theme.Animation
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.viewmodel.book.BooksViewModel
|
||||
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
|
||||
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
||||
booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
||||
) {
|
||||
// navigation
|
||||
val page by navigationViewModel.page.observeAsState()
|
||||
// coroutine
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
// scaffold & toolbar
|
||||
val canNavigateBack = page !is Page.Home
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
|
||||
// TODO:
|
||||
LaunchedEffect(key1 = "MainScreen", block = {
|
||||
navigationViewModel.navigateTo(Page.Home.New, true)
|
||||
})
|
||||
|
||||
Scaffold(
|
||||
scaffoldState = scaffoldState,
|
||||
topBar = {
|
||||
BibLibToolbar(
|
||||
canNavigateBack,
|
||||
onBackPress = {
|
||||
navigationViewModel.navigateBack()
|
||||
},
|
||||
onDrawerPress = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.open() }
|
||||
}
|
||||
)
|
||||
},
|
||||
drawerContent = {
|
||||
BibLibDrawer(
|
||||
onNewClick = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||
},
|
||||
onBookClick = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||
},
|
||||
onSeriesClick = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||
},
|
||||
onAuthorClick = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||
},
|
||||
onTagClick = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||
},
|
||||
)
|
||||
}
|
||||
) {
|
||||
HomePage(
|
||||
navigationViewModel = navigationViewModel,
|
||||
booksViewModel = booksViewModel,
|
||||
page = page,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = page is Page.Detail,
|
||||
initiallyVisible = false,
|
||||
enter = slideInVertically(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
initialOffsetY = { height -> height },
|
||||
),
|
||||
exit = slideOutVertically(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
targetOffsetY = { height -> height },
|
||||
),
|
||||
) {
|
||||
// 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 { DetailPage(booksViewModel, it.bookId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun MainScreenComposablePreview() {
|
||||
BibLibTheme {
|
||||
HomeScreen(
|
||||
INavigationViewModel.Mock(page = Page.Home.New),
|
||||
IBooksViewModel.Mock()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,10 +38,10 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.network.client.IBibLibClient.Companion.REGISTER_URL
|
||||
import com.pixelized.biblib.ui.composable.items.dialog.CrossFadeOverlay
|
||||
import com.pixelized.biblib.ui.composable.items.dialog.ErrorCard
|
||||
import com.pixelized.biblib.ui.composable.items.dialog.LoadingCard
|
||||
import com.pixelized.biblib.ui.composable.items.dialog.SuccessCard
|
||||
import com.pixelized.biblib.ui.composable.dialog.CrossFadeOverlay
|
||||
import com.pixelized.biblib.ui.composable.dialog.ErrorCard
|
||||
import com.pixelized.biblib.ui.composable.dialog.LoadingCard
|
||||
import com.pixelized.biblib.ui.composable.dialog.SuccessCard
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.viewmodel.authentication.AuthenticationViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.authentication.IAuthenticationViewModel
|
||||
|
|
@ -58,7 +58,7 @@ import kotlinx.coroutines.delay
|
|||
private const val LE_LOAD_BOOK = "LE_LOAD_BOOK"
|
||||
|
||||
@Composable
|
||||
fun LoginScreenComposable(
|
||||
fun LoginScreen(
|
||||
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
||||
credentialViewModel: ICredentialViewModel = viewModel<CredentialViewModel>(),
|
||||
authenticationViewModel: IAuthenticationViewModel = viewModel<AuthenticationViewModel>(),
|
||||
|
|
@ -76,13 +76,13 @@ fun LoginScreenComposable(
|
|||
authenticationViewModel,
|
||||
bookViewModel
|
||||
)
|
||||
LoginScreenContentComposable(credentialViewModel, authenticationViewModel)
|
||||
LoginScreenDialogComposable(authenticationViewModel, bookViewModel)
|
||||
Content(credentialViewModel, authenticationViewModel)
|
||||
Dialogs(authenticationViewModel, bookViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoginScreenContentComposable(
|
||||
private fun Content(
|
||||
credentialViewModel: ICredentialViewModel,
|
||||
authenticationViewModel: IAuthenticationViewModel,
|
||||
) {
|
||||
|
|
@ -103,7 +103,7 @@ private fun LoginScreenContentComposable(
|
|||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun LoginScreenDialogComposable(
|
||||
private fun Dialogs(
|
||||
authenticationViewModel: IAuthenticationViewModel,
|
||||
booksViewModel: IBooksViewModel,
|
||||
) {
|
||||
|
|
@ -383,7 +383,7 @@ private fun CredentialRemember(
|
|||
@Composable
|
||||
fun LoginScreenComposablePreview() {
|
||||
BibLibTheme {
|
||||
LoginScreenComposable(
|
||||
LoginScreen(
|
||||
navigationViewModel = INavigationViewModel.Mock(),
|
||||
credentialViewModel = ICredentialViewModel.Mock(),
|
||||
authenticationViewModel = IAuthenticationViewModel.Mock(),
|
||||
|
|
@ -1,285 +0,0 @@
|
|||
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.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.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 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
|
||||
import com.pixelized.biblib.ui.theme.Animation
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.viewmodel.book.BooksViewModel
|
||||
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)
|
||||
@Composable
|
||||
fun MainScreenComposable(
|
||||
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
||||
booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
||||
) {
|
||||
// navigation
|
||||
val page by navigationViewModel.page.observeAsState()
|
||||
// coroutine
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
// scaffold & toolbar
|
||||
val canNavigateBack = page !is Page.Home
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
|
||||
// TODO:
|
||||
LaunchedEffect(key1 = "MainScreen", block = {
|
||||
navigationViewModel.navigateTo(Page.Home.New, true)
|
||||
})
|
||||
|
||||
Scaffold(
|
||||
scaffoldState = scaffoldState,
|
||||
topBar = {
|
||||
ToolbarComposable(
|
||||
canNavigateBack,
|
||||
onBackPress = {
|
||||
navigationViewModel.navigateBack()
|
||||
},
|
||||
onDrawerPress = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.open() }
|
||||
}
|
||||
)
|
||||
},
|
||||
drawerContent = {
|
||||
DrawerContentComposable(
|
||||
onNewClick = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||
},
|
||||
onBookClick = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||
},
|
||||
onSeriesClick = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||
},
|
||||
onAuthorClick = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||
},
|
||||
onTagClick = {
|
||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||
},
|
||||
)
|
||||
}
|
||||
) {
|
||||
HomePageComposable(
|
||||
navigationViewModel = navigationViewModel,
|
||||
booksViewModel = booksViewModel,
|
||||
page = page,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = page is Page.Detail,
|
||||
initiallyVisible = false,
|
||||
enter = slideInVertically(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
initialOffsetY = { height -> height },
|
||||
),
|
||||
exit = slideOutVertically(
|
||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||
targetOffsetY = { height -> height },
|
||||
),
|
||||
) {
|
||||
// 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(
|
||||
canNavigateBack: Boolean,
|
||||
onBackPress: () -> Unit,
|
||||
onDrawerPress: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(stringResource(id = R.string.app_name))
|
||||
},
|
||||
navigationIcon = {
|
||||
NavigationIcon(
|
||||
canNavigateBack,
|
||||
onBackPress,
|
||||
onDrawerPress,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
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(
|
||||
onNewClick: () -> Unit,
|
||||
onBookClick: () -> Unit,
|
||||
onSeriesClick: () -> Unit,
|
||||
onAuthorClick: () -> Unit,
|
||||
onTagClick: () -> Unit,
|
||||
) {
|
||||
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 = onNewClick,
|
||||
)
|
||||
DrawerLink(
|
||||
text = "Livres",
|
||||
imageVector = Icons.Default.AutoStories,
|
||||
onClick = onBookClick,
|
||||
)
|
||||
DrawerLink(
|
||||
text = "Séries",
|
||||
imageVector = Icons.Default.AutoAwesomeMotion,
|
||||
onClick = onSeriesClick,
|
||||
)
|
||||
DrawerLink(
|
||||
text = "Auteurs",
|
||||
imageVector = Icons.Default.SupervisorAccount,
|
||||
onClick = onAuthorClick,
|
||||
)
|
||||
DrawerLink(
|
||||
text = "Étiquettes",
|
||||
imageVector = Icons.Default.LocalOffer,
|
||||
onClick = onTagClick,
|
||||
)
|
||||
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(false, {}, {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ToolbarComposableLightPreview() {
|
||||
BibLibTheme(darkTheme = true) {
|
||||
ToolbarComposable(true, {}, {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun MainScreenComposablePreview() {
|
||||
BibLibTheme {
|
||||
MainScreenComposable(
|
||||
INavigationViewModel.Mock(page = Page.Home.New),
|
||||
IBooksViewModel.Mock()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import androidx.compose.animation.core.tween
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
|
|
@ -16,8 +17,8 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.biblib.BuildConfig
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.composable.items.dialog.CrossFadeOverlay
|
||||
import com.pixelized.biblib.ui.composable.items.dialog.ErrorCard
|
||||
import com.pixelized.biblib.ui.composable.dialog.CrossFadeOverlay
|
||||
import com.pixelized.biblib.ui.composable.dialog.ErrorCard
|
||||
import com.pixelized.biblib.ui.theme.Animation
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.viewmodel.authentication.AuthenticationViewModel
|
||||
|
|
@ -30,29 +31,14 @@ import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
|
|||
import kotlinx.coroutines.delay
|
||||
import java.util.*
|
||||
|
||||
private const val LAUNCH_EFFECT_AUTHENTICATION = "LAUNCH_EFFECT_AUTHENTICATION"
|
||||
private const val LAUNCH_EFFECT_ENTER = "LAUNCH_EFFECT_ENTER"
|
||||
private const val LAUNCH_EFFECT_EXIT = "LAUNCH_EFFECT_EXIT"
|
||||
private const val LAUNCH_EFFECT_BOOK = "LAUNCH_EFFECT_BOOK"
|
||||
private const val LAUNCH_EFFECT_NAVIGATION = "LAUNCH_EFFECT_NAVIGATION"
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SplashScreenComposablePreview() {
|
||||
BibLibTheme {
|
||||
val authenticationViewModel = IAuthenticationViewModel.Mock()
|
||||
val bookViewModel = IBooksViewModel.Mock()
|
||||
val navigation = INavigationViewModel.Mock()
|
||||
SplashScreenComposable(
|
||||
authenticationViewModel = authenticationViewModel,
|
||||
bookViewModel = bookViewModel,
|
||||
navigationViewModel = navigation,
|
||||
initiallyVisible = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun SplashScreenComposable(
|
||||
fun SplashScreen(
|
||||
authenticationViewModel: IAuthenticationViewModel = viewModel<AuthenticationViewModel>(),
|
||||
bookViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
||||
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
||||
|
|
@ -60,23 +46,24 @@ fun SplashScreenComposable(
|
|||
) {
|
||||
val authenticationState: IAuthenticationViewModel.State? by authenticationViewModel.state.observeAsState()
|
||||
val bookState by bookViewModel.state.observeAsState()
|
||||
val contentVisible = remember { mutableStateOf(false) }
|
||||
|
||||
val contentVisibility = remember { mutableStateOf(false) }
|
||||
|
||||
// Content
|
||||
ContentComposable(
|
||||
visible = contentVisible.value,
|
||||
Content(
|
||||
visible = contentVisibility.value,
|
||||
initiallyVisible = initiallyVisible
|
||||
)
|
||||
|
||||
// Dialog
|
||||
AuthenticationError(state = authenticationState as? IAuthenticationViewModel.State.Error)
|
||||
BookError(state = bookState as? IBooksViewModel.State.Error)
|
||||
AuthenticationError(
|
||||
state = authenticationState as? IAuthenticationViewModel.State.Error
|
||||
)
|
||||
BookError(
|
||||
state = bookState as? IBooksViewModel.State.Error
|
||||
)
|
||||
|
||||
LaunchedEffect(LAUNCH_EFFECT_AUTHENTICATION) {
|
||||
delay(Animation.SHORT_DURATION.toLong())
|
||||
contentVisible.value = true
|
||||
delay(Animation.LONG_DURATION.toLong())
|
||||
delay(Animation.SHORT_DURATION.toLong())
|
||||
HandleEnterAnimation(contentVisibility) {
|
||||
authenticationViewModel.autoLogin()
|
||||
}
|
||||
|
||||
|
|
@ -86,16 +73,12 @@ fun SplashScreenComposable(
|
|||
bookViewModel.updateBooks()
|
||||
}
|
||||
(bookState as? IBooksViewModel.State.Finished)?.let {
|
||||
LaunchedEffect(LAUNCH_EFFECT_NAVIGATION) {
|
||||
contentVisible.value = false
|
||||
delay(Animation.LONG_DURATION.toLong())
|
||||
HandleExitAnimation(contentVisibility) {
|
||||
navigationViewModel.navigateTo(Screen.MainScreen)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LaunchedEffect(LAUNCH_EFFECT_NAVIGATION) {
|
||||
contentVisible.value = false
|
||||
delay(Animation.LONG_DURATION.toLong())
|
||||
HandleExitAnimation(contentVisibility) {
|
||||
navigationViewModel.navigateTo(Screen.LoginScreen)
|
||||
}
|
||||
}
|
||||
|
|
@ -104,81 +87,83 @@ fun SplashScreenComposable(
|
|||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun ContentComposable(
|
||||
private fun Content(
|
||||
duration: Int = Animation.LONG_DURATION,
|
||||
visible: Boolean = false,
|
||||
initiallyVisible: Boolean = false,
|
||||
) {
|
||||
val typography = MaterialTheme.typography
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
) {
|
||||
Column(
|
||||
Surface {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(240.dp)
|
||||
.align(Alignment.Center)
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
initiallyVisible = initiallyVisible,
|
||||
enter = fadeIn(animationSpec = tween(duration))
|
||||
+ slideInVertically(
|
||||
initialOffsetY = { height -> -height },
|
||||
animationSpec = tween(duration)
|
||||
),
|
||||
exit = fadeOut(animationSpec = tween(duration))
|
||||
+ slideOutVertically(
|
||||
targetOffsetY = { height -> -height },
|
||||
animationSpec = tween(duration)
|
||||
),
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.width(240.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
style = typography.h4,
|
||||
text = stringResource(id = R.string.splash_welcome)
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
initiallyVisible = initiallyVisible,
|
||||
enter = fadeIn(animationSpec = tween(duration))
|
||||
+ slideInVertically(
|
||||
initialOffsetY = { height -> -height },
|
||||
animationSpec = tween(duration)
|
||||
),
|
||||
exit = fadeOut(animationSpec = tween(duration))
|
||||
+ slideOutVertically(
|
||||
targetOffsetY = { height -> -height },
|
||||
animationSpec = tween(duration)
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
style = typography.h4,
|
||||
text = stringResource(id = R.string.splash_welcome)
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
visible = visible,
|
||||
initiallyVisible = initiallyVisible,
|
||||
enter = fadeIn(animationSpec = tween(duration))
|
||||
+ slideInVertically(
|
||||
initialOffsetY = { height -> height },
|
||||
animationSpec = tween(duration)
|
||||
),
|
||||
exit = fadeOut(animationSpec = tween(duration))
|
||||
+ slideOutVertically(
|
||||
targetOffsetY = { height -> height },
|
||||
animationSpec = tween(duration)
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
style = typography.h4,
|
||||
text = stringResource(id = R.string.app_name)
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
visible = visible,
|
||||
initiallyVisible = initiallyVisible,
|
||||
enter = fadeIn(animationSpec = tween(duration))
|
||||
+ slideInVertically(
|
||||
initialOffsetY = { height -> height },
|
||||
animationSpec = tween(duration)
|
||||
),
|
||||
exit = fadeOut(animationSpec = tween(duration))
|
||||
+ slideOutVertically(
|
||||
targetOffsetY = { height -> height },
|
||||
animationSpec = tween(duration)
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
style = typography.h4,
|
||||
text = stringResource(id = R.string.app_name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.align(Alignment.BottomEnd),
|
||||
visible = visible,
|
||||
initiallyVisible = initiallyVisible,
|
||||
enter = fadeIn(animationSpec = tween(duration)),
|
||||
exit = fadeOut(animationSpec = tween(duration)),
|
||||
) {
|
||||
Text(
|
||||
style = typography.caption,
|
||||
text = stringResource(
|
||||
R.string.app_version,
|
||||
BuildConfig.BUILD_TYPE.toUpperCase(Locale.getDefault()),
|
||||
BuildConfig.VERSION_NAME,
|
||||
BuildConfig.VERSION_CODE
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.align(Alignment.BottomEnd),
|
||||
visible = visible,
|
||||
initiallyVisible = initiallyVisible,
|
||||
enter = fadeIn(animationSpec = tween(duration)),
|
||||
exit = fadeOut(animationSpec = tween(duration)),
|
||||
) {
|
||||
Text(
|
||||
style = typography.caption,
|
||||
text = stringResource(
|
||||
R.string.app_version,
|
||||
BuildConfig.BUILD_TYPE.toUpperCase(Locale.getDefault()),
|
||||
BuildConfig.VERSION_NAME,
|
||||
BuildConfig.VERSION_CODE
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -225,4 +210,39 @@ private fun BookError(state: IBooksViewModel.State.Error?) {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HandleEnterAnimation(contentVisibility: MutableState<Boolean>, then: suspend () -> Unit) {
|
||||
LaunchedEffect(LAUNCH_EFFECT_ENTER) {
|
||||
delay(Animation.SHORT_DURATION.toLong())
|
||||
contentVisibility.value = true
|
||||
delay(Animation.LONG_DURATION.toLong() + Animation.SHORT_DURATION.toLong())
|
||||
then()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HandleExitAnimation(contentVisibility: MutableState<Boolean>, then: suspend () -> Unit) {
|
||||
LaunchedEffect(LAUNCH_EFFECT_EXIT) {
|
||||
contentVisibility.value = false
|
||||
delay(Animation.LONG_DURATION.toLong())
|
||||
then()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SplashScreenPreview() {
|
||||
BibLibTheme(darkTheme = true) {
|
||||
val authenticationViewModel = IAuthenticationViewModel.Mock()
|
||||
val bookViewModel = IBooksViewModel.Mock()
|
||||
val navigation = INavigationViewModel.Mock()
|
||||
SplashScreen(
|
||||
authenticationViewModel = authenticationViewModel,
|
||||
bookViewModel = bookViewModel,
|
||||
navigationViewModel = navigation,
|
||||
initiallyVisible = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.pixelized.biblib.ui.theme
|
|||
|
||||
|
||||
object Animation {
|
||||
const val SHORT_DURATION = 300
|
||||
const val MEDIUM_DURATION = 500
|
||||
const val LONG_DURATION = 1000
|
||||
const val SHORT_DURATION = 250
|
||||
const val MEDIUM_DURATION = SHORT_DURATION * 2
|
||||
const val LONG_DURATION = MEDIUM_DURATION * 2
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,5 +35,6 @@
|
|||
<string name="detail_rating">Rating</string>
|
||||
<string name="detail_language">Language</string>
|
||||
<string name="detail_release">Release</string>
|
||||
<string name="detail_genre">Genre</string>
|
||||
<string name="detail_series">Series</string>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue