Add login management.
This commit is contained in:
parent
8fbe3c0b7b
commit
45d2fe1336
14 changed files with 366 additions and 158 deletions
6
.idea/misc.xml
generated
6
.idea/misc.xml
generated
|
|
@ -90,6 +90,12 @@
|
|||
<entry key="../../../../../layout/compose-model-1620403674440.xml" value="0.2962962962962963" />
|
||||
<entry key="../../../../../layout/compose-model-1620414619902.xml" value="0.28125" />
|
||||
<entry key="../../../../../layout/compose-model-1620421200338.xml" value="0.3" />
|
||||
<entry key="../../../../../layout/compose-model-1620464601516.xml" value="0.28125" />
|
||||
<entry key="../../../../../layout/compose-model-1620467872903.xml" value="0.23058252427184467" />
|
||||
<entry key="../../../../../layout/compose-model-1620467872904.xml" value="0.2962962962962963" />
|
||||
<entry key="../../../../../layout/compose-model-1620467930629.xml" value="0.16300675675675674" />
|
||||
<entry key="../../../../../layout/compose-model-1620468466480.xml" value="0.5818181818181818" />
|
||||
<entry key="../../../../../layout/compose-model-1620469838058.xml" value="0.5818181818181818" />
|
||||
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2898148148148148" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_local_library_24.xml" value="0.25462962962962965" />
|
||||
<entry key="app/src/main/res/drawable/ic_google.xml" value="0.2962962962962963" />
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ import com.google.gson.GsonBuilder
|
|||
import com.pixelized.biblib.injection.Bob
|
||||
import com.pixelized.biblib.network.client.BibLibClient
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import com.pixelized.biblib.repository.credential.CredentialRepository
|
||||
import com.pixelized.biblib.repository.credential.ICredentialRepository
|
||||
import com.pixelized.biblib.repository.googlesignin.GoogleSingInRepository
|
||||
import com.pixelized.biblib.repository.googlesignin.IGoogleSingInRepository
|
||||
import com.pixelized.biblib.utils.BitmapCache
|
||||
|
||||
class BibLibApplication : Application() {
|
||||
|
|
@ -18,5 +22,8 @@ class BibLibApplication : Application() {
|
|||
Bob[Gson::class] = GsonBuilder().create()
|
||||
|
||||
Bob[IBibLibClient::class] = BibLibClient()
|
||||
|
||||
Bob[IGoogleSingInRepository::class] = GoogleSingInRepository(this)
|
||||
Bob[ICredentialRepository::class] = CredentialRepository(this)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.pixelized.biblib.repository.credential
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
|
||||
|
||||
class CredentialRepository(application: Application) : ICredentialRepository {
|
||||
private val preferences =
|
||||
application.getSharedPreferences(AuthenticationViewModel.SHARED_PREF, Context.MODE_PRIVATE)
|
||||
|
||||
override var login
|
||||
get() = preferences.login
|
||||
set(value) {
|
||||
preferences.login = value
|
||||
}
|
||||
|
||||
override var password: String?
|
||||
get() = preferences.password
|
||||
set(value) {
|
||||
preferences.password = value
|
||||
}
|
||||
|
||||
override var rememberCredential: Boolean
|
||||
get() = preferences.rememberCredential
|
||||
set(value) {
|
||||
preferences.rememberCredential = value
|
||||
}
|
||||
|
||||
private var SharedPreferences.login: String?
|
||||
get() = getString(AuthenticationViewModel.REMEMBER_USER, null)
|
||||
set(value) = edit { putString(AuthenticationViewModel.REMEMBER_USER, value) }
|
||||
|
||||
private var SharedPreferences.password: String?
|
||||
get() = getString(AuthenticationViewModel.REMEMBER_PASSWORD, null)
|
||||
set(value) = edit { putString(AuthenticationViewModel.REMEMBER_PASSWORD, value) }
|
||||
|
||||
private var SharedPreferences.rememberCredential: Boolean
|
||||
get() = getBoolean(AuthenticationViewModel.REMEMBER_CREDENTIAL, false)
|
||||
set(value) = edit { putBoolean(AuthenticationViewModel.REMEMBER_CREDENTIAL, value) }
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.pixelized.biblib.repository.credential
|
||||
|
||||
interface ICredentialRepository {
|
||||
var login: String?
|
||||
var password: String?
|
||||
var rememberCredential: Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.pixelized.biblib.repository.googlesignin
|
||||
|
||||
import android.app.Application
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInClient
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.utils.exception.MissingTokenException
|
||||
|
||||
class GoogleSingInRepository(application: Application) : IGoogleSingInRepository {
|
||||
|
||||
override val googleSignInOption: GoogleSignInOptions by lazy {
|
||||
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||
.requestIdToken(application.getString(R.string.biblib_server_id))
|
||||
.requestEmail()
|
||||
.build()
|
||||
}
|
||||
|
||||
override val googleSignIn: GoogleSignInClient by lazy {
|
||||
GoogleSignIn.getClient(application, googleSignInOption)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun prepareLoginWithGoogle(): IGoogleSingInRepository.Request {
|
||||
val result = remember {
|
||||
mutableStateOf<IGoogleSingInRepository.AuthenticationState>(
|
||||
IGoogleSingInRepository.AuthenticationState.Initial
|
||||
)
|
||||
}
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
try {
|
||||
val task = GoogleSignIn.getSignedInAccountFromIntent(it.data)
|
||||
val account: GoogleSignInAccount? = task.getResult(ApiException::class.java)
|
||||
val idToken = account?.idToken ?: throw MissingTokenException()
|
||||
result.value = IGoogleSingInRepository.AuthenticationState.Connect(idToken)
|
||||
} catch (exception: Exception) {
|
||||
result.value = IGoogleSingInRepository.AuthenticationState.Error(exception)
|
||||
}
|
||||
}
|
||||
return IGoogleSingInRepository.Request(result, launcher)
|
||||
}
|
||||
|
||||
override fun loginWithGoogle(request: IGoogleSingInRepository.Request) {
|
||||
request.result.value = IGoogleSingInRepository.AuthenticationState.Loading
|
||||
request.launcher.launch(googleSignIn.signInIntent)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.pixelized.biblib.repository.googlesignin
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInClient
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||
|
||||
interface IGoogleSingInRepository {
|
||||
val googleSignInOption: GoogleSignInOptions
|
||||
val googleSignIn: GoogleSignInClient
|
||||
|
||||
@Composable
|
||||
fun prepareLoginWithGoogle(): Request
|
||||
|
||||
fun loginWithGoogle(request: Request)
|
||||
|
||||
data class Request(
|
||||
val result: MutableState<AuthenticationState>,
|
||||
val launcher: ActivityResultLauncher<Intent>,
|
||||
)
|
||||
|
||||
sealed class AuthenticationState {
|
||||
object Initial : AuthenticationState()
|
||||
object Loading : AuthenticationState()
|
||||
data class Connect(val token: String) : AuthenticationState()
|
||||
data class Error(val exception: Exception) : AuthenticationState()
|
||||
}
|
||||
}
|
||||
|
|
@ -22,17 +22,6 @@ import com.pixelized.biblib.ui.viewmodel.NavigationViewModel.Screen
|
|||
class MainActivity : ComponentActivity() {
|
||||
private val navigationViewModel: NavigationViewModel by viewModels()
|
||||
|
||||
private val googleSignInOption by lazy {
|
||||
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||
.requestIdToken(getString(R.string.biblib_server_id))
|
||||
.requestEmail()
|
||||
.build()
|
||||
}
|
||||
|
||||
val googleSignIn by lazy {
|
||||
GoogleSignIn.getClient(this, googleSignInOption)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
|
@ -57,9 +46,9 @@ fun ContentComposable() {
|
|||
|
||||
Crossfade(targetState = main) {
|
||||
when (it) {
|
||||
is Screen.SplashScreen -> SplashScreenComposable()
|
||||
is Screen.LoginScreen -> LoginScreenComposable()
|
||||
is Screen.MainScreen -> MainScreenComposable()
|
||||
is Screen.SplashScreen -> SplashScreenComposable(viewModel())
|
||||
is Screen.LoginScreen -> LoginScreenComposable(viewModel(), viewModel())
|
||||
is Screen.MainScreen -> MainScreenComposable(viewModel())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
package com.pixelized.biblib.ui.composable.items
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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
|
||||
|
||||
|
||||
@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) {
|
||||
Card(elevation = 8.dp) {
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
package com.pixelized.biblib.ui.composable.screen
|
||||
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -24,7 +21,6 @@ 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.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -33,12 +29,9 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
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.R
|
||||
import com.pixelized.biblib.ui.MainActivity
|
||||
import com.pixelized.biblib.repository.googlesignin.IGoogleSingInRepository.AuthenticationState.Loading
|
||||
import com.pixelized.biblib.ui.composable.items.WaitingComposable
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
|
||||
|
|
@ -47,121 +40,116 @@ import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
|
|||
@Composable
|
||||
fun LoginScreenComposablePreview() {
|
||||
BibLibTheme {
|
||||
LoginScreenComposable()
|
||||
val navigationViewModel = NavigationViewModel()
|
||||
val authenticationViewModel = AuthenticationViewModel()
|
||||
LoginScreenComposable(navigationViewModel, authenticationViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun LoginScreenComposable(
|
||||
navigationViewModel: NavigationViewModel = viewModel(),
|
||||
authenticationViewModel: AuthenticationViewModel = viewModel(),
|
||||
navigationViewModel: NavigationViewModel,
|
||||
authenticationViewModel: AuthenticationViewModel,
|
||||
) {
|
||||
// TODO : c'est de la merde ça
|
||||
val activity = LocalContext.current as MainActivity
|
||||
val result = remember { mutableStateOf<String?>(null) }
|
||||
val launcher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
try {
|
||||
val task = GoogleSignIn.getSignedInAccountFromIntent(it.data)
|
||||
val account: GoogleSignInAccount? = task.getResult(ApiException::class.java)
|
||||
|
||||
val idToken = account?.idToken
|
||||
// if (idToken != null) {
|
||||
// viewModel.loginWithGoogle(idToken).observeLogin()
|
||||
// } else {
|
||||
// Toast.makeText(requireActivity(), "GoogleSignIn missing Token", Toast.LENGTH_SHORT).show()
|
||||
// }
|
||||
Log.e("AuthLoginFragment", "idToken: $idToken")
|
||||
} catch (exception: Exception) {
|
||||
// Toast.makeText(requireActivity(), "GoogleSignIn exception: ${exception.message}", Toast.LENGTH_SHORT).show()
|
||||
Log.e("AuthLoginFragment", exception.message, exception)
|
||||
}
|
||||
// // Here we just update the state, but you could imagine
|
||||
// // pre-processing the result, or updating a MutableSharedFlow that
|
||||
// // your composable collects
|
||||
// result.value = it
|
||||
}
|
||||
|
||||
val typography = MaterialTheme.typography
|
||||
|
||||
Column(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
val loginWithGoogleRequest = authenticationViewModel.prepareLoginWithGoogle()
|
||||
val typography = MaterialTheme.typography
|
||||
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 16.dp)
|
||||
.align(alignment = Alignment.CenterHorizontally),
|
||||
style = typography.h4,
|
||||
text = stringResource(id = R.string.welcome_sign_in)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val localFocus = LocalFocusManager.current
|
||||
LoginField(
|
||||
viewModel = authenticationViewModel,
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
keyboardActions = KeyboardActions { focusRequester.requestFocus() }
|
||||
)
|
||||
PasswordField(
|
||||
viewModel = authenticationViewModel,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp)
|
||||
.focusRequester(focusRequester),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions { localFocus.clearFocus() }
|
||||
)
|
||||
CredentialRemember(
|
||||
viewModel = authenticationViewModel,
|
||||
modifier = Modifier
|
||||
.height(48.dp)
|
||||
.padding(bottom = 16.dp)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
.align(Alignment.End)
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 16.dp)
|
||||
.align(alignment = Alignment.CenterHorizontally),
|
||||
style = typography.h4,
|
||||
text = stringResource(id = R.string.welcome_sign_in)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val localFocus = LocalFocusManager.current
|
||||
LoginField(
|
||||
viewModel = authenticationViewModel,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
keyboardActions = KeyboardActions { focusRequester.requestFocus() }
|
||||
)
|
||||
PasswordField(
|
||||
viewModel = authenticationViewModel,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp)
|
||||
.focusRequester(focusRequester),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions { localFocus.clearFocus() }
|
||||
)
|
||||
CredentialRemember(
|
||||
viewModel = authenticationViewModel,
|
||||
modifier = Modifier
|
||||
.height(48.dp)
|
||||
.padding(bottom = 16.dp)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
.align(Alignment.End)
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
colors = outlinedButtonColors(),
|
||||
onClick = {
|
||||
authenticationViewModel.register()
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.action_register))
|
||||
}
|
||||
Button(onClick = {
|
||||
authenticationViewModel.login()
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.action_login))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(2f))
|
||||
|
||||
Button(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = outlinedButtonColors(),
|
||||
onClick = {
|
||||
authenticationViewModel.register()
|
||||
authenticationViewModel.loginWithGoogle(loginWithGoogleRequest)
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.action_register))
|
||||
}
|
||||
Button(onClick = {
|
||||
authenticationViewModel.login()
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.action_login))
|
||||
Image(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
painter = painterResource(id = R.drawable.ic_google), contentDescription = ""
|
||||
)
|
||||
Text(text = stringResource(id = R.string.action_google_sign_in))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(2f))
|
||||
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = outlinedButtonColors(),
|
||||
onClick = {
|
||||
launcher.launch(activity.googleSignIn.signInIntent)
|
||||
}) {
|
||||
Image(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
painter = painterResource(id = R.drawable.ic_google), contentDescription = ""
|
||||
)
|
||||
Text(text = stringResource(id = R.string.action_google_sign_in))
|
||||
var waiting: Boolean by remember { mutableStateOf(false) }
|
||||
waiting = when (loginWithGoogleRequest.result.value) {
|
||||
is Loading -> true
|
||||
else -> false
|
||||
}
|
||||
WaitingComposable(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
visible = waiting,
|
||||
message = stringResource(id = R.string.loading)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,7 +164,7 @@ private fun LoginField(
|
|||
TextField(
|
||||
modifier = modifier,
|
||||
value = login.value ?: "",
|
||||
onValueChange = { viewModel.updateLogin(it) },
|
||||
onValueChange = { viewModel.updateLoginField(it) },
|
||||
label = { Text(text = stringResource(id = R.string.authentication_login)) },
|
||||
colors = outlinedTextFieldColors(),
|
||||
maxLines = 1,
|
||||
|
|
@ -198,7 +186,7 @@ private fun PasswordField(
|
|||
TextField(
|
||||
modifier = modifier,
|
||||
value = password.value ?: "",
|
||||
onValueChange = { viewModel.updatePassword(it) },
|
||||
onValueChange = { viewModel.updatePasswordField(it) },
|
||||
label = { Text(text = stringResource(id = R.string.authentication_password)) },
|
||||
colors = outlinedTextFieldColors(),
|
||||
maxLines = 1,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.pixelized.biblib.ui.composable.screen
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.sharp.ArrowBack
|
||||
|
|
@ -12,7 +11,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.composable.pages.DetailPageComposable
|
||||
import com.pixelized.biblib.ui.composable.pages.HomePageComposable
|
||||
|
|
@ -42,13 +40,15 @@ fun ToolbarComposableLightPreview() {
|
|||
@Composable
|
||||
fun MainScreenComposablePreview() {
|
||||
BibLibTheme {
|
||||
MainScreenComposable()
|
||||
val viewModel = NavigationViewModel()
|
||||
MainScreenComposable(viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainScreenComposable() {
|
||||
val navigationViewModel = viewModel<NavigationViewModel>()
|
||||
fun MainScreenComposable(
|
||||
navigationViewModel: NavigationViewModel
|
||||
) {
|
||||
val page by navigationViewModel.page.observeAsState()
|
||||
|
||||
LaunchedEffect(key1 = "MainScreen", block = {
|
||||
|
|
|
|||
|
|
@ -11,23 +11,25 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SplashScreenComposablePreview() {
|
||||
BibLibTheme {
|
||||
SplashScreenComposable()
|
||||
val viewModel = NavigationViewModel()
|
||||
SplashScreenComposable(viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SplashScreenComposable() {
|
||||
val navigationViewModel = viewModel<NavigationViewModel>()
|
||||
fun SplashScreenComposable(
|
||||
navigationViewModel: NavigationViewModel
|
||||
) {
|
||||
val typography = MaterialTheme.typography
|
||||
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
package com.pixelized.biblib.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.biblib.data.network.query.AuthLoginQuery
|
||||
import com.pixelized.biblib.injection.inject
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import com.pixelized.biblib.repository.credential.ICredentialRepository
|
||||
import com.pixelized.biblib.repository.googlesignin.IGoogleSingInRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AuthenticationViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val preferences = application.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
|
||||
class AuthenticationViewModel : ViewModel() {
|
||||
private val credentialRepository: ICredentialRepository by inject()
|
||||
private val googleSignIn: IGoogleSingInRepository by inject()
|
||||
private val client: IBibLibClient by inject()
|
||||
|
||||
private val _login = MutableLiveData<String>()
|
||||
|
|
@ -31,27 +30,27 @@ class AuthenticationViewModel(application: Application) : AndroidViewModel(appli
|
|||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
_login.value = preferences.login
|
||||
_password.value = preferences.password
|
||||
_rememberCredential.value = preferences.rememberCredential
|
||||
_login.value = credentialRepository.login
|
||||
_password.value = credentialRepository.password
|
||||
_rememberCredential.value = credentialRepository.rememberCredential
|
||||
}
|
||||
}
|
||||
|
||||
fun updateLogin(login: String) {
|
||||
fun updateLoginField(login: String) {
|
||||
_login.postValue(login)
|
||||
}
|
||||
|
||||
fun updatePassword(password: String) {
|
||||
fun updatePasswordField(password: String) {
|
||||
_password.postValue(password)
|
||||
}
|
||||
|
||||
fun updateRememberCredential(rememberCredential: Boolean) {
|
||||
_rememberCredential.postValue(rememberCredential)
|
||||
viewModelScope.launch {
|
||||
preferences.rememberCredential = rememberCredential
|
||||
credentialRepository.rememberCredential = rememberCredential
|
||||
if (rememberCredential.not()) {
|
||||
preferences.login = null
|
||||
preferences.password = null
|
||||
credentialRepository.login = null
|
||||
credentialRepository.password = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -63,8 +62,8 @@ class AuthenticationViewModel(application: Application) : AndroidViewModel(appli
|
|||
fun login() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (rememberCredential.value == true) {
|
||||
preferences.login = login.value
|
||||
preferences.password = password.value
|
||||
credentialRepository.login = login.value
|
||||
credentialRepository.password = password.value
|
||||
}
|
||||
// TODO : validation !
|
||||
val query = AuthLoginQuery(
|
||||
|
|
@ -77,17 +76,12 @@ class AuthenticationViewModel(application: Application) : AndroidViewModel(appli
|
|||
}
|
||||
}
|
||||
|
||||
private var SharedPreferences.login: String?
|
||||
get() = getString(REMEMBER_USER, null)
|
||||
set(value) = edit { putString(REMEMBER_USER, value) }
|
||||
@Composable
|
||||
fun prepareLoginWithGoogle(): IGoogleSingInRepository.Request =
|
||||
googleSignIn.prepareLoginWithGoogle()
|
||||
|
||||
private var SharedPreferences.password: String?
|
||||
get() = getString(REMEMBER_PASSWORD, null)
|
||||
set(value) = edit { putString(REMEMBER_PASSWORD, value) }
|
||||
|
||||
private var SharedPreferences.rememberCredential: Boolean
|
||||
get() = getBoolean(REMEMBER_CREDENTIAL, false)
|
||||
set(value) = edit { putBoolean(REMEMBER_CREDENTIAL, value) }
|
||||
fun loginWithGoogle(request: IGoogleSingInRepository.Request) =
|
||||
googleSignIn.loginWithGoogle(request)
|
||||
|
||||
companion object {
|
||||
const val SHARED_PREF = "BIB_LIB_SHARED_PREF"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
package com.pixelized.biblib.utils.exception
|
||||
|
||||
class MissingTokenException: RuntimeException("Login response miss token data.")
|
||||
|
|
@ -5,6 +5,8 @@
|
|||
<string name="action_login">Login</string>
|
||||
<string name="action_google_sign_in">Sign in with Google</string>
|
||||
|
||||
<string name="loading">Entering the Imperial Library of Trantor.</string>
|
||||
|
||||
<string name="welcome_sign_in">Sign in to BibLib</string>
|
||||
|
||||
<string name="authentication_login">Login</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue