diff --git a/app/build.gradle b/app/build.gradle index 7d26422..0890e64 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,8 +77,6 @@ android { excludes += '/META-INF/{AL2.0,LGPL2.1}' } } - - } dependencies { @@ -109,6 +107,7 @@ dependencies { // Accompanist implementation "com.google.accompanist:accompanist-systemuicontroller:0.24.3-alpha" implementation "com.google.accompanist:accompanist-insets:0.24.3-alpha" + implementation "com.google.accompanist:accompanist-drawablepainter:0.24.3-alpha" // Navigation implementation "androidx.navigation:navigation-compose:2.4.2" diff --git a/app/src/main/java/com/pixelized/biblib/ui/MainActivity.kt b/app/src/main/java/com/pixelized/biblib/ui/MainActivity.kt index 3650741..0316ca6 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/MainActivity.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/MainActivity.kt @@ -4,15 +4,19 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.runtime.getValue import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import com.google.accompanist.insets.ProvideWindowInsets -import com.pixelized.biblib.ui.launch.LauncherViewModel -import com.pixelized.biblib.ui.navigation.FullScreenNavHost -import com.pixelized.biblib.ui.navigation.Screen +import com.google.accompanist.systemuicontroller.SystemUiController +import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.pixelized.biblib.ui.composable.SystemThemeColor +import com.pixelized.biblib.ui.screen.launch.LauncherViewModel +import com.pixelized.biblib.ui.navigation.screen.ScreenNavHost +import com.pixelized.biblib.ui.navigation.screen.Screen import com.pixelized.biblib.ui.theme.BibLibTheme import dagger.hilt.android.AndroidEntryPoint @@ -40,9 +44,17 @@ class MainActivity : ComponentActivity() { BibLibTheme { ProvideWindowInsets { Surface(color = MaterialTheme.colors.background) { + // handle the system colors + val systemUiController: SystemUiController = rememberSystemUiController() + SystemThemeColor( + systemUiController = systemUiController, + statusDarkIcons = isSystemInDarkTheme().not(), + navigationDarkIcons = isSystemInDarkTheme().not(), + ) + // handle the main composition val loading by launcherViewModel.isLoading if (loading.not()) { - FullScreenNavHost( + ScreenNavHost( startDestination = Screen.Authentication ) } diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/AnimatedDelayer.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/AnimatedDelayer.kt deleted file mode 100644 index d3e45c0..0000000 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/AnimatedDelayer.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.pixelized.biblib.ui.composable - -import androidx.compose.runtime.Composable - -@Composable -fun AnimatedDelayer( - content: @Composable AnimatedDelayerScope.() -> Unit -) { - val scope = AnimatedDelayerScope() - scope.content() -} - -class AnimatedDelayerScope( - var delay: Delay = Delay() -) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/animation/AnimatedDelayer.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/animation/AnimatedDelayer.kt new file mode 100644 index 0000000..ea78796 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/animation/AnimatedDelayer.kt @@ -0,0 +1,28 @@ +package com.pixelized.biblib.ui.composable.animation + +import androidx.compose.runtime.Composable + +@Composable +fun AnimatedDelayer( + content: @Composable AnimatedDelayerScope.() -> Unit +) { + val scope = AnimatedDelayerScope() + scope.content() +} + +class AnimatedDelayerScope( + var delay: Delay = Delay() +) + +@JvmInline +value class Delay( + val value: Int = DELTA, +) { + operator fun inc(): Delay { + return Delay(value = value + DELTA) + } + + companion object { + private const val DELTA = 100 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/AnimatedOffset.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/animation/AnimatedOffset.kt similarity index 93% rename from app/src/main/java/com/pixelized/biblib/ui/composable/AnimatedOffset.kt rename to app/src/main/java/com/pixelized/biblib/ui/composable/animation/AnimatedOffset.kt index 531e19d..3207ffc 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/AnimatedOffset.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/animation/AnimatedOffset.kt @@ -1,4 +1,4 @@ -package com.pixelized.biblib.ui.composable +package com.pixelized.biblib.ui.composable.animation import androidx.compose.animation.core.* import androidx.compose.foundation.layout.Box @@ -115,19 +115,6 @@ private class TransitionData( val offset by offset } -@JvmInline -value class Delay( - val value: Int = DELTA, -) { - operator fun inc(): Delay { - return Delay(value = value + DELTA) - } - - companion object { - private const val DELTA = 100 - } -} - @Composable private inline fun rememberSavableMutableTransitionState( initialState: T, targetState: T diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/ErrorCard.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/ErrorCard.kt index 7e61b4b..bd7a9cb 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/ErrorCard.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/ErrorCard.kt @@ -35,8 +35,8 @@ fun ErrorCard( modifier = Modifier .padding(MaterialTheme.bibLib.dimen.medium) .sizeIn( - minWidth = MaterialTheme.bibLib.dimen.dialog.minWidth, - minHeight = MaterialTheme.bibLib.dimen.dialog.minHeight, + minWidth = MaterialTheme.bibLib.dimen.dialog.minimum.width, + minHeight = MaterialTheme.bibLib.dimen.dialog.minimum.height, ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/LoadingCard.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/LoadingCard.kt index 0a9e080..17290e1 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/LoadingCard.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/LoadingCard.kt @@ -31,8 +31,8 @@ fun LoadingCard( modifier = Modifier .padding(MaterialTheme.bibLib.dimen.medium) .sizeIn( - minWidth = MaterialTheme.bibLib.dimen.dialog.minWidth, - minHeight = MaterialTheme.bibLib.dimen.dialog.minHeight, + minWidth = MaterialTheme.bibLib.dimen.dialog.minimum.width, + minHeight = MaterialTheme.bibLib.dimen.dialog.minimum.height, ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/SuccesCard.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/SuccesCard.kt index 855c053..91f735c 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/SuccesCard.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/dialog/SuccesCard.kt @@ -32,8 +32,8 @@ fun SuccessCard( modifier = Modifier .padding(MaterialTheme.bibLib.dimen.medium) .sizeIn( - minWidth = MaterialTheme.bibLib.dimen.dialog.minWidth, - minHeight = MaterialTheme.bibLib.dimen.dialog.minHeight, + minWidth = MaterialTheme.bibLib.dimen.dialog.minimum.width, + minHeight = MaterialTheme.bibLib.dimen.dialog.minimum.height, ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, diff --git a/app/src/main/java/com/pixelized/biblib/ui/navigation/FullScreenNavHost.kt b/app/src/main/java/com/pixelized/biblib/ui/navigation/FullScreenNavHost.kt deleted file mode 100644 index d61da07..0000000 --- a/app/src/main/java/com/pixelized/biblib/ui/navigation/FullScreenNavHost.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.pixelized.biblib.ui.navigation - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.compositionLocalOf -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import com.google.accompanist.systemuicontroller.SystemUiController -import com.google.accompanist.systemuicontroller.rememberSystemUiController -import com.pixelized.biblib.ui.authentication.AuthenticationScreen -import com.pixelized.biblib.ui.composable.SystemThemeColor - -val LocalFullScreenNavHostController = compositionLocalOf { - error("LocalFullScreenNavHostController is not ready yet.") -} - -@Composable -fun FullScreenNavHost( - navHostController: NavHostController = rememberNavController(), - startDestination: Screen = Screen.Authentication -) { - val systemUiController: SystemUiController = rememberSystemUiController() - - CompositionLocalProvider(LocalFullScreenNavHostController provides navHostController) { - NavHost( - navController = navHostController, - startDestination = startDestination.route, - ) { - composable(Screen.Authentication.route) { - SystemThemeColor( - systemUiController = systemUiController, - statusDarkIcons = isSystemInDarkTheme().not(), - navigationDarkIcons = isSystemInDarkTheme().not(), - ) - AuthenticationScreen() - } - composable(Screen.Home.route) { - - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/navigation/page/Page.kt b/app/src/main/java/com/pixelized/biblib/ui/navigation/page/Page.kt new file mode 100644 index 0000000..b415030 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/navigation/page/Page.kt @@ -0,0 +1,47 @@ +package com.pixelized.biblib.ui.navigation.page + +import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.ui.graphics.vector.ImageVector +import com.pixelized.biblib.R + +sealed class Page( + val route: String, + val icon: ImageVector, + @StringRes val label: Int, +) { + object News : Page( + route = "news", + icon = Icons.Default.NewReleases, + label = R.string.menu_new, + ) + + object Books : Page( + route = "books", + icon = Icons.Default.AutoStories, + label = R.string.menu_book, + ) + + object Series : Page( + route = "series", + icon = Icons.Default.AutoAwesomeMotion, + label = R.string.menu_series, + ) + + object Author : Page( + route = "author", + icon = Icons.Default.SupervisorAccount, + label = R.string.menu_author, + ) + + object Tag : Page( + route = "tag", + icon = Icons.Default.LocalOffer, + label = R.string.menu_tag, + ) + + companion object { + val all = listOf(News, Books, Series, Author, Tag) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/navigation/page/PageNavHost.kt b/app/src/main/java/com/pixelized/biblib/ui/navigation/page/PageNavHost.kt new file mode 100644 index 0000000..4521e24 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/navigation/page/PageNavHost.kt @@ -0,0 +1,47 @@ +package com.pixelized.biblib.ui.navigation.page + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.pixelized.biblib.ui.screen.home.BooksPage +import com.pixelized.biblib.ui.screen.home.NewsPage + +val LocalPageNavHostController = compositionLocalOf { + error("LocalHomePageNavHostController is not ready yet.") +} + +@Composable +fun PageNavHost( + modifier: Modifier = Modifier, + navHostController: NavHostController = rememberNavController(), + startDestination: Page = Page.News +) { + CompositionLocalProvider(LocalPageNavHostController provides navHostController) { + NavHost( + modifier = modifier, + navController = navHostController, + startDestination = startDestination.route, + ) { + composable(Page.News.route) { + NewsPage() + } + composable(Page.Books.route) { + BooksPage() + } + composable(Page.Series.route) { + + } + composable(Page.Author.route) { + + } + composable(Page.Tag.route) { + + } + } + } +} diff --git a/app/src/main/java/com/pixelized/biblib/ui/navigation/Screen.kt b/app/src/main/java/com/pixelized/biblib/ui/navigation/screen/Screen.kt similarity index 78% rename from app/src/main/java/com/pixelized/biblib/ui/navigation/Screen.kt rename to app/src/main/java/com/pixelized/biblib/ui/navigation/screen/Screen.kt index 08e1554..2201aee 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/navigation/Screen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/navigation/screen/Screen.kt @@ -1,4 +1,4 @@ -package com.pixelized.biblib.ui.navigation +package com.pixelized.biblib.ui.navigation.screen sealed class Screen( val route: String, diff --git a/app/src/main/java/com/pixelized/biblib/ui/navigation/screen/ScreenNavHost.kt b/app/src/main/java/com/pixelized/biblib/ui/navigation/screen/ScreenNavHost.kt new file mode 100644 index 0000000..fcc98d0 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/navigation/screen/ScreenNavHost.kt @@ -0,0 +1,38 @@ +package com.pixelized.biblib.ui.navigation.screen + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.pixelized.biblib.ui.screen.authentication.AuthenticationScreen +import com.pixelized.biblib.ui.screen.home.HomeScreen + +val LocalScreenNavHostController = compositionLocalOf { + error("LocalFullScreenNavHostController is not ready yet.") +} + +@Composable +fun ScreenNavHost( + modifier: Modifier = Modifier, + navHostController: NavHostController = rememberNavController(), + startDestination: Screen = Screen.Authentication +) { + CompositionLocalProvider(LocalScreenNavHostController provides navHostController) { + NavHost( + modifier = modifier, + navController = navHostController, + startDestination = startDestination.route, + ) { + composable(Screen.Authentication.route) { + AuthenticationScreen() + } + composable(Screen.Home.route) { + HomeScreen() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/old/composable/common/BibLibDrawer.kt b/app/src/main/java/com/pixelized/biblib/ui/old/composable/common/BibLibDrawer.kt index 9d28554..b977b63 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/old/composable/common/BibLibDrawer.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/old/composable/common/BibLibDrawer.kt @@ -1,5 +1,7 @@ package com.pixelized.biblib.ui.old.composable.common +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CutCornerShape @@ -21,14 +23,14 @@ import androidx.compose.ui.unit.dp import com.pixelized.biblib.BuildConfig import com.pixelized.biblib.R import com.pixelized.biblib.model.user.User -import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.ui.old.viewmodel.user.IUserViewModel +import com.pixelized.biblib.ui.theme.BibLibTheme import java.util.* @Composable fun BibLibDrawer( - userViewModel : IUserViewModel, + userViewModel: IUserViewModel, onNewClick: () -> Unit = {}, onBookClick: () -> Unit = {}, onSeriesClick: () -> Unit = {}, @@ -95,7 +97,7 @@ fun BibLibDrawer( style = typography.caption, text = stringResource( R.string.app_version, - BuildConfig.BUILD_TYPE.toUpperCase(Locale.getDefault()), + BuildConfig.BUILD_TYPE.uppercase(Locale.getDefault()), BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE ) @@ -106,30 +108,34 @@ fun BibLibDrawer( @Composable private fun DrawerItem(text: String, imageVector: ImageVector, onClick: () -> Unit) { Row( - verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .height(52.dp) - .clickable { onClick() }) { + .clickable { onClick() }, + verticalAlignment = Alignment.CenterVertically, + ) { Icon( modifier = Modifier.padding(horizontal = 16.dp), imageVector = imageVector, - contentDescription = "" + contentDescription = null, ) Text( text = text, ) - Spacer(modifier = Modifier.weight(1f)) + Spacer( + modifier = Modifier.weight(1f) + ) Icon( modifier = Modifier.padding(end = 8.dp), imageVector = Icons.Default.NavigateNext, - contentDescription = "" + contentDescription = null, ) } } -@Preview @Composable +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES) fun BibLibDrawerLightPreview() { BibLibTheme { BibLibDrawer( diff --git a/app/src/main/java/com/pixelized/biblib/ui/old/composable/common/Image.kt b/app/src/main/java/com/pixelized/biblib/ui/old/composable/common/Image.kt index 28662b2..df6b0cb 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/old/composable/common/Image.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/old/composable/common/Image.kt @@ -66,23 +66,6 @@ private fun download( url: URL, ): State { val state = mutableStateOf(placeHolder) - val cache = EntryPointAccessors - .fromApplication(context, PersistenceModule::class.java) - .provideBitmapCache(context) - - coroutineScope.launch { - val resource = cache.readFromDisk(url)?.let { BitmapPainter(it.asImageBitmap()) } - if (resource != null) { - state.value = resource - } else { - cache.download(url) { downloaded -> - if (downloaded != null) { - cache.writeToDisk(url, downloaded) - state.value = BitmapPainter(downloaded.asImageBitmap()) - } - } - } - } return state } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/old/viewmodel/book/BooksViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/old/viewmodel/book/BooksViewModel.kt index 017a2b7..3c80015 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/old/viewmodel/book/BooksViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/old/viewmodel/book/BooksViewModel.kt @@ -1,6 +1,7 @@ package com.pixelized.biblib.ui.old.viewmodel.book import android.util.Log +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -14,6 +15,7 @@ import com.pixelized.biblib.network.client.IBibLibClient import com.pixelized.biblib.network.factory.BookFactory import com.pixelized.biblib.repository.apiCache.IAPICacheRepository import com.pixelized.biblib.repository.book.IBookRepository +import com.pixelized.biblib.ui.composable.StateUio import com.pixelized.biblib.ui.old.data.BookThumbnailUio import com.pixelized.biblib.ui.old.data.BookUio import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/app/src/main/java/com/pixelized/biblib/ui/old/viewmodel/navigation/NavigationViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/old/viewmodel/navigation/NavigationViewModel.kt index 75ed5ea..cdc793b 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/old/viewmodel/navigation/NavigationViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/old/viewmodel/navigation/NavigationViewModel.kt @@ -32,11 +32,11 @@ class NavigationViewModel : ViewModel(), INavigationViewModel { override fun navigateBack(): Boolean { return when (val navigable: Navigable? = popBackStack()) { is Page -> { - _page.postValue(navigable) + // _page.postValue(navigable) true } is Screen -> { - _screen.postValue(navigable) + // _screen.postValue(navigable) true } else -> false diff --git a/app/src/main/java/com/pixelized/biblib/ui/authentication/AuthenticationFormUIO.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationFormUIO.kt similarity index 86% rename from app/src/main/java/com/pixelized/biblib/ui/authentication/AuthenticationFormUIO.kt rename to app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationFormUIO.kt index 15e26f0..13cf86b 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/authentication/AuthenticationFormUIO.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationFormUIO.kt @@ -1,4 +1,4 @@ -package com.pixelized.biblib.ui.authentication +package com.pixelized.biblib.ui.screen.authentication import androidx.compose.runtime.Stable import androidx.compose.runtime.State diff --git a/app/src/main/java/com/pixelized/biblib/ui/authentication/AuthenticationScreen.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreen.kt similarity index 96% rename from app/src/main/java/com/pixelized/biblib/ui/authentication/AuthenticationScreen.kt rename to app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreen.kt index d730b17..0083dc4 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/authentication/AuthenticationScreen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreen.kt @@ -1,4 +1,4 @@ -package com.pixelized.biblib.ui.authentication +package com.pixelized.biblib.ui.screen.authentication import android.content.Intent import android.content.res.Configuration @@ -32,12 +32,12 @@ import androidx.navigation.NavHostController import com.google.accompanist.insets.systemBarsPadding import com.pixelized.biblib.R import com.pixelized.biblib.network.client.IBibLibClient -import com.pixelized.biblib.ui.composable.AnimatedDelayer -import com.pixelized.biblib.ui.composable.AnimatedOffset +import com.pixelized.biblib.ui.composable.animation.AnimatedDelayer +import com.pixelized.biblib.ui.composable.animation.AnimatedOffset import com.pixelized.biblib.ui.composable.StateUio import com.pixelized.biblib.ui.composable.StateUioHandler -import com.pixelized.biblib.ui.navigation.LocalFullScreenNavHostController -import com.pixelized.biblib.ui.navigation.Screen +import com.pixelized.biblib.ui.navigation.screen.LocalScreenNavHostController +import com.pixelized.biblib.ui.navigation.screen.Screen import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.ui.theme.color.GoogleColorPalette import com.pixelized.biblib.utils.extention.bibLib @@ -46,7 +46,7 @@ import com.pixelized.biblib.utils.extention.bibLib fun AuthenticationScreen( viewModel: AuthenticationViewModel = hiltViewModel(), ) { - val navHostController = LocalFullScreenNavHostController.current + val navHostController = LocalScreenNavHostController.current AuthenticationScreenContent( login = viewModel.form.login, diff --git a/app/src/main/java/com/pixelized/biblib/ui/authentication/AuthenticationViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationViewModel.kt similarity index 99% rename from app/src/main/java/com/pixelized/biblib/ui/authentication/AuthenticationViewModel.kt rename to app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationViewModel.kt index 962cfc0..18beb9a 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/authentication/AuthenticationViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationViewModel.kt @@ -1,4 +1,4 @@ -package com.pixelized.biblib.ui.authentication +package com.pixelized.biblib.ui.screen.authentication import android.content.Intent import android.util.Log diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/BooksPage.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/BooksPage.kt new file mode 100644 index 0000000..146dfba --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/BooksPage.kt @@ -0,0 +1,29 @@ +package com.pixelized.biblib.ui.screen.home + +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import com.pixelized.biblib.ui.screen.home.common.LazyBookThumbnailColumn +import com.pixelized.biblib.ui.screen.home.viewModel.BooksViewModel +import com.pixelized.biblib.ui.theme.BibLibTheme + +@Composable +fun BooksPage( + booksViewModel: BooksViewModel = hiltViewModel() +) { + LazyBookThumbnailColumn( + books = booksViewModel.books + ) +} + +@Composable +@Preview +private fun BooksPagePreview() { + BibLibTheme { + Column { + NewsPage() + } + } +} + diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt new file mode 100644 index 0000000..0f1df5a --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt @@ -0,0 +1,110 @@ +package com.pixelized.biblib.ui.screen.home + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.padding +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.google.accompanist.insets.systemBarsPadding +import com.pixelized.biblib.R +import com.pixelized.biblib.ui.navigation.page.Page +import com.pixelized.biblib.ui.navigation.page.PageNavHost + +@Composable +fun HomeScreen() { + val pageNavHostController = rememberNavController() + Scaffold( + modifier = Modifier.systemBarsPadding(), + topBar = { + Column { + TopAppBar( + title = { + Text(text = stringResource(id = R.string.app_name)) + } + ) + BottomBarNavigation( + homePageNavController = pageNavHostController + ) + } + }, + content = { + PageNavHost( + modifier = Modifier.padding(it), + navHostController = pageNavHostController, + ) + } + ) +} + +@Composable +private fun BottomBarNavigation( + homePageNavController: NavHostController, + pages: List = Page.all +) { + BottomNavigation( + backgroundColor = MaterialTheme.colors.background, + ) { + val navBackStackEntry by homePageNavController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + + pages.forEach { page -> + val selected = currentDestination?.hierarchy?.any { it.route == page.route } + BottomNavigationIcon( + page = page, + selected = selected ?: false, + onClick = { + homePageNavController.navigate(page.route) { + // Pop up to the start destination of the graph to avoid building up a + // large stack of destinations on the back stack as users select items + popUpTo(homePageNavController.graph.findStartDestination().id) { + saveState = true + } + // Avoid multiple copies of the same destination when re-selecting the same item + launchSingleTop = true + // Restore state when re-selecting a previously selected item + restoreState = true + } + } + ) + } + } +} + +@Composable +fun RowScope.BottomNavigationIcon( + modifier: Modifier = Modifier, + page: Page, + selected: Boolean, + selectedColor: Color = MaterialTheme.colors.primary, + defaultColor: Color = MaterialTheme.colors.onSurface, + onClick: () -> Unit = {}, +) { + BottomNavigationItem( + modifier = modifier, + icon = { + Icon( + imageVector = page.icon, + contentDescription = null, + ) + }, + label = { + Text( + style = MaterialTheme.typography.caption, + text = stringResource(id = page.label), + ) + }, + selectedContentColor = selectedColor, + unselectedContentColor = defaultColor, + selected = selected, + onClick = onClick, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/NewsPage.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/NewsPage.kt new file mode 100644 index 0000000..d85b131 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/NewsPage.kt @@ -0,0 +1,29 @@ +package com.pixelized.biblib.ui.screen.home + +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import com.pixelized.biblib.ui.screen.home.common.LazyBookThumbnailColumn +import com.pixelized.biblib.ui.screen.home.viewModel.BooksViewModel +import com.pixelized.biblib.ui.theme.BibLibTheme + +@Composable +fun NewsPage( + booksViewModel: BooksViewModel = hiltViewModel() +) { + LazyBookThumbnailColumn( + books = booksViewModel.news + ) +} + +@Composable +@Preview +private fun NewPagePreview() { + BibLibTheme { + Column { + NewsPage() + } + } +} + diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/LazyBookThumbnailColumn.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/LazyBookThumbnailColumn.kt new file mode 100644 index 0000000..1188ccb --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/LazyBookThumbnailColumn.kt @@ -0,0 +1,130 @@ +package com.pixelized.biblib.ui.screen.home.common + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.paging.PagingData +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.items +import com.pixelized.biblib.R +import com.pixelized.biblib.ui.screen.home.item.BookThumbnail +import com.pixelized.biblib.ui.screen.home.uio.BookThumbnailUio +import com.pixelized.biblib.ui.screen.home.uio.CoverUio +import com.pixelized.biblib.ui.theme.BibLibTheme +import com.pixelized.biblib.utils.extention.bibLib +import kotlinx.coroutines.flow.flowOf + +@Composable +fun LazyBookThumbnailColumn( + modifier: Modifier = Modifier, + state: LazyListState = rememberLazyListState(), + books: LazyPagingItems, +) { + LazyColumn( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.small), + contentPadding = PaddingValues(all = MaterialTheme.bibLib.dimen.small), + state = state, + ) { + items(books) { thumbnail -> + BookThumbnail(thumbnail) + } + } +} + +@Composable +@Preview +fun LazyBookThumbnailColumnPreview() { + BibLibTheme { + LazyBookThumbnailColumn( + modifier = Modifier.fillMaxSize(), + books = previewResources(), + ) + } +} + +@Composable +fun previewResources(): LazyPagingItems { + val cover = CoverUio( + painter = painterResource(id = R.drawable.ic_baseline_auto_stories_24), + contentScale = ContentScale.None, + ) + val thumbnails = listOf( + BookThumbnailUio( + id = 112, + title = "Prélude à Fondation", + author = "Asimov", + date = "1988", + genre = "Sci-Fi", + isNew = false, + cover = remember { mutableStateOf(cover) }, + ), + BookThumbnailUio( + id = 78, + title = "L'Aube de Fondation", + author = "Asimov", + date = "1993", + genre = "Sci-Fi", + isNew = false, + cover = remember { mutableStateOf(cover) }, + ), + BookThumbnailUio( + id = 90, + title = "Fondation", + author = "Asimov", + date = "1951", + genre = "Sci-Fi", + isNew = false, + cover = remember { mutableStateOf(cover) }, + ), + BookThumbnailUio( + id = 184, + title = "Fondation et Empire", + author = "Asimov", + date = "1952", + genre = "Sci-Fi", + isNew = false, + cover = remember { mutableStateOf(cover) }, + ), + BookThumbnailUio( + id = 185, + title = "Seconde Fondation", + author = "Asimov", + date = "1953", + genre = "Sci-Fi", + isNew = false, + cover = remember { mutableStateOf(cover) }, + ), + BookThumbnailUio( + id = 119, + title = "Fondation foudroyée", + author = "Asimov", + date = "1982", + genre = "Sci-Fi", + isNew = false, + cover = remember { mutableStateOf(cover) }, + ), + BookThumbnailUio( + id = 163, + title = "Terre et Fondation", + author = "Asimov", + date = "1986", + genre = "Sci-Fi", + isNew = false, + cover = remember { mutableStateOf(cover) }, + ), + ) + return flowOf(PagingData.from(thumbnails)).collectAsLazyPagingItems() +} diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/item/BookThumbnail.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/item/BookThumbnail.kt new file mode 100644 index 0000000..da9a237 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/item/BookThumbnail.kt @@ -0,0 +1,133 @@ +package com.pixelized.biblib.ui.screen.home.item + +import android.content.res.Configuration +import androidx.compose.animation.* +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.Card +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import com.pixelized.biblib.R +import com.pixelized.biblib.ui.screen.home.uio.BookThumbnailUio +import com.pixelized.biblib.ui.screen.home.uio.CoverUio +import com.pixelized.biblib.ui.theme.BibLibTheme +import com.pixelized.biblib.utils.extention.bibLib + +@Composable +fun BookThumbnail( + thumbnail: BookThumbnailUio?, + onClick: (BookThumbnailUio) -> Unit = { }, +) { + val currentOnClick by rememberUpdatedState(newValue = onClick) + Card( + modifier = Modifier + .clickable(enabled = thumbnail != null) { + thumbnail?.let { currentOnClick(it) } + } + .wrapContentHeight(), + ) { + Row( + modifier = Modifier.height(MaterialTheme.bibLib.dimen.thumbnail.cover.height), + verticalAlignment = Alignment.CenterVertically, + ) { + thumbnail?.cover?.let { it -> + Cover( + modifier = Modifier.size(MaterialTheme.bibLib.dimen.thumbnail.cover), + image = it, + ) + } + Column( + modifier = Modifier + .fillMaxWidth() + .padding(MaterialTheme.bibLib.dimen.small) + ) { + Text( + style = MaterialTheme.typography.body1, + color = MaterialTheme.colors.onSurface, + text = thumbnail?.title ?: "", + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + Text( + style = MaterialTheme.typography.caption, + color = MaterialTheme.colors.onSurface, + text = thumbnail?.author ?: "" + ) + + Spacer(modifier = Modifier.weight(1f)) + + Row(modifier = Modifier.fillMaxWidth()) { + Text( + style = MaterialTheme.typography.caption, + color = MaterialTheme.colors.onSurface, + text = thumbnail?.genre ?: "" + ) + + Spacer(modifier = Modifier.weight(1f)) + + Text( + style = MaterialTheme.typography.caption, + color = MaterialTheme.colors.onSurface, + text = thumbnail?.date ?: "" + ) + } + } + } + } +} + +@OptIn(ExperimentalAnimationApi::class) +@Composable +private fun Cover( + modifier: Modifier = Modifier, + image: State +) { + val cover by image + AnimatedContent( + targetState = cover, + transitionSpec = { fadeIn() with fadeOut() } + ) { + Image( + modifier = modifier, + alignment = Alignment.Center, + contentScale = it.contentScale, + painter = it.painter, + contentDescription = null, + ) + } +} + +@Composable +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun BookThumbnailPreview() { + val painter = painterResource(id = R.drawable.ic_baseline_auto_stories_24) + val thumbnail = BookThumbnailUio( + id = 0, + genre = "Sci-Fi", + title = "Foundation", + author = "Asimov", + date = "1951", + isNew = false, + cover = remember { + mutableStateOf( + CoverUio( + painter = painter, + contentScale = ContentScale.None, + ) + ) + }, + ) + BibLibTheme { + BookThumbnail(thumbnail = thumbnail) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/uio/BookThumbnailUio.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/uio/BookThumbnailUio.kt new file mode 100644 index 0000000..2cfd5c4 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/uio/BookThumbnailUio.kt @@ -0,0 +1,13 @@ +package com.pixelized.biblib.ui.screen.home.uio + +import androidx.compose.runtime.State + +class BookThumbnailUio( + val id: Int, + val genre: String, + val title: String, + val author: String, + val date: String?, + val isNew: Boolean, + val cover: State, +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/uio/BookUio.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/uio/BookUio.kt new file mode 100644 index 0000000..ea9c2f1 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/uio/BookUio.kt @@ -0,0 +1,17 @@ +package com.pixelized.biblib.ui.screen.home.uio + +import com.pixelized.biblib.network.client.IBibLibClient.Companion.COVER_URL +import java.net.URL + +data class BookUio( + val id: Int, + val title: String, + val author: String, + val rating: Float, + val language: String, + val date: String?, + val series: String?, + val description: String, +) { + val cover: URL = URL("${COVER_URL}/$id.jpg") +} diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/uio/CoverUio.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/uio/CoverUio.kt new file mode 100644 index 0000000..3f701a3 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/uio/CoverUio.kt @@ -0,0 +1,13 @@ +package com.pixelized.biblib.ui.screen.home.uio + +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.ContentScale + +@Stable +@Immutable +data class CoverUio( + val contentScale: ContentScale = ContentScale.FillBounds, + val painter: Painter, +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/viewModel/BooksViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/viewModel/BooksViewModel.kt new file mode 100644 index 0000000..6891b41 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/viewModel/BooksViewModel.kt @@ -0,0 +1,208 @@ +package com.pixelized.biblib.ui.screen.home.viewModel + +import android.app.Application +import android.util.Log +import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.ContentScale +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.compose.collectAsLazyPagingItems +import com.google.accompanist.drawablepainter.DrawablePainter +import com.pixelized.biblib.R +import com.pixelized.biblib.model.book.Book +import com.pixelized.biblib.network.client.IBibLibClient +import com.pixelized.biblib.network.factory.BookFactory +import com.pixelized.biblib.repository.apiCache.IAPICacheRepository +import com.pixelized.biblib.repository.book.IBookRepository +import com.pixelized.biblib.ui.composable.StateUio +import com.pixelized.biblib.ui.screen.home.uio.BookThumbnailUio +import com.pixelized.biblib.ui.screen.home.uio.BookUio +import com.pixelized.biblib.ui.screen.home.uio.CoverUio +import com.pixelized.biblib.utils.BitmapCache +import com.pixelized.biblib.utils.extention.capitalize +import com.pixelized.biblib.utils.extention.context +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch +import java.net.URL +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject + + +/** + * TODO: there is some book related code that should be inside a Repository // DataSource. + */ +@HiltViewModel +class BooksViewModel @Inject constructor( + application: Application, + private val bookRepository: IBookRepository, + private val client: IBibLibClient, + private val apiCache: IAPICacheRepository, + private val cache: BitmapCache, +) : AndroidViewModel(application) { + private val formatterLong = SimpleDateFormat("MMMM yyyy", Locale.getDefault()) + private val formatterShort = SimpleDateFormat("MMM yyyy", Locale.getDefault()) + + private val _state = mutableStateOf?>(null) + val state: State?> = _state + + private val booksSource = Pager( + config = PagingConfig(pageSize = PAGING_SIZE), + pagingSourceFactory = bookRepository.getBooksSource() + .map { it.toThumbnail() } + .asPagingSourceFactory(Dispatchers.IO) + ).flow + val books @Composable get() = booksSource.collectAsLazyPagingItems() + + private val newsSource: Flow> = Pager( + config = PagingConfig(pageSize = PAGING_SIZE), + pagingSourceFactory = bookRepository.getNewsSource() + .map { it.toThumbnail() } + .asPagingSourceFactory(Dispatchers.IO) + ).flow + val news @Composable get() = newsSource.collectAsLazyPagingItems() + + fun updateBooks() { + viewModelScope.launch(Dispatchers.IO) { + _state.value = StateUio.Progress() + try { + val updated = loadNewBooks() && loadAllBooks() + _state.value = StateUio.Success(updated) + } catch (exception: Exception) { + Log.e("BooksViewModel", exception.message, exception) + _state.value = StateUio.Failure(exception = exception) + } + } + } + + fun getBookDetail(id: Int): State { + val data = mutableStateOf(null) + viewModelScope.launch(Dispatchers.IO) { + val factory = BookFactory() + val response = client.service.detail(id) + val book = factory.fromDetailResponseToBook(response) + data.value = book.toUio() + } + return data + } + + fun send(id: Int, mail: String) { + viewModelScope.launch(Dispatchers.IO) { + client.service.send(bookId = id, mail = mail) + } + } + + private suspend fun loadNewBooks(): Boolean { + val cached = apiCache.new + val updated = client.service.new() + return if (cached != updated) { + apiCache.new = updated + true + } else { + false + } + } + + private suspend fun loadAllBooks(): Boolean { + client.service.list().let { response -> + val newIds = apiCache.new?.data?.map { it.book_id } ?: listOf() + val factory = BookFactory() + val books = response.data?.map { dto -> + val isNew = newIds.contains(dto.book_id) + val index = newIds.indexOf(dto.book_id) + factory.fromListResponseToBook(dto, isNew, index) + } + books?.let { data -> bookRepository.update(data) } + } + return true + } + + ////////////////////////////////////// + // region: UIO conversion + + private fun Book.toThumbnail() = BookThumbnailUio( + id = id, + genre = genre?.joinToString { it.name } ?: "", + title = title, + author = author.joinToString { it.name }, + date = if (releaseDate.time < 0) { + null + } else { + formatterLong.format(releaseDate).capitalize() + }, + isNew = isNew, + cover = cover(url = URL("${IBibLibClient.THUMBNAIL_URL}/$id.jpg")) + ) + + private fun Book.toUio() = BookUio( + id = id, + title = title, + author = author.joinToString { it.name }, + rating = rating?.toFloat() ?: 0.0f, + language = language?.displayLanguage?.capitalize() ?: "", + date = if (releaseDate.time < 0) { + null + } else { + formatterShort.format(releaseDate).capitalize() + }, + series = series?.name, + description = synopsis ?: "", + ) + + // endregion + + private fun cover( + cache: BitmapCache = this.cache, + placeHolder: Painter = DrawablePainter( + drawable = AppCompatResources.getDrawable( + context, + R.drawable.ic_baseline_auto_stories_24 + )!! + ), + coroutineScope: CoroutineScope = viewModelScope, + url: URL, + ): State { + if (cache.exist(url)) { + val bitmap = cache.readFromDisk(url) ?: throw RuntimeException("") + val resource = BitmapPainter(bitmap.asImageBitmap()) + return mutableStateOf(CoverUio(painter = resource)) + } else { + val state = mutableStateOf( + CoverUio( + contentScale = ContentScale.None, + painter = placeHolder + ) + ) + coroutineScope.launch { + val resource = cache.readFromDisk(url)?.let { BitmapPainter(it.asImageBitmap()) } + if (resource != null) { + state.value = CoverUio(painter = resource) + } else { + val downloaded = cache.download(url) + if (downloaded != null) { + cache.writeToDisk(url, downloaded) + state.value = CoverUio(painter = BitmapPainter(downloaded.asImageBitmap())) + } + } + } + + return state + } + } + + companion object { + private const val PAGING_SIZE = 30 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/launch/LauncherViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/launch/LauncherViewModel.kt similarity index 93% rename from app/src/main/java/com/pixelized/biblib/ui/launch/LauncherViewModel.kt rename to app/src/main/java/com/pixelized/biblib/ui/screen/launch/LauncherViewModel.kt index dbea8e0..02637aa 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/launch/LauncherViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/launch/LauncherViewModel.kt @@ -1,4 +1,4 @@ -package com.pixelized.biblib.ui.launch +package com.pixelized.biblib.ui.screen.launch import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf diff --git a/app/src/main/java/com/pixelized/biblib/ui/theme/BibLibTheme.kt b/app/src/main/java/com/pixelized/biblib/ui/theme/BibLibTheme.kt index a88ef59..4a9f8e4 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/theme/BibLibTheme.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/theme/BibLibTheme.kt @@ -5,6 +5,8 @@ import androidx.compose.runtime.Stable import androidx.compose.runtime.compositionLocalOf import com.pixelized.biblib.ui.theme.color.BibLibColor import com.pixelized.biblib.ui.theme.dimen.BibLibDimen +import com.pixelized.biblib.ui.theme.shape.BibLibShape +import com.pixelized.biblib.ui.theme.typography.BibLibTypography val LocalBibLibTheme = compositionLocalOf { error("BibLibTheme not ready yet.") @@ -13,6 +15,8 @@ val LocalBibLibTheme = compositionLocalOf { @Stable @Immutable data class BibLibTheme( - val dimen: BibLibDimen, val color: BibLibColor, + val dimen: BibLibDimen, + val typography: BibLibTypography, + val shape: BibLibShape, ) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/theme/Theme.kt b/app/src/main/java/com/pixelized/biblib/ui/theme/Theme.kt index 956c6c3..6a9ffc9 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/theme/Theme.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/theme/Theme.kt @@ -7,8 +7,8 @@ import androidx.compose.runtime.CompositionLocalProvider import com.pixelized.biblib.ui.theme.color.bibLibDarkColors import com.pixelized.biblib.ui.theme.color.bibLibLightColors import com.pixelized.biblib.ui.theme.dimen.BibLibDimen -import com.pixelized.biblib.ui.theme.shape.Shapes -import com.pixelized.biblib.ui.theme.typography.Typography +import com.pixelized.biblib.ui.theme.shape.BibLibShape +import com.pixelized.biblib.ui.theme.typography.BibLibTypography @Composable fun BibLibTheme( @@ -16,15 +16,16 @@ fun BibLibTheme( content: @Composable () -> Unit ) { val theme = BibLibTheme( + color = if (darkTheme) bibLibDarkColors() else bibLibLightColors(), dimen = BibLibDimen(), - color = if (darkTheme) bibLibDarkColors() else bibLibLightColors() + typography = BibLibTypography(), + shape = BibLibShape(), ) - CompositionLocalProvider(LocalBibLibTheme provides theme) { MaterialTheme( colors = theme.color.base, - typography = Typography, - shapes = Shapes, + typography = theme.typography.base, + shapes = theme.shape.base, content = content ) } diff --git a/app/src/main/java/com/pixelized/biblib/ui/theme/dimen/BibLibDimen.kt b/app/src/main/java/com/pixelized/biblib/ui/theme/dimen/BibLibDimen.kt index 3bac37c..9e1e4e0 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/theme/dimen/BibLibDimen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/theme/dimen/BibLibDimen.kt @@ -3,6 +3,7 @@ package com.pixelized.biblib.ui.theme.dimen import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp @Stable @@ -14,14 +15,21 @@ data class BibLibDimen( val medium: Dp = 16.dp, val large: Dp = 32.dp, val extraLarge: Dp = 64.dp, - val dialog: Dialog = Dialog() + val dialog: Dialog = Dialog(), + val thumbnail: BookThumbnail = BookThumbnail(), ) { @Stable @Immutable data class Dialog( + val minimum: DpSize = DpSize(240.dp, 120.dp), val elevation: Dp = 4.dp, - val minWidth: Dp = 240.dp, - val minHeight: Dp = 120.dp, val iconSize: Dp = 52.dp, ) + + @Stable + @Immutable + data class BookThumbnail( + val cover: DpSize = DpSize(60.dp, 96.dp), + val corner: Dp = 8.dp, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/theme/shape/BibLibShape.kt b/app/src/main/java/com/pixelized/biblib/ui/theme/shape/BibLibShape.kt new file mode 100644 index 0000000..807cee0 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/theme/shape/BibLibShape.kt @@ -0,0 +1,18 @@ +package com.pixelized.biblib.ui.theme.shape + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.dp + +@Stable +@Immutable +data class BibLibShape( + val base: Shapes = Shapes( + small = RoundedCornerShape(50), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp), + ), +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/theme/shape/Shape.kt b/app/src/main/java/com/pixelized/biblib/ui/theme/shape/Shape.kt deleted file mode 100644 index 8f03859..0000000 --- a/app/src/main/java/com/pixelized/biblib/ui/theme/shape/Shape.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.pixelized.biblib.ui.theme.shape - -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Shapes -import androidx.compose.ui.unit.dp - -val Shapes = Shapes( - small = RoundedCornerShape(50), - medium = RoundedCornerShape(4.dp), - large = RoundedCornerShape(0.dp) -) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/theme/typography/BibLibTypography.kt b/app/src/main/java/com/pixelized/biblib/ui/theme/typography/BibLibTypography.kt new file mode 100644 index 0000000..2058161 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/theme/typography/BibLibTypography.kt @@ -0,0 +1,11 @@ +package com.pixelized.biblib.ui.theme.typography + +import androidx.compose.material.Typography +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable + +@Stable +@Immutable +data class BibLibTypography( + val base: Typography = Typography() +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/theme/typography/Typography.kt b/app/src/main/java/com/pixelized/biblib/ui/theme/typography/Typography.kt deleted file mode 100644 index 9f75f9c..0000000 --- a/app/src/main/java/com/pixelized/biblib/ui/theme/typography/Typography.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.pixelized.biblib.ui.theme.typography - -import androidx.compose.material.Typography - -val Typography = Typography() \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/utils/BitmapCache.kt b/app/src/main/java/com/pixelized/biblib/utils/BitmapCache.kt index 53299cc..568c469 100644 --- a/app/src/main/java/com/pixelized/biblib/utils/BitmapCache.kt +++ b/app/src/main/java/com/pixelized/biblib/utils/BitmapCache.kt @@ -9,13 +9,13 @@ import kotlinx.coroutines.withContext import java.io.File import java.io.IOException import java.net.URL +import javax.inject.Inject -class BitmapCache(context: Context) { +class BitmapCache @Inject constructor(context: Context) { private var cache: File? = context.cacheDir fun writeToDisk(url: URL, bitmap: Bitmap) { - val path = cache?.absolutePath + url.file - val file = File(path) + val file = file(url) try { file.mkdirs() file.delete() @@ -26,8 +26,7 @@ class BitmapCache(context: Context) { } fun readFromDisk(url: URL): Bitmap? { - val path = cache?.absolutePath + url.file - val file = File(path) + val file = file(url) return try { BitmapFactory.decodeStream(file.inputStream()) } catch (e: IOException) { @@ -35,8 +34,14 @@ class BitmapCache(context: Context) { } } - suspend fun download(url: URL, callback: suspend (Bitmap?) -> Unit) { - val bitmap = withContext(Dispatchers.IO) { + fun exist(url: URL): Boolean { + val file = file(url) + return file.exists() + } + + suspend fun download(url: URL): Bitmap? { + Log.v("BitmapCache","download: $url") + return withContext(Dispatchers.IO) { try { BitmapFactory.decodeStream(url.openStream()) } catch (e: IOException) { @@ -44,6 +49,7 @@ class BitmapCache(context: Context) { null } } - callback.invoke(bitmap) } + + private fun file(url: URL): File = File(cache?.absolutePath + url.file) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/utils/extention/AndroidViewModelEx.kt b/app/src/main/java/com/pixelized/biblib/utils/extention/AndroidViewModelEx.kt new file mode 100644 index 0000000..39d7d18 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/utils/extention/AndroidViewModelEx.kt @@ -0,0 +1,6 @@ +package com.pixelized.biblib.utils.extention + +import android.content.Context +import androidx.lifecycle.AndroidViewModel + +val AndroidViewModel.context: Context get() = this.getApplication() \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/utils/extention/StringEx.kt b/app/src/main/java/com/pixelized/biblib/utils/extention/StringEx.kt new file mode 100644 index 0000000..a1c28c9 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/utils/extention/StringEx.kt @@ -0,0 +1,7 @@ +package com.pixelized.biblib.utils.extention + +import java.util.* + +fun String.capitalize() = this.replaceFirstChar { + if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() +} \ No newline at end of file diff --git a/app/src/main/res/drawable-night/ic_baseline_auto_stories_24.xml b/app/src/main/res/drawable-night/ic_baseline_auto_stories_24.xml new file mode 100644 index 0000000..529e5e4 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_baseline_auto_stories_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_auto_stories_24.xml b/app/src/main/res/drawable/ic_baseline_auto_stories_24.xml new file mode 100644 index 0000000..4c0eef3 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_auto_stories_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..e1f8b13 --- /dev/null +++ b/app/src/main/res/values-fr/strings.xml @@ -0,0 +1,50 @@ + + BibLib + %1$s: %2$s - %3$d + + + + Register + Login + EPUB + MOBI + SEND + Sign in with + + + + + + + + + + + + Oops! + Oops, connection failed! + Oops! library download failed! + + Entering the Imperial Library of Trantor. + Downloading the Imperial Library of Trantor. + + Authentication successful + Library successfully loaded + + + + Welcome to + + Sign in to BibLib + Login + Password + Remember my credential + + Rating + Language + Release + Genre + Series + + Not implemented yet. + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a634932..20ac5e6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,14 @@ SEND Sign in with + + + News + Books + Series + Authors + tags + Oops!