Profile refresh mechanism when trying to send a book with no associated emails.

This commit is contained in:
Thomas Andres Gomez 2022-10-26 10:19:46 +02:00
parent 29a427ac5a
commit 80bcb9a686
11 changed files with 117 additions and 60 deletions

View file

@ -1,5 +1,3 @@
# TODO # TODO
* Profile refresh.
* especially when no mail available.
* Profile dialog close button. * Profile dialog close button.

View file

@ -1,8 +1,9 @@
# 0.1.4 # 0.1.4
> Not yet > Not yet
* Fix SignIn and EditProfile Url. * Fix SignIn and EditProfile Urls.
* Fix Search IME padding. * Fix Search IME padding.
* Clear search filter after validation. * Clear search filter after validation.
* Profile refresh mechanism when trying to send a book with no associated emails.
# 0.1.3 # 0.1.3
> Published 25 October 2022 > Published 25 October 2022

View file

@ -12,6 +12,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.pixelized.biblib.ui.navigation.ScreenNavHost import com.pixelized.biblib.ui.navigation.ScreenNavHost
import com.pixelized.biblib.ui.screen.launch.LauncherViewModel import com.pixelized.biblib.ui.screen.launch.LauncherViewModel
import com.pixelized.biblib.utils.extention.bibLib
import com.skydoves.landscapist.glide.LocalGlideRequestOptions import com.skydoves.landscapist.glide.LocalGlideRequestOptions
val LocalSnackHostState = staticCompositionLocalOf<SnackbarHostState> { val LocalSnackHostState = staticCompositionLocalOf<SnackbarHostState> {
@ -40,6 +41,7 @@ fun MainContent(
) { snackBarData -> ) { snackBarData ->
Snackbar( Snackbar(
snackbarData = snackBarData, snackbarData = snackBarData,
shape = MaterialTheme.shapes.medium,
backgroundColor = MaterialTheme.colors.error, backgroundColor = MaterialTheme.colors.error,
contentColor = MaterialTheme.colors.onError, contentColor = MaterialTheme.colors.onError,
actionColor = MaterialTheme.colors.onError, actionColor = MaterialTheme.colors.onError,

View file

@ -1,23 +1,15 @@
package com.pixelized.biblib.ui.screen.home.detail package com.pixelized.biblib.ui.screen.home.detail
import android.util.Log
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.network.factory.UserFactory
import com.pixelized.biblib.utils.extention.capitalize import com.pixelized.biblib.utils.extention.capitalize
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class BookOptionViewModel @Inject constructor( class BookOptionViewModel @Inject constructor() : ViewModel() {
private val client: IBibLibClient,
) : ViewModel() {
var emails by mutableStateOf(listOf<OptionUio>()) var emails by mutableStateOf(listOf<OptionUio>())
private set private set
@ -25,19 +17,9 @@ class BookOptionViewModel @Inject constructor(
var formats by mutableStateOf(initialFormat()) var formats by mutableStateOf(initialFormat())
private set private set
init { fun updateEmails(amazonEmails: List<String>) {
viewModelScope.launch(Dispatchers.IO) { emails = amazonEmails.mapIndexed { index, mail ->
try { OptionUio(value = mail, selected = index == 0)
val factory = UserFactory()
val response = client.service.user()
val data = factory.fromUserResponseToUser(response)
emails = data.amazonEmails.mapIndexed { index, mail ->
OptionUio(value = mail, selected = index == 0)
}
} catch (exception: Exception) {
Log.e("AccountViewModel", exception.message, exception)
}
} }
} }

View file

@ -1,8 +1,8 @@
package com.pixelized.biblib.ui.screen.home.detail package com.pixelized.biblib.ui.screen.home.detail
import android.content.Context
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
@ -20,6 +20,7 @@ import com.pixelized.biblib.ui.screen.home.page.profile.ProfileViewModel
import com.pixelized.biblib.ui.screen.home.page.profile.UserUio import com.pixelized.biblib.ui.screen.home.page.profile.UserUio
import com.pixelized.biblib.ui.theme.color.ShadowPalette import com.pixelized.biblib.ui.theme.color.ShadowPalette
import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.bibLib
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Stable @Stable
@ -77,6 +78,7 @@ fun DetailScreen(
modifier = Modifier modifier = Modifier
.navigationBarsPadding() .navigationBarsPadding()
.padding(bottom = MaterialTheme.bibLib.dimen.dp16), .padding(bottom = MaterialTheme.bibLib.dimen.dp16),
emails = profileViewModel.mails,
onHelp = { uri -> onHelp = { uri ->
uriHandler.openUri(uri) uriHandler.openUri(uri)
}, },
@ -96,17 +98,13 @@ fun DetailScreen(
.systemBarsPadding(), .systemBarsPadding(),
book = detail, book = detail,
onSend = { onSend = {
scope.launch { onSend(
val user = profileViewModel.user as? StateUio.Success<UserUio> context = context,
if (user?.value?.amazonEmails?.isEmpty() == true) { profileViewModel = profileViewModel,
snackBarHost.showSnackbar( snackBarHost = snackBarHost,
message = context.getString(R.string.error_no_amazon_email), emailSheetState = emailSheetState,
duration = SnackbarDuration.Long, userState = profileViewModel.user
) )
} else {
emailSheetState.show()
}
}
} }
) )
DetailScreenSendLoader( DetailScreenSendLoader(
@ -159,6 +157,35 @@ fun DetailScreen(
} }
} }
@OptIn(ExperimentalMaterialApi::class)
private suspend fun onSend(
context: Context,
profileViewModel: ProfileViewModel,
snackBarHost: SnackbarHostState,
emailSheetState: ModalBottomSheetState,
userState: StateUio<UserUio>,
) {
val user = userState as? StateUio.Success<UserUio>
if (user?.value?.amazonEmails?.isEmpty() == true) {
val result = snackBarHost.showSnackbar(
message = context.getString(R.string.error_send_no_amazon_email_message),
actionLabel = context.getString(R.string.error_send_no_amazon_email_action),
duration = SnackbarDuration.Indefinite,
)
if (result == SnackbarResult.ActionPerformed) {
onSend(
context = context,
profileViewModel = profileViewModel,
snackBarHost = snackBarHost,
emailSheetState = emailSheetState,
userState = profileViewModel.updateUser()
)
}
} else {
emailSheetState.show()
}
}
@Composable @Composable
private fun EmptyDetail( private fun EmptyDetail(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,

View file

@ -15,6 +15,7 @@ import androidx.compose.material.icons.filled.Send
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
@ -37,6 +38,7 @@ import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.utils.extention.default import com.pixelized.biblib.utils.extention.default
import com.skydoves.landscapist.CircularReveal import com.skydoves.landscapist.CircularReveal
import com.skydoves.landscapist.glide.GlideImage import com.skydoves.landscapist.glide.GlideImage
import kotlinx.coroutines.launch
import java.io.Serializable import java.io.Serializable
@Stable @Stable
@ -75,8 +77,10 @@ fun DetailScreenContent(
book: BookDetailUio, book: BookDetailUio,
onMobi: () -> Unit = default(), onMobi: () -> Unit = default(),
onEpub: () -> Unit = default(), onEpub: () -> Unit = default(),
onSend: () -> Unit = default(), onSend: suspend () -> Unit = { },
) { ) {
val scope = rememberCoroutineScope()
AnimatedDelayer( AnimatedDelayer(
targetState = book.id, targetState = book.id,
) { ) {
@ -130,7 +134,7 @@ fun DetailScreenContent(
) { ) {
Button( Button(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
onClick = onSend, onClick = { scope.launch { onSend() } },
) { ) {
Icon(imageVector = Icons.Default.Send, contentDescription = "") Icon(imageVector = Icons.Default.Send, contentDescription = "")
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.dp4)) Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.dp4))

View file

@ -15,6 +15,7 @@ import androidx.compose.material.RadioButton
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -26,6 +27,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.biblib.R import com.pixelized.biblib.R
import com.pixelized.biblib.ui.screen.home.page.profile.UserUio
import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.* import com.pixelized.biblib.utils.extention.*
@ -49,9 +51,13 @@ annotation class Format {
fun DetailScreenSendOption( fun DetailScreenSendOption(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
optionViewModel: BookOptionViewModel = hiltViewModel(), optionViewModel: BookOptionViewModel = hiltViewModel(),
emails: List<String>,
onHelp: (url: String) -> Unit = default<String>(), onHelp: (url: String) -> Unit = default<String>(),
onSend: (email: String, format: String) -> Unit = { _, _ -> }, onSend: (email: String, format: String) -> Unit = { _, _ -> },
) { ) {
LaunchedEffect(key1 = emails) {
optionViewModel.updateEmails(amazonEmails = emails)
}
DetailScreenSendOption( DetailScreenSendOption(
modifier = modifier, modifier = modifier,
emails = optionViewModel.emails, emails = optionViewModel.emails,
@ -138,7 +144,7 @@ fun DetailScreenSendOption(
.padding(horizontal = MaterialTheme.bibLib.dimen.dp16), .padding(horizontal = MaterialTheme.bibLib.dimen.dp16),
color = MaterialTheme.bibLib.colors.typography.easy, color = MaterialTheme.bibLib.colors.typography.easy,
style = MaterialTheme.typography.caption, style = MaterialTheme.typography.caption,
text = stringResource(id = R.string.detail_option_mail), text = stringResource(id = R.string.detail_option_format),
) )
} }
items(items = formats) { format -> items(items = formats) { format ->

View file

@ -5,6 +5,8 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.net.Uri import android.net.Uri
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
@ -12,8 +14,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.biblib.R
import com.pixelized.biblib.network.client.IBibLibClient import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.ui.composable.StateUio import com.pixelized.biblib.ui.composable.StateUio
import com.pixelized.biblib.ui.navigation.LocalScreenNavHostController import com.pixelized.biblib.ui.navigation.LocalScreenNavHostController
@ -62,8 +66,9 @@ private fun ProfileScreenContent(
Text( Text(
style = MaterialTheme.typography.body1, style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface, color = MaterialTheme.colors.onSurface,
text = "Welcome" text = stringResource(id = R.string.profile_title)
) )
Text( Text(
style = MaterialTheme.typography.h6, style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.primary, color = MaterialTheme.colors.primary,
@ -94,14 +99,28 @@ private fun ProfileScreenContent(
modifier = Modifier.padding(top = MaterialTheme.bibLib.dimen.dp16), modifier = Modifier.padding(top = MaterialTheme.bibLib.dimen.dp16),
style = MaterialTheme.typography.body1, style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface, color = MaterialTheme.colors.onSurface,
text = "Linked emails:" text = stringResource(id = R.string.profile_emails)
) )
user.amazonEmails.forEach { LazyColumn {
Text( if (user.amazonEmails.isEmpty()) {
style = MaterialTheme.typography.caption, item {
color = MaterialTheme.colors.onSurface, Text(
text = it, style = MaterialTheme.typography.caption,
) color = MaterialTheme.colors.error,
text = stringResource(id = R.string.profile_emails_empty),
)
}
} else {
items(
items = user.amazonEmails
) {
Text(
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onSurface,
text = it,
)
}
}
} }
Button( Button(
@ -117,19 +136,18 @@ private fun ProfileScreenContent(
) )
Text( Text(
modifier = Modifier.padding(start = MaterialTheme.bibLib.dimen.dp8), modifier = Modifier.padding(start = MaterialTheme.bibLib.dimen.dp8),
text = "Edit profile" text = stringResource(id = R.string.profile_edit_action)
) )
} }
Button( Button(
modifier = Modifier modifier = Modifier
.align(Alignment.End), .align(Alignment.End),
colors = ButtonDefaults.outlinedButtonColors(), colors = ButtonDefaults.outlinedButtonColors(),
onClick = onLogoutClick, onClick = onLogoutClick,
) { ) {
Text( Text(
text = "Logout" text = stringResource(id = R.string.profile_logout_action)
) )
} }
} }

View file

@ -1,6 +1,7 @@
package com.pixelized.biblib.ui.screen.home.page.profile package com.pixelized.biblib.ui.screen.home.page.profile
import android.util.Log import android.util.Log
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -15,6 +16,7 @@ import com.pixelized.biblib.ui.composable.StateUio
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -27,13 +29,19 @@ class ProfileViewModel @Inject constructor(
var user by mutableStateOf<StateUio<UserUio>>(StateUio.Progress()) var user by mutableStateOf<StateUio<UserUio>>(StateUio.Progress())
private set private set
init { val mails: List<String> by derivedStateOf {
updateUser() (user as? StateUio.Success<UserUio>)?.value?.amazonEmails ?: emptyList()
} }
private fun updateUser() { init {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
user = try { updateUser()
}
}
suspend fun updateUser(): StateUio<UserUio> {
return withContext(Dispatchers.IO) {
try {
val factory = UserFactory() val factory = UserFactory()
val response = client.service.user() val response = client.service.user()
val data = factory.fromUserResponseToUser(response) val data = factory.fromUserResponseToUser(response)
@ -41,6 +49,8 @@ class ProfileViewModel @Inject constructor(
} catch (exception: Exception) { } catch (exception: Exception) {
Log.e("AccountViewModel", exception.message, exception) Log.e("AccountViewModel", exception.message, exception)
StateUio.Failure(exception) StateUio.Failure(exception)
}.also {
user = it
} }
} }
} }

View file

@ -21,6 +21,7 @@
<string name="error_book_update_message">La mise à jour de la librarie à échouée.</string> <string name="error_book_update_message">La mise à jour de la librarie à échouée.</string>
<string name="error_get_book_detail_message">La récupération des détails a échouée.</string> <string name="error_get_book_detail_message">La récupération des détails a échouée.</string>
<string name="error_send_book_message">L\'envoi de l\'eBook a échoué.</string> <string name="error_send_book_message">L\'envoi de l\'eBook a échoué.</string>
<string name="error_send_no_amazon_email_message">Vous n\'avez aucun mail lié à votre compte. Merci de vous rendre sur https://bib.bibulle.fr/profile pour en ajouter un.</string>
<!-- Dialogs --> <!-- Dialogs -->
@ -28,7 +29,6 @@
<string name="error_authentication">Oups, la connection a échoué !</string> <string name="error_authentication">Oups, la connection a échoué !</string>
<string name="error_book">Oups! le téléchargement de la librairy à échoué !</string> <string name="error_book">Oups! le téléchargement de la librairy à échoué !</string>
<string name="error_offline">Vous êtes hors ligne.</string> <string name="error_offline">Vous êtes hors ligne.</string>
<string name="error_no_amazon_email">Vous n\'avez aucun mail lié à votre compte. Merci de vous rendre sur https://bib.bibulle.fr/ et d\'éditez voter profile pour en ajouter un.</string>
<string name="loading_authentication">Ouverture de BibLibrary.</string> <string name="loading_authentication">Ouverture de BibLibrary.</string>
<string name="loading_book">Téléchargement de BibLibrary.</string> <string name="loading_book">Téléchargement de BibLibrary.</string>
@ -70,6 +70,10 @@
<string name="search_sort_by">Trié par : %1$s</string> <string name="search_sort_by">Trié par : %1$s</string>
<string name="profile_title">Profile</string> <string name="profile_title">Bonjour</string>
<string name="profile_emails">Amails associés :</string>
<string name="profile_emails_empty">Aucun email associé</string>
<string name="profile_edit_action">Édition du profile</string>
<string name="profile_logout_action">Déconnexion</string>
</resources> </resources>

View file

@ -32,6 +32,8 @@
<string name="error_get_book_detail_action" translatable="false">@string/error_action_retry</string> <string name="error_get_book_detail_action" translatable="false">@string/error_action_retry</string>
<string name="error_send_book_message">Failed to send the book to your kindle.</string> <string name="error_send_book_message">Failed to send the book to your kindle.</string>
<string name="error_send_book_action" translatable="false">@string/error_action_retry</string> <string name="error_send_book_action" translatable="false">@string/error_action_retry</string>
<string name="error_send_no_amazon_email_message">You have no email linked to your account. Please go to https://bib.bibulle.fr/profile to add one.</string>
<string name="error_send_no_amazon_email_action" translatable="false">@string/error_action_retry</string>
<!-- Dialogs --> <!-- Dialogs -->
@ -39,7 +41,6 @@
<string name="error_authentication">Oops, connection failed!</string> <string name="error_authentication">Oops, connection failed!</string>
<string name="error_book">Oops! library download failed!</string> <string name="error_book">Oops! library download failed!</string>
<string name="error_offline">You are offline.</string> <string name="error_offline">You are offline.</string>
<string name="error_no_amazon_email">You have no email linked to your account. Please go to https://bib.bibulle.fr/ and edit your profile to add one.</string>
<string name="loading_authentication">Opening the BibLibrary.</string> <string name="loading_authentication">Opening the BibLibrary.</string>
<string name="loading_book">Downloading the BibLibrary.</string> <string name="loading_book">Downloading the BibLibrary.</string>
@ -82,6 +83,10 @@
<string name="search_sort_by">Sort by: %1$s</string> <string name="search_sort_by">Sort by: %1$s</string>
<string name="profile_title">Profil</string> <string name="profile_title">Welcome</string>
<string name="profile_emails">Linked emails:</string>
<string name="profile_emails_empty">no associated email</string>
<string name="profile_edit_action">Edit profile</string>
<string name="profile_logout_action">Logout</string>
</resources> </resources>