From 2a92fd2b8b033c74de5f45c53f508bb27d139e89 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sat, 22 Oct 2022 13:26:42 +0200 Subject: [PATCH] Merge confirm & option dialog / bottomsheet --- .../screen/home/detail/BookDetailViewModel.kt | 26 +-- .../screen/home/detail/BookOptionViewModel.kt | 8 +- .../home/detail/ConfirmDialogViewModel.kt | 30 --- .../ui/screen/home/detail/DetailScreen.kt | 47 ++--- .../screen/home/detail/DetailScreenConfirm.kt | 191 ------------------ .../home/detail/DetailScreenSendOption.kt | 73 ++++++- 6 files changed, 103 insertions(+), 272 deletions(-) delete mode 100644 app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/ConfirmDialogViewModel.kt delete mode 100644 app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenConfirm.kt diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookDetailViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookDetailViewModel.kt index 7854aaa..454deff 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookDetailViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookDetailViewModel.kt @@ -1,19 +1,18 @@ package com.pixelized.biblib.ui.screen.home.detail -import android.app.Application import android.util.Log import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.biblib.R import com.pixelized.biblib.network.client.IBibLibClient import com.pixelized.biblib.network.factory.BookFactory import com.pixelized.biblib.repository.book.BookRepository -import com.pixelized.biblib.utils.extention.stringResource import com.pixelized.biblib.utils.extention.toDetailUio import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch @@ -21,14 +20,15 @@ import javax.inject.Inject @HiltViewModel class BookDetailViewModel @Inject constructor( - application: Application, private val bookRepository: BookRepository, private val client: IBibLibClient, -) : AndroidViewModel(application) { +) : ViewModel() { + private var detailJob: Job? = null private val _detail = mutableStateOf(null) val detail: State get() = _detail + private var sendJob: Job? = null private val _sendStatus = MutableSharedFlow() val sendStatus: Flow get() = _sendStatus @@ -36,7 +36,8 @@ class BookDetailViewModel @Inject constructor( val error: Flow get() = _error fun getDetail(id: Int) { - viewModelScope.launch(Dispatchers.IO) { + detailJob?.cancel() + detailJob = viewModelScope.launch(Dispatchers.IO) { try { _detail.value = getCacheBookDetail(id = id) _detail.value = getBookDetail(id = id) @@ -46,8 +47,9 @@ class BookDetailViewModel @Inject constructor( } } - suspend fun send(bookId: Int, email: String, format: String) { - viewModelScope.launch(Dispatchers.IO) { + fun send(bookId: Int, email: String, format: String) { + sendJob?.cancel() + sendJob = viewModelScope.launch(Dispatchers.IO) { try { val data = client.service.send(bookId = bookId, mail = email, format = format) _sendStatus.emit(true) @@ -72,8 +74,8 @@ class BookDetailViewModel @Inject constructor( } private fun toDetailErrorUio(bookId: Int) = BookDetailUioErrorUio.GetDetailInput( - message = stringResource(R.string.error_get_book_detail_message), - action = stringResource(R.string.error_get_book_detail_action), + message = R.string.error_get_book_detail_message, + action = R.string.error_get_book_detail_action, bookId = bookId, ) @@ -82,8 +84,8 @@ class BookDetailViewModel @Inject constructor( mail: String, format: String ) = BookDetailUioErrorUio.SendBookInput( - message = stringResource(R.string.error_send_book_message), - action = stringResource(R.string.error_send_book_action), + message = R.string.error_send_book_message, + action = R.string.error_send_book_action, bookId = bookId, mail = mail, format = format, diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookOptionViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookOptionViewModel.kt index a7a411d..f7a74fb 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookOptionViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookOptionViewModel.kt @@ -8,6 +8,7 @@ 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 dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -21,7 +22,7 @@ class BookOptionViewModel @Inject constructor( var emails by mutableStateOf(listOf()) private set - var formats by mutableStateOf(listOf(OptionUio(Format.EPUB, true), OptionUio(Format.MOBI))) + var formats by mutableStateOf(initialFormat()) private set init { @@ -51,4 +52,9 @@ class BookOptionViewModel @Inject constructor( it.copy(selected = it.value == format) } } + + private fun initialFormat() = listOf( + OptionUio(Format.EPUB.capitalize(), true), + OptionUio(Format.MOBI.capitalize()), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/ConfirmDialogViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/ConfirmDialogViewModel.kt deleted file mode 100644 index 8130b03..0000000 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/ConfirmDialogViewModel.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.pixelized.biblib.ui.screen.home.detail - -import android.app.Application -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.AndroidViewModel -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class ConfirmDialogViewModel @Inject constructor( - application: Application, -) : AndroidViewModel(application) { - - var dialog by mutableStateOf(null) - private set - - fun show(bookId: Int, email: String, @Format format: String) { - this.dialog = ConfirmDialogUio( - bookId = bookId, - email = email, - format = format, - ) - } - - fun hide() { - dialog = null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreen.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreen.kt index d7e07df..b69d3bc 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreen.kt @@ -1,6 +1,7 @@ package com.pixelized.biblib.ui.screen.home.detail import androidx.activity.compose.BackHandler +import androidx.annotation.StringRes import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -19,7 +20,6 @@ import com.pixelized.biblib.ui.screen.home.page.profile.ProfileViewModel 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.flow.collect import kotlinx.coroutines.launch import java.io.Serializable @@ -54,22 +54,22 @@ data class BookDetailUio( @Stable @Immutable sealed class BookDetailUioErrorUio( - val message: String, - val action: String?, + @StringRes val message: Int, + @StringRes val action: Int, ) { @Stable @Immutable class GetDetailInput( - message: String, - action: String?, + @StringRes message: Int, + @StringRes action: Int, val bookId: Int, ) : BookDetailUioErrorUio(message, action) @Stable @Immutable class SendBookInput( - message: String, - action: String?, + @StringRes message: Int, + @StringRes action: Int, val bookId: Int, val mail: String, val format: String, @@ -80,7 +80,6 @@ sealed class BookDetailUioErrorUio( @Composable fun DetailScreen( detailViewModel: BookDetailViewModel = hiltViewModel(), - confirmViewModel: ConfirmDialogViewModel = hiltViewModel(), profileViewModel: ProfileViewModel = hiltViewModel(), bookId: Int? = null, ) { @@ -107,8 +106,12 @@ fun DetailScreen( modifier = Modifier .navigationBarsPadding() .padding(bottom = MaterialTheme.bibLib.dimen.dp16), + onHelp = { uri -> + uriHandler.openUri(uri) + }, onSend = { mail, format -> - confirmViewModel.show(bookId = detail.id, email = mail, format = format) + scope.launch { emailSheetState.hide() } + detailViewModel.send(bookId = detail.id, email = mail, format = format) } ) }, @@ -131,13 +134,6 @@ fun DetailScreen( mails.isEmpty() -> { context.showToast(context.getString(R.string.error_no_amazon_email)) } - mails.size == 1 -> { - confirmViewModel.show( - bookId = detail.id, - email = mails.first(), - format = Format.EPUB, - ) - } else -> { scope.launch { emailSheetState.show() } } @@ -152,20 +148,6 @@ fun DetailScreen( } }, ) - - ConfirmDialog( - modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.dp32), - confirmViewModel = confirmViewModel, - onConfirm = { id, email, format -> - scope.launch { - confirmViewModel.hide() - emailSheetState.hide() - detailViewModel.send(bookId = id, email = email, format = format) - } - }, - onDismiss = { confirmViewModel.hide() }, - onHelp = { url -> uriHandler.openUri(url) } - ) } } @@ -173,9 +155,8 @@ fun DetailScreen( LaunchedEffect(key1 = "DetailScreenError") { detailViewModel.error.collect { val result = snackBarHost.showSnackbar( - message = it.message, - actionLabel = it.action, - duration = SnackbarDuration.Indefinite, + message = context.getString(it.message), + actionLabel = context.getString(it.action), ) if (result == SnackbarResult.ActionPerformed) { when (it) { diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenConfirm.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenConfirm.kt deleted file mode 100644 index 67655fd..0000000 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenConfirm.kt +++ /dev/null @@ -1,191 +0,0 @@ -package com.pixelized.biblib.ui.screen.home.detail - -import android.content.Context.* -import android.content.res.Configuration -import androidx.annotation.StringDef -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.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -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.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import com.pixelized.biblib.R -import com.pixelized.biblib.ui.theme.BibLibTheme -import com.pixelized.biblib.utils.extention.bibLib -import com.pixelized.biblib.utils.extention.default -import com.pixelized.biblib.utils.extention.highlight -import com.pixelized.biblib.utils.extention.stringRegex - - -@Retention(AnnotationRetention.SOURCE) -@StringDef(Format.EPUB, Format.MOBI) -annotation class Format { - companion object { - const val EPUB = "epub" - const val MOBI = "mobi" - } -} - -@Stable -@Immutable -data class ConfirmDialogUio( - val bookId: Int, - val email: String, - @Format val format: String, -) { - companion object { - @Composable - fun preview() = ConfirmDialogUio( - bookId = 90, - email = "R.Daneel.Olivaw.Kindle@gmail.com", - format = Format.EPUB, - ) - } -} - -@Composable -fun ConfirmDialog( - modifier: Modifier = Modifier, - confirmViewModel: ConfirmDialogViewModel, - onConfirm: (bookId: Int, mail: String, format: String) -> Unit = { _, _, _ -> }, - onHelp: (url: String) -> Unit = default(), - onDismiss: () -> Unit -) { - confirmViewModel.dialog?.let { dialog -> - ConfirmDialog( - modifier = modifier, - uio = dialog, - onDismissRequest = onDismiss, - onHelp = onHelp, - onConfirm = onConfirm, - ) - } -} - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -fun ConfirmDialog( - modifier: Modifier = Modifier, - uio: ConfirmDialogUio, - onDismissRequest: () -> Unit = default(), - onHelp: (url: String) -> Unit = default(), - onConfirm: (bookId: Int, mail: String, format: String) -> Unit = { _, _, _ -> }, -) { - Dialog( - onDismissRequest = onDismissRequest, - properties = remember { DialogProperties(usePlatformDefaultWidth = false) }, - ) { - ConfirmDialogContent( - modifier = modifier, - uio = uio, - onDismissRequest = onDismissRequest, - onHelp = onHelp, - onConfirm = onConfirm, - ) - } -} - -@Composable -fun ConfirmDialogContent( - modifier: Modifier = Modifier, - uio: ConfirmDialogUio, - onDismissRequest: () -> Unit = default(), - onHelp: (url: String) -> Unit = default(), - onConfirm: (bookId: Int, mail: String, format: String) -> Unit = { _, _, _ -> }, -) { - val amazonHelpUri = stringResource(R.string.detail_send_confirm_help_url) - - Card( - modifier = modifier, - backgroundColor = MaterialTheme.bibLib.colors.dialogBackground, - ) { - Column(modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.dp16)) { - Text( - modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp16), - style = MaterialTheme.typography.h6, - color = MaterialTheme.bibLib.colors.typography.medium, - text = stringResource(R.string.detail_send_confirm_title) - ) - - Text( - modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp16), - style = MaterialTheme.typography.body1, - color = MaterialTheme.bibLib.colors.typography.medium, - text = rememberDescription() - ) - - Text( - modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp8), - style = MaterialTheme.typography.caption, - color = MaterialTheme.bibLib.colors.typography.easy, - text = uio.email - ) - - Text( - modifier = Modifier - .clickable(onClick = { onHelp(amazonHelpUri) }) - .padding(vertical = MaterialTheme.bibLib.dimen.dp8), - textAlign = TextAlign.Center, - style = MaterialTheme.typography.caption, - color = MaterialTheme.bibLib.colors.typography.strong, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - text = stringResource(R.string.detail_send_confirm_help), - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = MaterialTheme.bibLib.dimen.dp8), - horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End), - ) { - Button( - colors = ButtonDefaults.outlinedButtonColors(), - onClick = onDismissRequest, - ) { - Text( - text = stringResource(R.string.detail_send_confirm_cancel_action) - ) - } - Button( - colors = ButtonDefaults.buttonColors(), - onClick = { onConfirm(uio.bookId, uio.email, uio.format) } - ) { - Text( - text = stringResource(R.string.detail_send_confirm_confirm_action) - ) - } - } - } - } -} - -@Composable -private fun rememberDescription(): AnnotatedString { - val email = stringResource(id = R.string.martin_sender) - val description = stringResource(R.string.detail_send_confirm_description, email) - return description.highlight(highlight = stringRegex(email)) -} - -@Composable -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun DetailConfirmPreview() { - BibLibTheme { - ConfirmDialogContent( - uio = ConfirmDialogUio.preview(), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenSendOption.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenSendOption.kt index ca58265..5e3fcd1 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenSendOption.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenSendOption.kt @@ -1,10 +1,11 @@ package com.pixelized.biblib.ui.screen.home.detail import android.content.res.Configuration +import androidx.annotation.StringDef import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -18,13 +19,15 @@ import androidx.compose.runtime.Stable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.biblib.R import com.pixelized.biblib.ui.theme.BibLibTheme -import com.pixelized.biblib.utils.extention.bibLib -import com.pixelized.biblib.utils.extention.capitalize +import com.pixelized.biblib.utils.extention.* @Stable @Immutable @@ -33,10 +36,20 @@ data class OptionUio( val selected: Boolean = false, ) +@Retention(AnnotationRetention.SOURCE) +@StringDef(Format.EPUB, Format.MOBI) +annotation class Format { + companion object { + const val EPUB = "epub" + const val MOBI = "mobi" + } +} + @Composable fun DetailScreenSendOption( modifier: Modifier = Modifier, optionViewModel: BookOptionViewModel = hiltViewModel(), + onHelp: (url: String) -> Unit = default(), onSend: (email: String, format: String) -> Unit = { _, _ -> }, ) { DetailScreenSendOption( @@ -45,6 +58,7 @@ fun DetailScreenSendOption( formats = optionViewModel.formats, onEmail = { optionViewModel.selectMail(it.value) }, onFormat = { optionViewModel.selectFormat(it.value) }, + onHelp = onHelp, onSend = onSend, ) } @@ -54,19 +68,56 @@ fun DetailScreenSendOption( modifier: Modifier = Modifier, emails: List, formats: List, + onHelp: (url: String) -> Unit = default(), onEmail: (OptionUio) -> Unit = { }, onFormat: (OptionUio) -> Unit = { }, onSend: (email: String, format: String) -> Unit = { _, _ -> }, ) { + val amazonHelpUri = stringResource(R.string.detail_send_confirm_help_url) + LazyColumn( modifier = modifier.fillMaxWidth(), ) { + item { + Column( + modifier = Modifier + .padding(top = MaterialTheme.bibLib.dimen.dp16) + .padding(horizontal = MaterialTheme.bibLib.dimen.dp16), + ) { + Text( + modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp16), + style = MaterialTheme.typography.h6, + color = MaterialTheme.bibLib.colors.typography.medium, + text = stringResource(R.string.detail_send_confirm_title) + ) + + Text( + modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp8), + style = MaterialTheme.typography.caption, + color = MaterialTheme.bibLib.colors.typography.easy, + text = rememberDescription() + ) + + Text( + modifier = Modifier + .clickable(onClick = { onHelp(amazonHelpUri) }) + .padding(vertical = MaterialTheme.bibLib.dimen.dp8), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.caption, + color = MaterialTheme.bibLib.colors.typography.strong, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = stringResource(R.string.detail_send_confirm_help), + ) + } + } + item { Text( modifier = Modifier .padding(vertical = MaterialTheme.bibLib.dimen.dp8) .padding(horizontal = MaterialTheme.bibLib.dimen.dp16), - color = MaterialTheme.colors.primary, + color = MaterialTheme.bibLib.colors.typography.easy, style = MaterialTheme.typography.caption, text = stringResource(id = R.string.detail_option_mail), ) @@ -85,7 +136,7 @@ fun DetailScreenSendOption( modifier = Modifier .padding(vertical = MaterialTheme.bibLib.dimen.dp8) .padding(horizontal = MaterialTheme.bibLib.dimen.dp16), - color = MaterialTheme.colors.primary, + color = MaterialTheme.bibLib.colors.typography.easy, style = MaterialTheme.typography.caption, text = stringResource(id = R.string.detail_option_mail), ) @@ -147,6 +198,18 @@ private fun OptionItem( } } +@Composable +private fun rememberDescription( + style: TextStyle = MaterialTheme.typography.caption, +): AnnotatedString { + val email = stringResource(id = R.string.martin_sender) + val description = stringResource(R.string.detail_send_confirm_description, email) + return description.highlight( + defaultStyle = style.toSpanStyle(), + highlight = stringRegex(email), + ) +} + @Composable @Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)