Add progress and success to send process.
This commit is contained in:
parent
b3f9da7dc7
commit
896c917348
6 changed files with 196 additions and 71 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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(""))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue