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
|
package com.pixelized.biblib.ui.screen.home.detail
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
@ -13,6 +12,7 @@ import com.pixelized.biblib.utils.extention.toDetailUio
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -32,31 +32,43 @@ class BookDetailViewModel @Inject constructor(
|
||||||
private val _sendStatus = MutableSharedFlow<Boolean>()
|
private val _sendStatus = MutableSharedFlow<Boolean>()
|
||||||
val sendStatus: Flow<Boolean> get() = _sendStatus
|
val sendStatus: Flow<Boolean> get() = _sendStatus
|
||||||
|
|
||||||
|
private val _sendingFlow = MutableSharedFlow<Boolean>()
|
||||||
|
val sendingFlow: Flow<Boolean> get() = _sendingFlow
|
||||||
|
|
||||||
private val _error = MutableSharedFlow<BookDetailUioErrorUio>()
|
private val _error = MutableSharedFlow<BookDetailUioErrorUio>()
|
||||||
val error: Flow<BookDetailUioErrorUio> get() = _error
|
val error: Flow<BookDetailUioErrorUio> get() = _error
|
||||||
|
|
||||||
fun getDetail(id: Int) {
|
fun getDetail(bookId: Int) {
|
||||||
detailJob?.cancel()
|
detailJob?.cancel()
|
||||||
detailJob = viewModelScope.launch(Dispatchers.IO) {
|
detailJob = viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
_detail.value = getCacheBookDetail(id = id)
|
_detail.value = getCacheBookDetail(id = bookId)
|
||||||
_detail.value = getBookDetail(id = id)
|
_detail.value = getBookDetail(id = bookId)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
_error.emit(toDetailErrorUio(bookId = id))
|
_error.emit(toDetailErrorUio(bookId = bookId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LiftReturnOrAssignment")
|
||||||
fun send(bookId: Int, email: String, format: String) {
|
fun send(bookId: Int, email: String, format: String) {
|
||||||
sendJob?.cancel()
|
sendJob?.cancel()
|
||||||
sendJob = viewModelScope.launch(Dispatchers.IO) {
|
sendJob = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
_sendingFlow.emit(true)
|
||||||
|
var isSuccess = false
|
||||||
try {
|
try {
|
||||||
val data = client.service.send(bookId = bookId, mail = email, format = format)
|
val data = client.service.send(bookId = bookId, mail = email, format = format)
|
||||||
_sendStatus.emit(true)
|
isSuccess = data.containsKey("ok")
|
||||||
Log.d("send", data.toString())
|
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Log.d("send", exception.message, exception)
|
_error.emit(toSendBookUio(bookId = bookId, mail = email, format = format))
|
||||||
_error.emit(toSendBookUio(bookId = bookId, mail = email, 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 androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.pixelized.biblib.R
|
import com.pixelized.biblib.R
|
||||||
import com.pixelized.biblib.ui.LocalSnackHostState
|
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.scaffold.LocalDetailBottomSheetState
|
||||||
import com.pixelized.biblib.ui.screen.home.page.profile.ProfileViewModel
|
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.ui.theme.color.ShadowPalette
|
||||||
import com.pixelized.biblib.utils.extention.bibLib
|
import com.pixelized.biblib.utils.extention.bibLib
|
||||||
import com.pixelized.biblib.utils.extention.showToast
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|
@ -92,28 +92,27 @@ fun DetailScreen(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.systemBarsPadding()
|
|
||||||
.padding(all = MaterialTheme.bibLib.dimen.dp16)
|
.padding(all = MaterialTheme.bibLib.dimen.dp16)
|
||||||
.animateContentSize(),
|
.systemBarsPadding(),
|
||||||
book = detail,
|
book = detail,
|
||||||
onSend = {
|
onSend = {
|
||||||
// check
|
scope.launch {
|
||||||
val user = profileViewModel.user
|
val user = profileViewModel.user as? StateUio.Success<UserUio>
|
||||||
if (user.isSuccessful()) {
|
if (user?.value?.amazonEmails?.isEmpty() == true) {
|
||||||
val mails = user.value.amazonEmails
|
snackBarHost.showSnackbar(
|
||||||
when {
|
message = context.getString(R.string.error_no_amazon_email),
|
||||||
mails.isEmpty() -> {
|
duration = SnackbarDuration.Long,
|
||||||
context.showToast(context.getString(R.string.error_no_amazon_email))
|
)
|
||||||
}
|
} else {
|
||||||
else -> {
|
emailSheetState.show()
|
||||||
scope.launch { emailSheetState.show() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// TODO()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
DetailScreenSendLoader(
|
||||||
|
loadingVisibility = detailViewModel.sendStatus,
|
||||||
|
successVisibility = detailViewModel.sendingFlow,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
EmptyDetail()
|
EmptyDetail()
|
||||||
}
|
}
|
||||||
|
|
@ -123,38 +122,33 @@ fun DetailScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (detailState.bottomSheetState.isVisible || detailState.bottomSheetState.isAnimationRunning) {
|
if (detailState.bottomSheetState.isVisible || detailState.bottomSheetState.isAnimationRunning) {
|
||||||
LaunchedEffect(key1 = "DetailScreenError$bookId") {
|
LaunchedEffect(key1 = "DetailScreen$bookId") {
|
||||||
detailViewModel.error.collect {
|
launch {
|
||||||
val result = snackBarHost.showSnackbar(
|
detailViewModel.error.collect {
|
||||||
message = context.getString(it.message),
|
val result = snackBarHost.showSnackbar(
|
||||||
actionLabel = context.getString(it.action),
|
message = context.getString(it.message),
|
||||||
)
|
actionLabel = context.getString(it.action),
|
||||||
if (result == SnackbarResult.ActionPerformed) {
|
)
|
||||||
when (it) {
|
if (result == SnackbarResult.ActionPerformed) {
|
||||||
is BookDetailUioErrorUio.GetDetailInput -> detailViewModel.getDetail(
|
when (it) {
|
||||||
id = it.bookId
|
is BookDetailUioErrorUio.GetDetailInput -> detailViewModel.getDetail(
|
||||||
)
|
bookId = it.bookId
|
||||||
is BookDetailUioErrorUio.SendBookInput -> detailViewModel.send(
|
)
|
||||||
bookId = it.bookId,
|
is BookDetailUioErrorUio.SendBookInput -> detailViewModel.send(
|
||||||
email = it.mail,
|
bookId = it.bookId,
|
||||||
format = it.format,
|
email = it.mail,
|
||||||
)
|
format = it.format,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
launch {
|
||||||
|
bookId?.let { detailViewModel.getDetail(it) }
|
||||||
LaunchedEffect(key1 = "DetailScreenSuccess:$bookId") {
|
}
|
||||||
detailViewModel.sendStatus.collect {
|
launch {
|
||||||
snackBarHost.showSnackbar(
|
emailSheetState.hide()
|
||||||
message = context.getString(R.string.detail_send_success)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = bookId) {
|
|
||||||
bookId?.let { detailViewModel.getDetail(it) }
|
|
||||||
emailSheetState.hide()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler(emailSheetState.isVisible || emailSheetState.isAnimationRunning) {
|
BackHandler(emailSheetState.isVisible || emailSheetState.isAnimationRunning) {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,9 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.text.HtmlCompat
|
import androidx.core.text.HtmlCompat
|
||||||
import androidx.core.text.toSpannable
|
import androidx.core.text.toSpannable
|
||||||
|
import com.google.accompanist.placeholder.PlaceholderHighlight
|
||||||
import com.google.accompanist.placeholder.material.placeholder
|
import com.google.accompanist.placeholder.material.placeholder
|
||||||
|
import com.google.accompanist.placeholder.shimmer
|
||||||
import com.pixelized.biblib.R
|
import com.pixelized.biblib.R
|
||||||
import com.pixelized.biblib.ui.composable.SpannedText
|
import com.pixelized.biblib.ui.composable.SpannedText
|
||||||
import com.pixelized.biblib.ui.composable.animation.AnimatedDelayer
|
import com.pixelized.biblib.ui.composable.animation.AnimatedDelayer
|
||||||
|
|
@ -204,11 +206,18 @@ fun DetailScreenContent(
|
||||||
SpannedText(
|
SpannedText(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.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,
|
style = MaterialTheme.typography.body1,
|
||||||
color = MaterialTheme.colors.onSurface,
|
color = MaterialTheme.colors.onSurface,
|
||||||
text = HtmlCompat.fromHtml(
|
text = HtmlCompat.fromHtml(
|
||||||
book.description.ifEmpty { "placeholder" },
|
book.description,
|
||||||
HtmlCompat.FROM_HTML_MODE_COMPACT,
|
HtmlCompat.FROM_HTML_MODE_COMPACT,
|
||||||
).toSpannable(),
|
).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 base: Colors,
|
||||||
val typography: Typography,
|
val typography: Typography,
|
||||||
val placeHolder: Color,
|
val placeHolder: Color,
|
||||||
|
val shimmer: Color,
|
||||||
val dialogBackground: Color,
|
val dialogBackground: Color,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
@ -41,11 +42,13 @@ fun bibLibDarkColors(
|
||||||
strong = base.primary,
|
strong = base.primary,
|
||||||
),
|
),
|
||||||
placeHolder: Color = BibLibColorPalette.DarkGrey,
|
placeHolder: Color = BibLibColorPalette.DarkGrey,
|
||||||
|
shimmer: Color = BibLibColorPalette.Grey,
|
||||||
dialogBackground: Color = BibLibColorPalette.VeryDarkGrey,
|
dialogBackground: Color = BibLibColorPalette.VeryDarkGrey,
|
||||||
) = BibLibColor(
|
) = BibLibColor(
|
||||||
base = base,
|
base = base,
|
||||||
typography = typography,
|
typography = typography,
|
||||||
placeHolder = placeHolder,
|
placeHolder = placeHolder,
|
||||||
|
shimmer = shimmer,
|
||||||
dialogBackground = dialogBackground,
|
dialogBackground = dialogBackground,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -64,10 +67,12 @@ fun bibLibLightColors(
|
||||||
strong = base.primary,
|
strong = base.primary,
|
||||||
),
|
),
|
||||||
placeHolder: Color = BibLibColorPalette.LightGrey,
|
placeHolder: Color = BibLibColorPalette.LightGrey,
|
||||||
|
shimmer: Color = BibLibColorPalette.Grey,
|
||||||
dialogBackground: Color = Color.White,
|
dialogBackground: Color = Color.White,
|
||||||
) = BibLibColor(
|
) = BibLibColor(
|
||||||
base = base,
|
base = base,
|
||||||
typography = typography,
|
typography = typography,
|
||||||
placeHolder = placeHolder,
|
placeHolder = placeHolder,
|
||||||
|
shimmer = shimmer,
|
||||||
dialogBackground = dialogBackground,
|
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