Add logout.
This commit is contained in:
parent
6aca43bc5f
commit
1c9f856e65
11 changed files with 103 additions and 30 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue