diff --git a/.idea/misc.xml b/.idea/misc.xml
index 5a9794c..a0d7d47 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -107,6 +107,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/pixelized/biblib/network/client/BibLibClient.kt b/app/src/main/java/com/pixelized/biblib/network/client/BibLibClient.kt
index 679a9c8..2add48a 100644
--- a/app/src/main/java/com/pixelized/biblib/network/client/BibLibClient.kt
+++ b/app/src/main/java/com/pixelized/biblib/network/client/BibLibClient.kt
@@ -22,7 +22,7 @@ class BibLibClient : IBibLibClient {
///////////////////////////////////
// region BibLib webservice Auth
- override fun updateBearerToken(token: String?) {
+ override fun updateBearerToken(token: String) {
interceptor.token = token
}
diff --git a/app/src/main/java/com/pixelized/biblib/network/client/IBibLibClient.kt b/app/src/main/java/com/pixelized/biblib/network/client/IBibLibClient.kt
index 7fdc9fa..d5028cb 100644
--- a/app/src/main/java/com/pixelized/biblib/network/client/IBibLibClient.kt
+++ b/app/src/main/java/com/pixelized/biblib/network/client/IBibLibClient.kt
@@ -4,7 +4,7 @@ interface IBibLibClient {
val service: IBibLibWebServiceAPI
- fun updateBearerToken(token: String?)
+ fun updateBearerToken(token: String)
companion object {
const val BASE_URL = "https://bib.bibulle.fr"
diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/items/WaitingComposable.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/items/WaitingComposable.kt
deleted file mode 100644
index 237c5ff..0000000
--- a/app/src/main/java/com/pixelized/biblib/ui/composable/items/WaitingComposable.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-package com.pixelized.biblib.ui.composable.items
-
-import androidx.compose.animation.Crossfade
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.Card
-import androidx.compose.material.CircularProgressIndicator
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import com.pixelized.biblib.R
-import com.pixelized.biblib.ui.theme.BibLibTheme
-
-
-@Preview
-@Composable
-fun WaitingComposableLightPreview() {
- BibLibTheme(darkTheme = false) {
- WaitingComposable(
- visible = true,
- message = stringResource(id = R.string.loading)
- )
- }
-}
-
-@Preview
-@Composable
-fun WaitingComposableDarkPreview() {
- BibLibTheme(darkTheme = true) {
- WaitingComposable(
- visible = true,
- message = stringResource(id = R.string.loading)
- )
- }
-}
-
-@Composable
-fun WaitingComposable(
- visible: Boolean,
- modifier: Modifier = Modifier,
- message: String? = null
-) {
- Crossfade(
- modifier = modifier,
- targetState = visible
- ) {
- if (it) {
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .fillMaxHeight()
- .clickable { }
- ) {
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .fillMaxHeight()
- .alpha(.25f)
- .background(Color.Black)
- )
- Card(
- elevation = 8.dp,
- modifier = Modifier.align(Alignment.Center)
- ) {
- Column(
- modifier = Modifier
- .width(200.dp)
- .padding(16.dp)
- ) {
- CircularProgressIndicator(
- modifier = Modifier
- .align(Alignment.CenterHorizontally)
- .padding(16.dp)
- )
- if (message?.isNotEmpty() == true) {
- val typography = MaterialTheme.typography
- Text(
- modifier = Modifier.align(Alignment.CenterHorizontally),
- style = typography.body1,
- textAlign = TextAlign.Center,
- text = message
- )
- }
- }
- }
- }
- } else {
- Box {}
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/items/dialog/ErrorCard.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/items/dialog/ErrorCard.kt
new file mode 100644
index 0000000..5d2ce6f
--- /dev/null
+++ b/app/src/main/java/com/pixelized/biblib/ui/composable/items/dialog/ErrorCard.kt
@@ -0,0 +1,82 @@
+package com.pixelized.biblib.ui.composable.items.dialog
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.Card
+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.sharp.ErrorOutline
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.pixelized.biblib.R
+import com.pixelized.biblib.ui.theme.BibLibTheme
+import com.pixelized.biblib.utils.exception.NoBearerException
+
+
+@Composable
+fun ErrorCard(
+ modifier: Modifier = Modifier,
+ message: String = stringResource(id = R.string.error_generic),
+ exception: Exception? = null,
+) {
+ Card(elevation = 8.dp, modifier = modifier) {
+ Column(
+ modifier = Modifier
+ .width(200.dp)
+ .padding(16.dp)
+ ) {
+ Icon(
+ modifier = Modifier
+ .width(72.dp)
+ .height(72.dp)
+ .align(Alignment.CenterHorizontally)
+ .padding(16.dp),
+ tint = MaterialTheme.colors.error,
+ imageVector = Icons.Sharp.ErrorOutline,
+ contentDescription = "error"
+ )
+ val typography = MaterialTheme.typography
+ Text(
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = typography.body1,
+ textAlign = TextAlign.Center,
+ text = message
+ )
+ if (exception != null) {
+ Text(
+ modifier = Modifier
+ .padding(top = 8.dp)
+ .align(Alignment.CenterHorizontally),
+ style = typography.caption,
+ textAlign = TextAlign.Center,
+ text = exception::class.java.simpleName
+ )
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun ErrorCardLightPreview() {
+ BibLibTheme(darkTheme = false) {
+ ErrorCard(exception = NoBearerException())
+ }
+}
+
+@Preview
+@Composable
+private fun ErrorCardDarkPreview() {
+ BibLibTheme(darkTheme = true) {
+ ErrorCard(exception = NoBearerException())
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/items/dialog/LoadingCard.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/items/dialog/LoadingCard.kt
new file mode 100644
index 0000000..3f4bbd0
--- /dev/null
+++ b/app/src/main/java/com/pixelized/biblib/ui/composable/items/dialog/LoadingCard.kt
@@ -0,0 +1,68 @@
+package com.pixelized.biblib.ui.composable.items.dialog
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.Card
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.pixelized.biblib.R
+import com.pixelized.biblib.ui.theme.BibLibTheme
+
+
+@Composable
+fun LoadingCard(
+ modifier: Modifier = Modifier,
+ message: String? = null
+) {
+ Card(elevation = 8.dp, modifier = modifier) {
+ Column(
+ modifier = Modifier
+ .width(200.dp)
+ .padding(16.dp)
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .align(Alignment.CenterHorizontally)
+ .padding(16.dp)
+ )
+ if (message?.isNotEmpty() == true) {
+ val typography = MaterialTheme.typography
+ Text(
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = typography.body1,
+ textAlign = TextAlign.Center,
+ text = message
+ )
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun LoadingCardLightPreview() {
+ BibLibTheme(darkTheme = false) {
+ LoadingCard(
+ message = stringResource(id = R.string.loading)
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun LoadingCardDarkPreview() {
+ BibLibTheme(darkTheme = true) {
+ LoadingCard(
+ message = stringResource(id = R.string.loading)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/items/dialog/OverlayComposable.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/items/dialog/OverlayComposable.kt
new file mode 100644
index 0000000..7df10d4
--- /dev/null
+++ b/app/src/main/java/com/pixelized/biblib/ui/composable/items/dialog/OverlayComposable.kt
@@ -0,0 +1,43 @@
+package com.pixelized.biblib.ui.composable.items.dialog
+
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+
+@Composable
+fun CrossFadeOverlay(
+ visible: Boolean,
+ content: @Composable BoxScope.() -> Unit
+) {
+ Crossfade(targetState = visible) {
+ if (it) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight()
+ .clickable { }
+ ) {
+ // Transparent background.
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight()
+ .alpha(.25f)
+ .background(Color.Black)
+ )
+ // Overlay content.
+ content()
+ }
+ } else {
+ Box {}
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/screen/LoginScreenComposable.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/screen/LoginScreenComposable.kt
index 1ae21f8..4d3fa27 100644
--- a/app/src/main/java/com/pixelized/biblib/ui/composable/screen/LoginScreenComposable.kt
+++ b/app/src/main/java/com/pixelized/biblib/ui/composable/screen/LoginScreenComposable.kt
@@ -30,12 +30,15 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.pixelized.biblib.R
-import com.pixelized.biblib.ui.composable.items.WaitingComposable
+import com.pixelized.biblib.ui.composable.items.dialog.CrossFadeOverlay
+import com.pixelized.biblib.ui.composable.items.dialog.ErrorCard
+import com.pixelized.biblib.ui.composable.items.dialog.LoadingCard
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.ui.viewmodel.authentication.AuthenticationViewModel
import com.pixelized.biblib.ui.viewmodel.authentication.IAuthentication
import com.pixelized.biblib.ui.viewmodel.navigation.INavigation
import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
+import com.pixelized.biblib.utils.exception.NoBearerException
@Composable
@@ -49,14 +52,15 @@ fun LoginScreenComposable(
.fillMaxHeight()
) {
authentication.PrepareLoginWithGoogle()
- LoginScreenContentComposable(navigation, authentication)
- LoginScreenWaitingComposable(authentication)
+ LoginScreenNavigationComposable(navigation, authentication)
+
+ LoginScreenContentComposable(authentication)
+ LoginScreenDialogComposable(authentication)
}
}
@Composable
private fun LoginScreenContentComposable(
- navigation: INavigation,
authentication: IAuthentication,
) {
val typography = MaterialTheme.typography
@@ -142,12 +146,40 @@ private fun LoginScreenContentComposable(
}
@Composable
-private fun LoginScreenWaitingComposable(authentication: IAuthentication) {
+private fun LoginScreenDialogComposable(
+ authentication: IAuthentication
+) {
val state = authentication.state.observeAsState()
- WaitingComposable(
- visible = state.value is IAuthentication.State.Loading,
- message = stringResource(id = R.string.loading)
- )
+ CrossFadeOverlay(
+ visible = (state.value is IAuthentication.State.Initial).not()
+ ) {
+ when (val currentState = state.value) {
+ is IAuthentication.State.Error -> ErrorCard(
+ modifier = Modifier
+ .align(Alignment.Center)
+ .clickable { authentication.clearState() },
+ message = stringResource(id = R.string.error_generic),
+ exception = currentState.exception
+ )
+ is IAuthentication.State.Connect,
+ is IAuthentication.State.Loading -> LoadingCard(
+ modifier = Modifier.align(Alignment.Center),
+ message = stringResource(id = R.string.loading)
+ )
+ else -> Box {}
+ }
+ }
+}
+
+@Composable
+private fun LoginScreenNavigationComposable(
+ navigation: INavigation,
+ authentication: IAuthentication
+) {
+ val state = authentication.state.observeAsState()
+ if (state.value == IAuthentication.State.Connect) {
+ navigation.navigateTo(INavigation.Screen.MainScreen)
+ }
}
@Composable
@@ -237,12 +269,24 @@ fun LoginScreenComposablePreview() {
}
}
-@Preview
+@Preview(name = "Loading")
@Composable
fun LoginScreenComposableWaitingPreview() {
BibLibTheme {
val navigationViewModel = INavigation.Mock()
- val authenticationViewModel = IAuthentication.Mock(true)
+ val state = IAuthentication.State.Loading
+ val authenticationViewModel = IAuthentication.Mock(state)
+ LoginScreenComposable(navigationViewModel, authenticationViewModel)
+ }
+}
+
+@Preview(name = "Error")
+@Composable
+fun LoginScreenComposableErrorPreview() {
+ BibLibTheme {
+ val navigationViewModel = INavigation.Mock()
+ val state = IAuthentication.State.Error(NoBearerException())
+ val authenticationViewModel = IAuthentication.Mock(state)
LoginScreenComposable(navigationViewModel, authenticationViewModel)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/AuthenticationViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/AuthenticationViewModel.kt
index 09c9ec3..6a1a0f7 100644
--- a/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/AuthenticationViewModel.kt
+++ b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/AuthenticationViewModel.kt
@@ -1,7 +1,6 @@
package com.pixelized.biblib.ui.viewmodel.authentication
import android.content.Intent
-import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
@@ -14,12 +13,12 @@ import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.common.api.ApiException
import com.pixelized.biblib.data.network.query.AuthLoginQuery
-import com.pixelized.biblib.utils.injection.inject
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.repository.credential.ICredentialRepository
import com.pixelized.biblib.repository.googlesignin.IGoogleSingInRepository
import com.pixelized.biblib.ui.viewmodel.authentication.IAuthentication.State
import com.pixelized.biblib.utils.exception.MissingTokenException
+import com.pixelized.biblib.utils.injection.inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -61,10 +60,13 @@ class AuthenticationViewModel : ViewModel(), IAuthentication {
}
override fun updateRememberCredential(rememberCredential: Boolean) {
- _rememberCredential.postValue(rememberCredential)
viewModelScope.launch {
+ _rememberCredential.postValue(rememberCredential)
credentialRepository.rememberCredential = rememberCredential
- if (rememberCredential.not()) {
+ if (rememberCredential) {
+ credentialRepository.login = login.value
+ credentialRepository.password = password.value
+ } else {
credentialRepository.login = null
credentialRepository.password = null
}
@@ -75,24 +77,37 @@ class AuthenticationViewModel : ViewModel(), IAuthentication {
viewModelScope.launch {
_state.postValue(State.Loading)
delay(3000)
- _state.postValue(State.Initial)
+ _state.postValue(State.Error(MissingTokenException()))
}
}
+ override fun clearState() {
+ _state.postValue(State.Initial)
+ }
+
override fun login() {
viewModelScope.launch(Dispatchers.IO) {
+ // TODO : validation !
if (rememberCredential.value == true) {
credentialRepository.login = login.value
credentialRepository.password = password.value
+ } else {
+ credentialRepository.login = null
+ credentialRepository.password = null
}
- // TODO : validation !
val query = AuthLoginQuery(
username = login.value,
password = password.value
)
- // TODO : Repository (token management & co)
- val response = client.service.login(query)
- Log.e("pouet", response.toString())
+ _state.postValue(State.Loading)
+ try {
+ val response = client.service.login(query)
+ val idToken = response.token ?: throw MissingTokenException()
+ client.updateBearerToken(idToken)
+ _state.postValue(State.Connect)
+ } catch (exception: Exception) {
+ _state.postValue(State.Error(exception))
+ }
}
}
@@ -105,7 +120,8 @@ class AuthenticationViewModel : ViewModel(), IAuthentication {
val task = GoogleSignIn.getSignedInAccountFromIntent(it.data)
val account: GoogleSignInAccount? = task.getResult(ApiException::class.java)
val idToken = account?.idToken ?: throw MissingTokenException()
- _state.postValue(State.Connect(idToken))
+ client.updateBearerToken(idToken)
+ _state.postValue(State.Connect)
} catch (exception: Exception) {
_state.postValue(State.Error(exception))
}
diff --git a/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/IAuthentication.kt b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/IAuthentication.kt
index 402e506..85dbd90 100644
--- a/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/IAuthentication.kt
+++ b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/IAuthentication.kt
@@ -13,6 +13,7 @@ interface IAuthentication {
fun updateLoginField(login: String)
fun updatePasswordField(password: String)
fun updateRememberCredential(rememberCredential: Boolean)
+ fun clearState()
fun register()
fun login()
@@ -24,13 +25,12 @@ interface IAuthentication {
sealed class State {
object Initial : State()
object Loading : State()
- data class Connect(val token: String) : State()
+ object Connect : State()
data class Error(val exception: Exception) : State()
}
- class Mock(waiting: Boolean = false) : IAuthentication {
- override val state: LiveData =
- MutableLiveData(if (waiting) State.Loading else State.Initial)
+ class Mock(state: State = State.Initial) : IAuthentication {
+ override val state: LiveData = MutableLiveData(state)
override val login: LiveData = MutableLiveData("")
override val password: LiveData = MutableLiveData("")
override val rememberCredential: LiveData = MutableLiveData(true)
@@ -38,6 +38,8 @@ interface IAuthentication {
override fun updateLoginField(login: String) = Unit
override fun updatePasswordField(password: String) = Unit
override fun updateRememberCredential(rememberCredential: Boolean) = Unit
+ override fun clearState() = Unit
+
override fun register() = Unit
override fun login() = Unit
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 447fae5..c92c441 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -7,6 +7,8 @@
Entering the Imperial Library of Trantor.
+ Oops!
+
Sign in to BibLib
Login