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.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.pixelized.biblib.ui.composable.screen.LoginScreenComposable
|
import com.pixelized.biblib.ui.composable.screen.LoginScreen
|
||||||
import com.pixelized.biblib.ui.composable.screen.MainScreenComposable
|
import com.pixelized.biblib.ui.composable.screen.HomeScreen
|
||||||
import com.pixelized.biblib.ui.composable.screen.SplashScreenComposable
|
import com.pixelized.biblib.ui.composable.screen.SplashScreen
|
||||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Screen
|
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Screen
|
||||||
import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
|
import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
|
||||||
|
|
@ -43,9 +43,9 @@ fun ContentComposable() {
|
||||||
|
|
||||||
Crossfade(targetState = main) {
|
Crossfade(targetState = main) {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Screen.SplashScreen -> SplashScreenComposable()
|
is Screen.SplashScreen -> SplashScreen()
|
||||||
is Screen.LoginScreen -> LoginScreenComposable()
|
is Screen.LoginScreen -> LoginScreen()
|
||||||
is Screen.MainScreen -> MainScreenComposable()
|
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.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
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.Column
|
||||||
import androidx.compose.foundation.layout.height
|
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.Column
|
||||||
import androidx.compose.foundation.layout.padding
|
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.Column
|
||||||
import androidx.compose.foundation.layout.height
|
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.res.painterResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.biblib.R
|
import com.pixelized.biblib.R
|
||||||
import com.pixelized.biblib.ui.data.BookThumbnailUio
|
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.injection.Bob
|
||||||
import com.pixelized.biblib.utils.mock.BookThumbnailMock
|
import com.pixelized.biblib.utils.mock.BookThumbnailMock
|
||||||
|
|
||||||
|
private val THUMBNAIL_WIDTH: Dp = 60.dp
|
||||||
@Preview
|
private val THUMBNAIL_HEIGHT: Dp = 96.dp
|
||||||
@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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookThumbnailComposable(
|
fun BookItem(
|
||||||
thumbnail: BookThumbnailUio,
|
thumbnail: BookThumbnailUio,
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onClick: ((BookThumbnailUio) -> Unit)? = null,
|
onClick: ((BookThumbnailUio) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val typography = MaterialTheme.typography
|
val typography = MaterialTheme.typography
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = modifier.clickable {
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.clickable {
|
||||||
onClick?.invoke(thumbnail)
|
onClick?.invoke(thumbnail)
|
||||||
},
|
},
|
||||||
elevation = 4.dp,
|
elevation = 4.dp,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.height(96.dp),
|
modifier = Modifier.height(THUMBNAIL_HEIGHT),
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
contentModifier = Modifier
|
contentModifier = Modifier
|
||||||
.width(60.dp)
|
.size(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
|
||||||
.fillMaxHeight()
|
|
||||||
.clip(RoundedCornerShape(4.dp)),
|
.clip(RoundedCornerShape(4.dp)),
|
||||||
placeHolder = painterResource(id = R.drawable.ic_launcher_foreground),
|
placeHolder = painterResource(id = R.drawable.ic_launcher_foreground),
|
||||||
contentScale = ContentScale.FillBounds,
|
contentScale = ContentScale.FillBounds,
|
||||||
|
|
@ -77,7 +62,9 @@ fun BookThumbnailComposable(
|
||||||
contentDescription = thumbnail.title
|
contentDescription = thumbnail.title
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(8.dp).weight(1f)
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.weight(1f)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
style = typography.h6,
|
style = typography.h6,
|
||||||
|
|
@ -91,17 +78,19 @@ fun BookThumbnailComposable(
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
)
|
)
|
||||||
Row(modifier = Modifier.weight(1f), verticalAlignment = Alignment.Bottom) {
|
Row(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalAlignment = Alignment.Bottom
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
style = typography.caption,
|
style = typography.caption,
|
||||||
text = thumbnail.genre,
|
text = thumbnail.genre,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Spacer(
|
Spacer(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(0.dp)
|
.widthIn(min = 4.dp)
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -114,8 +103,28 @@ fun BookThumbnailComposable(
|
||||||
}
|
}
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.align(Alignment.CenterVertically),
|
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.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.Button
|
import androidx.compose.material.*
|
||||||
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.Icons
|
||||||
import androidx.compose.material.icons.filled.Download
|
import androidx.compose.material.icons.filled.Download
|
||||||
import androidx.compose.material.icons.filled.Send
|
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.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
|
@ -40,22 +38,25 @@ import com.pixelized.biblib.utils.mock.BookMock
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DetailPageComposable(
|
fun DetailPage(
|
||||||
booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
||||||
bookId: Int
|
bookId: Int
|
||||||
) {
|
) {
|
||||||
Box(
|
Surface(
|
||||||
Modifier.fillMaxWidth().fillMaxHeight()
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight(),
|
||||||
|
elevation = 4.dp
|
||||||
) {
|
) {
|
||||||
val book by booksViewModel.getBookDetail(bookId).observeAsState()
|
val book by booksViewModel.getBookDetail(bookId).observeAsState()
|
||||||
book?.let {
|
book?.let {
|
||||||
DetailPageComposable(book = it)
|
DetailPage(book = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DetailPageComposable(book: BookUio) {
|
fun DetailPage(book: BookUio) {
|
||||||
val typography = MaterialTheme.typography
|
val typography = MaterialTheme.typography
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -81,32 +82,20 @@ fun DetailPageComposable(book: BookUio) {
|
||||||
)
|
)
|
||||||
Row(modifier = Modifier.padding(bottom = 16.dp)) {
|
Row(modifier = Modifier.padding(bottom = 16.dp)) {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
.weight(1f)
|
imageVector = Icons.Default.Download,
|
||||||
.padding(end = 4.dp),
|
text = stringResource(id = R.string.action_epub)
|
||||||
onClick = { }) {
|
) { }
|
||||||
Icon(imageVector = Icons.Default.Download, contentDescription = "")
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(text = stringResource(id = R.string.action_epub))
|
|
||||||
}
|
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(horizontal = 4.dp),
|
||||||
.weight(1f)
|
imageVector = Icons.Default.Download,
|
||||||
.padding(horizontal = 4.dp),
|
text = stringResource(id = R.string.action_mobi),
|
||||||
onClick = { }) {
|
) { }
|
||||||
Icon(imageVector = Icons.Default.Download, contentDescription = "")
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(text = stringResource(id = R.string.action_mobi))
|
|
||||||
}
|
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(start = 4.dp),
|
||||||
.weight(1f)
|
imageVector = Icons.Default.Send,
|
||||||
.padding(start = 4.dp),
|
text = stringResource(id = R.string.action_send),
|
||||||
onClick = { }) {
|
) { }
|
||||||
Icon(imageVector = Icons.Default.Send, contentDescription = "")
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(text = stringResource(id = R.string.action_send))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -124,62 +113,29 @@ fun DetailPageComposable(book: BookUio) {
|
||||||
text = book.author,
|
text = book.author,
|
||||||
)
|
)
|
||||||
Row(modifier = Modifier.padding(bottom = 8.dp)) {
|
Row(modifier = Modifier.padding(bottom = 8.dp)) {
|
||||||
Column(
|
TitleLabel(
|
||||||
modifier = Modifier.weight(1f),
|
title = stringResource(id = R.string.detail_rating),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
label = book.rating.toString(),
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
style = typography.body2,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
text = stringResource(id = R.string.detail_rating),
|
|
||||||
)
|
)
|
||||||
Text(
|
TitleLabel(
|
||||||
style = typography.body1,
|
title = stringResource(id = R.string.detail_language),
|
||||||
text = book.rating.toString(),
|
label = book.language,
|
||||||
|
)
|
||||||
|
TitleLabel(
|
||||||
|
title = stringResource(id = R.string.detail_release),
|
||||||
|
label = book.date,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Column(
|
Row(modifier = Modifier.padding(bottom = 16.dp)) {
|
||||||
modifier = Modifier.weight(1f),
|
TitleLabel(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
title = stringResource(id = R.string.detail_genre),
|
||||||
) {
|
label = book.genre,
|
||||||
Text(
|
|
||||||
style = typography.body2,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
text = stringResource(id = R.string.detail_language),
|
|
||||||
)
|
)
|
||||||
Text(
|
TitleLabel(
|
||||||
style = typography.body1,
|
title = stringResource(id = R.string.detail_series),
|
||||||
text = book.language,
|
label = book.series ?: "-",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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(
|
Text(
|
||||||
style = typography.body1,
|
style = typography.body1,
|
||||||
text = book.description,
|
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
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun DetailPageComposablePreview() {
|
fun DetailPageLightPreview() {
|
||||||
Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
|
Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||||
BibLibTheme {
|
BibLibTheme {
|
||||||
val mock = BookMock()
|
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.gestures.scrollable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
|
@ -15,7 +13,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
import androidx.paging.compose.items
|
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.data.BookThumbnailUio
|
||||||
import com.pixelized.biblib.ui.viewmodel.book.IBooksViewModel
|
import com.pixelized.biblib.ui.viewmodel.book.IBooksViewModel
|
||||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel
|
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel
|
||||||
|
|
@ -23,7 +21,7 @@ import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigab
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomePageComposable(
|
fun HomePage(
|
||||||
navigationViewModel: INavigationViewModel,
|
navigationViewModel: INavigationViewModel,
|
||||||
booksViewModel: IBooksViewModel,
|
booksViewModel: IBooksViewModel,
|
||||||
page: Page?,
|
page: Page?,
|
||||||
|
|
@ -47,12 +45,7 @@ fun HomePageComposable(
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
) {
|
) {
|
||||||
items(data) { thumbnail ->
|
items(data) { thumbnail ->
|
||||||
BookThumbnailComposable(
|
BookItem(thumbnail = thumbnail!!) { item ->
|
||||||
thumbnail = thumbnail!!,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.wrapContentHeight(),
|
|
||||||
) { item ->
|
|
||||||
navigationViewModel.navigateTo(Page.Detail(item.id))
|
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 androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.pixelized.biblib.R
|
import com.pixelized.biblib.R
|
||||||
import com.pixelized.biblib.network.client.IBibLibClient.Companion.REGISTER_URL
|
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.dialog.CrossFadeOverlay
|
||||||
import com.pixelized.biblib.ui.composable.items.dialog.ErrorCard
|
import com.pixelized.biblib.ui.composable.dialog.ErrorCard
|
||||||
import com.pixelized.biblib.ui.composable.items.dialog.LoadingCard
|
import com.pixelized.biblib.ui.composable.dialog.LoadingCard
|
||||||
import com.pixelized.biblib.ui.composable.items.dialog.SuccessCard
|
import com.pixelized.biblib.ui.composable.dialog.SuccessCard
|
||||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||||
import com.pixelized.biblib.ui.viewmodel.authentication.AuthenticationViewModel
|
import com.pixelized.biblib.ui.viewmodel.authentication.AuthenticationViewModel
|
||||||
import com.pixelized.biblib.ui.viewmodel.authentication.IAuthenticationViewModel
|
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"
|
private const val LE_LOAD_BOOK = "LE_LOAD_BOOK"
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreenComposable(
|
fun LoginScreen(
|
||||||
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
||||||
credentialViewModel: ICredentialViewModel = viewModel<CredentialViewModel>(),
|
credentialViewModel: ICredentialViewModel = viewModel<CredentialViewModel>(),
|
||||||
authenticationViewModel: IAuthenticationViewModel = viewModel<AuthenticationViewModel>(),
|
authenticationViewModel: IAuthenticationViewModel = viewModel<AuthenticationViewModel>(),
|
||||||
|
|
@ -76,13 +76,13 @@ fun LoginScreenComposable(
|
||||||
authenticationViewModel,
|
authenticationViewModel,
|
||||||
bookViewModel
|
bookViewModel
|
||||||
)
|
)
|
||||||
LoginScreenContentComposable(credentialViewModel, authenticationViewModel)
|
Content(credentialViewModel, authenticationViewModel)
|
||||||
LoginScreenDialogComposable(authenticationViewModel, bookViewModel)
|
Dialogs(authenticationViewModel, bookViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoginScreenContentComposable(
|
private fun Content(
|
||||||
credentialViewModel: ICredentialViewModel,
|
credentialViewModel: ICredentialViewModel,
|
||||||
authenticationViewModel: IAuthenticationViewModel,
|
authenticationViewModel: IAuthenticationViewModel,
|
||||||
) {
|
) {
|
||||||
|
|
@ -103,7 +103,7 @@ private fun LoginScreenContentComposable(
|
||||||
|
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoginScreenDialogComposable(
|
private fun Dialogs(
|
||||||
authenticationViewModel: IAuthenticationViewModel,
|
authenticationViewModel: IAuthenticationViewModel,
|
||||||
booksViewModel: IBooksViewModel,
|
booksViewModel: IBooksViewModel,
|
||||||
) {
|
) {
|
||||||
|
|
@ -383,7 +383,7 @@ private fun CredentialRemember(
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreenComposablePreview() {
|
fun LoginScreenComposablePreview() {
|
||||||
BibLibTheme {
|
BibLibTheme {
|
||||||
LoginScreenComposable(
|
LoginScreen(
|
||||||
navigationViewModel = INavigationViewModel.Mock(),
|
navigationViewModel = INavigationViewModel.Mock(),
|
||||||
credentialViewModel = ICredentialViewModel.Mock(),
|
credentialViewModel = ICredentialViewModel.Mock(),
|
||||||
authenticationViewModel = IAuthenticationViewModel.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.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
|
@ -16,8 +17,8 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.pixelized.biblib.BuildConfig
|
import com.pixelized.biblib.BuildConfig
|
||||||
import com.pixelized.biblib.R
|
import com.pixelized.biblib.R
|
||||||
import com.pixelized.biblib.ui.composable.items.dialog.CrossFadeOverlay
|
import com.pixelized.biblib.ui.composable.dialog.CrossFadeOverlay
|
||||||
import com.pixelized.biblib.ui.composable.items.dialog.ErrorCard
|
import com.pixelized.biblib.ui.composable.dialog.ErrorCard
|
||||||
import com.pixelized.biblib.ui.theme.Animation
|
import com.pixelized.biblib.ui.theme.Animation
|
||||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||||
import com.pixelized.biblib.ui.viewmodel.authentication.AuthenticationViewModel
|
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 kotlinx.coroutines.delay
|
||||||
import java.util.*
|
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_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)
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SplashScreenComposable(
|
fun SplashScreen(
|
||||||
authenticationViewModel: IAuthenticationViewModel = viewModel<AuthenticationViewModel>(),
|
authenticationViewModel: IAuthenticationViewModel = viewModel<AuthenticationViewModel>(),
|
||||||
bookViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
bookViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
||||||
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
||||||
|
|
@ -60,23 +46,24 @@ fun SplashScreenComposable(
|
||||||
) {
|
) {
|
||||||
val authenticationState: IAuthenticationViewModel.State? by authenticationViewModel.state.observeAsState()
|
val authenticationState: IAuthenticationViewModel.State? by authenticationViewModel.state.observeAsState()
|
||||||
val bookState by bookViewModel.state.observeAsState()
|
val bookState by bookViewModel.state.observeAsState()
|
||||||
val contentVisible = remember { mutableStateOf(false) }
|
|
||||||
|
val contentVisibility = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
ContentComposable(
|
Content(
|
||||||
visible = contentVisible.value,
|
visible = contentVisibility.value,
|
||||||
initiallyVisible = initiallyVisible
|
initiallyVisible = initiallyVisible
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dialog
|
// Dialog
|
||||||
AuthenticationError(state = authenticationState as? IAuthenticationViewModel.State.Error)
|
AuthenticationError(
|
||||||
BookError(state = bookState as? IBooksViewModel.State.Error)
|
state = authenticationState as? IAuthenticationViewModel.State.Error
|
||||||
|
)
|
||||||
|
BookError(
|
||||||
|
state = bookState as? IBooksViewModel.State.Error
|
||||||
|
)
|
||||||
|
|
||||||
LaunchedEffect(LAUNCH_EFFECT_AUTHENTICATION) {
|
HandleEnterAnimation(contentVisibility) {
|
||||||
delay(Animation.SHORT_DURATION.toLong())
|
|
||||||
contentVisible.value = true
|
|
||||||
delay(Animation.LONG_DURATION.toLong())
|
|
||||||
delay(Animation.SHORT_DURATION.toLong())
|
|
||||||
authenticationViewModel.autoLogin()
|
authenticationViewModel.autoLogin()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,16 +73,12 @@ fun SplashScreenComposable(
|
||||||
bookViewModel.updateBooks()
|
bookViewModel.updateBooks()
|
||||||
}
|
}
|
||||||
(bookState as? IBooksViewModel.State.Finished)?.let {
|
(bookState as? IBooksViewModel.State.Finished)?.let {
|
||||||
LaunchedEffect(LAUNCH_EFFECT_NAVIGATION) {
|
HandleExitAnimation(contentVisibility) {
|
||||||
contentVisible.value = false
|
|
||||||
delay(Animation.LONG_DURATION.toLong())
|
|
||||||
navigationViewModel.navigateTo(Screen.MainScreen)
|
navigationViewModel.navigateTo(Screen.MainScreen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LaunchedEffect(LAUNCH_EFFECT_NAVIGATION) {
|
HandleExitAnimation(contentVisibility) {
|
||||||
contentVisible.value = false
|
|
||||||
delay(Animation.LONG_DURATION.toLong())
|
|
||||||
navigationViewModel.navigateTo(Screen.LoginScreen)
|
navigationViewModel.navigateTo(Screen.LoginScreen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,13 +87,14 @@ fun SplashScreenComposable(
|
||||||
|
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentComposable(
|
private fun Content(
|
||||||
duration: Int = Animation.LONG_DURATION,
|
duration: Int = Animation.LONG_DURATION,
|
||||||
visible: Boolean = false,
|
visible: Boolean = false,
|
||||||
initiallyVisible: Boolean = false,
|
initiallyVisible: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val typography = MaterialTheme.typography
|
val typography = MaterialTheme.typography
|
||||||
|
|
||||||
|
Surface {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
|
|
@ -182,6 +166,7 @@ private fun ContentComposable(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -226,3 +211,38 @@ 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 {
|
object Animation {
|
||||||
const val SHORT_DURATION = 300
|
const val SHORT_DURATION = 250
|
||||||
const val MEDIUM_DURATION = 500
|
const val MEDIUM_DURATION = SHORT_DURATION * 2
|
||||||
const val LONG_DURATION = 1000
|
const val LONG_DURATION = MEDIUM_DURATION * 2
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,5 +35,6 @@
|
||||||
<string name="detail_rating">Rating</string>
|
<string name="detail_rating">Rating</string>
|
||||||
<string name="detail_language">Language</string>
|
<string name="detail_language">Language</string>
|
||||||
<string name="detail_release">Release</string>
|
<string name="detail_release">Release</string>
|
||||||
|
<string name="detail_genre">Genre</string>
|
||||||
<string name="detail_series">Series</string>
|
<string name="detail_series">Series</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue