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,41 +40,24 @@ 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
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .fillMaxWidth()
 | 
			
		||||
            .fillMaxHeight()
 | 
			
		||||
    ) {
 | 
			
		||||
        val loginWithGoogleRequest = authenticationViewModel.prepareLoginWithGoogle()
 | 
			
		||||
        val typography = MaterialTheme.typography
 | 
			
		||||
 | 
			
		||||
        Column(
 | 
			
		||||
| 
						 | 
				
			
			@ -154,7 +130,7 @@ fun LoginScreenComposable(
 | 
			
		|||
                modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
                colors = outlinedButtonColors(),
 | 
			
		||||
                onClick = {
 | 
			
		||||
                launcher.launch(activity.googleSignIn.signInIntent)
 | 
			
		||||
                    authenticationViewModel.loginWithGoogle(loginWithGoogleRequest)
 | 
			
		||||
                }) {
 | 
			
		||||
                Image(
 | 
			
		||||
                    modifier = Modifier.padding(end = 8.dp),
 | 
			
		||||
| 
						 | 
				
			
			@ -163,6 +139,18 @@ fun LoginScreenComposable(
 | 
			
		|||
                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)
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
| 
						 | 
				
			
			@ -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