diff --git a/app/build.gradle b/app/build.gradle index 21c4173..a94c25d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -123,6 +123,7 @@ dependencies { implementation "com.google.accompanist:accompanist-drawablepainter:0.26.5-rc" implementation "com.google.accompanist:accompanist-insets:0.26.5-rc" implementation "com.google.accompanist:accompanist-pager:0.26.5-rc" + implementation "com.google.accompanist:accompanist-placeholder-material:0.26.5-rc" // Landscapist implementation "com.github.skydoves:landscapist-glide:1.5.2" diff --git a/app/src/main/java/com/pixelized/biblib/database/dao/BookDao.kt b/app/src/main/java/com/pixelized/biblib/database/dao/BookDao.kt index 3ecacdc..03a669e 100644 --- a/app/src/main/java/com/pixelized/biblib/database/dao/BookDao.kt +++ b/app/src/main/java/com/pixelized/biblib/database/dao/BookDao.kt @@ -23,6 +23,10 @@ interface BookDao { @Query("SELECT * FROM ${BookDbo.TABLE} ORDER BY ${BookDbo.SORT}") fun getBook(): DataSource.Factory + @Transaction + @Query("SELECT * FROM ${BookDbo.TABLE} WHERE ${BookDbo.ID} LIKE :id") + fun getBook(id: Int): BookRelation + @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(vararg books: BookDbo) diff --git a/app/src/main/java/com/pixelized/biblib/repository/book/BookRepository.kt b/app/src/main/java/com/pixelized/biblib/repository/book/BookRepository.kt index 9a10ec4..60fb717 100644 --- a/app/src/main/java/com/pixelized/biblib/repository/book/BookRepository.kt +++ b/app/src/main/java/com/pixelized/biblib/repository/book/BookRepository.kt @@ -6,8 +6,8 @@ import com.pixelized.biblib.database.BibLibDatabase import com.pixelized.biblib.database.crossref.BookAuthorCrossRef import com.pixelized.biblib.database.crossref.BookGenreCrossRef import com.pixelized.biblib.database.data.* -import com.pixelized.biblib.model.book.* import com.pixelized.biblib.database.factory.* +import com.pixelized.biblib.model.book.* import javax.inject.Inject class BookRepository @Inject constructor( @@ -17,6 +17,9 @@ class BookRepository @Inject constructor( override fun getAll(): List = database.bookDao().getAll().map { it.toBook() } + override fun getBook(id: Int): Book = + database.bookDao().getBook(id = id).toBook() + override fun getBookCount(): Int = database.bookDao().count() diff --git a/app/src/main/java/com/pixelized/biblib/repository/book/IBookRepository.kt b/app/src/main/java/com/pixelized/biblib/repository/book/IBookRepository.kt index 44bfa4d..6d5d857 100644 --- a/app/src/main/java/com/pixelized/biblib/repository/book/IBookRepository.kt +++ b/app/src/main/java/com/pixelized/biblib/repository/book/IBookRepository.kt @@ -7,6 +7,8 @@ interface IBookRepository { fun getAll(): List + fun getBook(id: Int): Book + fun getBookCount(): Int fun getNewsSource(): DataSource.Factory diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/StateUioHandler.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/StateUioHandler.kt index df64129..acb9fb6 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/StateUioHandler.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/StateUioHandler.kt @@ -19,8 +19,8 @@ import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract sealed class StateUio { - class Progress(val progress: Float? = null) : StateUio() - class Failure(val exception: Exception) : StateUio() + class Progress(val progress: Float? = null, val cache : T? = null) : StateUio() + class Failure(val exception: Exception, val cache: T? = null) : StateUio() class Success(val value: T) : StateUio() } diff --git a/app/src/main/java/com/pixelized/biblib/ui/scaffold/DetailBottomSheet.kt b/app/src/main/java/com/pixelized/biblib/ui/scaffold/DetailBottomSheet.kt index 30bbedd..cc3abee 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/scaffold/DetailBottomSheet.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/scaffold/DetailBottomSheet.kt @@ -11,13 +11,9 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalContext import androidx.hilt.navigation.compose.hiltViewModel -import com.pixelized.biblib.R -import com.pixelized.biblib.ui.composable.StateUio -import com.pixelized.biblib.ui.screen.home.detail.BookDetailUio import com.pixelized.biblib.ui.screen.home.detail.BookDetailViewModel import com.pixelized.biblib.ui.screen.home.detail.DetailScreen import com.pixelized.biblib.ui.theme.color.ShadowPalette -import com.pixelized.biblib.utils.extention.showToast import kotlinx.coroutines.launch val LocalDetailBottomSheetState = staticCompositionLocalOf { @@ -39,10 +35,7 @@ fun DetailBottomSheet( scrimColor = ShadowPalette.scrim, sheetState = bottomDetailState.bottomSheetState, sheetContent = { - DetailScreen( - detailViewModel = bottomDetailState.viewModel, - detail = bottomDetailState.bookDetail, - ) + DetailScreen(bookId = bottomDetailState.bookId) }, content = content, ) @@ -58,48 +51,34 @@ fun DetailBottomSheet( @OptIn(ExperimentalMaterialApi::class) @Composable fun rememberDetailBottomSheetState( - viewModel: BookDetailViewModel = hiltViewModel(), bottomSheetState: ModalBottomSheetState = rememberModalBottomSheetState( initialValue = Hidden, skipHalfExpanded = true, ), ): DetailBottomSheetState { - val context: Context = LocalContext.current - val detail = rememberSaveable(viewModel, bottomSheetState) { - mutableStateOf(null) + val detail = rememberSaveable(bottomSheetState) { + mutableStateOf(null) + } + return remember(bottomSheetState) { + DetailBottomSheetState( + bottomSheetState = bottomSheetState, + bookDetail = detail, + ) } - val controller = DetailBottomSheetState( - context = context, - viewModel = viewModel, - bottomSheetState = bottomSheetState, - bookDetail = detail, - ) - return remember(viewModel, bottomSheetState) { controller } } @OptIn(ExperimentalMaterialApi::class) @Stable class DetailBottomSheetState constructor( - private val context: Context, - val viewModel: BookDetailViewModel, val bottomSheetState: ModalBottomSheetState, - bookDetail: MutableState, + bookDetail: MutableState, ) { - var bookDetail: BookDetailUio? by bookDetail + var bookId: Int? by bookDetail private set suspend fun expandBookDetail(id: Int) { - when (val book = viewModel.getDetail(id)) { - is StateUio.Failure -> { - val mes = book.exception.message ?: context.getString(R.string.error_generic) - context.showToast(message = mes) - } - is StateUio.Success -> { - bookDetail = book.value - bottomSheetState.show() - } - else -> Unit - } + bookId = id + bottomSheetState.show() } suspend fun collapse() { 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 e1c9ead..4504e27 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,36 +1,62 @@ 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 +import androidx.lifecycle.viewModelScope import com.pixelized.biblib.network.client.IBibLibClient import com.pixelized.biblib.network.factory.BookFactory -import com.pixelized.biblib.ui.composable.StateUio +import com.pixelized.biblib.repository.book.BookRepository +import com.pixelized.biblib.ui.screen.home.detail.BookDetailUioErrorUio.Type import com.pixelized.biblib.utils.extention.toDetailUio import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class BookDetailViewModel @Inject constructor( + private val bookRepository: BookRepository, private val client: IBibLibClient, ) : ViewModel() { - suspend fun getDetail(id: Int): StateUio { - return withContext(Dispatchers.IO) { - try { - val book = getBookDetail(id = id) - StateUio.Success(book) - } catch (exception: Exception) { - Log.e("BookDetailViewModel", exception.message, exception) - StateUio.Failure(exception) + private val _error = MutableSharedFlow() + val error: Flow get() = _error + + fun getDetail(id: Int?): State { + return mutableStateOf(null).apply { + viewModelScope.launch(Dispatchers.IO) { + try { + requireNotNull(id) + value = getCacheBookDetail(id = id) + value = getBookDetail(id = id) + } catch (exception: Exception) { + _error.emit(exception.toUio(Type.GET_DETAIL)) + } } } } - suspend fun send(bookId: Int, email: String) { - val data = client.service.send(bookId = bookId, mail = email) - Log.d("send", data.toString()) + suspend fun send(bookId: Int, email: String): State { + return mutableStateOf(null).apply { + viewModelScope.launch(Dispatchers.IO) { + try { + val data = client.service.send(bookId = bookId, mail = email) + Log.d("send", data.toString()) + } catch (exception: Exception) { + Log.d("send", exception.message, exception) + _error.emit(exception.toUio(Type.SEND_BOOK)) + } + } + } + } + + private fun getCacheBookDetail(id: Int): BookDetailUio { + return bookRepository.getBook(id = id).toDetailUio() } private suspend fun getBookDetail(id: Int): BookDetailUio { @@ -39,4 +65,9 @@ class BookDetailViewModel @Inject constructor( val book = factory.fromDetailResponseToBook(response) return book.toDetailUio() } + + private fun Exception.toUio(type: Type) = BookDetailUioErrorUio( + type = type, + message = this.message ?: "An error occurred." + ) } \ 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 index d40f329..dec0327 100644 --- 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 @@ -1,39 +1,33 @@ package com.pixelized.biblib.ui.screen.home.detail -import androidx.compose.runtime.derivedStateOf +import android.app.Application import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.ViewModel +import androidx.lifecycle.AndroidViewModel +import com.pixelized.biblib.R +import com.pixelized.biblib.utils.extention.context import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @HiltViewModel -class ConfirmDialogViewModel @Inject constructor() : ViewModel() { - - val shouldDisplayConfirmDialog by derivedStateOf { dialog != null } +class ConfirmDialogViewModel @Inject constructor( + application: Application, +) : AndroidViewModel(application) { var dialog by mutableStateOf(null) private set - fun show( - email: String, - title: String? = null, - description: String? = null, - help: String? = null, - options: List, - ) = show( - ConfirmDialogUio( + fun show(email: String) { + this.dialog = ConfirmDialogUio( email = email, - title = title, - description = description, - help = help, - options = options, + title = context.getString(R.string.detail_send_confirm_title), + description = context.getString(R.string.detail_send_confirm_description), + help = context.getString(R.string.detail_send_confirm_help), + helpUrl = context.getString(R.string.detail_send_confirm_help_url), + confirm = context.getString(R.string.detail_send_confirm_confirm_action), + cancel = context.getString(R.string.detail_send_confirm_cancel_action), ) - ) - - fun show(dialog: ConfirmDialogUio?) { - this.dialog = dialog } fun hide() { 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 e36cea9..56bb6f3 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,49 +1,23 @@ 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.layout.* 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.Send -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.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale 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.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.core.text.HtmlCompat -import androidx.core.text.toSpannable +import androidx.compose.ui.platform.LocalUriHandler import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.biblib.R -import com.pixelized.biblib.ui.composable.SpannedText -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.theme.BibLibTheme import com.pixelized.biblib.ui.theme.color.ShadowPalette import com.pixelized.biblib.utils.extention.bibLib -import com.pixelized.biblib.utils.extention.default import com.pixelized.biblib.utils.extention.showToast -import com.skydoves.landscapist.CircularReveal -import com.skydoves.landscapist.glide.GlideImage import kotlinx.coroutines.launch import java.io.Serializable @@ -59,332 +33,138 @@ data class BookDetailUio( val series: String?, val description: String, val cover: String, -) : Serializable +) : Serializable { + companion object { + fun preview() = BookDetailUio( + id = 90, + title = "Foundation", + author = "Asimov", + date = "1951", + series = "Foundation - 1", + description = "", // "En ce début de treizième millénaire, l'Empire n'a jamais été aussi puissant, aussi étendu à travers toute la galaxie. C'est dans sa capitale, Trantor, que l'éminent savant Hari Seldon invente la psychohistoire, une science nouvelle permettant de prédire l'avenir. Grâce à elle, Seldon prévoit l'effondrement de l'Empire d'ici cinq siècles, suivi d'une ère de ténèbres de trente mille ans. Réduire cette période à mille ans est peut-être possible, à condition de mener à terme son projet : la Fondation, chargée de rassembler toutes les connaissances humaines. Une entreprise visionnaire qui rencontre de nombreux et puissants détracteurs...", + rating = 4.5f, + language = "Français", + cover = "", + ) + } +} -private const val CONFIRM_OPTION_YES = 0 -private const val CONFIRM_OPTION_NO = 1 +@Stable +@Immutable +data class BookDetailUioErrorUio( + val type: Type, + val message: String, +) { + enum class Type { + GET_DETAIL, + SEND_BOOK, + } +} @OptIn(ExperimentalMaterialApi::class) @Composable fun DetailScreen( - detailState: DetailBottomSheetState = LocalDetailBottomSheetState.current, + detailViewModel: BookDetailViewModel = hiltViewModel(), confirmViewModel: ConfirmDialogViewModel = hiltViewModel(), profileViewModel: ProfileViewModel = hiltViewModel(), - detailViewModel: BookDetailViewModel = hiltViewModel(), - detail: BookDetailUio? = null, + bookId: Int? = null, ) { + val detailState = LocalDetailBottomSheetState.current + val uriHandler = LocalUriHandler.current val context = LocalContext.current + val scope = rememberCoroutineScope() val emailSheetState = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Hidden, skipHalfExpanded = true, ) - ModalBottomSheetLayout( - sheetState = emailSheetState, - scrimColor = ShadowPalette.scrim, - sheetContent = { - Box( - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 1.dp) - .animateContentSize() - ) { - val user = profileViewModel.user - if (user.isSuccessful()) { + val bookDetailState: State = rememberSaveable(bookId) { + detailViewModel.getDetail(bookId) + } + + when (val detail = bookDetailState.value) { + null -> EmptyDetail() + else -> { + ModalBottomSheetLayout( + modifier = Modifier.fillMaxSize(), + sheetState = emailSheetState, + scrimColor = ShadowPalette.scrim, + sheetContent = { DetailScreenEmailList( modifier = Modifier .navigationBarsPadding() .padding(bottom = MaterialTheme.bibLib.dimen.dp16), - emails = user.value.amazonEmails, + profileViewModel = profileViewModel, onEmail = { mail -> confirmViewModel.show(email = mail) } ) - } - } - }, - content = { - 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() } + }, + content = { + if (detailState.bottomSheetState.isVisible) { + DetailScreenContent( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .systemBarsPadding() + .padding(all = MaterialTheme.bibLib.dimen.dp16) + .animateContentSize(), + 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()) - } - }, - ) - - 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 -private fun DetailScreenContent( - modifier: Modifier = Modifier, - book: BookDetailUio, - onMobi: () -> Unit = default(), - onEpub: () -> Unit = default(), - onSend: () -> Unit = default(), -) { - AnimatedDelayer( - targetState = book, - ) { - Column( - modifier = Modifier - .verticalScroll(rememberScrollState()) - .systemBarsPadding() - .padding(all = MaterialTheme.bibLib.dimen.dp16) - .then(modifier) - ) { - GlideImage( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = MaterialTheme.bibLib.dimen.dp16) - .height(MaterialTheme.bibLib.dimen.detail.cover), - loading = { - Box(modifier = Modifier.matchParentSize()) { - CircularProgressIndicator( - modifier = Modifier.align(Alignment.Center) ) + } else { + EmptyDetail() } }, - previewPlaceholder = R.drawable.ic_fondatoin_cover, - circularReveal = CircularReveal(duration = 1000), - contentScale = ContentScale.FillHeight, - imageModel = book.cover, ) - Row(modifier = Modifier.padding(vertical = MaterialTheme.bibLib.dimen.dp16)) { - AnimatedOffset( - modifier = Modifier.weight(1f), - ) { - Button( - modifier = Modifier.fillMaxSize(), - onClick = onMobi, - ) { - Icon(imageVector = Icons.Default.Download, contentDescription = null) - Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.dp4)) - Text(text = stringResource(id = R.string.action_mobi)) + ConfirmDialog( + modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.dp32), + confirmViewModel = confirmViewModel, + detail = detail, + onConfirm = { id, email -> + scope.launch { + confirmViewModel.hide() + emailSheetState.hide() + detailViewModel.send(bookId = id, email = email) } - } + }, + onDismiss = { confirmViewModel.hide() }, + onHelp = { url -> uriHandler.openUri(url) } + ) + } + } - Spacer(modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.dp4)) - - AnimatedOffset( - modifier = Modifier.weight(1f), - ) { - Button( - modifier = Modifier.fillMaxSize(), - onClick = onEpub, - ) { - Icon(imageVector = Icons.Default.Download, contentDescription = null) - Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.dp4)) - Text(text = stringResource(id = R.string.action_epub)) - } - } - - Spacer(modifier = Modifier.padding(all = MaterialTheme.bibLib.dimen.dp4)) - - AnimatedOffset( - modifier = Modifier.weight(1f), - ) { - Button( - modifier = Modifier.fillMaxSize(), - onClick = onSend, - ) { - Icon(imageVector = Icons.Default.Send, contentDescription = "") - Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.dp4)) - Text(text = stringResource(id = R.string.action_send)) - } - } - } - - AnimatedOffset( - modifier = Modifier - .align(alignment = Alignment.CenterHorizontally) - .padding(bottom = 4.dp), - ) { - Text( - style = MaterialTheme.typography.h5, - color = MaterialTheme.colors.onSurface, - text = book.title, - ) - } - - AnimatedOffset( - modifier = Modifier - .align(alignment = Alignment.CenterHorizontally) - .padding(bottom = MaterialTheme.bibLib.dimen.dp16), - ) { - Text( - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.h6, - color = MaterialTheme.colors.onSurface, - text = book.author, - ) - } - - Row( - modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp8), - ) { - AnimatedOffset(modifier = Modifier.weight(1f)) { - TitleLabel( - title = stringResource(id = R.string.detail_rating), - label = book.rating.toString(), - ) - } - - AnimatedOffset(modifier = Modifier.weight(1f)) { - TitleLabel( - title = stringResource(id = R.string.detail_language), - label = book.language, - ) - } - - AnimatedOffset(modifier = Modifier.weight(1f)) { - TitleLabel( - title = stringResource(id = R.string.detail_release), - label = book.date ?: "-", - ) - } - } - - AnimatedOffset( - modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp16), - ) { - TitleLabel( - title = stringResource(id = R.string.detail_series), - label = book.series ?: "-", - ) - } - - AnimatedOffset { - SpannedText( - style = MaterialTheme.typography.body1, - color = MaterialTheme.colors.onSurface, - text = HtmlCompat.fromHtml( - book.description, - HtmlCompat.FROM_HTML_MODE_COMPACT - ).toSpannable(), - ) - } + LaunchedEffect(key1 = "DetailScreenError") { + detailViewModel.error.collect { + context.showToast(message = it.message) } } } @Composable -private fun TitleLabel( - title: String, - label: String, -) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - style = MaterialTheme.typography.body2, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colors.onSurface, - text = title, - ) - Text( - style = MaterialTheme.typography.body1, - textAlign = TextAlign.Center, - color = MaterialTheme.colors.onSurface, - text = label, - ) - } -} - -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") - ) -) - - -@Composable -@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) -@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES) -private fun DetailScreenContentPreview() { - val book = BookDetailUio( - id = 90, - title = "Foundation", - author = "Asimov", - date = "1951", - series = "Foundation - 1", - description = "En ce début de treizième millénaire, l'Empire n'a jamais été aussi puissant, aussi étendu à travers toute la galaxie. C'est dans sa capitale, Trantor, que l'éminent savant Hari Seldon invente la psychohistoire, une science nouvelle permettant de prédire l'avenir. Grâce à elle, Seldon prévoit l'effondrement de l'Empire d'ici cinq siècles, suivi d'une ère de ténèbres de trente mille ans. Réduire cette période à mille ans est peut-être possible, à condition de mener à terme son projet : la Fondation, chargée de rassembler toutes les connaissances humaines. Une entreprise visionnaire qui rencontre de nombreux et puissants détracteurs...", - rating = 4.5f, - language = "Français", - cover = "", - ) - BibLibTheme { - DetailScreenContent(book = book) - } -} \ No newline at end of file +private fun EmptyDetail( + modifier : Modifier = Modifier, +) = Box( + modifier = modifier.fillMaxSize() +) \ No newline at end of file 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 index 296e171..956cac6 100644 --- 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 @@ -7,11 +7,18 @@ 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.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 com.pixelized.biblib.ui.screen.home.detail.ConfirmDialogUio.Companion.option +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 @@ -20,99 +27,133 @@ import com.pixelized.biblib.utils.extention.default @Immutable data class ConfirmDialogUio( val email: String, - val title: String? = null, - val description: String? = null, - val help: String? = null, - val options: List