Add logout.

This commit is contained in:
Thomas Andres Gomez 2022-07-05 08:38:19 +02:00
parent 6aca43bc5f
commit 1c9f856e65
11 changed files with 103 additions and 30 deletions

View file

@ -68,7 +68,7 @@ android {
}
composeOptions {
kotlinCompilerExtensionVersion '1.2.0-rc01'
kotlinCompilerExtensionVersion '1.2.0-rc02'
}
packagingOptions {
@ -88,7 +88,7 @@ dependencies {
implementation 'androidx.activity:activity-compose:1.4.0'
// Android Compose
implementation "androidx.compose.ui:ui:1.2.0-rc01"
implementation "androidx.compose.ui:ui:1.2.0-rc02"
implementation "androidx.compose.material:material:1.1.1"
implementation "androidx.compose.runtime:runtime-livedata:1.1.1"
implementation "androidx.compose.ui:ui-tooling-preview:1.1.1"

View file

@ -4,14 +4,10 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.ui.graphics.Color
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import com.google.accompanist.systemuicontroller.SystemUiController
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.pixelized.biblib.ui.composable.SystemThemeColor
import com.pixelized.biblib.ui.navigation.screen.ScreenNavHost
import com.pixelized.biblib.ui.screen.launch.LauncherViewModel
@ -33,7 +29,7 @@ class MainActivity : ComponentActivity() {
// splashscreen management
installSplashScreen().apply {
setKeepOnScreenCondition {
launcherViewModel.isLoading
launcherViewModel.isLoadingDone.not()
}
}
@ -43,7 +39,7 @@ class MainActivity : ComponentActivity() {
SystemThemeColor {
Surface(color = MaterialTheme.colors.background) {
// Handle the main Navigation
if (launcherViewModel.isLoading.not()) {
if (launcherViewModel.isLoadingDone) {
ScreenNavHost(
startDestination = launcherViewModel.startDestination
)

View file

@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -79,16 +80,24 @@ fun Search(
),
onValueChange = { search = it }
)
avatar?.let {
IconButton(
modifier = Modifier.padding(end = horizontalPadding),
onClick = onAvatar,
) {
IconButton(
modifier = Modifier.padding(end = horizontalPadding),
onClick = onAvatar,
) {
if (avatar != null) {
GlideImage(
modifier = Modifier.clip(CircleShape).size(32.dp),
previewPlaceholder = R.drawable.ic_google,
contentScale = ContentScale.Fit,
imageModel = it,
imageModel = avatar,
)
} else {
Icon(
modifier = Modifier.clip(CircleShape).size(32.dp),
tint = MaterialTheme.colors.onSurface,
imageVector = Icons.Default.Person,
contentDescription = null,
)
}
}

View file

@ -45,6 +45,17 @@ fun ScreenNavHost(
}
}
fun NavHostController.navigateToAuthentication() {
navigate(Screen.Authentication.route) {
launchSingleTop = true
restoreState = true
popUpTo(0) {
saveState = true
inclusive = true
}
}
}
fun NavHostController.navigateToHome() {
navigate(Screen.Home.route) {
launchSingleTop = true

View file

@ -5,13 +5,17 @@ import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.common.api.ApiException
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.network.data.query.AuthLoginQuery
import com.pixelized.biblib.repository.credential.CredentialRepository_Factory
import com.pixelized.biblib.repository.credential.ICredentialRepository
import com.pixelized.biblib.repository.googleSignIn.IGoogleSingInRepository
import com.pixelized.biblib.ui.composable.StateUio
import com.pixelized.biblib.utils.exception.MissingGoogleTokenException
@ -24,6 +28,7 @@ import javax.inject.Inject
@HiltViewModel
class AuthenticationViewModel @Inject constructor(
private val credentialRepository: ICredentialRepository,
private val googleSignIn: IGoogleSingInRepository,
private val client: IBibLibClient,
) : ViewModel() {
@ -45,6 +50,7 @@ class AuthenticationViewModel @Inject constructor(
val response = client.service.login(query)
val idToken = response.token ?: throw MissingTokenException()
client.token = idToken
credentialRepository.bearer = response.token
_authenticationProcess.value = StateUio.Success(Unit)
} catch (exception: Exception) {
Log.e("AuthenticationViewModel", exception.message, exception)
@ -68,7 +74,7 @@ class AuthenticationViewModel @Inject constructor(
val task = GoogleSignIn.getSignedInAccountFromIntent(it.data)
val account = task.getResult(ApiException::class.java)
val googleToken = account?.idToken ?: throw MissingGoogleTokenException()
val response = client.service.loginWithGoogle(googleToken)
val response = client.service.loginWithGoogle(token = googleToken)
val token = response.token ?: throw MissingTokenException()
client.token = token
_authenticationProcess.value = StateUio.Success(Unit)

View file

@ -4,6 +4,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.activity.compose.BackHandler
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@ -12,6 +13,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.NavigateNext
import androidx.compose.material.icons.filled.Send
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -21,6 +23,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.text.HtmlCompat
@ -70,8 +73,13 @@ fun DetailScreen(
val user by derivedStateOf { profileViewModel.user }
if (user.isSuccessful()) {
DetailScreenSendContent(
modifier = Modifier.navigationBarsPadding(),
modifier = Modifier
.navigationBarsPadding()
.padding(bottom = MaterialTheme.bibLib.dimen.dp16),
emails = (user as StateUio.Success<UserUio>).value.amazonEmails,
onAction = {
}
)
}
}
@ -273,6 +281,7 @@ private fun TitleLabel(
private fun DetailScreenSendContent(
modifier: Modifier = Modifier,
emails: List<String>,
onAction: (email: String) -> Unit = default<String>(),
) {
Column(
modifier = modifier.fillMaxWidth(),
@ -287,18 +296,32 @@ private fun DetailScreenSendContent(
)
LazyColumn {
items(items = emails) {
Box(
items(items = emails) { email ->
Row(
modifier = Modifier
.height(height = MaterialTheme.bibLib.dimen.dp48)
.clickable(onClick = { onAction(email) })
.height(height = MaterialTheme.bibLib.dimen.dp52)
.fillMaxWidth(),
contentAlignment = Alignment.CenterStart
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.dp16),
modifier = Modifier
.padding(
start = MaterialTheme.bibLib.dimen.dp16,
end = MaterialTheme.bibLib.dimen.dp8,
)
.weight(1f),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
text = it
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = email
)
Icon(
modifier = Modifier.padding(end = MaterialTheme.bibLib.dimen.dp16),
imageVector = Icons.Default.NavigateNext,
contentDescription = null
)
}
}
@ -333,8 +356,8 @@ private fun DetailScreenSendPreview() {
BibLibTheme {
DetailScreenSendContent(
emails = listOf(
"R.Giskard.Reventlov.Kindle@gmailcom",
"R.Daneel.Olivaw.Kindle@gmailcom",
"R.Giskard.Reventlov.Kindle@gmail.com",
"R.Daneel.Olivaw.Kindle@gmail.com",
),
)
}

View file

@ -2,6 +2,7 @@ package com.pixelized.biblib.ui.screen.launch
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.biblib.network.client.IBibLibClient
@ -26,8 +27,8 @@ class LauncherViewModel @Inject constructor(
private val apiCache: IAPICacheRepository,
) : ViewModel() {
private val _isLoading = mutableStateOf(true)
val isLoading: Boolean by _isLoading
var isLoadingDone by mutableStateOf(false)
private set
var startDestination: Screen = Screen.Authentication
private set
@ -36,14 +37,15 @@ class LauncherViewModel @Inject constructor(
viewModelScope.launch(Dispatchers.IO) {
// Try to Authenticate
if (autoLoginWithGoogle() || autologinWithCredential()) {
startDestination = Screen.Home
// Update book
if (loadNewBooks()) {
loadAllBooks()
}
// Change the start destination
startDestination = Screen.Home
}
// Update loading state.
_isLoading.value = false
isLoadingDone = true
}
}
@ -73,12 +75,13 @@ class LauncherViewModel @Inject constructor(
private suspend fun autologinWithCredential(): Boolean {
val login = credentialRepository.login
val password = credentialRepository.password
return if (login != null && password != null) {
return if (login != null && password != null && credentialRepository.bearer != null) {
try {
val query = AuthLoginQuery(login, password)
client.service.login(query).let { response ->
if (response.token != null) {
client.token = response.token
credentialRepository.bearer = response.token
true
} else {
false

View file

@ -16,6 +16,9 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.ui.composable.StateUio
import com.pixelized.biblib.ui.navigation.screen.LocalScreenNavHostController
import com.pixelized.biblib.ui.navigation.screen.navigateToAuthentication
import com.pixelized.biblib.ui.navigation.screen.navigateToProfile
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.utils.extention.default
@ -25,6 +28,8 @@ fun ProfileScreen(
viewModel: ProfileViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val navigation = LocalScreenNavHostController.current
when (val user = viewModel.user) {
is StateUio.Progress -> Unit
is StateUio.Success -> ProfileScreenContent(
@ -34,6 +39,10 @@ fun ProfileScreen(
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(IBibLibClient.EDIT_PROFILE))
context.startActivity(intent)
},
onLogoutClick = {
viewModel.logout()
navigation.navigateToAuthentication()
}
)
is StateUio.Failure -> Unit
}

View file

@ -9,6 +9,8 @@ import androidx.lifecycle.viewModelScope
import com.pixelized.biblib.model.user.User
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.network.factory.UserFactory
import com.pixelized.biblib.repository.credential.ICredentialRepository
import com.pixelized.biblib.repository.googleSignIn.IGoogleSingInRepository
import com.pixelized.biblib.ui.composable.StateUio
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
@ -17,6 +19,8 @@ import javax.inject.Inject
@HiltViewModel
class ProfileViewModel @Inject constructor(
private val credentialRepository: ICredentialRepository,
private val googleSignIn: IGoogleSingInRepository,
private val client: IBibLibClient,
) : ViewModel() {
@ -41,6 +45,11 @@ class ProfileViewModel @Inject constructor(
}
}
fun logout() {
credentialRepository.bearer = null
googleSignIn.client.signOut()
}
private fun User.toUio() = UserUio(
username = username,
firstname = firstname,

View file

@ -15,6 +15,7 @@ data class BibLibDimen(
val dp16: Dp = 16.dp,
val dp32: Dp = 32.dp,
val dp48: Dp = 48.dp,
val dp52: Dp = 52.dp,
val dp64: Dp = 64.dp,
val dialog: Dialog = Dialog(),
val thumbnail: BookThumbnail = BookThumbnail(),

View file

@ -16,4 +16,10 @@ fun Context.notImplemented() {
fun default(): () -> Unit {
val context = LocalContext.current
return { context.notImplemented() }
}
@Composable
fun <T> default(): (T) -> Unit {
val context = LocalContext.current
return { context.notImplemented() }
}