Add format selection on book send.

This commit is contained in:
Thomas Andres Gomez 2022-10-21 21:53:03 +02:00
parent 1401c7159c
commit 3e234cc37d
9 changed files with 286 additions and 140 deletions

View file

@ -25,7 +25,7 @@ interface IBibLibWebServiceAPI {
suspend fun detail(@Path("id") bookId: Int): BookDetailResponse suspend fun detail(@Path("id") bookId: Int): BookDetailResponse
@GET("book/{id}/send/kindle") @GET("book/{id}/send/kindle")
suspend fun send(@Path("id") bookId: Int, @Query("mail") mail: String): LinkedTreeMap<String, Any> suspend fun send(@Path("id") bookId: Int, @Query("mail") mail: String, @Query("format") format: String): LinkedTreeMap<String, Any>
@GET("series") @GET("series")
suspend fun series(): SeriesListResponse suspend fun series(): SeriesListResponse

View file

@ -46,16 +46,16 @@ class BookDetailViewModel @Inject constructor(
} }
} }
suspend fun send(bookId: Int, email: String) { suspend fun send(bookId: Int, email: String, format: String) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val data = client.service.send(bookId = bookId, mail = email) val data = client.service.send(bookId = bookId, mail = email, format = format)
_sendStatus.emit(true) _sendStatus.emit(true)
Log.d("send", data.toString()) Log.d("send", data.toString())
} catch (exception: Exception) { } catch (exception: Exception) {
Log.d("send", exception.message, exception) Log.d("send", exception.message, exception)
_sendStatus.emit(false) _sendStatus.emit(false)
_error.emit(toSendBookUio(bookId = bookId, mail = email)) _error.emit(toSendBookUio(bookId = bookId, mail = email, format))
} }
} }
} }
@ -77,10 +77,15 @@ class BookDetailViewModel @Inject constructor(
bookId = bookId, bookId = bookId,
) )
private fun toSendBookUio(bookId: Int, mail: String) = BookDetailUioErrorUio.SendBookInput( private fun toSendBookUio(
bookId: Int,
mail: String,
format: String
) = BookDetailUioErrorUio.SendBookInput(
message = stringResource(R.string.error_send_book_message), message = stringResource(R.string.error_send_book_message),
action = stringResource(R.string.error_send_book_action), action = stringResource(R.string.error_send_book_action),
bookId = bookId, bookId = bookId,
mail = mail, mail = mail,
format = format,
) )
} }

View file

@ -0,0 +1,54 @@
package com.pixelized.biblib.ui.screen.home.detail
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.network.factory.UserFactory
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class BookOptionViewModel @Inject constructor(
private val client: IBibLibClient,
) : ViewModel() {
var emails by mutableStateOf(listOf<OptionUio>())
private set
var formats by mutableStateOf(listOf(OptionUio(Format.EPUB, true), OptionUio(Format.MOBI)))
private set
init {
viewModelScope.launch(Dispatchers.IO) {
try {
val factory = UserFactory()
val response = client.service.user()
val data = factory.fromUserResponseToUser(response)
emails = data.amazonEmails.mapIndexed { index, mail ->
OptionUio(value = mail, selected = index == 0)
}
} catch (exception: Exception) {
Log.e("AccountViewModel", exception.message, exception)
}
}
}
fun selectMail(mail: String) {
emails = emails.map {
it.copy(selected = it.value == mail)
}
}
fun selectFormat(@Format format: String) {
formats = formats.map {
it.copy(selected = it.value == format)
}
}
}

View file

@ -16,10 +16,11 @@ class ConfirmDialogViewModel @Inject constructor(
var dialog by mutableStateOf<ConfirmDialogUio?>(null) var dialog by mutableStateOf<ConfirmDialogUio?>(null)
private set private set
fun show(bookId: Int, email: String) { fun show(bookId: Int, email: String, @Format format: String) {
this.dialog = ConfirmDialogUio( this.dialog = ConfirmDialogUio(
bookId = bookId, bookId = bookId,
email = email, email = email,
format = format,
) )
} }

View file

@ -1,5 +1,6 @@
package com.pixelized.biblib.ui.screen.home.detail package com.pixelized.biblib.ui.screen.home.detail
import androidx.activity.compose.BackHandler
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@ -18,6 +19,7 @@ import com.pixelized.biblib.ui.screen.home.page.profile.ProfileViewModel
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 com.pixelized.biblib.utils.extention.showToast
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.Serializable import java.io.Serializable
@ -70,6 +72,7 @@ sealed class BookDetailUioErrorUio(
action: String?, action: String?,
val bookId: Int, val bookId: Int,
val mail: String, val mail: String,
val format: String,
) : BookDetailUioErrorUio(message, action) ) : BookDetailUioErrorUio(message, action)
} }
@ -100,13 +103,12 @@ fun DetailScreen(
sheetState = emailSheetState, sheetState = emailSheetState,
scrimColor = ShadowPalette.scrim, scrimColor = ShadowPalette.scrim,
sheetContent = { sheetContent = {
DetailScreenEmailList( DetailScreenSendOption(
modifier = Modifier modifier = Modifier
.navigationBarsPadding() .navigationBarsPadding()
.padding(bottom = MaterialTheme.bibLib.dimen.dp16), .padding(bottom = MaterialTheme.bibLib.dimen.dp16),
profileViewModel = profileViewModel, onSend = { mail, format ->
onEmail = { mail -> confirmViewModel.show(bookId = detail.id, email = mail, format = format)
confirmViewModel.show(bookId = detail.id, email = mail)
} }
) )
}, },
@ -132,7 +134,8 @@ fun DetailScreen(
mails.size == 1 -> { mails.size == 1 -> {
confirmViewModel.show( confirmViewModel.show(
bookId = detail.id, bookId = detail.id,
email = mails.first() email = mails.first(),
format = Format.EPUB,
) )
} }
else -> { else -> {
@ -153,11 +156,11 @@ fun DetailScreen(
ConfirmDialog( ConfirmDialog(
modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.dp32), modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.dp32),
confirmViewModel = confirmViewModel, confirmViewModel = confirmViewModel,
onConfirm = { id, email -> onConfirm = { id, email, format ->
scope.launch { scope.launch {
confirmViewModel.hide() confirmViewModel.hide()
emailSheetState.hide() emailSheetState.hide()
detailViewModel.send(bookId = id, email = email) detailViewModel.send(bookId = id, email = email, format = format)
} }
}, },
onDismiss = { confirmViewModel.hide() }, onDismiss = { confirmViewModel.hide() },
@ -176,20 +179,34 @@ fun DetailScreen(
) )
if (result == SnackbarResult.ActionPerformed) { if (result == SnackbarResult.ActionPerformed) {
when (it) { when (it) {
is BookDetailUioErrorUio.GetDetailInput -> { is BookDetailUioErrorUio.GetDetailInput -> detailViewModel.getDetail(
detailViewModel.getDetail(id = it.bookId) id = it.bookId
} )
is BookDetailUioErrorUio.SendBookInput -> { is BookDetailUioErrorUio.SendBookInput -> detailViewModel.send(
detailViewModel.send(bookId = it.bookId, email = it.mail) bookId = it.bookId,
} email = it.mail,
format = it.format,
)
} }
} }
} }
} }
LaunchedEffect(key1 = "") {
detailViewModel.sendStatus.collect {
}
}
LaunchedEffect(key1 = bookId) { LaunchedEffect(key1 = bookId) {
bookId?.let { detailViewModel.getDetail(it) } bookId?.let { detailViewModel.getDetail(it) }
} }
BackHandler(emailSheetState.isVisible || emailSheetState.isAnimationRunning) {
scope.launch {
emailSheetState.hide()
}
}
} }
} }

View file

@ -1,6 +1,8 @@
package com.pixelized.biblib.ui.screen.home.detail package com.pixelized.biblib.ui.screen.home.detail
import android.content.Context.*
import android.content.res.Configuration import android.content.res.Configuration
import androidx.annotation.StringDef
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.* import androidx.compose.material.*
@ -26,17 +28,29 @@ import com.pixelized.biblib.utils.extention.default
import com.pixelized.biblib.utils.extention.highlight import com.pixelized.biblib.utils.extention.highlight
import com.pixelized.biblib.utils.extention.stringRegex 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 @Stable
@Immutable @Immutable
data class ConfirmDialogUio( data class ConfirmDialogUio(
val bookId: Int, val bookId: Int,
val email: String, val email: String,
@Format val format: String,
) { ) {
companion object { companion object {
@Composable @Composable
fun preview() = ConfirmDialogUio( fun preview() = ConfirmDialogUio(
bookId = 90, bookId = 90,
email = "R.Daneel.Olivaw.Kindle@gmail.com", email = "R.Daneel.Olivaw.Kindle@gmail.com",
format = Format.EPUB,
) )
} }
} }
@ -45,7 +59,7 @@ data class ConfirmDialogUio(
fun ConfirmDialog( fun ConfirmDialog(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
confirmViewModel: ConfirmDialogViewModel, confirmViewModel: ConfirmDialogViewModel,
onConfirm: (bookId: Int, mail: String) -> Unit = { _, _ -> }, onConfirm: (bookId: Int, mail: String, format: String) -> Unit = { _, _, _ -> },
onHelp: (url: String) -> Unit = default<String>(), onHelp: (url: String) -> Unit = default<String>(),
onDismiss: () -> Unit onDismiss: () -> Unit
) { ) {
@ -67,7 +81,7 @@ fun ConfirmDialog(
uio: ConfirmDialogUio, uio: ConfirmDialogUio,
onDismissRequest: () -> Unit = default(), onDismissRequest: () -> Unit = default(),
onHelp: (url: String) -> Unit = default<String>(), onHelp: (url: String) -> Unit = default<String>(),
onConfirm: (bookId: Int, mail: String) -> Unit = { _, _ -> }, onConfirm: (bookId: Int, mail: String, format: String) -> Unit = { _, _, _ -> },
) { ) {
Dialog( Dialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
@ -89,7 +103,7 @@ fun ConfirmDialogContent(
uio: ConfirmDialogUio, uio: ConfirmDialogUio,
onDismissRequest: () -> Unit = default(), onDismissRequest: () -> Unit = default(),
onHelp: (url: String) -> Unit = default<String>(), onHelp: (url: String) -> Unit = default<String>(),
onConfirm: (bookId: Int, mail: String) -> Unit = { _, _ -> }, onConfirm: (bookId: Int, mail: String, format: String) -> Unit = { _, _, _ -> },
) { ) {
val amazonHelpUri = stringResource(R.string.detail_send_confirm_help_url) val amazonHelpUri = stringResource(R.string.detail_send_confirm_help_url)
@ -142,15 +156,15 @@ fun ConfirmDialogContent(
onClick = onDismissRequest, onClick = onDismissRequest,
) { ) {
Text( Text(
text = stringResource(R.string.detail_send_confirm_confirm_action) text = stringResource(R.string.detail_send_confirm_cancel_action)
) )
} }
Button( Button(
colors = ButtonDefaults.buttonColors(), colors = ButtonDefaults.buttonColors(),
onClick = { onConfirm(uio.bookId, uio.email) } onClick = { onConfirm(uio.bookId, uio.email, uio.format) }
) { ) {
Text( Text(
text = stringResource(R.string.detail_send_confirm_cancel_action) text = stringResource(R.string.detail_send_confirm_confirm_action)
) )
} }
} }

View file

@ -1,114 +0,0 @@
package com.pixelized.biblib.ui.screen.home.detail
import android.content.res.Configuration
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.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 androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.biblib.ui.composable.isSuccessful
import com.pixelized.biblib.ui.screen.home.page.profile.ProfileViewModel
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,
profileViewModel: ProfileViewModel,
onEmail: (email: String) -> Unit = default<String>(),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 1.dp)
.animateContentSize()
) {
val user = profileViewModel.user
if (user.isSuccessful()) {
DetailScreenEmailList(
modifier = modifier,
emails = user.value.amazonEmails,
onEmail = onEmail,
)
}
}
}
@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",
),
)
}
}

View file

@ -0,0 +1,166 @@
package com.pixelized.biblib.ui.screen.home.detail
import android.content.res.Configuration
import androidx.compose.foundation.clickable
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
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.RadioButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
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.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
@Stable
@Immutable
data class OptionUio(
val value: String,
val selected: Boolean = false,
)
@Composable
fun DetailScreenSendOption(
modifier: Modifier = Modifier,
optionViewModel: BookOptionViewModel = hiltViewModel(),
onSend: (email: String, format: String) -> Unit = { _, _ -> },
) {
DetailScreenSendOption(
modifier = modifier,
emails = optionViewModel.emails,
formats = optionViewModel.formats,
onEmail = { optionViewModel.selectMail(it.value) },
onFormat = { optionViewModel.selectFormat(it.value) },
onSend = onSend,
)
}
@Composable
fun DetailScreenSendOption(
modifier: Modifier = Modifier,
emails: List<OptionUio>,
formats: List<OptionUio>,
onEmail: (OptionUio) -> Unit = { },
onFormat: (OptionUio) -> Unit = { },
onSend: (email: String, format: String) -> Unit = { _, _ -> },
) {
LazyColumn(
modifier = modifier.fillMaxWidth(),
) {
item {
Text(
modifier = Modifier
.padding(vertical = MaterialTheme.bibLib.dimen.dp8)
.padding(horizontal = MaterialTheme.bibLib.dimen.dp16),
color = MaterialTheme.colors.primary,
style = MaterialTheme.typography.caption,
text = stringResource(id = R.string.detail_option_mail),
)
}
items(items = emails) { email ->
OptionItem(
modifier = Modifier
.clickable { onEmail(email) }
.fillMaxWidth()
.padding(all = MaterialTheme.bibLib.dimen.dp16),
uio = email,
)
}
item {
Text(
modifier = Modifier
.padding(vertical = MaterialTheme.bibLib.dimen.dp8)
.padding(horizontal = MaterialTheme.bibLib.dimen.dp16),
color = MaterialTheme.colors.primary,
style = MaterialTheme.typography.caption,
text = stringResource(id = R.string.detail_option_mail),
)
}
items(items = formats) { format ->
OptionItem(
modifier = Modifier
.clickable { onFormat(format) }
.fillMaxWidth()
.padding(all = MaterialTheme.bibLib.dimen.dp16),
uio = format,
)
}
item {
Button(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = MaterialTheme.bibLib.dimen.dp16),
onClick = {
val format = formats.first { it.selected }
val email = emails.first { it.selected }
onSend(email.value, format.value)
},
) {
Text(
text = stringResource(id = R.string.detail_option_action)
)
}
}
}
}
@Composable
private fun OptionItem(
modifier: Modifier = Modifier,
uio: OptionUio,
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
RadioButton(
selected = uio.selected,
onClick = null,
)
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 = uio.value
)
}
}
@Composable
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun DetailScreenEmailListPreview() {
BibLibTheme {
DetailScreenSendOption(
emails = listOf(
OptionUio("R.Giskard.Reventlov.Kindle@gmail.com", selected = true),
OptionUio("R.Daneel.Olivaw.Kindle@gmail.com"),
),
formats = listOf(
OptionUio(Format.EPUB.capitalize(), selected = true),
OptionUio(Format.MOBI.capitalize()),
),
)
}
}

View file

@ -59,7 +59,10 @@
<string name="detail_release">Release</string> <string name="detail_release">Release</string>
<string name="detail_genre">Genre</string> <string name="detail_genre">Genre</string>
<string name="detail_series">Series</string> <string name="detail_series">Series</string>
<string name="detail_emails_title">Send this eBook to:</string>
<string name="detail_option_mail">Send this eBook to:</string>
<string name="detail_option_format">Use this format:</string>
<string name="detail_option_action">Confirm</string>
<string name="detail_send_confirm_title">Send to your Kindle</string> <string name="detail_send_confirm_title">Send to your Kindle</string>
<string name="detail_send_confirm_description">Make sure your Kindle has an internet connection, that %1$s is an approved expeditor and that the following email address is correctly set up on your Amazon Account.</string> <string name="detail_send_confirm_description">Make sure your Kindle has an internet connection, that %1$s is an approved expeditor and that the following email address is correctly set up on your Amazon Account.</string>