Add a detail confirm dialog on send action.

This commit is contained in:
Thomas Andres Gomez 2022-07-26 14:34:02 +02:00
parent f14c2258f6
commit baa3055c0d
6 changed files with 363 additions and 113 deletions

View file

@ -39,7 +39,7 @@ fun DetailBottomSheet(
sheetState = bottomDetailState.bottomSheetState,
sheetContent = {
DetailScreen(
viewModel = bottomDetailState.viewModel,
detailViewModel = bottomDetailState.viewModel,
detail = bottomDetailState.bookDetail,
)
},

View file

@ -28,8 +28,9 @@ class BookDetailViewModel @Inject constructor(
}
}
suspend fun send(bookId: Int, mail: String) {
client.service.send(bookId = bookId, mail = mail)
suspend fun send(bookId: Int, email: String) {
val data = client.service.send(bookId = bookId, mail = email)
Log.d("send", data.toString())
}
private suspend fun getBookDetail(id: Int): BookDetailUio {

View file

@ -0,0 +1,42 @@
package com.pixelized.biblib.ui.screen.home.detail
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class ConfirmDialogViewModel @Inject constructor() : ViewModel() {
val shouldDisplayConfirmDialog by derivedStateOf { dialog != null }
var dialog by mutableStateOf<ConfirmDialogUio?>(null)
private set
fun show(
email: String,
title: String? = null,
description: String? = null,
help: String? = null,
options: List<ConfirmDialogUio.Option>,
) = show(
ConfirmDialogUio(
email = email,
title = title,
description = description,
help = help,
options = options,
)
)
fun show(dialog: ConfirmDialogUio?) {
this.dialog = dialog
}
fun hide() {
dialog = null
}
}

View file

@ -1,20 +1,21 @@
package com.pixelized.biblib.ui.screen.home.detail
import android.content.Intent
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.net.Uri
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
import androidx.compose.foundation.rememberScrollState
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.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
@ -22,7 +23,6 @@ import androidx.compose.ui.platform.LocalContext
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
@ -30,14 +30,13 @@ import androidx.core.text.toSpannable
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.biblib.R
import com.pixelized.biblib.ui.composable.SpannedText
import com.pixelized.biblib.ui.composable.StateUio
import com.pixelized.biblib.ui.composable.animation.AnimatedDelayer
import com.pixelized.biblib.ui.composable.animation.AnimatedOffset
import com.pixelized.biblib.ui.composable.isSuccessful
import com.pixelized.biblib.ui.scaffold.DetailBottomSheetState
import com.pixelized.biblib.ui.scaffold.LocalDetailBottomSheetState
import com.pixelized.biblib.ui.screen.home.detail.ConfirmDialogUio.Companion.option
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.BibLibTheme
import com.pixelized.biblib.ui.theme.color.ShadowPalette
import com.pixelized.biblib.utils.extention.bibLib
@ -62,23 +61,27 @@ data class BookDetailUio(
val cover: String,
) : Serializable
private const val CONFIRM_OPTION_YES = 0
private const val CONFIRM_OPTION_NO = 1
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun DetailScreen(
detailState: DetailBottomSheetState = LocalDetailBottomSheetState.current,
confirmViewModel: ConfirmDialogViewModel = hiltViewModel(),
profileViewModel: ProfileViewModel = hiltViewModel(),
viewModel: BookDetailViewModel = hiltViewModel(),
detailViewModel: BookDetailViewModel = hiltViewModel(),
detail: BookDetailUio? = null,
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val sheet = rememberModalBottomSheetState(
val emailSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
skipHalfExpanded = true,
)
ModalBottomSheetLayout(
sheetState = sheet,
sheetState = emailSheetState,
scrimColor = ShadowPalette.scrim,
sheetContent = {
Box(
@ -89,59 +92,91 @@ fun DetailScreen(
) {
val user = profileViewModel.user
if (user.isSuccessful()) {
DetailScreenSendContent(
DetailScreenEmailList(
modifier = Modifier
.navigationBarsPadding()
.padding(bottom = MaterialTheme.bibLib.dimen.dp16),
emails = user.value.amazonEmails,
onEmail = { mail ->
detail?.id?.let { bookId ->
scope.launch {
viewModel.send(bookId = bookId, mail = mail)
}
}
confirmViewModel.show(email = mail)
}
)
}
}
},
content = {
if (detailState.bottomSheetState.isVisible) {
if (detail != null) {
DetailScreenContent(
modifier = Modifier.fillMaxSize(),
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),)
}
mails.size == 1 -> {
scope.launch {
viewModel.send(bookId = detail.id, mail = mails.first())
}
}
else -> {
scope.launch { sheet.show() }
}
if (detailState.bottomSheetState.isVisible && detail != null) {
DetailScreenContent(
modifier = Modifier.fillMaxSize(),
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))
}
mails.size == 1 -> {
confirmViewModel.show(email = mails.first())
}
else -> {
scope.launch { emailSheetState.show() }
}
} else {
// TODO()
}
} else {
// TODO()
}
)
} else {
Box(modifier = Modifier.fillMaxSize())
}
}
)
} else {
LaunchedEffect(key1 = "email bottom sheet close") { sheet.hide() }
Box(modifier = Modifier.fillMaxSize())
}
},
)
ConfirmDialogHandler(
detail = detail,
confirmViewModel = confirmViewModel,
onConfirm = { bookId, email ->
scope.launch {
confirmViewModel.hide()
emailSheetState.hide()
detailViewModel.send(bookId = bookId, email = email)
}
},
onDismiss = {
confirmViewModel.hide()
}
)
}
@Composable
fun ConfirmDialogHandler(
detail: BookDetailUio? = null,
confirmViewModel: ConfirmDialogViewModel,
onConfirm: (bookId: Int, mail: String) -> Unit = { _, _ -> },
onDismiss: () -> Unit,
) {
val context = LocalContext.current
confirmViewModel.dialog?.let { dialog ->
ConfirmDialog(
uio = dialog,
onDismissRequest = onDismiss,
onOption = { option ->
when (option.id) {
CONFIRM_OPTION_YES -> detail?.id?.let { onConfirm(it, dialog.email) }
else -> onDismiss()
}
},
onHelp = {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(dialog.help))
context.startActivity(intent)
},
)
}
}
@Composable
@ -322,57 +357,17 @@ private fun TitleLabel(
}
}
@Composable
private fun DetailScreenSendContent(
modifier: Modifier = Modifier,
emails: List<String>,
onEmail: (email: String) -> Unit = default<String>(),
) {
Column(
modifier = modifier.fillMaxWidth(),
) {
Text(
modifier = Modifier
.padding(top = MaterialTheme.bibLib.dimen.dp8)
.padding(horizontal = MaterialTheme.bibLib.dimen.dp16),
color = MaterialTheme.colors.primary,
style = MaterialTheme.typography.caption,
text = "Send this eBook to:",
)
private fun ConfirmDialogViewModel.show(email: String) = show(
email = email,
title = "Envoyer sur votre Kindle",
description = "Assurez-vous que votre Kindle dispose d'une connexion Internet et que l'adresse e-mail suivante est correctement configurée.",
help = "https://www.amazon.fr/gp/help/customer/display.html?nodeId=G7NECT4B4ZWHQ8WV",
options = listOf(
option(id = CONFIRM_OPTION_YES, label = "Oui"),
option(id = CONFIRM_OPTION_NO, label = "Non")
)
)
LazyColumn {
items(items = emails) { email ->
Row(
modifier = Modifier
.clickable(onClick = { onEmail(email) })
.height(height = MaterialTheme.bibLib.dimen.dp52)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.padding(
start = MaterialTheme.bibLib.dimen.dp16,
end = MaterialTheme.bibLib.dimen.dp8,
)
.weight(1f),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = email
)
Icon(
modifier = Modifier.padding(end = MaterialTheme.bibLib.dimen.dp16),
imageVector = Icons.Default.NavigateNext,
contentDescription = null
)
}
}
}
}
}
@Composable
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
@ -392,18 +387,4 @@ private fun DetailScreenContentPreview() {
BibLibTheme {
DetailScreenContent(book = book)
}
}
@Composable
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES)
private fun DetailScreenSendPreview() {
BibLibTheme {
DetailScreenSendContent(
emails = listOf(
"R.Giskard.Reventlov.Kindle@gmail.com",
"R.Daneel.Olivaw.Kindle@gmail.com",
),
)
}
}

View file

@ -0,0 +1,140 @@
package com.pixelized.biblib.ui.screen.home.detail
import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.window.Dialog
import com.pixelized.biblib.ui.screen.home.detail.ConfirmDialogUio.Companion.option
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.utils.extention.default
@Stable
@Immutable
data class ConfirmDialogUio(
val email: String,
val title: String? = null,
val description: String? = null,
val help: String? = null,
val options: List<Option>,
) {
@Stable
@Immutable
data class Option(
val id: Int,
val label: String,
)
companion object {
fun option(
id: Int,
label: String,
) = Option(
id = id,
label = label
)
}
}
@Composable
fun ConfirmDialog(
modifier: Modifier = Modifier,
uio: ConfirmDialogUio,
onDismissRequest: () -> Unit = default(),
onOption: (ConfirmDialogUio.Option) -> Unit = default<ConfirmDialogUio.Option>(),
onHelp: () -> Unit = default(),
) {
Dialog(
onDismissRequest = onDismissRequest,
) {
Card(modifier = modifier) {
Column(modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.dp16)) {
if (uio.title != null) {
Text(
modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp16),
style = MaterialTheme.typography.h6,
color = MaterialTheme.bibLib.colors.typography.medium,
text = uio.title
)
}
if (uio.description != null) {
Text(
modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp16),
style = MaterialTheme.typography.body1,
color = MaterialTheme.bibLib.colors.typography.medium,
text = uio.description
)
}
Text(
modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp16),
style = MaterialTheme.typography.caption,
color = MaterialTheme.bibLib.colors.typography.easy,
text = uio.email
)
if (uio.help != null) {
Text(
modifier = Modifier
.clickable(onClick = onHelp)
.padding(bottom = MaterialTheme.bibLib.dimen.dp16),
style = MaterialTheme.typography.caption,
color = MaterialTheme.bibLib.colors.typography.strong,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = uio.help
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
uio.options.reversed().forEachIndexed { index, option ->
Button(
modifier = when (index) {
uio.options.lastIndex -> Modifier
else -> Modifier.padding(end = MaterialTheme.bibLib.dimen.dp8)
},
colors = when (index) {
uio.options.lastIndex -> ButtonDefaults.buttonColors()
else -> ButtonDefaults.outlinedButtonColors()
},
onClick = { onOption(option) }
) {
Text(text = option.label)
}
}
}
}
}
}
}
@Composable
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun DetailConfirmPreview() {
BibLibTheme {
ConfirmDialog(
uio = ConfirmDialogUio(
email = "R.Daneel.Olivaw.Kindle@gmail.com",
title = "Envoyer sur votre Kindle",
description = "Assurez-vous que votre Kindle dispose d'une connexion Internet et que l'adresse e-mail suivante est correctement configurée.",
help = "https://www.amazon.fr/gp/help/customer/display.html?nodeId=G7NECT4B4ZWHQ8WV",
options = listOf(
option(id = 0, "Oui"),
option(id = 1, "Non")
)
)
)
}
}

View file

@ -0,0 +1,86 @@
package com.pixelized.biblib.ui.screen.home.detail
import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.NavigateNext
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.utils.extention.default
@Composable
fun DetailScreenEmailList(
modifier: Modifier = Modifier,
emails: List<String>,
onEmail: (email: String) -> Unit = default<String>(),
) {
Column(
modifier = modifier.fillMaxWidth(),
) {
Text(
modifier = Modifier
.padding(top = MaterialTheme.bibLib.dimen.dp8)
.padding(horizontal = MaterialTheme.bibLib.dimen.dp16),
color = MaterialTheme.colors.primary,
style = MaterialTheme.typography.caption,
text = "Send this eBook to:",
)
LazyColumn {
items(items = emails) { email ->
Row(
modifier = Modifier
.clickable(onClick = { onEmail(email) })
.height(height = MaterialTheme.bibLib.dimen.dp52)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.padding(
start = MaterialTheme.bibLib.dimen.dp16,
end = MaterialTheme.bibLib.dimen.dp8,
)
.weight(1f),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = email
)
Icon(
modifier = Modifier.padding(end = MaterialTheme.bibLib.dimen.dp16),
imageVector = Icons.Default.NavigateNext,
contentDescription = null
)
}
}
}
}
}
@Composable
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun DetailScreenEmailListPreview() {
BibLibTheme {
DetailScreenEmailList(
emails = listOf(
"R.Giskard.Reventlov.Kindle@gmail.com",
"R.Daneel.Olivaw.Kindle@gmail.com",
),
)
}
}