diff --git a/app/.DS_Store b/app/.DS_Store new file mode 100644 index 0000000..f3cb335 Binary files /dev/null and b/app/.DS_Store differ diff --git a/app/build.gradle b/app/build.gradle index 20e9e96..e44fc6d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,7 +9,7 @@ plugins { } android { - compileSdk 33 + compileSdk 34 namespace 'com.pixelized.biblib' signingConfigs { @@ -24,9 +24,9 @@ android { defaultConfig { applicationId "com.pixelized.biblib" minSdk 26 - targetSdk 33 + targetSdk 34 versionCode generateVersionCode() - versionName "0.1.5" + versionName "0.1.6" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -59,20 +59,21 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = "17" } buildFeatures { compose true + buildConfig = true } composeOptions { - kotlinCompilerExtensionVersion '1.4.4' + kotlinCompilerExtensionVersion = "1.5.11" } packagingOptions { @@ -89,75 +90,74 @@ android { dependencies { // Android core - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.appcompat:appcompat:1.7.0-alpha02' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1" + implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.2' + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.2" // Android Compose - implementation "androidx.compose.ui:ui:1.4.0" - implementation "androidx.compose.ui:ui-util:1.4.0" - implementation "androidx.compose.foundation:foundation:1.4.0" - implementation "androidx.compose.material:material:1.4.0" - implementation "androidx.compose.runtime:runtime-livedata:1.4.0" - implementation "androidx.compose.ui:ui-tooling-preview:1.4.0" - debugImplementation "androidx.compose.ui:ui-tooling:1.4.0" + implementation "androidx.compose.ui:ui:1.6.8" + implementation "androidx.compose.ui:ui-util:1.6.8" + implementation "androidx.compose.foundation:foundation:1.6.8" + implementation "androidx.compose.material:material:1.6.8" + implementation "androidx.compose.runtime:runtime-livedata:1.6.8" + implementation "androidx.compose.ui:ui-tooling-preview:1.6.8" + debugImplementation "androidx.compose.ui:ui-tooling:1.6.8" // Android Compose extension implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1" - implementation 'androidx.activity:activity-compose:1.7.0' - implementation "androidx.paging:paging-compose:1.0.0-alpha18" + implementation 'androidx.activity:activity-compose:1.9.0' + implementation "androidx.paging:paging-compose:3.3.0" // Material design - implementation 'com.google.android.material:material:1.8.0' - implementation "androidx.compose.material:material-icons-core:1.4.0" - implementation "androidx.compose.material:material-icons-extended:1.4.0" + implementation 'com.google.android.material:material:1.12.0' + implementation "androidx.compose.material:material-icons-core:1.6.8" + implementation "androidx.compose.material:material-icons-extended:1.6.8" // Injection - implementation 'androidx.hilt:hilt-navigation-compose:1.0.0' - implementation "com.google.dagger:hilt-android:2.43.2" - kapt "com.google.dagger:hilt-compiler:2.43.2" + implementation 'androidx.hilt:hilt-navigation-compose:1.2.0' + implementation "com.google.dagger:hilt-android:2.51.1" + kapt "com.google.dagger:hilt-compiler:2.51.1" // Accompanist - implementation "com.google.accompanist:accompanist-navigation-material:0.30.1" - implementation "com.google.accompanist:accompanist-systemuicontroller:0.30.1" - implementation "com.google.accompanist:accompanist-pager-indicators:0.30.1" - implementation "com.google.accompanist:accompanist-drawablepainter:0.30.1" - implementation "com.google.accompanist:accompanist-placeholder-material:0.30.1" - implementation "com.google.accompanist:accompanist-swiperefresh:0.30.1" + implementation "com.google.accompanist:accompanist-navigation-material:0.34.0" + implementation "com.google.accompanist:accompanist-systemuicontroller:0.34.0" + implementation "com.google.accompanist:accompanist-pager-indicators:0.34.0" + implementation "com.google.accompanist:accompanist-drawablepainter:0.34.0" + implementation "com.google.accompanist:accompanist-placeholder-material:0.34.0" + implementation "com.google.accompanist:accompanist-swiperefresh:0.34.0" // Landscapist - implementation "com.github.skydoves:landscapist-glide:1.5.2" - kapt 'com.github.bumptech.glide:compiler:4.13.2' // this have to be align with landscapist-glide + implementation("io.coil-kt:coil-compose:2.6.0") // Reorder element in lazylist implementation "org.burnoutcrew.composereorderable:reorderable:0.9.6" // Navigation - implementation "androidx.navigation:navigation-compose:2.5.3" + implementation "androidx.navigation:navigation-compose:2.7.7" // Splash Screen support prior to Android 12 - implementation "androidx.core:core-splashscreen:1.0.0" + implementation "androidx.core:core-splashscreen:1.0.1" // Google sign in. - implementation "com.google.android.gms:play-services-auth:20.4.1" + implementation "com.google.android.gms:play-services-auth:21.2.0" // Firebase - implementation platform('com.google.firebase:firebase-bom:31.3.0') + implementation platform('com.google.firebase:firebase-bom:33.1.1') implementation 'com.google.firebase:firebase-crashlytics' // RetroFit & Gson for webservice call implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - implementation 'com.google.code.gson:gson:2.9.0' + implementation 'com.google.code.gson:gson:2.10.1' // Logging Network Calls - implementation "com.squareup.okhttp3:logging-interceptor:4.8.1" + implementation "com.squareup.okhttp3:logging-interceptor:4.11.0" // Room - implementation "androidx.room:room-runtime:2.5.1" - implementation "androidx.room:room-ktx:2.5.1" - kapt "androidx.room:room-compiler:2.5.1" + implementation "androidx.room:room-runtime:2.6.1" + implementation "androidx.room:room-ktx:2.6.1" + kapt "androidx.room:room-compiler:2.6.1" } static def generateVersionCode() { diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..36ab27c --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.pixelized.biblib", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 161, + "versionName": "0.1.6", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/module/AppNameGlideModule.kt b/app/src/main/java/com/pixelized/biblib/module/AppNameGlideModule.kt deleted file mode 100644 index 58e73f8..0000000 --- a/app/src/main/java/com/pixelized/biblib/module/AppNameGlideModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.pixelized.biblib.module - -import android.content.Context -import com.bumptech.glide.GlideBuilder -import com.bumptech.glide.annotation.GlideModule -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.module.AppGlideModule -import com.bumptech.glide.request.RequestOptions - -@GlideModule -class AppNameGlideModule : AppGlideModule() { - override fun applyOptions(context: Context, builder: GlideBuilder) { - super.applyOptions(context, builder) - builder.apply { - this.setDefaultRequestOptions { - RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt b/app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt index d5e9df3..24b2c08 100644 --- a/app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt +++ b/app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt @@ -1,6 +1,10 @@ package com.pixelized.biblib.network.factory -import com.pixelized.biblib.model.book.* +import com.pixelized.biblib.model.book.Author +import com.pixelized.biblib.model.book.Book +import com.pixelized.biblib.model.book.Genre +import com.pixelized.biblib.model.book.Language +import com.pixelized.biblib.model.book.Series import com.pixelized.biblib.network.data.response.BookDetailResponse import com.pixelized.biblib.network.data.response.BookListResponse import com.pixelized.biblib.utils.exception.missingField @@ -45,6 +49,17 @@ class BookFactory( authorName.joinToString { it }, authorSort.joinToString { it }) ) + } else if (authorIdSize == authorSortSize) { + val contactedAuthorNames = authorSort.map { sortItem -> + authorName.filter { name -> sortItem.contains(name) } + } + (authorId.indices).map { index -> + Author( + authorId[index], + contactedAuthorNames[index].joinToString { it }, + authorSort[index] + ) + } } else { null } 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 c7eb9fa..7c1b17e 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/MainActivity.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/MainActivity.kt @@ -9,7 +9,6 @@ import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat -import com.pixelized.biblib.ui.composable.SystemThemeColor import com.pixelized.biblib.ui.screen.launch.LauncherViewModel import com.pixelized.biblib.ui.theme.BibLibTheme import dagger.hilt.android.AndroidEntryPoint @@ -48,12 +47,10 @@ class MainActivity : ComponentActivity() { content: @Composable () -> Unit, ) { BibLibTheme { - SystemThemeColor { - Surface( - color = MaterialTheme.colors.surface, - content = content, - ) - } + Surface( + color = MaterialTheme.colors.surface, + content = content, + ) } } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/MainContent.kt b/app/src/main/java/com/pixelized/biblib/ui/MainContent.kt index 4113e85..d48a9af 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/MainContent.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/MainContent.kt @@ -3,17 +3,20 @@ package com.pixelized.biblib.ui import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.material.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.ScaffoldState +import androidx.compose.material.Snackbar +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState +import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.request.RequestOptions import com.pixelized.biblib.ui.navigation.ScreenNavHost import com.pixelized.biblib.ui.screen.launch.LauncherViewModel -import com.skydoves.landscapist.glide.LocalGlideRequestOptions val LocalSnackHostState = staticCompositionLocalOf { error("SnackBarHostState is not ready yet.") @@ -22,12 +25,10 @@ val LocalSnackHostState = staticCompositionLocalOf { @Composable fun MainContent( launcherViewModel: LauncherViewModel = hiltViewModel(), - glideOptions: RequestOptions = RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL) ) { val scaffoldState: ScaffoldState = rememberScaffoldState() CompositionLocalProvider( - LocalGlideRequestOptions provides glideOptions, LocalSnackHostState provides scaffoldState.snackbarHostState, ) { Scaffold( diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/Handle.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/Handle.kt new file mode 100644 index 0000000..a7046a4 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/Handle.kt @@ -0,0 +1,29 @@ +package com.pixelized.biblib.ui.composable + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp +import com.pixelized.biblib.utils.extention.bibLib + +@Composable +fun Handle( + modifier: Modifier = Modifier, + width: Dp = MaterialTheme.bibLib.dimen.handle.width, + thickness: Dp = MaterialTheme.bibLib.dimen.handle.thickness, + color: Color = MaterialTheme.bibLib.colors.handle, + shape: Shape = CircleShape +) = Box( + modifier = modifier + .size(width = width, height = thickness) + .background( + color = color, + shape = shape, + ) +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/Loader.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/Loader.kt new file mode 100644 index 0000000..79a41ac --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/Loader.kt @@ -0,0 +1,82 @@ +package com.pixelized.biblib.ui.composable + +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.* +import androidx.compose.material.pullrefresh.PullRefreshState +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun Loader( + modifier: Modifier = Modifier, + isLoading: State, + refreshState: PullRefreshState +) { + val state = rememberLoaderState(refreshState) + + Box( + modifier = modifier, + contentAlignment = Alignment.Center, + ) { + Divider() + + Crossfade(targetState = isLoading.value) { loading -> + if (loading) { + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .clip(shape = CircleShape) + ) + } else { + Box( + modifier = Modifier + .height(height = state.height) + .fillMaxWidth(fraction = state.faction) + .background( + shape = CircleShape, + color = state.color, + ) + ) + } + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun rememberLoaderState( + refreshState: PullRefreshState, +): LoaderState { + val color = MaterialTheme.colors.primary + + val state by remember { + derivedStateOf { + LoaderState( + color = color.copy( + alpha = (3f * refreshState.progress / 4f + 0.25f).coerceIn(0f, 1f)) + , + height = 1.dp + 2.dp * refreshState.progress, + faction = refreshState.progress * 0.75f, + ) + } + } + return state +} + +@Stable +data class LoaderState( + val color: Color, + val height: Dp, + val faction: Float, +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/SystemTheme.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/SystemTheme.kt new file mode 100644 index 0000000..7a149f6 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/SystemTheme.kt @@ -0,0 +1,144 @@ +package com.pixelized.biblib.ui.composable + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import com.google.accompanist.systemuicontroller.SystemUiController +import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.pixelized.biblib.utils.extention.navigationBarsHeight +import com.pixelized.biblib.utils.extention.statusBarsHeight + +val LocalStatusTheme = staticCompositionLocalOf { + error("LocalStatusTheme is not ready yet.") +} + +class StatusThemeController( + private val systemController: SystemUiController, + private val statusBackground: MutableState, + private var statusDarkIcons: Boolean, + private val navigationBackground: MutableState, + private var navigationDarkIcons: Boolean, +) { + init { + systemController.setStatusBarColor( + color = Color.Transparent, + darkIcons = statusDarkIcons, + ) + systemController.setNavigationBarColor( + color = Color.Transparent, + darkIcons = navigationDarkIcons, + navigationBarContrastEnforced = false, + ) + } + + fun updateStatusTheme( + statusBackground: Brush = this.statusBackground.value, + statusDarkIcons: Boolean = this.statusDarkIcons, + navigationBackground: Brush = this.navigationBackground.value, + navigationDarkIcons: Boolean = this.navigationDarkIcons, + ) { + if (this.statusDarkIcons != statusDarkIcons) { + this.statusDarkIcons = statusDarkIcons + systemController.setStatusBarColor( + color = Color.Transparent, + darkIcons = statusDarkIcons, + ) + } + if (this.statusBackground.value != statusBackground) { + this.statusBackground.value = statusBackground + } + if (navigationDarkIcons != this.navigationDarkIcons) { + this.navigationDarkIcons = navigationDarkIcons + systemController.setNavigationBarColor( + color = Color.Transparent, + darkIcons = navigationDarkIcons, + navigationBarContrastEnforced = false, + ) + } + if (this.navigationBackground.value != navigationBackground) { + this.statusBackground.value = navigationBackground + } + } +} + +@Composable +fun SystemTheme( + systemController: SystemUiController = rememberSystemUiController(), + statusBackground: Color, + statusDarkIcons: Boolean, + navigationBackground: Color, + navigationDarkIcons: Boolean, + content: @Composable () -> Unit, +) { + val statusBackgroundState = remember { + mutableStateOf( + Brush.verticalGradient( + colors = listOf(statusBackground, Color.Transparent) + ) + ) + } + val navigationBackgroundState = remember { + mutableStateOf( + Brush.verticalGradient( + colors = listOf(Color.Transparent, navigationBackground) + ) + ) + } + val statusController = remember(systemController) { + StatusThemeController( + systemController = systemController, + statusBackground = statusBackgroundState, + statusDarkIcons = statusDarkIcons, + navigationBackground = navigationBackgroundState, + navigationDarkIcons = navigationDarkIcons, + ) + } + CompositionLocalProvider( + LocalStatusTheme provides statusController, + ) { + Box( + modifier = Modifier.fillMaxSize(), + ) { + content() + + Overlay( + height = statusBarsHeight(), + brush = statusBackgroundState, + ) + + Overlay( + modifier = Modifier.align(Alignment.BottomStart), + height = navigationBarsHeight(), + brush = navigationBackgroundState, + ) + } + } +} + +@Composable +private fun Overlay( + modifier: Modifier = Modifier, + height: Dp, + brush: State, +) { + Box( + modifier = modifier + .height(height = height) + .fillMaxWidth() + .background(brush = brush.value) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/SystemThemeColor.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/SystemThemeColor.kt deleted file mode 100644 index 2bd59c7..0000000 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/SystemThemeColor.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.pixelized.biblib.ui.composable - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.ui.graphics.Color -import com.google.accompanist.systemuicontroller.SystemUiController -import com.google.accompanist.systemuicontroller.rememberSystemUiController -import com.pixelized.biblib.ui.theme.color.ShadowPalette - -val LocalSystemUiController = compositionLocalOf { error("") } - -@Composable -fun SystemThemeColor( - systemUiController: SystemUiController = rememberSystemUiController(), - color: Color = ShadowPalette.system, - statusDarkIcons: Boolean = false, - navigationDarkIcons: Boolean = false, - content: @Composable () -> Unit, -) { - CompositionLocalProvider(LocalSystemUiController provides systemUiController) { - SideEffect { - systemUiController.setStatusBarColor( - color = color, - darkIcons = statusDarkIcons, - ) - systemUiController.setNavigationBarColor( - color = color, - darkIcons = navigationDarkIcons, - navigationBarContrastEnforced = false, - ) - } - content() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/rememberColorElevation.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/rememberColorElevation.kt new file mode 100644 index 0000000..0bdcc97 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/rememberColorElevation.kt @@ -0,0 +1,18 @@ +package com.pixelized.biblib.ui.composable + +import androidx.compose.material.LocalAbsoluteElevation +import androidx.compose.material.LocalElevationOverlay +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp + +@Composable +fun colorElevation( + base: Color = MaterialTheme.colors.surface, + elevation: Dp, +): Color { + val localElevation = LocalElevationOverlay.current + val absoluteElevation = LocalAbsoluteElevation.current + return localElevation?.apply(color = base, elevation = absoluteElevation + elevation) ?: base +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/FilterBottomSheet.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/FilterBottomSheet.kt index c67f76e..a29dd8c 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/FilterBottomSheet.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/FilterBottomSheet.kt @@ -2,18 +2,18 @@ package com.pixelized.biblib.ui.composable.scaffold import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetLayout -import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.* import androidx.compose.material.ModalBottomSheetValue.Hidden -import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.* import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.unit.dp @@ -26,6 +26,8 @@ import com.pixelized.biblib.ui.screen.home.filter.viewModel.AuthorFilterViewMode import com.pixelized.biblib.ui.screen.home.filter.viewModel.GenreFilterViewModel import com.pixelized.biblib.ui.screen.home.filter.viewModel.LanguageFilterViewModel import com.pixelized.biblib.ui.screen.home.filter.viewModel.SeriesFilterViewModel +import com.pixelized.biblib.utils.extention.bibLib +import com.pixelized.biblib.utils.extention.statusBarsHeight import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.sync.Mutex @@ -102,6 +104,7 @@ fun FilterBottomSheet( skipHalfExpanded = true, ), sheetState: FilterBottomSheetState = rememberFilterBottomSheetState(), + sheetShape: Shape = MaterialTheme.bibLib.shapes.sheet, content: @Composable () -> Unit, ) { CompositionLocalProvider( @@ -134,47 +137,53 @@ fun FilterBottomSheet( onBack = { currentBottomSheetData?.dismiss() }, ) - ModalBottomSheetLayout( - sheetState = bottomSheetState, - content = content, - scrimColor = Color.Transparent, - sheetContent = { - val viewModel: IFilterViewModel? = when (sheetState.type) { - FilterType.Author -> hiltViewModel() - FilterType.Series -> hiltViewModel() - FilterType.Genre -> hiltViewModel() - FilterType.Language -> hiltViewModel() - else -> null - } + BoxWithConstraints { + val statusBarHeight = statusBarsHeight() + val bottomSheetHeight = remember(statusBarHeight) { + maxHeight - statusBarHeight - 16.dp + } - if (viewModel != null) { - val focusRequester = remember { FocusRequester() } - - FilterPage( - viewModel = viewModel, - focusRequester = focusRequester, - onFilter = { - currentBottomSheetData?.performAction(filter = it) - }, - onClose = { - currentBottomSheetData?.dismiss() - }, - onIMEDone = { - currentBottomSheetData?.dismiss() - } - ) - - LaunchedEffect(key1 = "FilterPageFocusRequest-${sheetState.type}") { - if (currentBottomSheetData != null) { - focusRequester.requestFocus() - keyboard?.show() - } + ModalBottomSheetLayout( + sheetState = bottomSheetState, + sheetShape = sheetShape, + content = content, + scrimColor = Color.Transparent, + sheetContent = { + val viewModel: IFilterViewModel? = when (sheetState.type) { + FilterType.Author -> hiltViewModel() + FilterType.Series -> hiltViewModel() + FilterType.Genre -> hiltViewModel() + FilterType.Language -> hiltViewModel() + else -> null } - } else { - Box(modifier = Modifier.height(1.dp)) - } - }, - ) + if (viewModel != null) { + val focusRequester = remember { FocusRequester() } + + FilterPage( + modifier = Modifier + .fillMaxWidth() + .height(bottomSheetHeight), + viewModel = viewModel, + focusRequester = focusRequester, + onFilter = { + currentBottomSheetData?.performAction(filter = it) + }, + onIMEDone = { + currentBottomSheetData?.dismiss() + } + ) + LaunchedEffect(key1 = "FilterPageFocusRequest-${sheetState.type}") { + if (currentBottomSheetData != null) { + focusRequester.requestFocus() + keyboard?.show() + } + } + } else { + Box(modifier = Modifier.height(1.dp)) + } + }, + ) + } } } diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/SortBottomSheet.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/SortBottomSheet.kt index 1e6bd09..d5b7ee8 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/SortBottomSheet.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/SortBottomSheet.kt @@ -1,16 +1,15 @@ package com.pixelized.biblib.ui.composable.scaffold import androidx.activity.compose.BackHandler -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetLayout -import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.* import androidx.compose.material.ModalBottomSheetValue.Hidden -import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.* import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import com.pixelized.biblib.model.search.SortType import com.pixelized.biblib.model.search.SortValue import com.pixelized.biblib.ui.screen.home.sort.BookSortPage +import com.pixelized.biblib.utils.extention.bibLib import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.sync.Mutex @@ -82,6 +81,7 @@ fun SortBottomSheet( initialValue = Hidden, skipHalfExpanded = true, ), + sheetShape: Shape = MaterialTheme.bibLib.shapes.sheet, sheetState: SortBottomSheetState = rememberSortBottomSheetState(), content: @Composable () -> Unit, ) { @@ -98,7 +98,7 @@ fun SortBottomSheet( ) // Check the state of the bottomSheetState and call for dismiss if needed. HandleBottomSheetDismiss( - bottomSheetState =bottomSheetState, + bottomSheetState = bottomSheetState, onDismiss = { currentBottomSheetData?.dismiss() } ) // Handle back event. @@ -110,6 +110,7 @@ fun SortBottomSheet( ModalBottomSheetLayout( sheetState = bottomSheetState, content = content, + sheetShape = sheetShape, scrimColor = Color.Transparent, sheetContent = { BookSortPage( diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreen.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreen.kt index aa3c901..c8cf616 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreen.kt @@ -1,32 +1,91 @@ package com.pixelized.biblib.ui.screen.authentication +import android.content.res.Configuration +import androidx.activity.compose.BackHandler +import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.SnackbarResult -import androidx.compose.runtime.* +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.BlurredEdgeTreatment +import androidx.compose.ui.draw.blur +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.biblib.R import com.pixelized.biblib.network.client.IBibLibClient import com.pixelized.biblib.ui.LocalSnackHostState import com.pixelized.biblib.ui.navigation.LocalScreenNavHostController import com.pixelized.biblib.ui.navigation.navigateToHome +import com.pixelized.biblib.ui.screen.authentication.composable.CredentialRemember +import com.pixelized.biblib.ui.screen.authentication.composable.LoginField +import com.pixelized.biblib.ui.screen.authentication.composable.PasswordField +import com.pixelized.biblib.ui.screen.authentication.composable.googleStringResource import com.pixelized.biblib.ui.screen.authentication.viewModel.AuthenticationFormViewModel import com.pixelized.biblib.ui.screen.authentication.viewModel.AuthenticationViewModel +import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.ui.theme.color.ShadowPalette import com.pixelized.biblib.utils.extention.bibLib +import com.pixelized.biblib.utils.extention.modifier.unclickable import kotlinx.coroutines.launch @Stable @@ -65,60 +124,289 @@ fun AuthenticationScreen( val snackBarHostState = LocalSnackHostState.current val uriHandler = LocalUriHandler.current val context = LocalContext.current + val focusManager = LocalFocusManager.current val scope = rememberCoroutineScope() - AuthenticationScreenContent( - modifier = Modifier.systemBarsPadding(), - login = formViewModel.form.login, - password = formViewModel.form.password, - rememberPassword = formViewModel.form.remember, - onLoginChange = formViewModel::onLoginChange, - onPasswordChange = formViewModel::onPasswordChange, - onRememberPasswordChange = formViewModel::onRememberChange, - onGoogleSignIn = authenticationViewModel::loginWithGoogle, - onSignIn = { - authenticationViewModel.login( - login = formViewModel.form.login, - password = formViewModel.form.password, - ) - }, - onRegister = { - uriHandler.openUri(uri = IBibLibClient.REGISTER_URL) - }, - ) + val background = rememberSaveable { + val tmp = listOf( + R.drawable.biblib_background_1, + R.drawable.biblib_background_2, + R.drawable.biblib_background_3, + ) + mutableIntStateOf(tmp.random()) + } + val displayBibLibSignIn = rememberSaveable { mutableStateOf(false) } + val blur = remember(displayBibLibSignIn) { + derivedStateOf { if (displayBibLibSignIn.value) 8.dp else 0.dp } + } - AuthenticationHandler( - onFailure = { - scope.launch { - val result = snackBarHostState.showSnackbar( - message = context.getString(it.message), - actionLabel = context.getString(it.action), + Surface( + modifier = Modifier.fillMaxSize(), + ) { + AuthenticationScreenContent( + modifier = Modifier.fillMaxSize(), + background = background.intValue, + blur = blur, + onGoogleSignIn = authenticationViewModel::loginWithGoogle, + onBibLibSingIn = { displayBibLibSignIn.value = true }, + ) + + AuthenticationSignDialog( + focusManager = focusManager, + visibility = displayBibLibSignIn, + login = formViewModel.form.login, + password = formViewModel.form.password, + rememberPassword = formViewModel.form.remember, + onLoginChange = formViewModel::onLoginChange, + onPasswordChange = formViewModel::onPasswordChange, + onRememberPasswordChange = formViewModel::onRememberChange, + onSignIn = { + authenticationViewModel.login( + login = formViewModel.form.login, + password = formViewModel.form.password, ) - if (result == SnackbarResult.ActionPerformed) { - when (it) { - is AuthenticationErrorUio.GoogleLogin -> { - authenticationViewModel.loginWithGoogle() - } - is AuthenticationErrorUio.Login -> { - authenticationViewModel.login( - login = formViewModel.form.login, - password = formViewModel.form.password, - ) - } + }, + onRegister = { + uriHandler.openUri(uri = IBibLibClient.REGISTER_URL) + }, + onDismissRequest = { + displayBibLibSignIn.value = false + }, + ) + } + + BackHandler( + enabled = displayBibLibSignIn.value + ) { + displayBibLibSignIn.value = false + } + + AuthenticationHandler(onFailure = { + scope.launch { + val result = snackBarHostState.showSnackbar( + message = context.getString(it.message), + actionLabel = context.getString(it.action), + ) + if (result == SnackbarResult.ActionPerformed) { + when (it) { + is AuthenticationErrorUio.GoogleLogin -> { + authenticationViewModel.loginWithGoogle() + } + + is AuthenticationErrorUio.Login -> { + authenticationViewModel.login( + login = formViewModel.form.login, + password = formViewModel.form.password, + ) } } } - }, - onSuccess = { - navHostController.navigateToHome() } - ) + }, onSuccess = { + navHostController.navigateToHome() + }) AuthenticationProgress() } @Composable -fun AuthenticationHandler( +private fun AuthenticationScreenContent( + modifier: Modifier = Modifier, + blur: State, + @DrawableRes background: Int, + onGoogleSignIn: () -> Unit, + onBibLibSingIn: () -> Unit, +) { + val colorScheme = MaterialTheme.colors + val animatedBlur = animateDpAsState( + targetValue = blur.value, + label = "AnimatedContentBlur", + ) + + Column( + modifier = modifier.blur( + radius = animatedBlur.value, + edgeTreatment = BlurredEdgeTreatment.Unbounded, + ), + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + ) { + Image( + modifier = Modifier.matchParentSize(), + contentScale = ContentScale.FillHeight, + painter = painterResource(id = background), + contentDescription = null, + ) + Box( + modifier = Modifier + .matchParentSize() + .background(brush = remember { + Brush.verticalGradient( + listOf( + colorScheme.surface.copy(alpha = 0.0f), + colorScheme.surface.copy(alpha = 0.0f), + colorScheme.surface.copy(alpha = 0.0f), + colorScheme.surface.copy(alpha = 0.3f), + colorScheme.surface.copy(alpha = 1.0f), + ) + ) + }), + ) + } + Box( + modifier = Modifier.navigationBarsPadding(), + ) { + Column( + verticalArrangement = Arrangement.spacedBy( + space = 8.dp, alignment = Alignment.Bottom + ), + ) { + Button( + modifier = Modifier + .padding(top = 16.dp) + .padding(horizontal = 16.dp) + .fillMaxWidth() + .height(height = 48.dp), + colors = ButtonDefaults.outlinedButtonColors(), + onClick = onGoogleSignIn, + ) { + Text(text = googleStringResource()) + } + TextButton( + modifier = Modifier + .padding(bottom = 16.dp) + .padding(horizontal = 16.dp) + .fillMaxWidth() + .height(height = 48.dp), + colors = ButtonDefaults.textButtonColors(), + onClick = onBibLibSingIn, + ) { + Text( + maxLines = 1, + overflow = TextOverflow.Ellipsis, + text = stringResource(id = R.string.authentication_sign_in_with_biblib), + ) + } + } + } + } +} + +@Composable +private fun AuthenticationSignDialog( + focusManager: FocusManager = LocalFocusManager.current, + visibility: State, + login: String, + onLoginChange: (String) -> Unit, + password: String, + onPasswordChange: (String) -> Unit, + rememberPassword: Boolean, + onRememberPasswordChange: (Boolean) -> Unit, + onSignIn: () -> Unit, + onRegister: () -> Unit, + onDismissRequest: () -> Unit, +) { + val density = LocalDensity.current + val colorScheme = MaterialTheme.colors + val slideOffset = remember { with(density) { 16.dp.roundToPx() } } + + Box( + modifier = Modifier + .unclickable(disable = visibility.value) + .fillMaxSize() + .background( + color = animateColorAsState( + targetValue = when (visibility.value) { + true -> colorScheme.surface.copy(alpha = 0.5f) + else -> Color.Transparent + }, + label = "AnimatedSignInScrim" + ).value + ), + ) { + AnimatedVisibility( + modifier = Modifier.fillMaxSize(), + enter = fadeIn() + slideInVertically { slideOffset }, + exit = fadeOut() + slideOutVertically { slideOffset }, + visible = visibility.value, + ) { + Box( + modifier = Modifier.fillMaxSize(), + ) { + IconButton( + modifier = Modifier + .statusBarsPadding() + .align(alignment = Alignment.TopEnd), + onClick = onDismissRequest, + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = null, + ) + } + Column( + modifier = Modifier.align(alignment = Alignment.Center), + verticalArrangement = Arrangement.spacedBy( + space = 24.dp, + alignment = Alignment.CenterVertically, + ), + ) { + Column( + modifier = Modifier.padding(horizontal = 16.dp), + ) { + LoginField( + modifier = Modifier.fillMaxWidth(), + value = login, + onValueChange = onLoginChange, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + ) + PasswordField( + modifier = Modifier.fillMaxWidth(), + value = password, + onValueChange = onPasswordChange, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions { focusManager.clearFocus() }, + ) + } + CredentialRemember( + modifier = Modifier.padding(start = 24.dp, end = 16.dp), + value = rememberPassword, + onValueChange = onRememberPasswordChange, + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy( + space = 16.dp, alignment = Alignment.End + ), + ) { + TextButton( + modifier = Modifier.height(height = 48.dp), + colors = ButtonDefaults.textButtonColors(), + onClick = onRegister, + ) { + Text(text = stringResource(id = R.string.action_register)) + } + Button( + modifier = Modifier.height(height = 48.dp), + colors = ButtonDefaults.buttonColors(), + onClick = onSignIn, + ) { + Text(text = stringResource(id = R.string.action_login)) + } + } + } + } + } + } +} + +@Composable +private fun AuthenticationHandler( viewModel: AuthenticationViewModel = hiltViewModel(), onFailure: (AuthenticationErrorUio) -> Unit = { }, onSuccess: (AuthenticationUio.Done) -> Unit = { }, @@ -160,4 +448,42 @@ fun AuthenticationProgress( ) } } +} + +@Composable +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun AuthenticationScreenContentPreview( + @PreviewParameter(AuthenticationPreviewProvider::class) preview: State, +) { + BibLibTheme { + Surface { + AuthenticationScreenContent( + modifier = Modifier.fillMaxSize(), + blur = remember { mutableStateOf(if (preview.value) 8.dp else 0.dp) }, + background = R.drawable.biblib_background_3, + onGoogleSignIn = { }, + onBibLibSingIn = { }, + ) + AuthenticationSignDialog( + visibility = preview, + login = "", + onLoginChange = { }, + password = "", + onPasswordChange = { }, + rememberPassword = true, + onRememberPasswordChange = { }, + onSignIn = { }, + onRegister = { }, + onDismissRequest = { }, + ) + } + } +} + +private class AuthenticationPreviewProvider : PreviewParameterProvider> { + override val values: Sequence> = sequenceOf( + mutableStateOf(false), + mutableStateOf(true), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreenContent.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreenContent.kt deleted file mode 100644 index 5ee926f..0000000 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreenContent.kt +++ /dev/null @@ -1,330 +0,0 @@ -package com.pixelized.biblib.ui.screen.authentication - -import android.content.res.Configuration -import androidx.compose.foundation.ScrollState -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.sharp.Visibility -import androidx.compose.material.icons.sharp.VisibilityOff -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.autofill.AutofillType -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.text.withStyle -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.pixelized.biblib.R -import com.pixelized.biblib.ui.composable.animation.AnimatedDelayer -import com.pixelized.biblib.ui.composable.animation.AnimatedOffset -import com.pixelized.biblib.ui.theme.BibLibTheme -import com.pixelized.biblib.ui.theme.color.GoogleColorPalette -import com.pixelized.biblib.utils.extention.autofill - -@Composable -fun AuthenticationScreenContent( - modifier: Modifier = Modifier, - scrollState: ScrollState = rememberScrollState(), - login: String, - onLoginChange: (String) -> Unit, - password: String, - onPasswordChange: (String) -> Unit, - rememberPassword: Boolean, - onRememberPasswordChange: (Boolean) -> Unit, - onGoogleSignIn: () -> Unit, - onSignIn: () -> Unit, - onRegister: () -> Unit, -) { - val focusManager = LocalFocusManager.current - - AnimatedDelayer(targetState = "AuthenticationScreenContent") { - Column( - modifier = modifier - .fillMaxSize() - .verticalScroll(scrollState) - ) { - Spacer(modifier = Modifier.weight(1f)) - - AnimatedOffset( - modifier = Modifier - .padding(all = 16.dp) - .align(alignment = Alignment.CenterHorizontally), - ) { - Text( - style = MaterialTheme.typography.h4, - color = MaterialTheme.colors.onBackground, - text = stringResource(id = R.string.authentication_title), - ) - } - - Spacer(modifier = Modifier.weight(1f)) - - AnimatedOffset( - modifier = Modifier.padding(horizontal = 16.dp), - ) { - LoginField( - modifier = Modifier.fillMaxWidth(), - value = login, - onValueChange = onLoginChange, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), - ) - } - - Spacer(modifier = Modifier.height(8.dp)) - - AnimatedOffset( - modifier = Modifier.padding(horizontal = 16.dp), - ) { - PasswordField( - modifier = Modifier.fillMaxWidth(), - value = password, - onValueChange = onPasswordChange, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions { focusManager.clearFocus() }, - ) - } - - Spacer(modifier = Modifier.height(16.dp)) - - AnimatedOffset( - modifier = Modifier.padding(horizontal = 16.dp), - ) { - CredentialRemember( - value = rememberPassword, - onValueChange = onRememberPasswordChange, - ) - } - - Spacer(modifier = Modifier.height(16.dp)) - - AnimatedOffset( - modifier = Modifier - .padding(horizontal = 16.dp) - .align(Alignment.End), - ) { - Row { - Button( - colors = ButtonDefaults.outlinedButtonColors(), - onClick = onRegister, - ) { - Text(text = stringResource(id = R.string.action_register)) - } - - Spacer(modifier = Modifier.width(8.dp)) - - Button( - colors = ButtonDefaults.buttonColors(), - onClick = onSignIn, - ) { - Text(text = stringResource(id = R.string.action_login)) - } - } - } - - Spacer(modifier = Modifier.weight(2f)) - - AnimatedOffset { - Button( - modifier = Modifier - .padding(all = 16.dp) - .fillMaxWidth(), - colors = ButtonDefaults.outlinedButtonColors(), - onClick = onGoogleSignIn, - ) { - Text(text = googleStringResource()) - } - } - } - } -} - -////////////////////////////////////// -// region: Content Helper Composable - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -private fun LoginField( - modifier: Modifier = Modifier, - value: String, - onValueChange: (String) -> Unit, - keyboardOptions: KeyboardOptions = KeyboardOptions.Default, - keyboardActions: KeyboardActions = KeyboardActions(), -) { - TextField( - modifier = modifier.autofill( - autofillTypes = listOf(AutofillType.Username, AutofillType.EmailAddress), - onFill = onValueChange, - ), - value = value, - onValueChange = onValueChange, - label = { Text(text = stringResource(id = R.string.authentication_login)) }, - colors = TextFieldDefaults.outlinedTextFieldColors(), - maxLines = 1, - singleLine = true, - keyboardOptions = keyboardOptions, - keyboardActions = keyboardActions, - ) -} - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -private fun PasswordField( - modifier: Modifier = Modifier, - value: String, - onValueChange: (String) -> Unit, - keyboardOptions: KeyboardOptions = KeyboardOptions.Default, - keyboardActions: KeyboardActions = KeyboardActions(), -) { - var passwordVisibility by remember { mutableStateOf(false) } - TextField( - modifier = modifier.autofill( - autofillTypes = listOf(AutofillType.Password), - onFill = onValueChange, - ), - value = value, - onValueChange = onValueChange, - label = { Text(text = stringResource(id = R.string.authentication_password)) }, - colors = TextFieldDefaults.outlinedTextFieldColors(), - maxLines = 1, - singleLine = true, - visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(), - trailingIcon = { - IconButton(onClick = { passwordVisibility = passwordVisibility.not() }) { - Icon( - imageVector = if (passwordVisibility) Icons.Sharp.VisibilityOff else Icons.Sharp.Visibility, - contentDescription = null, - ) - } - }, - keyboardOptions = keyboardOptions, - keyboardActions = keyboardActions, - ) -} - -@Composable -private fun CredentialRemember( - modifier: Modifier = Modifier, - value: Boolean, - onValueChange: (Boolean) -> Unit, -) { - Row( - modifier = modifier.clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = { onValueChange(value.not()) } - ) - ) { - Checkbox( - modifier = Modifier.align(Alignment.CenterVertically), - checked = value, - onCheckedChange = null - ) - - Spacer(modifier = Modifier.width(8.dp)) - - Text( - modifier = Modifier.align(Alignment.CenterVertically), - style = MaterialTheme.typography.caption, - color = MaterialTheme.colors.onBackground, - text = stringResource(id = R.string.authentication_credential_remember) - ) - } -} - -@Composable -private fun googleStringResource(): AnnotatedString { - val default = LocalTextStyle.current.toSpanStyle() - val google = stringResource(id = R.string.action_google_sign_in) - return remember { - buildAnnotatedString { - withStyle( - style = default - ) { - append(google) - append(" ") - } - withStyle( - style = default.copy( - color = GoogleColorPalette.blue, - fontWeight = FontWeight.ExtraBold - ), - ) { - append("G") - } - withStyle( - style = default.copy( - color = GoogleColorPalette.red, - fontWeight = FontWeight.ExtraBold - ), - ) { - append("o") - } - withStyle( - style = default.copy( - color = GoogleColorPalette.yellow, - fontWeight = FontWeight.ExtraBold - ), - ) { - append("o") - } - withStyle( - style = default.copy( - color = GoogleColorPalette.blue, - fontWeight = FontWeight.ExtraBold - ), - ) { - append("g") - } - withStyle( - style = default.copy( - color = GoogleColorPalette.green, - fontWeight = FontWeight.ExtraBold - ), - ) { - append("l") - } - withStyle( - style = default.copy( - color = GoogleColorPalette.red, - fontWeight = FontWeight.ExtraBold - ), - ) { - append("e") - } - } - } -} - -@Composable -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun AuthenticationScreenContentPreview() { - BibLibTheme { - AuthenticationScreenContent( - login = "", - onLoginChange = { }, - password = "", - onPasswordChange = { }, - rememberPassword = true, - onRememberPasswordChange = { }, - onGoogleSignIn = { }, - onSignIn = { }, - onRegister = { }, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/CredentialRemember.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/CredentialRemember.kt new file mode 100644 index 0000000..584dde1 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/CredentialRemember.kt @@ -0,0 +1,47 @@ +package com.pixelized.biblib.ui.screen.authentication.composable + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +import androidx.compose.material.Checkbox +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.pixelized.biblib.R + +@Composable +fun CredentialRemember( + modifier: Modifier = Modifier, + value: Boolean, + onValueChange: (Boolean) -> Unit, +) { + Row( + modifier = modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { onValueChange(value.not()) } + ) + ) { + Checkbox( + modifier = Modifier.align(Alignment.CenterVertically), + checked = value, + onCheckedChange = null + ) + + Spacer(modifier = Modifier.width(8.dp)) + + Text( + modifier = Modifier.align(Alignment.CenterVertically), + style = MaterialTheme.typography.caption, + color = MaterialTheme.colors.onBackground, + text = stringResource(id = R.string.authentication_credential_remember) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/LoginField.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/LoginField.kt new file mode 100644 index 0000000..a6efd0b --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/LoginField.kt @@ -0,0 +1,39 @@ +package com.pixelized.biblib.ui.screen.authentication.composable + +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.autofill.AutofillType +import androidx.compose.ui.res.stringResource +import com.pixelized.biblib.R +import com.pixelized.biblib.utils.extention.autofill + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun LoginField( + modifier: Modifier = Modifier, + value: String, + onValueChange: (String) -> Unit, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions(), +) { + TextField( + modifier = modifier.autofill( + autofillTypes = listOf(AutofillType.Username, AutofillType.EmailAddress), + onFill = onValueChange, + ), + value = value, + onValueChange = onValueChange, + label = { Text(text = stringResource(id = R.string.authentication_login)) }, + colors = TextFieldDefaults.outlinedTextFieldColors(), + maxLines = 1, + singleLine = true, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/PasswordField.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/PasswordField.kt new file mode 100644 index 0000000..b8ef5d8 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/PasswordField.kt @@ -0,0 +1,60 @@ +package com.pixelized.biblib.ui.screen.authentication.composable + +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.sharp.Visibility +import androidx.compose.material.icons.sharp.VisibilityOff +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.autofill.AutofillType +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import com.pixelized.biblib.R +import com.pixelized.biblib.utils.extention.autofill + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun PasswordField( + modifier: Modifier = Modifier, + value: String, + onValueChange: (String) -> Unit, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions(), +) { + var passwordVisibility by remember { mutableStateOf(false) } + TextField( + modifier = modifier.autofill( + autofillTypes = listOf(AutofillType.Password), + onFill = onValueChange, + ), + value = value, + onValueChange = onValueChange, + label = { Text(text = stringResource(id = R.string.authentication_password)) }, + colors = TextFieldDefaults.outlinedTextFieldColors(), + maxLines = 1, + singleLine = true, + visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(), + trailingIcon = { + IconButton(onClick = { passwordVisibility = passwordVisibility.not() }) { + Icon( + imageVector = if (passwordVisibility) Icons.Sharp.VisibilityOff else Icons.Sharp.Visibility, + contentDescription = null, + ) + } + }, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/googleStringResource.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/googleStringResource.kt new file mode 100644 index 0000000..fc34152 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/composable/googleStringResource.kt @@ -0,0 +1,76 @@ +package com.pixelized.biblib.ui.screen.authentication.composable + +import androidx.compose.material.LocalTextStyle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import com.pixelized.biblib.R +import com.pixelized.biblib.ui.theme.color.GoogleColorPalette + +@Composable +fun googleStringResource(): AnnotatedString { + val default = LocalTextStyle.current.toSpanStyle() + val google = stringResource(id = R.string.action_google_sign_in) + return remember { + buildAnnotatedString { + withStyle( + style = default + ) { + append(google) + append(" ") + } + withStyle( + style = default.copy( + color = GoogleColorPalette.blue, + fontWeight = FontWeight.ExtraBold + ), + ) { + append("G") + } + withStyle( + style = default.copy( + color = GoogleColorPalette.red, + fontWeight = FontWeight.ExtraBold + ), + ) { + append("o") + } + withStyle( + style = default.copy( + color = GoogleColorPalette.yellow, + fontWeight = FontWeight.ExtraBold + ), + ) { + append("o") + } + withStyle( + style = default.copy( + color = GoogleColorPalette.blue, + fontWeight = FontWeight.ExtraBold + ), + ) { + append("g") + } + withStyle( + style = default.copy( + color = GoogleColorPalette.green, + fontWeight = FontWeight.ExtraBold + ), + ) { + append("l") + } + withStyle( + style = default.copy( + color = GoogleColorPalette.red, + fontWeight = FontWeight.ExtraBold + ), + ) { + append("e") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/viewModel/AuthenticationFormViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/viewModel/AuthenticationFormViewModel.kt index 75c6db6..5aeffe0 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/viewModel/AuthenticationFormViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/viewModel/AuthenticationFormViewModel.kt @@ -2,6 +2,7 @@ package com.pixelized.biblib.ui.screen.authentication.viewModel import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel +import com.pixelized.biblib.BuildConfig import com.pixelized.biblib.repository.credential.ICredentialRepository import com.pixelized.biblib.ui.screen.authentication.AuthenticationFormUIO import dagger.hilt.android.lifecycle.HiltViewModel @@ -56,8 +57,16 @@ class AuthenticationFormViewModel @Inject constructor( } private val ICredentialRepository.rememberedLogin: String - get() = if (rememberCredential) login ?: "" else "" + get() = when { + rememberCredential -> login ?: "" + BuildConfig.DEBUG -> "ThomasTest" + else -> "" + } private val ICredentialRepository.rememberedPassword: String - get() = if (rememberCredential) password ?: "" else "" + get() = when { + rememberCredential -> password ?: "" + BuildConfig.DEBUG -> "MotDePasseRandom$2" + else -> "" + } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/BookSearchViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/BookSearchViewModel.kt index 70f3704..9e66fb7 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/BookSearchViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/BookSearchViewModel.kt @@ -1,6 +1,7 @@ package com.pixelized.biblib.ui.screen.home import android.app.Application +import android.util.Log import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.mutableStateOf @@ -94,6 +95,7 @@ class BookSearchViewModel @Inject constructor( bookRepository.update() searchSource?.invalidate() } catch (exception: Exception) { + Log.e("BookSearchViewModel", exception.message, exception) _error.emit( HomeScreenErrorUio.LibraryUpdate( message = R.string.error_book_update_message, 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 index 5dfbe7f..30ef4fe 100644 --- 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 @@ -2,23 +2,56 @@ package com.pixelized.biblib.ui.screen.home import android.content.res.Configuration import androidx.annotation.StringRes -import androidx.compose.animation.* +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.tween -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.* +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.* +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.SnackbarResult +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Search import androidx.compose.material.pullrefresh.PullRefreshState import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -37,15 +70,31 @@ import androidx.compose.ui.unit.max import androidx.hilt.navigation.compose.hiltViewModel import androidx.paging.PagingData import androidx.paging.compose.collectAsLazyPagingItems +import coil.compose.AsyncImage import com.pixelized.biblib.R import com.pixelized.biblib.model.search.FilterType import com.pixelized.biblib.model.search.SortType import com.pixelized.biblib.model.search.SortValue import com.pixelized.biblib.ui.LocalSnackHostState -import com.pixelized.biblib.ui.composable.scaffold.* +import com.pixelized.biblib.ui.composable.Loader +import com.pixelized.biblib.ui.composable.colorElevation +import com.pixelized.biblib.ui.composable.scaffold.DetailBottomSheet +import com.pixelized.biblib.ui.composable.scaffold.DetailBottomSheetState +import com.pixelized.biblib.ui.composable.scaffold.FilterBottomSheet +import com.pixelized.biblib.ui.composable.scaffold.FilterBottomSheetResult +import com.pixelized.biblib.ui.composable.scaffold.FilterBottomSheetState +import com.pixelized.biblib.ui.composable.scaffold.SortBottomSheet +import com.pixelized.biblib.ui.composable.scaffold.SortBottomSheetResult +import com.pixelized.biblib.ui.composable.scaffold.SortBottomSheetState +import com.pixelized.biblib.ui.composable.scaffold.rememberDetailBottomSheetState +import com.pixelized.biblib.ui.composable.scaffold.rememberFilterBottomSheetState +import com.pixelized.biblib.ui.composable.scaffold.rememberSortBottomSheetState import com.pixelized.biblib.ui.navigation.LocalScreenNavHostController import com.pixelized.biblib.ui.navigation.navigateToProfile -import com.pixelized.biblib.ui.screen.home.common.item.* +import com.pixelized.biblib.ui.screen.home.common.item.BookThumbnailUio +import com.pixelized.biblib.ui.screen.home.common.item.LargeBookThumbnail +import com.pixelized.biblib.ui.screen.home.common.item.MicroBookThumbnail +import com.pixelized.biblib.ui.screen.home.common.item.SmallBookThumbnail import com.pixelized.biblib.ui.screen.home.common.preview.thumbnailPreviewResources import com.pixelized.biblib.ui.screen.home.filter.FilterChip import com.pixelized.biblib.ui.screen.home.filter.FilterChipUio @@ -57,7 +106,7 @@ import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.imeHeight import com.pixelized.biblib.utils.extention.navigationBarsHeight -import com.skydoves.landscapist.glide.GlideImage +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch @@ -104,14 +153,7 @@ fun HomeScreen( val largeGridState = rememberLazyGridState() val smallListState = rememberLazyListState() val microListState = rememberLazyListState() - val refreshState = rememberPullRefreshState( - refreshing = false, - onRefresh = { - scope.launch { - bookViewModel.updateLibrary() - } - }, - ) + val refreshState = rememberPullRefreshState(viewModel = bookViewModel) FilterBottomSheet( sheetState = filterState, @@ -245,18 +287,22 @@ private fun rememberOnFilter( author = result.filter.filterLabel, authorId = result.filter.filterId, ) + FilterType.Series -> filterBySeries( series = result.filter.filterLabel, seriesId = result.filter.filterId, ) + FilterType.Genre -> filterByGenre( genre = result.filter.filterLabel, genreId = result.filter.filterId, ) + FilterType.Language -> filterByLanguage( language = result.filter.filterLabel, languageId = result.filter.filterId, ) + else -> Unit } } @@ -269,18 +315,22 @@ private fun rememberOnFilter( author = null, authorId = null, ) + FilterType.Series -> filterBySeries( series = null, seriesId = null, ) + FilterType.Genre -> filterByGenre( genre = null, genreId = null, ) + FilterType.Language -> filterByLanguage( language = null, languageId = null, ) + else -> Unit } } @@ -357,18 +407,13 @@ private fun HomeScreenContent( ) }, loader = { - Box( + Loader( modifier = Modifier .padding(horizontal = 16.dp) - .height(1.dp) - ) { - Divider() - if (isLoading.value) { - LinearProgressIndicator( - modifier = Modifier.fillMaxWidth() - ) - } - } + .height(3.dp), + refreshState = refreshState, + isLoading = isLoading, + ) }, content = { val navigationBarsHeight = navigationBarsHeight() @@ -444,14 +489,10 @@ private fun HomeToolbar( onAvatarTap: () -> Unit, ) { val direction = LocalLayoutDirection.current - val elevation = LocalElevationOverlay.current Surface( modifier = modifier, shape = CircleShape, - color = elevation?.apply( - MaterialTheme.colors.surface, - LocalAbsoluteElevation.current + 1.dp - ) ?: MaterialTheme.colors.surface, + color = colorElevation(elevation = 1.dp), ) { Row( modifier = Modifier.padding( @@ -490,14 +531,14 @@ private fun HomeToolbar( onClick = onAvatarTap, ) { if (avatar != null) { - GlideImage( + AsyncImage( modifier = Modifier .clip(CircleShape) .size(MaterialTheme.bibLib.dimen.avatar.medium), - previewPlaceholder = R.drawable.ic_google, - placeHolder = painterResource(id = R.drawable.ic_google), + model = avatar, + placeholder = painterResource(id = R.drawable.ic_google), contentScale = ContentScale.Fit, - imageModel = avatar, + contentDescription = null, ) } else { Icon( @@ -610,6 +651,22 @@ private fun BookList( } } +@Composable +@OptIn(ExperimentalMaterialApi::class) +private fun rememberPullRefreshState( + viewModel: BookSearchViewModel, + scope: CoroutineScope = rememberCoroutineScope() +): PullRefreshState { + return rememberPullRefreshState( + refreshing = viewModel.updating.value, + onRefresh = { + scope.launch { + viewModel.updateLibrary() + } + }, + ) +} + @OptIn(ExperimentalComposeUiApi::class) @Composable private fun CollapseKeyboardOnScrollHandler( @@ -654,7 +711,7 @@ private fun HomeScreenPreview() { onSort = { }, onDisplay = { }, isLoading = remember { mutableStateOf(false) }, - refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }), + refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }), display = remember { mutableStateOf(HomeDisplay.MICRO) }, gridState = rememberLazyGridState(), items = thumbnailPreviewResources(), diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/LargeBookThumbnail.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/LargeBookThumbnail.kt index fa08a02..2abc054 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/LargeBookThumbnail.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/LargeBookThumbnail.kt @@ -3,7 +3,11 @@ package com.pixelized.biblib.ui.screen.home.common.item import android.annotation.SuppressLint import android.content.res.Configuration import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material.Card import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable @@ -11,17 +15,18 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage import com.pixelized.biblib.R import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.modifier.drawDiagonalLabel import com.pixelized.biblib.utils.extention.placeholder -import com.skydoves.landscapist.glide.GlideImage @Composable fun LargeBookThumbnail( @@ -55,15 +60,17 @@ private fun LargeBookThumbnailContent( thumbnail: BookThumbnailUio, onClick: (BookThumbnailUio) -> Unit, ) { - GlideImage( + AsyncImage( modifier = Modifier .clip(shape = MaterialTheme.bibLib.shapes.bookThumbnailCoverLarge) .fillMaxWidth() .aspectRatio(64f / 102f) .isNew { thumbnail.isNew } - .clickable { thumbnail.let(onClick) }, - previewPlaceholder = R.drawable.ic_fondatoin_cover, - imageModel = thumbnail.cover, + .clickable { thumbnail.let(onClick) } + .then(other = modifier), + model = thumbnail.cover, + contentScale = ContentScale.Crop, + contentDescription = null, ) } @@ -79,6 +86,7 @@ private fun LargeBookThumbnailPlaceHolder( shape = MaterialTheme.bibLib.shapes.bookThumbnailCoverLarge, visible = { true } ) + .then(other = modifier) ) } diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/MicroBookThumbnail.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/MicroBookThumbnail.kt index 33ddcb1..5e993fb 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/MicroBookThumbnail.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/MicroBookThumbnail.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow @@ -24,12 +25,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import coil.compose.AsyncImage import com.pixelized.biblib.R import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.modifier.drawDiagonalLabel import com.pixelized.biblib.utils.extention.placeholder -import com.skydoves.landscapist.glide.GlideImage @Composable fun MicroBookThumbnail( @@ -68,7 +69,7 @@ private fun MicroBookThumbnailContent( ) { val (cover, title, author, series) = createRefs() - GlideImage( + AsyncImage( modifier = Modifier .constrainAs(cover) { top.linkTo(parent.top) @@ -78,8 +79,9 @@ private fun MicroBookThumbnailContent( .clip(shape = MaterialTheme.bibLib.shapes.bookThumbnailCoverSmall) .size(size = MaterialTheme.bibLib.dimen.thumbnail.micro) .isNew { thumbnail.isNew }, - previewPlaceholder = R.drawable.ic_fondation_thumbnail, - imageModel = thumbnail.thumbnail, + model = thumbnail.thumbnail, + contentScale = ContentScale.Crop, + contentDescription = null, ) Text( diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/SmallBookThumbnail.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/SmallBookThumbnail.kt index a8d4c96..22b9eb5 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/SmallBookThumbnail.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/item/SmallBookThumbnail.kt @@ -3,7 +3,10 @@ package com.pixelized.biblib.ui.screen.home.common.item import android.annotation.SuppressLint import android.content.res.Configuration import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material.Card import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -12,6 +15,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow @@ -20,12 +24,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import coil.compose.AsyncImage import com.pixelized.biblib.R import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.modifier.drawDiagonalLabel import com.pixelized.biblib.utils.extention.placeholder -import com.skydoves.landscapist.glide.GlideImage @Composable fun SmallBookThumbnail( @@ -64,7 +68,7 @@ private fun SmallBookThumbnailContent( ) { val (cover, title, author, series, genre, date) = createRefs() - GlideImage( + AsyncImage( modifier = Modifier .constrainAs(cover) { top.linkTo(parent.top) @@ -74,8 +78,9 @@ private fun SmallBookThumbnailContent( .clip(shape = MaterialTheme.bibLib.shapes.bookThumbnailCoverSmall) .size(size = MaterialTheme.bibLib.dimen.thumbnail.cover) .isNew { thumbnail.isNew }, - previewPlaceholder = R.drawable.ic_fondation_thumbnail, - imageModel = thumbnail.thumbnail, + model = thumbnail.thumbnail, + contentScale = ContentScale.Crop, + contentDescription = null, ) Text( diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreen.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreen.kt index 52611c5..1e02b9c 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreen.kt @@ -18,6 +18,7 @@ import com.pixelized.biblib.ui.composable.scaffold.LocalDetailBottomSheetState import com.pixelized.biblib.ui.screen.home.profile.ProfileViewModel import com.pixelized.biblib.ui.screen.home.profile.UserUio import com.pixelized.biblib.ui.theme.color.ShadowPalette +import com.pixelized.biblib.utils.extention.bibLib import kotlinx.coroutines.launch @Stable @@ -73,6 +74,7 @@ fun DetailScreen( ModalBottomSheetLayout( modifier = Modifier.fillMaxSize(), sheetState = emailSheetState, + sheetShape = MaterialTheme.bibLib.shapes.sheet, scrimColor = ShadowPalette.scrim, sheetContent = { DetailScreenSendOption( diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenContent.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenContent.kt index a9a5041..68c626e 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenContent.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenContent.kt @@ -2,12 +2,25 @@ package com.pixelized.biblib.ui.screen.home.detail import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES -import android.graphics.drawable.Drawable -import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.Image import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button @@ -15,12 +28,18 @@ 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.Send -import androidx.compose.runtime.* +import androidx.compose.material.icons.automirrored.filled.Send +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -31,10 +50,10 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.core.text.HtmlCompat import androidx.core.text.toSpannable -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.target.Target +import coil.compose.AsyncImagePainter +import coil.compose.rememberAsyncImagePainter +import coil.request.ImageRequest +import coil.size.Dimension import com.pixelized.biblib.R import com.pixelized.biblib.ui.composable.SpannedText import com.pixelized.biblib.ui.composable.animation.AnimatedDelayer @@ -42,8 +61,6 @@ import com.pixelized.biblib.ui.composable.animation.AnimatedOffset import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.placeholder -import com.skydoves.landscapist.CircularReveal -import com.skydoves.landscapist.glide.GlideImage import java.io.Serializable @Stable @@ -117,6 +134,7 @@ fun DetailScreenContent( .verticalScroll(rememberScrollState()) .padding(all = 16.dp) .systemBarsPadding(), + horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) ) { Column( @@ -148,7 +166,6 @@ fun DetailScreenContent( AnimatedOffset { Column( - modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { Text( @@ -161,7 +178,7 @@ fun DetailScreenContent( Text( modifier = Modifier.clickable(onClick = { onSeries(series) }), style = MaterialTheme.typography.body1, - color = MaterialTheme.colors.onSurface, + color = MaterialTheme.bibLib.colors.typography.emphasis, textAlign = TextAlign.Center, text = series.label, ) @@ -171,15 +188,14 @@ fun DetailScreenContent( AnimatedOffset { Column( - modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { book.authors.forEach { author -> Text( modifier = Modifier.clickable(onClick = { onAuthor(author) }), - fontWeight = FontWeight.Bold, + fontWeight = FontWeight.Normal, style = MaterialTheme.typography.h6, - color = MaterialTheme.colors.onSurface, + color = MaterialTheme.bibLib.colors.typography.emphasis, textAlign = TextAlign.Center, text = author.name, ) @@ -243,7 +259,7 @@ fun DetailScreenContent( Button( onClick = onSend, ) { - Icon(imageVector = Icons.Default.Send, contentDescription = "") + Icon(imageVector = Icons.AutoMirrored.Filled.Send, contentDescription = "") Spacer(modifier = Modifier.width(4.dp)) Text(text = stringResource(id = R.string.action_send)) } @@ -257,43 +273,75 @@ private fun Cover( modifier: Modifier = Modifier, cover: String, ) { - val placeholder = remember(cover) { mutableStateOf(true) } - val error = remember(cover) { mutableStateOf(false) } - - Box( - modifier = modifier, - contentAlignment = Alignment.Center - ) { - GlideImage( - requestListener = rememberRequestListener( - placeholder = placeholder, - error = error, - ), - previewPlaceholder = R.drawable.ic_fondatoin_cover, - circularReveal = CircularReveal(duration = 500), - contentScale = ContentScale.FillHeight, - imageModel = cover, - ) - Box( - modifier = Modifier - .height(MaterialTheme.bibLib.dimen.detail.cover) - .aspectRatio(ratio = remember { 1f / 1.5f }) - .placeholder( - shape = MaterialTheme.bibLib.shapes.bookThumbnailCoverLarge, - isShimmering = { error.value.not() }, - visible = { placeholder.value }, - ) - ) - AnimatedVisibility( - visible = error.value, - enter = fadeIn(), - exit = fadeOut(), - ) { - Icon( - modifier = Modifier.size(64.dp), - painter = painterResource(id = R.drawable.ic_no_photography_24), - contentDescription = null + val painter = rememberAsyncImagePainter( + model = ImageRequest.Builder(LocalContext.current) + .data(data = cover) + .size( + width = Dimension.Undefined, + height = Dimension.Pixels( + px = with(LocalDensity.current) { + MaterialTheme.bibLib.dimen.detail.cover.roundToPx() + }, + ), ) + .build() + ) + + AnimatedContent( + targetState = painter.state, + label = "Cover image animation", + transitionSpec = { fadeIn() togetherWith fadeOut() } + ) { + Box( + modifier = modifier, + contentAlignment = Alignment.Center, + ) { + when (it) { + AsyncImagePainter.State.Empty -> { + Box( + modifier = Modifier + .height(MaterialTheme.bibLib.dimen.detail.cover) + .aspectRatio(ratio = remember { 1f / 1.5f }) + ) + } + + is AsyncImagePainter.State.Error -> { + Box( + modifier = Modifier + .height(MaterialTheme.bibLib.dimen.detail.cover) + .aspectRatio(ratio = remember { 1f / 1.5f }), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier.size(64.dp), + painter = painterResource(id = R.drawable.ic_no_photography_24), + contentDescription = null + ) + } + } + + is AsyncImagePainter.State.Loading -> { + Box( + modifier = Modifier + .height(MaterialTheme.bibLib.dimen.detail.cover) + .aspectRatio(ratio = remember { 1f / 1.5f }) + .placeholder( + shape = MaterialTheme.bibLib.shapes.bookThumbnailCoverLarge, + isShimmering = { painter.state is AsyncImagePainter.State.Loading }, + visible = { true }, + ) + ) + } + + is AsyncImagePainter.State.Success -> { + Image( + modifier = Modifier.height(MaterialTheme.bibLib.dimen.detail.cover), + painter = painter, + contentScale = ContentScale.FillHeight, + contentDescription = null, + ) + } + } } } } @@ -327,37 +375,6 @@ private fun TitleLabel( } } -@Composable -fun rememberRequestListener( - placeholder: MutableState, - error: MutableState, -): RequestListener { - return remember(placeholder, error) { - object : RequestListener { - override fun onLoadFailed( - exception: GlideException?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ): Boolean { - error.value = true - return true - } - - override fun onResourceReady( - resource: Drawable?, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean - ): Boolean { - placeholder.value = false - return false - } - } - } -} - @Composable @Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, heightDp = 1000) @Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES, heightDp = 1000) diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenSendOption.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenSendOption.kt index 4d81c39..3a11ab8 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenSendOption.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenSendOption.kt @@ -15,6 +15,7 @@ import androidx.compose.material.RadioButton import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -25,6 +26,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.biblib.R +import com.pixelized.biblib.ui.composable.Handle import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.utils.extention.* @@ -87,6 +89,11 @@ fun DetailScreenSendOption( .padding(top = 16.dp) .padding(horizontal = 16.dp), ) { + Handle( + modifier = Modifier + .align(alignment = CenterHorizontally) + .padding(bottom = 16.dp), + ) Text( modifier = Modifier.padding(bottom = 16.dp), style = MaterialTheme.typography.h6, @@ -95,7 +102,6 @@ fun DetailScreenSendOption( ) Text( - modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.caption, color = MaterialTheme.bibLib.colors.typography.light, text = rememberDescription() diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/FilterPage.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/FilterPage.kt index 442e66f..deeef67 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/FilterPage.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/FilterPage.kt @@ -1,31 +1,52 @@ package com.pixelized.biblib.ui.screen.home.filter +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.text.KeyboardActionScope import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.* +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Clear import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.max +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.model.search.FilterType +import com.pixelized.biblib.ui.composable.Handle +import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.utils.extention.bibLib +import com.pixelized.biblib.utils.extention.imeHeight +import com.pixelized.biblib.utils.extention.navigationBarsHeight +import kotlinx.coroutines.flow.emptyFlow @Stable @Immutable @@ -37,26 +58,23 @@ data class FilterItemUio( @Composable fun FilterPage( + modifier: Modifier, viewModel: IFilterViewModel, focusRequester: FocusRequester, onFilter: (FilterItemUio) -> Unit, - onClose: () -> Unit, onIMEDone: KeyboardActionScope.() -> Unit, ) { val filters = viewModel.paging.collectAsLazyPagingItems() FilterPageContent( - modifier = Modifier - .fillMaxSize() - .imePadding() - .systemBarsPadding(), + modifier = modifier, focusRequester = focusRequester, title = viewModel.title, search = viewModel.search, onSearchUpdate = { viewModel.updateSearch(it) }, + onClear = { viewModel.updateSearch("") }, filters = filters, onFilter = onFilter, - onClose = onClose, onIMEDone = onIMEDone, ) } @@ -68,68 +86,98 @@ private fun FilterPageContent( title: String, search: State, onSearchUpdate: (String) -> Unit, + onClear: () -> Unit, filters: LazyPagingItems, onFilter: (FilterItemUio) -> Unit, - onClose: () -> Unit, onIMEDone: KeyboardActionScope.() -> Unit, ) { + val navigationBarsHeight = navigationBarsHeight() + val imeHeight = imeHeight() + val paddingValues = remember(navigationBarsHeight, imeHeight) { + PaddingValues( + bottom = max(navigationBarsHeight, imeHeight) + ) + } + Column( modifier = modifier, ) { - TopAppBar( - backgroundColor = MaterialTheme.colors.surface, - elevation = 0.dp, - title = { - Text( - style = MaterialTheme.typography.h6, - color = MaterialTheme.colors.onSurface, - text = title, - ) - }, - navigationIcon = { - IconButton(onClick = onClose) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = null, - ) - } - } + Handle( + modifier = Modifier + .align(alignment = Alignment.CenterHorizontally) + .padding(vertical = 16.dp), + ) + Text( + modifier = Modifier.padding(horizontal = 16.dp), + style = MaterialTheme.bibLib.typography.base.caption, + color = MaterialTheme.bibLib.colors.typography.light, + text = title, ) TextField( modifier = Modifier .focusRequester(focusRequester = focusRequester) .fillMaxWidth(), - label = { - Text( - color = MaterialTheme.colors.onSurface, - text = stringResource(id = R.string.search_filter_title) - ) + trailingIcon = { + AnimatedVisibility( + visible = search.value.isNotBlank(), + enter = fadeIn(), + exit = fadeOut(), + ) { + IconButton( + onClick = onClear, + ) { + Icon( + imageVector = Icons.Default.Clear, + tint = MaterialTheme.bibLib.colors.typography.emphasis, + contentDescription = null, + ) + } + } }, value = search.value, singleLine = true, - colors = TextFieldDefaults.outlinedTextFieldColors( - focusedBorderColor = Color.Transparent, - unfocusedBorderColor = Color.Transparent, - disabledBorderColor = Color.Transparent, - ), + colors = TextFieldDefaults.outlinedTextFieldColors(), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = onIMEDone), onValueChange = onSearchUpdate ) LazyColumn( - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), + contentPadding = paddingValues, ) { - items(items = filters, key = { it.filterId }) { + items( + count = filters.itemCount, + key = { filters[it]?.filterId ?: -1 }, + ) { + val item = filters[it] Text( modifier = Modifier - .clickable { it?.let { onFilter(it) } } + .clickable { item?.let(onFilter) } .fillMaxWidth() .padding(all = MaterialTheme.bibLib.dimen.thumbnail.padding), style = MaterialTheme.typography.body1, color = MaterialTheme.colors.onSurface, - text = it?.filterLabel ?: "" + text = item?.filterLabel ?: "" ) } } } +} + +@Composable +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES) +private fun FilterPageContentPreview() { + BibLibTheme { + FilterPageContent( + focusRequester = remember { FocusRequester() }, + title = "Search by Author", + search = remember { mutableStateOf("") }, + onSearchUpdate = { }, + onClear = { }, + filters = remember { emptyFlow>() }.collectAsLazyPagingItems(), + onFilter = { }, + onIMEDone = { }, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/AuthorFilterViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/AuthorFilterViewModel.kt index eab0d1b..d069f09 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/AuthorFilterViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/AuthorFilterViewModel.kt @@ -26,7 +26,10 @@ class AuthorFilterViewModel @Inject constructor( private val searchRepository: ISearchRepository, ) : ViewModel(), IFilterViewModel { - override val title: String = application.getString(R.string.search_filter_author) + override val title: String = application.getString( + R.string.search_filter_title, + application.getString(R.string.search_filter_author), + ) private var source: AuthorSearchSource? = null override val paging: Flow> diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/GenreFilterViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/GenreFilterViewModel.kt index 72c4cac..cf6c66c 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/GenreFilterViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/GenreFilterViewModel.kt @@ -26,7 +26,10 @@ class GenreFilterViewModel @Inject constructor( private val searchRepository: ISearchRepository, ) : ViewModel(), IFilterViewModel { - override val title: String = application.getString(R.string.search_filter_genre) + override val title: String = application.getString( + R.string.search_filter_title, + application.getString(R.string.search_filter_genre), + ) private var source: GenreSearchSource? = null override val paging: Flow> diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/LanguageFilterViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/LanguageFilterViewModel.kt index 0a48358..f6bdb0c 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/LanguageFilterViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/LanguageFilterViewModel.kt @@ -26,7 +26,10 @@ class LanguageFilterViewModel @Inject constructor( private val searchRepository: ISearchRepository, ) : ViewModel(), IFilterViewModel { - override val title: String = application.getString(R.string.search_filter_language) + override val title: String = application.getString( + R.string.search_filter_title, + application.getString(R.string.search_filter_language), + ) private var source: LanguageSearchSource? = null override val paging: Flow> diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/SeriesFilterViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/SeriesFilterViewModel.kt index 550b979..aead3bd 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/SeriesFilterViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/filter/viewModel/SeriesFilterViewModel.kt @@ -26,7 +26,10 @@ class SeriesFilterViewModel @Inject constructor( private val searchRepository: ISearchRepository, ) : ViewModel(), IFilterViewModel { - override val title: String = application.getString(R.string.search_filter_series) + override val title: String = application.getString( + R.string.search_filter_title, + application.getString(R.string.search_filter_series), + ) private var source: SeriesSearchSource? = null override val paging: Flow> diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/profile/ProfilePage.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/profile/ProfilePage.kt index 26a5801..185fb94 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/profile/ProfilePage.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/profile/ProfilePage.kt @@ -142,7 +142,7 @@ private fun ProfileScreenContent( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { Button( - colors = ButtonDefaults.textButtonColors(), + colors = ButtonDefaults.outlinedButtonColors(), onClick = onEditClick, ) { Text( @@ -151,7 +151,7 @@ private fun ProfileScreenContent( } Button( - colors = ButtonDefaults.textButtonColors(), + colors = ButtonDefaults.buttonColors(), onClick = onLogoutClick, ) { Text( diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/sort/BookSortPage.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/sort/BookSortPage.kt index 06a7b26..87d2145 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/sort/BookSortPage.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/sort/BookSortPage.kt @@ -26,6 +26,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.biblib.R import com.pixelized.biblib.model.search.SortType import com.pixelized.biblib.model.search.SortValue +import com.pixelized.biblib.ui.composable.Handle import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.utils.extention.bibLib import org.burnoutcrew.reorderable.* @@ -73,6 +74,10 @@ private fun BookSortPageContent( modifier = modifier, verticalArrangement = Arrangement.spacedBy(16.dp), ) { + Handle( + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + Text( modifier = Modifier.padding(horizontal = 16.dp), style = MaterialTheme.typography.h6, 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 ec75872..9865218 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 @@ -4,11 +4,13 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import com.pixelized.biblib.ui.composable.SystemTheme 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.BibLibShape import com.pixelized.biblib.ui.theme.typography.BibLibTypography +import com.pixelized.biblib.utils.extention.bibLib @Composable fun BibLibTheme( @@ -26,7 +28,15 @@ fun BibLibTheme( colors = theme.colors.base, typography = theme.typography.base, shapes = theme.shapes.base, - content = content + content = { + SystemTheme( + statusBackground = MaterialTheme.bibLib.colors.system.status, + statusDarkIcons = darkTheme.not(), + navigationBackground = MaterialTheme.bibLib.colors.system.navigation, + navigationDarkIcons = darkTheme.not(), + content = content, + ) + } ) } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/theme/color/BibLibColor.kt b/app/src/main/java/com/pixelized/biblib/ui/theme/color/BibLibColor.kt index ce49dd0..5d48cb4 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/theme/color/BibLibColor.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/theme/color/BibLibColor.kt @@ -1,10 +1,12 @@ package com.pixelized.biblib.ui.theme.color import androidx.compose.material.Colors +import androidx.compose.material.MaterialTheme import androidx.compose.material.darkColors import androidx.compose.material.lightColors import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.Color +import com.pixelized.biblib.utils.extention.bibLib import javax.annotation.concurrent.Immutable @@ -12,9 +14,18 @@ import javax.annotation.concurrent.Immutable @Immutable data class BibLibColor( val base: Colors, + val system: System, + val handle: Color, val typography: Typography, val placeHolder: PlaceHolder, ) { + @Stable + @Immutable + data class System( + val status: Color, + val navigation: Color, + ) + @Stable @Immutable data class Typography( @@ -40,6 +51,11 @@ fun bibLibDarkColors( error = BibLibColorPalette.Red, onError = Color.White, ), + system: BibLibColor.System = BibLibColor.System( + status = base.surface.copy(alpha = 0.5f), + navigation = base.surface.copy(alpha = 0.5f), + ), + handle: Color = base.onSurface.copy(alpha = 0.2f), typography: BibLibColor.Typography = BibLibColor.Typography( light = base.onSurface.copy(alpha = 0.67f), medium = base.onSurface, @@ -51,6 +67,8 @@ fun bibLibDarkColors( ), ) = BibLibColor( base = base, + system = system, + handle = handle, typography = typography, placeHolder = placeHolder, ) @@ -68,6 +86,11 @@ fun bibLibLightColors( surface = Color.White, onSurface = BibLibColorPalette.VeryDarkGrey, ), + system: BibLibColor.System = BibLibColor.System( + status = base.surface, + navigation = base.surface, + ), + handle: Color = base.onSurface.copy(alpha = 0.1f), typography: BibLibColor.Typography = BibLibColor.Typography( light = base.onSurface.copy(alpha = 0.67f), medium = base.onSurface, @@ -79,6 +102,8 @@ fun bibLibLightColors( ), ) = BibLibColor( base = base, + system = system, + handle = handle, typography = typography, placeHolder = placeHolder, ) \ No newline at end of file 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 57ba712..64b1069 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 @@ -11,6 +11,7 @@ import androidx.compose.ui.unit.dp data class BibLibDimen( val avatar: Avatar = Avatar(), val progress: Progress = Progress(), + val handle: Handle = Handle(), val detail: BookDetail = BookDetail(), val thumbnail: BookThumbnail = BookThumbnail(), ) { @@ -26,6 +27,13 @@ data class BibLibDimen( val iconSize: Dp = 52.dp, ) + @Stable + @Immutable + data class Handle( + val width: Dp = 32.dp, + val thickness: Dp = 4.dp, + ) + @Stable @Immutable data class BookDetail( 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 index 64adddc..1e58a6e 100644 --- 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 @@ -17,4 +17,5 @@ data class BibLibShape( ), val bookThumbnailCoverSmall: Shape = RoundedCornerShape(4.dp), val bookThumbnailCoverLarge: Shape = RoundedCornerShape(4.dp), + val sheet: Shape = RoundedCornerShape(topEnd = 16.dp, topStart = 16.dp), ) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/utils/DisableInteractionSource.kt b/app/src/main/java/com/pixelized/biblib/utils/DisableInteractionSource.kt new file mode 100644 index 0000000..a72b7f4 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/utils/DisableInteractionSource.kt @@ -0,0 +1,16 @@ +package com.pixelized.biblib.utils + +import androidx.compose.foundation.interaction.Interaction +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.runtime.Stable +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +@Stable +class DisableInteractionSource : MutableInteractionSource { + override val interactions: Flow = flowOf() + + override suspend fun emit(interaction: Interaction) = Unit + + override fun tryEmit(interaction: Interaction): Boolean = false +} diff --git a/app/src/main/java/com/pixelized/biblib/utils/extention/modifier/drawDiagonalLabel.kt b/app/src/main/java/com/pixelized/biblib/utils/extention/modifier/drawDiagonalLabel.kt index bc03837..6d2433c 100644 --- a/app/src/main/java/com/pixelized/biblib/utils/extention/modifier/drawDiagonalLabel.kt +++ b/app/src/main/java/com/pixelized/biblib/utils/extention/modifier/drawDiagonalLabel.kt @@ -32,7 +32,7 @@ fun Modifier.drawDiagonalLabel( val textWidth = textSize.width val textHeight = textSize.height - val rectWidth = textWidth * labelTextRatio * 1.1f + val rectWidth = textWidth * labelTextRatio * 1.5f val rect = Rect( offset = Offset(canvasWidth - rectWidth, 0f), diff --git a/app/src/main/java/com/pixelized/biblib/utils/extention/modifier/unclickable.kt b/app/src/main/java/com/pixelized/biblib/utils/extention/modifier/unclickable.kt new file mode 100644 index 0000000..9b73d6c --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/utils/extention/modifier/unclickable.kt @@ -0,0 +1,15 @@ +package com.pixelized.biblib.utils.extention.modifier + +import androidx.compose.foundation.clickable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Modifier +import com.pixelized.biblib.utils.DisableInteractionSource + +@Stable +fun Modifier.unclickable( + disable: Boolean +): Modifier = if (disable) this.clickable( + interactionSource = DisableInteractionSource(), + indication = null, + onClick = { }, +) else this \ No newline at end of file diff --git a/app/src/main/res/drawable/biblib_background_1.jpeg b/app/src/main/res/drawable/biblib_background_1.jpeg new file mode 100644 index 0000000..07c37d2 Binary files /dev/null and b/app/src/main/res/drawable/biblib_background_1.jpeg differ diff --git a/app/src/main/res/drawable/biblib_background_2.jpeg b/app/src/main/res/drawable/biblib_background_2.jpeg new file mode 100644 index 0000000..18d3bd3 Binary files /dev/null and b/app/src/main/res/drawable/biblib_background_2.jpeg differ diff --git a/app/src/main/res/drawable/biblib_background_3.jpeg b/app/src/main/res/drawable/biblib_background_3.jpeg new file mode 100644 index 0000000..4dd0e47 Binary files /dev/null and b/app/src/main/res/drawable/biblib_background_3.jpeg differ diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index bc47762..1fe9e01 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -37,10 +37,10 @@ - Authentification à BibLib Alias du compte Mot de passe Mémoriser mes identifiants + Authentifiez-vous avec votre compte BibLib ouvrir sur bib.bibulle.fr Note @@ -61,7 +61,7 @@ eBook envoyé avec succès Rechercher sur Biblib - Rechercher + Rechercher par %1$s %1$s : %2$s Nouveauté Auteur diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 58d0d21..c25671c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,10 +50,10 @@ - Sign in to BibLib Login Password Remember my credential + Sign in with your BibLib account New @@ -77,7 +77,7 @@ eBook sent with success Search on Biblib - Search + Search by %1$s %1$s: %2$s New Authors diff --git a/build.gradle b/build.gradle index 976caea..8808837 100644 --- a/build.gradle +++ b/build.gradle @@ -5,14 +5,10 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10' - classpath "com.google.dagger:hilt-android-gradle-plugin:2.43.2" - classpath 'com.google.gms:google-services:4.3.15' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.4' + classpath 'com.android.tools.build:gradle:8.5.0' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23' + classpath "com.google.dagger:hilt-android-gradle-plugin:2.51" + classpath 'com.google.gms:google-services:4.4.2' + classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.2' } -} - -task clean(type: Delete) { - delete rootProject.buildDir } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index dbc9506..8b07d1d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,4 +18,6 @@ android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=false # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official +android.nonTransitiveRClass=false +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c30efcb..29692df 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sat Oct 15 11:40:37 CEST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME