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