Add profile screen to the search bar.

This commit is contained in:
Thomas Andres Gomez 2022-06-30 12:56:04 +02:00
parent 5ac6d6b352
commit f6739f60ee
26 changed files with 390 additions and 262 deletions

View file

@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<application
android:name=".BibLibApplication"

View file

@ -1,12 +1,15 @@
package com.pixelized.biblib.module
import android.content.Context
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.pixelized.biblib.network.client.BibLibClient
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.repository.connectivity.ConnectivityRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@ -29,4 +32,12 @@ class NetworkModule {
gson = gson
)
}
@Provides
@Singleton
fun provideConnectivityRepository(
@ApplicationContext context: Context,
): ConnectivityRepository {
return ConnectivityRepository(context)
}
}

View file

@ -0,0 +1,50 @@
package com.pixelized.biblib.repository.connectivity
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import androidx.core.content.getSystemService
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
/**
* This class is responsible to check for internet availability.
* https://developer.android.com/training/monitoring-device-state/connectivity-status-type
*/
class ConnectivityRepository constructor(
@ApplicationContext context: Context,
) {
private val _networkAvailabilityFlow = MutableStateFlow(true)
val networkAvailabilityFlow: Flow<Boolean> get() = _networkAvailabilityFlow
val isNetworkAvailable: Boolean get() = _networkAvailabilityFlow.value
init {
context.getSystemService<ConnectivityManager>()?.let { connectivityManager ->
val networkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build()
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
_networkAvailabilityFlow.value = true
}
override fun onLost(network: Network) {
super.onLost(network)
_networkAvailabilityFlow.value = false
}
}
connectivityManager.requestNetwork(networkRequest, networkCallback)
}
}
}

View file

@ -2,12 +2,13 @@ package com.pixelized.biblib.ui.composable
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
@ -24,6 +25,7 @@ import com.pixelized.biblib.R
import com.pixelized.biblib.ui.scaffold.SearchScaffoldState
import com.pixelized.biblib.ui.scaffold.rememberSearchScaffoldState
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.utils.extention.todo
@ -35,7 +37,13 @@ fun Search(
onAvatar: () -> Unit = todo(),
onSearch: () -> Unit = todo(),
) {
var test by remember { mutableStateOf("") }
val horizontalPadding by animateDpAsState(
targetValue = when (state.isCollapsed()) {
true -> MaterialTheme.bibLib.dimen.dp8
else -> MaterialTheme.bibLib.dimen.dp16
}
)
var search by remember { mutableStateOf("") }
Row(
modifier = Modifier
.clickable(onClick = onSearch)
@ -44,8 +52,8 @@ fun Search(
) {
Icon(
modifier = Modifier
.size(42.dp)
.padding(horizontal = 8.dp),
.padding(start = horizontalPadding)
.size(24.dp),
imageVector = Icons.Default.Search,
tint = MaterialTheme.colors.onSurface,
contentDescription = null
@ -58,24 +66,23 @@ fun Search(
text = "Rechercher sur BibLib"
)
},
value = test,
value = if (state.isCollapsed()) "" else search,
singleLine = true,
enabled = state.isCollapsed().not(),
readOnly = state.isCollapsed(),
colors = TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
disabledBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
disabledBorderColor = Color.Transparent,
),
onValueChange = { test = it }
onValueChange = { search = it }
)
IconButton(
modifier = Modifier.padding(horizontal = 8.dp),
modifier = Modifier.padding(end = horizontalPadding),
onClick = onAvatar,
) {
Image(
modifier = Modifier
.size(32.dp)
.clip(RoundedCornerShape(50)),
modifier = Modifier.clip(CircleShape),
painter = avatar,
contentDescription = null
)

View file

@ -33,7 +33,7 @@ fun ErrorCard(
) {
Column(
modifier = Modifier
.padding(MaterialTheme.bibLib.dimen.medium)
.padding(MaterialTheme.bibLib.dimen.dp16)
.sizeIn(
minWidth = MaterialTheme.bibLib.dimen.dialog.minimum.width,
minHeight = MaterialTheme.bibLib.dimen.dialog.minimum.height,
@ -49,7 +49,7 @@ fun ErrorCard(
)
if (message.isNotEmpty()) {
Text(
modifier = Modifier.padding(top = MaterialTheme.bibLib.dimen.medium),
modifier = Modifier.padding(top = MaterialTheme.bibLib.dimen.dp16),
style = MaterialTheme.typography.body1,
textAlign = TextAlign.Center,
text = message
@ -57,7 +57,7 @@ fun ErrorCard(
}
if (exception != null) {
Text(
modifier = Modifier.padding(top = MaterialTheme.bibLib.dimen.small),
modifier = Modifier.padding(top = MaterialTheme.bibLib.dimen.dp8),
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
text = exception.message ?: exception::class.java.simpleName

View file

@ -29,7 +29,7 @@ fun LoadingCard(
) {
Column(
modifier = Modifier
.padding(MaterialTheme.bibLib.dimen.medium)
.padding(MaterialTheme.bibLib.dimen.dp16)
.sizeIn(
minWidth = MaterialTheme.bibLib.dimen.dialog.minimum.width,
minHeight = MaterialTheme.bibLib.dimen.dialog.minimum.height,
@ -50,7 +50,7 @@ fun LoadingCard(
if (message?.isNotEmpty() == true) {
val typography = MaterialTheme.typography
Text(
modifier = Modifier.padding(top = MaterialTheme.bibLib.dimen.medium),
modifier = Modifier.padding(top = MaterialTheme.bibLib.dimen.dp16),
style = typography.body1,
textAlign = TextAlign.Center,
text = message

View file

@ -30,7 +30,7 @@ fun SuccessCard(
) {
Column(
modifier = Modifier
.padding(MaterialTheme.bibLib.dimen.medium)
.padding(MaterialTheme.bibLib.dimen.dp16)
.sizeIn(
minWidth = MaterialTheme.bibLib.dimen.dialog.minimum.width,
minHeight = MaterialTheme.bibLib.dimen.dialog.minimum.height,
@ -48,7 +48,7 @@ fun SuccessCard(
Text(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(top = MaterialTheme.bibLib.dimen.medium),
.padding(top = MaterialTheme.bibLib.dimen.dp16),
style = MaterialTheme.typography.body1,
textAlign = TextAlign.Center,
text = message

View file

@ -3,6 +3,7 @@ package com.pixelized.biblib.ui.navigation.screen
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
@ -16,6 +17,7 @@ val LocalScreenNavHostController = compositionLocalOf<NavHostController> {
error("LocalFullScreenNavHostController is not ready yet.")
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun ScreenNavHost(
modifier: Modifier = Modifier,

View file

@ -2,14 +2,14 @@ package com.pixelized.biblib.ui.scaffold
import android.content.Context
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material.BottomSheetScaffold
import androidx.compose.material.BottomSheetScaffoldState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.rememberBottomSheetScaffoldState
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetState
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.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.biblib.R
import com.pixelized.biblib.ui.composable.StateUio
@ -20,60 +20,62 @@ import com.pixelized.biblib.utils.extention.showToast
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
val LocalBottomDetailController = staticCompositionLocalOf<BottomDetailStateController> {
val LocalBottomDetailController = staticCompositionLocalOf<BottomDetailState> {
error("LocalBottomDetailController is not ready yet")
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun BottomDetailScaffold(
bottomStateController: BottomDetailStateController = rememberBottomDetailStateController(),
content: @Composable (PaddingValues) -> Unit,
bottomDetailState: BottomDetailState = rememberBottomDetailState(),
content: @Composable () -> Unit,
) {
CompositionLocalProvider(
LocalBottomDetailController provides bottomStateController
LocalBottomDetailController provides bottomDetailState
) {
BottomSheetScaffold(
scaffoldState = bottomStateController.scaffoldState,
sheetPeekHeight = 0.dp,
sheetGesturesEnabled = false,
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.37f),
sheetState = bottomDetailState.bottomSheetState,
sheetContent = {
val detail by remember { bottomStateController.bookDetail }
val detail by remember { bottomDetailState.bookDetail }
DetailScreen(detail = detail)
},
content = content
content = content,
)
BackHandler(bottomStateController.scaffoldState.bottomSheetState.isExpanded) {
bottomStateController.collapse()
BackHandler(bottomDetailState.bottomSheetState.isVisible) {
bottomDetailState.collapse()
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun rememberBottomDetailStateController(
fun rememberBottomDetailState(
viewModel: BookDetailViewModel = hiltViewModel(),
scope: CoroutineScope = rememberCoroutineScope(),
scaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(),
): BottomDetailStateController {
bottomSheetState: ModalBottomSheetState = rememberModalBottomSheetState(
initialValue = Hidden,
skipHalfExpanded = true,
),
): BottomDetailState {
val context: Context = LocalContext.current
val controller = BottomDetailStateController(
val controller = BottomDetailState(
context = context,
viewModel = viewModel,
scope = scope,
scaffoldState = scaffoldState
bottomSheetState = bottomSheetState
)
return remember(scope, viewModel, scaffoldState) { controller }
return remember(scope, viewModel, bottomSheetState) { controller }
}
@OptIn(ExperimentalMaterialApi::class)
@Stable
class BottomDetailStateController constructor(
class BottomDetailState constructor(
private val context: Context,
private val viewModel: BookDetailViewModel,
private val scope: CoroutineScope,
val scaffoldState: BottomSheetScaffoldState,
val bottomSheetState: ModalBottomSheetState,
) {
var bookDetail = mutableStateOf<BookUio?>(null)
private set
@ -87,7 +89,8 @@ class BottomDetailStateController constructor(
}
is StateUio.Success -> {
bookDetail.value = book.value
scaffoldState.bottomSheetState.expand()
// bottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
bottomSheetState.show()
}
else -> Unit
}
@ -96,7 +99,7 @@ class BottomDetailStateController constructor(
fun collapse() {
scope.launch {
scaffoldState.bottomSheetState.collapse()
bottomSheetState.hide()
}
}
}

View file

@ -1,10 +1,8 @@
package com.pixelized.biblib.ui.scaffold
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.*
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.ScrollState.Companion.Saver
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
@ -36,13 +34,13 @@ fun SearchScaffold(
)
val horizontal by transition.animateDp(label = "horizontal") {
when (it) {
true -> MaterialTheme.bibLib.dimen.medium
true -> MaterialTheme.bibLib.dimen.dp16
else -> MaterialTheme.bibLib.dimen.default
}
}
val vertical by transition.animateDp(label = "vertical") {
when (it) {
true -> MaterialTheme.bibLib.dimen.small
true -> MaterialTheme.bibLib.dimen.dp8
else -> MaterialTheme.bibLib.dimen.default
}
}
@ -53,7 +51,11 @@ fun SearchScaffold(
) {
Column {
topBar()
AnimatedVisibility(visible = state.isCollapsed().not()) {
AnimatedVisibility(
visible = state.isCollapsed().not(),
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
search()
}
}

View file

@ -114,7 +114,7 @@ private fun AuthenticationScreenContent(
AnimatedOffset(
modifier = Modifier
.padding(all = MaterialTheme.bibLib.dimen.medium)
.padding(all = MaterialTheme.bibLib.dimen.dp16)
.align(alignment = Alignment.CenterHorizontally),
) {
Text(
@ -127,7 +127,7 @@ private fun AuthenticationScreenContent(
Spacer(modifier = Modifier.weight(1f))
AnimatedOffset(
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.medium),
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.dp16),
) {
LoginField(
modifier = Modifier.fillMaxWidth(),
@ -137,10 +137,10 @@ private fun AuthenticationScreenContent(
)
}
Spacer(modifier = Modifier.height(MaterialTheme.bibLib.dimen.small))
Spacer(modifier = Modifier.height(MaterialTheme.bibLib.dimen.dp8))
AnimatedOffset(
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.medium),
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.dp16),
) {
PasswordField(
modifier = Modifier.fillMaxWidth(),
@ -151,10 +151,10 @@ private fun AuthenticationScreenContent(
)
}
Spacer(modifier = Modifier.height(MaterialTheme.bibLib.dimen.medium))
Spacer(modifier = Modifier.height(MaterialTheme.bibLib.dimen.dp16))
AnimatedOffset(
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.medium),
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.dp16),
) {
CredentialRemember(
value = rememberPassword,
@ -162,11 +162,11 @@ private fun AuthenticationScreenContent(
)
}
Spacer(modifier = Modifier.height(MaterialTheme.bibLib.dimen.medium))
Spacer(modifier = Modifier.height(MaterialTheme.bibLib.dimen.dp16))
AnimatedOffset(
modifier = Modifier
.padding(horizontal = MaterialTheme.bibLib.dimen.medium)
.padding(horizontal = MaterialTheme.bibLib.dimen.dp16)
.align(Alignment.End),
) {
Row {
@ -177,7 +177,7 @@ private fun AuthenticationScreenContent(
Text(text = stringResource(id = R.string.action_register))
}
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.small))
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.dp8))
Button(
colors = ButtonDefaults.buttonColors(),
@ -193,7 +193,7 @@ private fun AuthenticationScreenContent(
AnimatedOffset {
Button(
modifier = Modifier
.padding(all = MaterialTheme.bibLib.dimen.medium)
.padding(all = MaterialTheme.bibLib.dimen.dp16)
.fillMaxWidth(),
colors = ButtonDefaults.outlinedButtonColors(),
onClick = onGoogleSignIn,
@ -299,7 +299,7 @@ private fun CredentialRemember(
onCheckedChange = null
)
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.small))
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.dp8))
Text(
modifier = Modifier.align(Alignment.CenterVertically),

View file

@ -0,0 +1,27 @@
package com.pixelized.biblib.ui.screen.connectivity
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.biblib.repository.connectivity.ConnectivityRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class ConnectivityViewModel @Inject constructor(
private val connectivityRepository: ConnectivityRepository,
) : ViewModel() {
private val _isNetworkAvailable = mutableStateOf(connectivityRepository.isNetworkAvailable)
val isNetworkAvailable: State<Boolean> get() = _isNetworkAvailable
init {
viewModelScope.launch {
connectivityRepository.networkAvailabilityFlow.collect {
_isNetworkAvailable.value = it
}
}
}
}

View file

@ -2,7 +2,6 @@ package com.pixelized.biblib.ui.screen.detail
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@ -68,13 +67,13 @@ private fun DetailScreenContent(
modifier = Modifier
.verticalScroll(rememberScrollState())
.systemBarsPadding()
.padding(all = MaterialTheme.bibLib.dimen.medium)
.padding(all = MaterialTheme.bibLib.dimen.dp16)
.then(modifier)
) {
AnimatedOffset(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = MaterialTheme.bibLib.dimen.medium)
.padding(vertical = MaterialTheme.bibLib.dimen.dp16)
.height(MaterialTheme.bibLib.dimen.detail.cover),
contentAlignment = Alignment.Center
) {
@ -89,7 +88,7 @@ private fun DetailScreenContent(
)
}
Row(modifier = Modifier.padding(vertical = MaterialTheme.bibLib.dimen.medium)) {
Row(modifier = Modifier.padding(vertical = MaterialTheme.bibLib.dimen.dp16)) {
AnimatedOffset(
modifier = Modifier.weight(1f),
) {
@ -98,12 +97,12 @@ private fun DetailScreenContent(
onClick = onMobi,
) {
Icon(imageVector = Icons.Default.Download, contentDescription = null)
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.extraSmall))
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.dp4))
Text(text = stringResource(id = R.string.action_mobi))
}
}
Spacer(modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.extraSmall))
Spacer(modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.dp4))
AnimatedOffset(
modifier = Modifier.weight(1f),
@ -113,12 +112,12 @@ private fun DetailScreenContent(
onClick = onEpub,
) {
Icon(imageVector = Icons.Default.Download, contentDescription = null)
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.extraSmall))
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.dp4))
Text(text = stringResource(id = R.string.action_epub))
}
}
Spacer(modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.extraSmall))
Spacer(modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.dp4))
AnimatedOffset(
modifier = Modifier.weight(1f),
@ -128,7 +127,7 @@ private fun DetailScreenContent(
onClick = onSend,
) {
Icon(imageVector = Icons.Default.Send, contentDescription = "")
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.extraSmall))
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.dp4))
Text(text = stringResource(id = R.string.action_send))
}
}
@ -149,7 +148,7 @@ private fun DetailScreenContent(
AnimatedOffset(
modifier = Modifier
.align(alignment = Alignment.CenterHorizontally)
.padding(bottom = MaterialTheme.bibLib.dimen.medium),
.padding(bottom = MaterialTheme.bibLib.dimen.dp16),
) {
Text(
fontWeight = FontWeight.Bold,
@ -160,7 +159,7 @@ private fun DetailScreenContent(
}
Row(
modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.small),
modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp8),
) {
AnimatedOffset(modifier = Modifier.weight(1f)) {
TitleLabel(
@ -185,7 +184,7 @@ private fun DetailScreenContent(
}
AnimatedOffset(
modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.medium),
modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp16),
) {
TitleLabel(
title = stringResource(id = R.string.detail_series),

View file

@ -1,15 +1,13 @@
package com.pixelized.biblib.ui.screen.home
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
@ -19,14 +17,18 @@ import com.pixelized.biblib.ui.scaffold.BottomDetailScaffold
import com.pixelized.biblib.ui.scaffold.SearchScaffold
import com.pixelized.biblib.ui.scaffold.SearchScaffoldState
import com.pixelized.biblib.ui.scaffold.rememberSearchScaffoldState
import com.pixelized.biblib.ui.screen.profile.ProfileScreen
@OptIn(ExperimentalMaterialApi::class)
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun HomeScreen(
accountViewModel: HomeViewModel = hiltViewModel(),
keyboard: SoftwareKeyboardController? = LocalSoftwareKeyboardController.current,
navHostController: NavHostController = rememberNavController(),
searchScaffoldState: SearchScaffoldState = rememberSearchScaffoldState(),
) {
var pouet: String by remember { mutableStateOf("aucun") }
BottomDetailScaffold {
SearchScaffold(
state = searchScaffoldState,
@ -39,6 +41,16 @@ fun HomeScreen(
avatar = accountViewModel.avatar,
onSearch = {
if (searchScaffoldState.isCollapsed()) {
pouet = "search"
searchScaffoldState.expand()
} else {
keyboard?.hide()
searchScaffoldState.collapse()
}
},
onAvatar = {
if (searchScaffoldState.isCollapsed()) {
pouet = "profile"
searchScaffoldState.expand()
} else {
searchScaffoldState.collapse()
@ -47,7 +59,12 @@ fun HomeScreen(
)
},
search = {
Box(modifier = Modifier.fillMaxSize())
Box(modifier = Modifier.fillMaxSize()) {
when (pouet) {
"profile" -> ProfileScreen()
"search" -> Unit
}
}
},
content = {
Column {
@ -55,12 +72,12 @@ fun HomeScreen(
homePageNavController = navHostController
)
PageNavHost(
modifier = Modifier.padding(it),
navHostController = navHostController,
)
}
},
)
BackHandler(searchScaffoldState.isCollapsed().not()) {
searchScaffoldState.collapse()
}

View file

@ -1,100 +0,0 @@
package com.pixelized.biblib.ui.screen.home
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
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.navigation.screen.LocalScreenNavHostController
import com.pixelized.biblib.ui.navigation.screen.navigateToProfile
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.todo
@Composable
fun HomeSearchBar(
accountViewModel: HomeViewModel = hiltViewModel()
) {
val screenNavHostController = LocalScreenNavHostController.current
val avatar by rememberUpdatedState(accountViewModel.avatar)
HomeSearchBarContent(
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
avatar = avatar,
onAvatar = {
screenNavHostController.navigateToProfile()
}
)
}
@Composable
private fun HomeSearchBarContent(
modifier: Modifier = Modifier,
avatar: Painter,
onAvatar: () -> Unit = todo(),
onSearch: () -> Unit = todo(),
) {
Card(
modifier = modifier,
elevation = 8.dp
) {
Row(
modifier = Modifier
.clickable(onClick = onSearch)
.fillMaxWidth()
.height(48.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier
.size(42.dp)
.padding(horizontal = 8.dp),
imageVector = Icons.Default.Search,
contentDescription = null
)
Text(
modifier = Modifier.weight(1f),
color = MaterialTheme.colors.onSurface,
text = "Rechercher sur BibLib"
)
IconButton(
modifier = Modifier.padding(horizontal = 8.dp),
onClick = onAvatar,
) {
Image(
modifier = Modifier
.size(32.dp)
.clip(RoundedCornerShape(50)),
painter = avatar,
contentDescription = null
)
}
}
}
}
@Composable
@Preview(uiMode = UI_MODE_NIGHT_NO)
@Preview(uiMode = UI_MODE_NIGHT_YES)
private fun HomeSearchBarContentPreview() {
BibLibTheme {
HomeSearchBarContent(
avatar = painterResource(R.drawable.ic_baseline_auto_stories_24)
)
}
}

View file

@ -23,12 +23,13 @@ import com.pixelized.biblib.utils.extention.bibLib
@Composable
fun BookThumbnail(
modifier: Modifier = Modifier,
thumbnail: BookThumbnailUio?,
onClick: (BookThumbnailUio) -> Unit = { },
) {
val currentOnClick by rememberUpdatedState(newValue = onClick)
Card(
modifier = Modifier
modifier = modifier
.clickable(enabled = thumbnail != null) {
thumbnail?.let { currentOnClick(it) }
}
@ -47,7 +48,7 @@ fun BookThumbnail(
Column(
modifier = Modifier
.fillMaxWidth()
.padding(MaterialTheme.bibLib.dimen.small)
.padding(MaterialTheme.bibLib.dimen.dp8)
) {
Text(
style = MaterialTheme.typography.body1,

View file

@ -1,16 +1,26 @@
package com.pixelized.biblib.ui.screen.home.common.composable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.ButtonDefaults
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.CloudOff
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
@ -23,12 +33,14 @@ import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib
import kotlinx.coroutines.flow.flowOf
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun LazyBookThumbnailColumn(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.medium),
contentPadding: PaddingValues = PaddingValues(all = MaterialTheme.bibLib.dimen.medium),
verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.dp16),
contentPadding: PaddingValues = PaddingValues(),
state: LazyListState = rememberLazyListState(),
isNetworkAvailable: () -> State<Boolean>,
books: LazyPagingItems<BookThumbnailUio>,
onItemClick: (BookThumbnailUio) -> Unit = {},
) {
@ -39,11 +51,17 @@ fun LazyBookThumbnailColumn(
contentPadding = contentPadding,
state = state,
) {
stickyHeader {
ConnectivityHeader(
isNetworkAvailable = isNetworkAvailable,
)
}
items(
items = books,
key = { it.id },
) { thumbnail ->
BookThumbnail(
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.thumbnail.padding),
thumbnail = thumbnail,
onClick = currentOnItemClick,
)
@ -51,12 +69,51 @@ fun LazyBookThumbnailColumn(
}
}
@Composable
fun ConnectivityHeader(
modifier : Modifier = Modifier,
isNetworkAvailable: () -> State<Boolean>
) {
val showHideHeader by isNetworkAvailable()
AnimatedVisibility(
visible = showHideHeader.not(),
enter = expandVertically(),
exit = shrinkVertically(),
) {
Row(
modifier = modifier
.background(color = MaterialTheme.colors.error)
.fillMaxWidth()
.sizeIn(minHeight = ButtonDefaults.MinHeight)
.padding(
horizontal = MaterialTheme.bibLib.dimen.thumbnail.padding,
vertical = MaterialTheme.bibLib.dimen.thumbnail.arrangement,
),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier.padding(end = MaterialTheme.bibLib.dimen.dp8),
imageVector = Icons.Default.CloudOff,
tint = MaterialTheme.colors.onError,
contentDescription = null
)
Text(
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onError,
text = stringResource(id = R.string.error_offline),
)
}
}
}
@Composable
@Preview
private fun LazyBookThumbnailColumnPreview() {
val isNetworkAvailable = remember { mutableStateOf(false) }
BibLibTheme {
LazyBookThumbnailColumn(
modifier = Modifier.fillMaxSize(),
isNetworkAvailable = { isNetworkAvailable },
books = previewResources(),
)
}

View file

@ -2,12 +2,12 @@ package com.pixelized.biblib.ui.screen.home.page.books
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.biblib.ui.scaffold.LocalBottomDetailController
import com.pixelized.biblib.ui.screen.connectivity.ConnectivityViewModel
import com.pixelized.biblib.ui.screen.home.common.composable.LazyBookThumbnailColumn
import com.pixelized.biblib.ui.screen.home.page.news.NewsPage
import com.pixelized.biblib.ui.theme.BibLibTheme
@ -15,13 +15,14 @@ import com.pixelized.biblib.utils.extention.bibLib
@Composable
fun BooksPage(
connectivityViewModel: ConnectivityViewModel = hiltViewModel(),
booksViewModel: BooksViewModel = hiltViewModel()
) {
val bottomDetailState = LocalBottomDetailController.current
LazyBookThumbnailColumn(
verticalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement),
contentPadding = PaddingValues(all = MaterialTheme.bibLib.dimen.thumbnail.padding),
isNetworkAvailable = { connectivityViewModel.isNetworkAvailable },
books = booksViewModel.books,
onItemClick = {
bottomDetailState.expandBookDetail(id = it.id)

View file

@ -2,25 +2,26 @@ package com.pixelized.biblib.ui.screen.home.page.news
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.biblib.ui.scaffold.LocalBottomDetailController
import com.pixelized.biblib.ui.screen.connectivity.ConnectivityViewModel
import com.pixelized.biblib.ui.screen.home.common.composable.LazyBookThumbnailColumn
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib
@Composable
fun NewsPage(
connectivityViewModel: ConnectivityViewModel = hiltViewModel(),
booksViewModel: NewsBookViewModel = hiltViewModel()
) {
val bottomDetail = LocalBottomDetailController.current
LazyBookThumbnailColumn(
verticalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement),
contentPadding = PaddingValues(all = MaterialTheme.bibLib.dimen.thumbnail.padding),
isNetworkAvailable = { connectivityViewModel.isNetworkAvailable },
books = booksViewModel.news,
onItemClick = {
bottomDetail.expandBookDetail(id = it.id)

View file

@ -4,66 +4,85 @@ import android.content.Intent
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.net.Uri
import androidx.compose.foundation.layout.Column
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.ui.composable.StateUio
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.utils.extention.todo
@Composable
fun ProfileScreen(
viewModel: ProfileViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val user by rememberUpdatedState(newValue = viewModel.user)
when (val unwrap = user) {
is StateUio.Success -> ProfileScreenContent(user = unwrap.value)
when (val user = viewModel.user) {
is StateUio.Progress -> Unit
is StateUio.Success -> ProfileScreenContent(
modifier = Modifier.padding(MaterialTheme.bibLib.dimen.dp16),
user = user.value,
onEditClick = {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(IBibLibClient.EDIT_PROFILE))
context.startActivity(intent)
}
)
is StateUio.Failure -> Unit
}
FloatingActionButton(
onClick = {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(IBibLibClient.EDIT_PROFILE)))
},
content = { },
)
}
@Composable
private fun ProfileScreenContent(
modifier: Modifier = Modifier,
user: ProfileViewModel.UserUio,
user: UserUio,
onEditClick: () -> Unit = todo()
) {
Column(modifier = modifier) {
Column(modifier = modifier.fillMaxWidth()) {
Text(
style = MaterialTheme.typography.h5,
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
text = "Welcome"
)
Text(
style = MaterialTheme.typography.h4,
color = MaterialTheme.colors.primary,
text = user.username,
)
user.firstname?.let {
Text(
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
text = it,
)
}
user.lastname?.let {
Text(
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
text = it,
)
if (user.firstname?.isNotEmpty() == true || user.lastname?.isNotEmpty() == true) {
Row {
user.firstname?.let {
Text(
modifier = Modifier.padding(end = MaterialTheme.bibLib.dimen.dp4),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
text = it,
)
}
user.lastname?.let {
Text(
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
text = it,
)
}
}
}
Text(
modifier = Modifier.padding(top = MaterialTheme.bibLib.dimen.dp16),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
text = "Linked emails:"
)
user.amazonEmails.forEach {
Text(
style = MaterialTheme.typography.body1,
@ -71,7 +90,25 @@ private fun ProfileScreenContent(
text = it,
)
}
Button(
modifier = Modifier
.padding(top = MaterialTheme.bibLib.dimen.dp16)
.align(Alignment.End),
colors = ButtonDefaults.outlinedButtonColors(),
onClick = onEditClick,
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = null
)
Text(
modifier = Modifier.padding(start = MaterialTheme.bibLib.dimen.dp8),
text = "Edit profile"
)
}
}
}
@Composable
@ -79,14 +116,19 @@ private fun ProfileScreenContent(
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES)
private fun ProfileScreenContentPreview() {
BibLibTheme {
val user = ProfileViewModel.UserUio(
username = "R. Daneel Olivaw",
firstname = "R. Daneel",
lastname = "Olivaw",
amazonEmails = listOf("r.daneel.olivaw@biblib.com"),
)
ProfileScreenContent(
user = user
)
Box {
ProfileScreenContent(
modifier = Modifier.padding(MaterialTheme.bibLib.dimen.dp16),
user = UserUio(
username = "DefinitelyNotARobot",
firstname = "R. Daneel",
lastname = "Olivaw",
amazonEmails = listOf(
"r.daneel.olivaw@robot.com",
"r.daneel.olivaw@biblib.com",
),
),
)
}
}
}

View file

@ -4,6 +4,7 @@ import android.app.Application
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.viewModelScope
import com.pixelized.biblib.model.user.User
import com.pixelized.biblib.network.client.IBibLibClient
@ -23,8 +24,8 @@ class ProfileViewModel @Inject constructor(
private val client: IBibLibClient,
) : ACoverViewModel(application, cacheBook) {
private val _user = mutableStateOf<StateUio<UserUio>>(StateUio.Progress())
val user by _user
var user by mutableStateOf<StateUio<UserUio>>(StateUio.Progress())
private set
init {
updateUser()
@ -32,29 +33,22 @@ class ProfileViewModel @Inject constructor(
private fun updateUser() {
viewModelScope.launch(Dispatchers.IO) {
try {
user = try {
val factory = UserFactory()
val response = client.service.user()
val user = factory.fromUserResponseToUser(response)
_user.value = StateUio.Success(user.toUio())
val data = factory.fromUserResponseToUser(response)
StateUio.Success(data.toUio())
} catch (exception: Exception) {
Log.e("AccountViewModel", exception.message, exception)
_user.value = StateUio.Failure(exception)
StateUio.Failure(exception)
}
}
}
data class UserUio(
val username: String,
val firstname: String?,
val lastname: String?,
val amazonEmails: List<String>,
private fun User.toUio() = UserUio(
username = username,
firstname = firstname,
lastname = lastname,
amazonEmails = amazonEmails.toList(),
)
}
private fun User.toUio() = ProfileViewModel.UserUio(
username = username,
firstname = firstname,
lastname = lastname,
amazonEmails = amazonEmails.toList(),
)
}

View file

@ -0,0 +1,9 @@
package com.pixelized.biblib.ui.screen.profile
data class UserUio(
val username: String,
val firstname: String?,
val lastname: String?,
val amazonEmails: List<String>,
)

View file

@ -21,6 +21,7 @@ fun bibLibDarkColors(
onPrimary = Color.White,
onSecondary = Color.White,
error = BibLibColorPalette.Red,
onError = Color.White,
),
) = BibLibColor(
base = base,
@ -33,6 +34,7 @@ fun bibLibLightColors(
onPrimary = Color.White,
onSecondary = Color.White,
error = BibLibColorPalette.Red,
onError = Color.White,
)
) = BibLibColor(
base = base,

View file

@ -10,11 +10,11 @@ import androidx.compose.ui.unit.dp
@Immutable
data class BibLibDimen(
val default: Dp = 0.dp,
val extraSmall: Dp = 4.dp,
val small: Dp = 8.dp,
val medium: Dp = 16.dp,
val large: Dp = 32.dp,
val extraLarge: Dp = 64.dp,
val dp4: Dp = 4.dp,
val dp8: Dp = 8.dp,
val dp16: Dp = 16.dp,
val dp32: Dp = 32.dp,
val dp64: Dp = 64.dp,
val dialog: Dialog = Dialog(),
val thumbnail: BookThumbnail = BookThumbnail(),
val detail: BookDetail = BookDetail(),

View file

@ -24,6 +24,7 @@
<string name="error_generic">Oops!</string>
<string name="error_authentication">Oops, connection failed!</string>
<string name="error_book">Oops! library download failed!</string>
<string name="error_offline">Vous êtes hors ligne.</string>
<string name="loading_authentication">Entering the Imperial Library of Trantor.</string>
<string name="loading_book">Downloading the Imperial Library of Trantor.</string>

View file

@ -24,6 +24,7 @@
<string name="error_generic">Oops!</string>
<string name="error_authentication">Oops, connection failed!</string>
<string name="error_book">Oops! library download failed!</string>
<string name="error_offline">You are offline.</string>
<string name="loading_authentication">Entering the Imperial Library of Trantor.</string>
<string name="loading_book">Downloading the Imperial Library of Trantor.</string>