Add login management.

This commit is contained in:
Thomas Andres Gomez 2021-05-08 14:01:57 +02:00
parent 8fbe3c0b7b
commit 45d2fe1336
14 changed files with 366 additions and 158 deletions

6
.idea/misc.xml generated
View file

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

View file

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

View file

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

View file

@ -0,0 +1,7 @@
package com.pixelized.biblib.repository.credential
interface ICredentialRepository {
var login: String?
var password: String?
var rememberCredential: Boolean
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,3 @@
package com.pixelized.biblib.utils.exception
class MissingTokenException: RuntimeException("Login response miss token data.")

View file

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