Add progress and success to send process.

This commit is contained in:
Thomas Andres Gomez 2022-10-24 10:42:21 +02:00
parent b3f9da7dc7
commit 896c917348
6 changed files with 196 additions and 71 deletions

View file

@ -1,6 +1,5 @@
package com.pixelized.biblib.ui.screen.home.detail
import android.util.Log
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
@ -13,6 +12,7 @@ import com.pixelized.biblib.utils.extention.toDetailUio
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
@ -32,31 +32,43 @@ class BookDetailViewModel @Inject constructor(
private val _sendStatus = MutableSharedFlow<Boolean>()
val sendStatus: Flow<Boolean> get() = _sendStatus
private val _sendingFlow = MutableSharedFlow<Boolean>()
val sendingFlow: Flow<Boolean> get() = _sendingFlow
private val _error = MutableSharedFlow<BookDetailUioErrorUio>()
val error: Flow<BookDetailUioErrorUio> get() = _error
fun getDetail(id: Int) {
fun getDetail(bookId: Int) {
detailJob?.cancel()
detailJob = viewModelScope.launch(Dispatchers.IO) {
try {
_detail.value = getCacheBookDetail(id = id)
_detail.value = getBookDetail(id = id)
_detail.value = getCacheBookDetail(id = bookId)
_detail.value = getBookDetail(id = bookId)
} catch (exception: Exception) {
_error.emit(toDetailErrorUio(bookId = id))
_error.emit(toDetailErrorUio(bookId = bookId))
}
}
}
@Suppress("LiftReturnOrAssignment")
fun send(bookId: Int, email: String, format: String) {
sendJob?.cancel()
sendJob = viewModelScope.launch(Dispatchers.IO) {
_sendingFlow.emit(true)
var isSuccess = false
try {
val data = client.service.send(bookId = bookId, mail = email, format = format)
_sendStatus.emit(true)
Log.d("send", data.toString())
isSuccess = data.containsKey("ok")
} catch (exception: Exception) {
Log.d("send", exception.message, exception)
_error.emit(toSendBookUio(bookId = bookId, mail = email, format))
_error.emit(toSendBookUio(bookId = bookId, mail = email, format = format))
isSuccess = false
} finally {
_sendingFlow.emit(false)
if (isSuccess) {
_sendStatus.emit(true)
delay(300 + 2000)
_sendStatus.emit(false)
}
}
}
}

View file

@ -14,12 +14,12 @@ import androidx.compose.ui.platform.LocalUriHandler
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.biblib.R
import com.pixelized.biblib.ui.LocalSnackHostState
import com.pixelized.biblib.ui.composable.isSuccessful
import com.pixelized.biblib.ui.composable.StateUio
import com.pixelized.biblib.ui.scaffold.LocalDetailBottomSheetState
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.theme.color.ShadowPalette
import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.utils.extention.showToast
import kotlinx.coroutines.launch
@Stable
@ -92,28 +92,27 @@ fun DetailScreen(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.systemBarsPadding()
.padding(all = MaterialTheme.bibLib.dimen.dp16)
.animateContentSize(),
.systemBarsPadding(),
book = detail,
onSend = {
// check
val user = profileViewModel.user
if (user.isSuccessful()) {
val mails = user.value.amazonEmails
when {
mails.isEmpty() -> {
context.showToast(context.getString(R.string.error_no_amazon_email))
}
else -> {
scope.launch { emailSheetState.show() }
}
scope.launch {
val user = profileViewModel.user as? StateUio.Success<UserUio>
if (user?.value?.amazonEmails?.isEmpty() == true) {
snackBarHost.showSnackbar(
message = context.getString(R.string.error_no_amazon_email),
duration = SnackbarDuration.Long,
)
} else {
emailSheetState.show()
}
} else {
// TODO()
}
}
)
DetailScreenSendLoader(
loadingVisibility = detailViewModel.sendStatus,
successVisibility = detailViewModel.sendingFlow,
)
} else {
EmptyDetail()
}
@ -123,38 +122,33 @@ fun DetailScreen(
}
if (detailState.bottomSheetState.isVisible || detailState.bottomSheetState.isAnimationRunning) {
LaunchedEffect(key1 = "DetailScreenError$bookId") {
detailViewModel.error.collect {
val result = snackBarHost.showSnackbar(
message = context.getString(it.message),
actionLabel = context.getString(it.action),
)
if (result == SnackbarResult.ActionPerformed) {
when (it) {
is BookDetailUioErrorUio.GetDetailInput -> detailViewModel.getDetail(
id = it.bookId
)
is BookDetailUioErrorUio.SendBookInput -> detailViewModel.send(
bookId = it.bookId,
email = it.mail,
format = it.format,
)
LaunchedEffect(key1 = "DetailScreen$bookId") {
launch {
detailViewModel.error.collect {
val result = snackBarHost.showSnackbar(
message = context.getString(it.message),
actionLabel = context.getString(it.action),
)
if (result == SnackbarResult.ActionPerformed) {
when (it) {
is BookDetailUioErrorUio.GetDetailInput -> detailViewModel.getDetail(
bookId = it.bookId
)
is BookDetailUioErrorUio.SendBookInput -> detailViewModel.send(
bookId = it.bookId,
email = it.mail,
format = it.format,
)
}
}
}
}
}
LaunchedEffect(key1 = "DetailScreenSuccess:$bookId") {
detailViewModel.sendStatus.collect {
snackBarHost.showSnackbar(
message = context.getString(R.string.detail_send_success)
)
launch {
bookId?.let { detailViewModel.getDetail(it) }
}
launch {
emailSheetState.hide()
}
}
LaunchedEffect(key1 = bookId) {
bookId?.let { detailViewModel.getDetail(it) }
emailSheetState.hide()
}
BackHandler(emailSheetState.isVisible || emailSheetState.isAnimationRunning) {

View file

@ -25,7 +25,9 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.text.HtmlCompat
import androidx.core.text.toSpannable
import com.google.accompanist.placeholder.PlaceholderHighlight
import com.google.accompanist.placeholder.material.placeholder
import com.google.accompanist.placeholder.shimmer
import com.pixelized.biblib.R
import com.pixelized.biblib.ui.composable.SpannedText
import com.pixelized.biblib.ui.composable.animation.AnimatedDelayer
@ -204,11 +206,18 @@ fun DetailScreenContent(
SpannedText(
modifier = Modifier
.fillMaxWidth()
.placeholder(visible = book.placeHolder),
.placeholder(
visible = book.placeHolder,
shape = MaterialTheme.bibLib.shapes.base.medium,
color = MaterialTheme.bibLib.colors.placeHolder,
highlight = PlaceholderHighlight.shimmer(
highlightColor = MaterialTheme.bibLib.colors.shimmer,
),
),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
text = HtmlCompat.fromHtml(
book.description.ifEmpty { "placeholder" },
book.description,
HtmlCompat.FROM_HTML_MODE_COMPACT,
).toSpannable(),
)

View file

@ -0,0 +1,120 @@
package com.pixelized.biblib.ui.screen.home.detail
import android.content.res.Configuration
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.pixelized.biblib.R
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.ui.theme.color.BibLibColorPalette
import com.pixelized.biblib.ui.theme.color.ShadowPalette
import com.pixelized.biblib.utils.extention.bibLib
import kotlinx.coroutines.flow.Flow
@Composable
fun DetailScreenSendLoader(
loadingVisibility: Flow<Boolean>,
successVisibility: Flow<Boolean>,
) {
val isLoadingVisible by successVisibility.collectAsState(initial = false)
val isSuccessVisible by loadingVisibility.collectAsState(initial = false)
DetailScreenSendLoaderContent(
modifier = Modifier.fillMaxSize(),
isLoadingVisible = isLoadingVisible,
isSuccessVisible = isSuccessVisible,
)
}
@Composable
fun DetailScreenSendLoaderContent(
modifier: Modifier = Modifier,
isLoadingVisible: Boolean,
isSuccessVisible: Boolean,
) {
Box(modifier = modifier) {
AnimatedVisibility(
visible = isLoadingVisible,
enter = fadeIn(),
exit = fadeOut(),
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(color = ShadowPalette.scrim)
.padding(all = MaterialTheme.bibLib.dimen.dp16),
)
}
AnimatedVisibility(
modifier = Modifier.align(Alignment.Center),
visible = isLoadingVisible,
enter = fadeIn() + slideInVertically { it },
exit = fadeOut() + slideOutVertically { it },
) {
CircularProgressIndicator(
modifier = Modifier.size(MaterialTheme.bibLib.dimen.dialog.iconSize),
)
}
AnimatedVisibility(
modifier = Modifier.align(alignment = Alignment.BottomCenter),
visible = isSuccessVisible,
enter = fadeIn() + slideInVertically { it },
exit = fadeOut(),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.navigationBarsPadding()
.padding(all = MaterialTheme.bibLib.dimen.dp16)
.background(
color = BibLibColorPalette.LightGreen,
shape = MaterialTheme.shapes.small,
)
.padding(
vertical = MaterialTheme.bibLib.dimen.dp16,
horizontal = MaterialTheme.bibLib.dimen.dp24,
),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.dp16)
) {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_auto_stories_24),
tint = Color.White,
contentDescription = null
)
Text(
style = MaterialTheme.typography.body1,
color = Color.White,
text = stringResource(id = R.string.detail_send_success)
)
}
}
}
}
@Composable
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
fun DetailScreenSendLoaderPreview() {
BibLibTheme {
DetailScreenSendLoaderContent(
isLoadingVisible = true,
isSuccessVisible = true,
)
}
}

View file

@ -14,6 +14,7 @@ data class BibLibColor(
val base: Colors,
val typography: Typography,
val placeHolder: Color,
val shimmer: Color,
val dialogBackground: Color,
) {
@ -41,11 +42,13 @@ fun bibLibDarkColors(
strong = base.primary,
),
placeHolder: Color = BibLibColorPalette.DarkGrey,
shimmer: Color = BibLibColorPalette.Grey,
dialogBackground: Color = BibLibColorPalette.VeryDarkGrey,
) = BibLibColor(
base = base,
typography = typography,
placeHolder = placeHolder,
shimmer = shimmer,
dialogBackground = dialogBackground,
)
@ -64,10 +67,12 @@ fun bibLibLightColors(
strong = base.primary,
),
placeHolder: Color = BibLibColorPalette.LightGrey,
shimmer: Color = BibLibColorPalette.Grey,
dialogBackground: Color = Color.White,
) = BibLibColor(
base = base,
typography = typography,
placeHolder = placeHolder,
shimmer = shimmer,
dialogBackground = dialogBackground,
)

View file

@ -1,15 +0,0 @@
package com.pixelized.biblib.utils
import android.content.Context
import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import com.google.accompanist.drawablepainter.DrawablePainter
fun painterResource(context: Context, @DrawableRes res: Int): Painter {
val drawable = AppCompatResources.getDrawable(context, res)
return DrawablePainter(drawable = drawable ?: error(""))
}