Compare commits

...

10 commits

Author SHA1 Message Date
Andres Gomez, Thomas (ITDV RL)
15eb013579 Migrate from glide to coil. 2024-06-27 15:13:13 +02:00
Andres Gomez, Thomas (ITDV RL)
ca41948a75 Update dependencies / gradle etc. 2024-06-27 11:50:24 +02:00
Andres Gomez, Thomas (ITDV RL)
b907e934c9 Change the Authentication Screen. 2024-06-26 16:39:08 +02:00
Andres Gomez, Thomas (ITDV RL)
529ea5f893 Bump version to 0.1.6 2024-06-25 10:01:22 +02:00
Andres Gomez, Thomas (ITDV RL)
a26fa26400 Fix an error on a misconstruct author data. 2024-06-25 09:45:23 +02:00
Thomas Andres Gomez
4c98949bb6 Add custom loader on the HomeScreen 2023-04-07 17:29:05 +02:00
Thomas Andres Gomez
76a5e6dd84 Adjust the theme overlay transparency for LightTheme 2023-04-07 16:22:45 +02:00
Thomas Andres Gomez
5bf4313fd8 Fix profile dialog button colors. 2023-04-07 16:20:10 +02:00
Thomas Andres Gomez
1f0c6de78d Apply Handle + shape to all bottomsheet 2023-04-07 16:14:18 +02:00
Thomas Andres Gomez
7cc08cf300 Change the system theme + filter bottomsheet 2023-04-07 15:49:08 +02:00
51 changed files with 1465 additions and 741 deletions

BIN
app/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -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() {

View 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"
}

View 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)
}
}
}
}

View file

@ -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
}

View file

@ -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,
)
}
}
}
}

View file

@ -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(

View file

@ -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,
)
)

View file

@ -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,
)

View file

@ -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)
)
}

View file

@ -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()
}
}

View file

@ -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
}

View file

@ -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,8 +137,15 @@ fun FilterBottomSheet(
onBack = { currentBottomSheetData?.dismiss() },
)
BoxWithConstraints {
val statusBarHeight = statusBarsHeight()
val bottomSheetHeight = remember(statusBarHeight) {
maxHeight - statusBarHeight - 16.dp
}
ModalBottomSheetLayout(
sheetState = bottomSheetState,
sheetShape = sheetShape,
content = content,
scrimColor = Color.Transparent,
sheetContent = {
@ -146,24 +156,22 @@ fun FilterBottomSheet(
FilterType.Language -> hiltViewModel<LanguageFilterViewModel>()
else -> null
}
if (viewModel != null) {
val focusRequester = remember { FocusRequester() }
FilterPage(
modifier = Modifier
.fillMaxWidth()
.height(bottomSheetHeight),
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()
@ -176,5 +184,6 @@ fun FilterBottomSheet(
},
)
}
}
}

View file

@ -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(

View file

@ -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,17 +124,42 @@ fun AuthenticationScreen(
val snackBarHostState = LocalSnackHostState.current
val uriHandler = LocalUriHandler.current
val context = LocalContext.current
val focusManager = LocalFocusManager.current
val scope = rememberCoroutineScope()
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 }
}
Surface(
modifier = Modifier.fillMaxSize(),
) {
AuthenticationScreenContent(
modifier = Modifier.systemBarsPadding(),
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,
onGoogleSignIn = authenticationViewModel::loginWithGoogle,
onSignIn = {
authenticationViewModel.login(
login = formViewModel.form.login,
@ -85,10 +169,19 @@ fun AuthenticationScreen(
onRegister = {
uriHandler.openUri(uri = IBibLibClient.REGISTER_URL)
},
onDismissRequest = {
displayBibLibSignIn.value = false
},
)
}
AuthenticationHandler(
onFailure = {
BackHandler(
enabled = displayBibLibSignIn.value
) {
displayBibLibSignIn.value = false
}
AuthenticationHandler(onFailure = {
scope.launch {
val result = snackBarHostState.showSnackbar(
message = context.getString(it.message),
@ -99,6 +192,7 @@ fun AuthenticationScreen(
is AuthenticationErrorUio.GoogleLogin -> {
authenticationViewModel.loginWithGoogle()
}
is AuthenticationErrorUio.Login -> {
authenticationViewModel.login(
login = formViewModel.form.login,
@ -108,17 +202,211 @@ fun AuthenticationScreen(
}
}
}
},
onSuccess = {
}, 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 = { },
@ -161,3 +449,41 @@ 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),
)
}

View file

@ -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 = { },
)
}
}

View file

@ -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)
)
}
}

View file

@ -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,
)
}

View file

@ -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,
)
}

View file

@ -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")
}
}
}
}

View file

@ -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 -> ""
}
}

View file

@ -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,

View file

@ -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(

View file

@ -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)
)
}

View file

@ -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(

View file

@ -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(

View file

@ -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(

View file

@ -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,37 +273,44 @@ private fun Cover(
modifier: Modifier = Modifier,
cover: String,
) {
val placeholder = remember(cover) { mutableStateOf(true) }
val error = remember(cover) { mutableStateOf(false) }
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
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,
)
when (it) {
AsyncImagePainter.State.Empty -> {
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(),
}
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),
@ -296,6 +319,31 @@ private fun Cover(
)
}
}
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,
)
}
}
}
}
}
@Composable
@ -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)

View file

@ -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()

View file

@ -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 = {
Handle(
modifier = Modifier
.align(alignment = Alignment.CenterHorizontally)
.padding(vertical = 16.dp),
)
Text(
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onSurface,
modifier = Modifier.padding(horizontal = 16.dp),
style = MaterialTheme.bibLib.typography.base.caption,
color = MaterialTheme.bibLib.colors.typography.light,
text = title,
)
},
navigationIcon = {
IconButton(onClick = onClose) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = null,
)
}
}
)
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 = { },
)
}
}

View file

@ -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>>

View file

@ -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>>

View file

@ -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>>

View file

@ -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>>

View file

@ -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(

View file

@ -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,

View file

@ -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,
)
}
)
}
}

View file

@ -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,
)

View file

@ -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(

View file

@ -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),
)

View file

@ -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
}

View file

@ -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),

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

View file

@ -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>

View file

@ -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>

View file

@ -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
}

View file

@ -19,3 +19,5 @@ android.useAndroidX=true
android.enableJetifier=false
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View file

@ -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