Compare commits
10 commits
785e181482
...
15eb013579
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15eb013579 | ||
|
|
ca41948a75 | ||
|
|
b907e934c9 | ||
|
|
529ea5f893 | ||
|
|
a26fa26400 | ||
|
|
4c98949bb6 | ||
|
|
76a5e6dd84 | ||
|
|
5bf4313fd8 | ||
|
|
1f0c6de78d | ||
|
|
7cc08cf300 |
51 changed files with 1465 additions and 741 deletions
BIN
app/.DS_Store
vendored
Normal file
BIN
app/.DS_Store
vendored
Normal file
Binary file not shown.
|
|
@ -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() {
|
||||
|
|
|
|||
20
app/release/output-metadata.json
Normal file
20
app/release/output-metadata.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SnackbarHostState> {
|
||||
error("SnackBarHostState is not ready yet.")
|
||||
|
|
@ -22,12 +25,10 @@ val LocalSnackHostState = staticCompositionLocalOf<SnackbarHostState> {
|
|||
@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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
|
@ -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<Boolean>,
|
||||
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,
|
||||
)
|
||||
|
|
@ -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<StatusThemeController> {
|
||||
error("LocalStatusTheme is not ready yet.")
|
||||
}
|
||||
|
||||
class StatusThemeController(
|
||||
private val systemController: SystemUiController,
|
||||
private val statusBackground: MutableState<Brush>,
|
||||
private var statusDarkIcons: Boolean,
|
||||
private val navigationBackground: MutableState<Brush>,
|
||||
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<Brush>,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.height(height = height)
|
||||
.fillMaxWidth()
|
||||
.background(brush = brush.value)
|
||||
)
|
||||
}
|
||||
|
|
@ -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<SystemUiController> { 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<AuthorFilterViewModel>()
|
||||
FilterType.Series -> hiltViewModel<SeriesFilterViewModel>()
|
||||
FilterType.Genre -> hiltViewModel<GenreFilterViewModel>()
|
||||
FilterType.Language -> hiltViewModel<LanguageFilterViewModel>()
|
||||
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<AuthorFilterViewModel>()
|
||||
FilterType.Series -> hiltViewModel<SeriesFilterViewModel>()
|
||||
FilterType.Genre -> hiltViewModel<GenreFilterViewModel>()
|
||||
FilterType.Language -> hiltViewModel<LanguageFilterViewModel>()
|
||||
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))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<Dp>,
|
||||
@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<Boolean>,
|
||||
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<Boolean>,
|
||||
) {
|
||||
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<State<Boolean>> {
|
||||
override val values: Sequence<State<Boolean>> = sequenceOf(
|
||||
mutableStateOf(false),
|
||||
mutableStateOf(true),
|
||||
)
|
||||
}
|
||||
|
|
@ -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 = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 -> ""
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<Boolean>,
|
||||
error: MutableState<Boolean>,
|
||||
): RequestListener<Drawable> {
|
||||
return remember(placeholder, error) {
|
||||
object : RequestListener<Drawable> {
|
||||
override fun onLoadFailed(
|
||||
exception: GlideException?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
error.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onResourceReady(
|
||||
resource: Drawable?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
onSearchUpdate: (String) -> Unit,
|
||||
onClear: () -> Unit,
|
||||
filters: LazyPagingItems<FilterItemUio>,
|
||||
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<PagingData<FilterItemUio>>() }.collectAsLazyPagingItems(),
|
||||
onFilter = { },
|
||||
onIMEDone = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<PagingData<FilterItemUio>>
|
||||
|
|
|
|||
|
|
@ -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<PagingData<FilterItemUio>>
|
||||
|
|
|
|||
|
|
@ -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<PagingData<FilterItemUio>>
|
||||
|
|
|
|||
|
|
@ -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<PagingData<FilterItemUio>>
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
|
|
@ -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<Interaction> = flowOf()
|
||||
|
||||
override suspend fun emit(interaction: Interaction) = Unit
|
||||
|
||||
override fun tryEmit(interaction: Interaction): Boolean = false
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
BIN
app/src/main/res/drawable/biblib_background_1.jpeg
Normal file
BIN
app/src/main/res/drawable/biblib_background_1.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 273 KiB |
BIN
app/src/main/res/drawable/biblib_background_2.jpeg
Normal file
BIN
app/src/main/res/drawable/biblib_background_2.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 312 KiB |
BIN
app/src/main/res/drawable/biblib_background_3.jpeg
Normal file
BIN
app/src/main/res/drawable/biblib_background_3.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 324 KiB |
|
|
@ -37,10 +37,10 @@
|
|||
|
||||
<!-- Screens & pages -->
|
||||
|
||||
<string name="authentication_title">Authentification à BibLib</string>
|
||||
<string name="authentication_login">Alias du compte</string>
|
||||
<string name="authentication_password">Mot de passe</string>
|
||||
<string name="authentication_credential_remember">Mémoriser mes identifiants</string>
|
||||
<string name="authentication_sign_in_with_biblib">Authentifiez-vous avec votre compte BibLib</string>
|
||||
|
||||
<string name="detail_open_on_biblib">ouvrir sur bib.bibulle.fr</string>
|
||||
<string name="detail_rating">Note</string>
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
<string name="detail_send_success">eBook envoyé avec succès</string>
|
||||
|
||||
<string name="search_title">Rechercher sur Biblib</string>
|
||||
<string name="search_filter_title">Rechercher</string>
|
||||
<string name="search_filter_title">Rechercher par %1$s</string>
|
||||
<string name="search_filter_param">%1$s : %2$s</string>
|
||||
<string name="search_filter_new">Nouveauté</string>
|
||||
<string name="search_filter_author">Auteur</string>
|
||||
|
|
|
|||
|
|
@ -50,10 +50,10 @@
|
|||
|
||||
<!-- Screens & pages -->
|
||||
|
||||
<string name="authentication_title">Sign in to BibLib</string>
|
||||
<string name="authentication_login">Login</string>
|
||||
<string name="authentication_password">Password</string>
|
||||
<string name="authentication_credential_remember">Remember my credential</string>
|
||||
<string name="authentication_sign_in_with_biblib">Sign in with your BibLib account</string>
|
||||
|
||||
<string name="list_is_new" translatable="false">New</string>
|
||||
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
<string name="detail_send_success">eBook sent with success</string>
|
||||
|
||||
<string name="search_title">Search on Biblib</string>
|
||||
<string name="search_filter_title">Search</string>
|
||||
<string name="search_filter_title">Search by %1$s</string>
|
||||
<string name="search_filter_param">%1$s: %2$s</string>
|
||||
<string name="search_filter_new">New</string>
|
||||
<string name="search_filter_author">Authors</string>
|
||||
|
|
|
|||
14
build.gradle
14
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
|
||||
}
|
||||
|
|
@ -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
|
||||
kotlin.code.style=official
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue