From 9c8d05e5787a2751a5f9ad2d42330801b276eb13 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Fri, 31 Mar 2023 15:37:32 +0200 Subject: [PATCH] Simplify the detail page. & small code clean up --- app/build.gradle | 3 - .../composable/scaffold/BottomSheetHelpers.kt | 23 +- .../ui/screen/home/BookSearchViewModel.kt | 292 +++++++++-------- .../biblib/ui/screen/home/HomeScreen.kt | 125 +++++--- .../ui/screen/home/detail/DetailScreen.kt | 25 +- .../screen/home/detail/DetailScreenContent.kt | 295 +++++++++--------- .../biblib/utils/extention/BookEx.kt | 19 +- app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values/strings.xml | 1 - 9 files changed, 428 insertions(+), 356 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5486a6b..a67d90b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -86,9 +86,6 @@ android { } dependencies { - // Kotlin core - implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.10" - // Android core implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.7.0-alpha02' diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/BottomSheetHelpers.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/BottomSheetHelpers.kt index 47d09f8..301ad2f 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/BottomSheetHelpers.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/scaffold/BottomSheetHelpers.kt @@ -4,6 +4,7 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable @Composable fun HandleBottomSheetData( @@ -34,12 +35,22 @@ fun HandleBottomSheetDismiss( val currentOnDismiss by rememberUpdatedState( newValue = onDismiss, ) - val haveBeenDismissed by remember(bottomSheetState) { - derivedStateOf { - bottomSheetState.currentValue != ModalBottomSheetValue.Hidden && bottomSheetState.targetValue == ModalBottomSheetValue.Hidden + // Showing a bottom sheet produce a : + // - currentValue == Hidden targetValue == Hidden + // before a : + // - currentValue == Hidden targetValue == Expanded + // We need to keep track of this behavior to avoid closing the bottom sheet immediately. + var skipLaunchEffect by rememberSaveable { + mutableStateOf(true) + } + LaunchedEffect(bottomSheetState.currentValue) { + if (skipLaunchEffect.not()) { + if (bottomSheetState.currentValue == ModalBottomSheetValue.Hidden && bottomSheetState.targetValue == ModalBottomSheetValue.Hidden) { + currentOnDismiss() + skipLaunchEffect = true + } + } else { + skipLaunchEffect = false } } - if (haveBeenDismissed) { - currentOnDismiss() - } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/BookSearchViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/BookSearchViewModel.kt index fa3007f..3421ac0 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/BookSearchViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/BookSearchViewModel.kt @@ -11,7 +11,6 @@ import com.pixelized.biblib.model.book.Book import com.pixelized.biblib.model.search.FilterType import com.pixelized.biblib.model.search.SortType import com.pixelized.biblib.model.search.SortValue -import com.pixelized.biblib.repository.book.BookRepository import com.pixelized.biblib.repository.book.IBookRepository import com.pixelized.biblib.repository.search.ISearchRepository import com.pixelized.biblib.ui.screen.home.common.item.LargeBookThumbnailUio @@ -105,142 +104,102 @@ class BookSearchViewModel @Inject constructor( } } - fun sort(sortBy: Map) { - this.sortBy = sortBy + fun source(clean: Boolean = false, block: SortAndFilterScope.() -> Unit) { + val scope = if (clean) { + SortAndFilterScope( + sortBy = mapOf(), + isNew = null, + title = null, + author = null, + authorId = null, + series = null, + seriesId = null, + genre = null, + genreId = null, + language = null, + languageId = null, + ) + } else { + SortAndFilterScope( + sortBy = sortBy, + isNew = filterByNew, + title = search.value, + author = authorFilter.value.label, + authorId = filterByAuthorId, + series = seriesFilter.value.label, + seriesId = filterBySeriesId, + genre = genreFilter.value.label, + genreId = filterByGenreId, + language = languageFilter.value.label, + languageId = filterByLanguageId, + ) + } + scope.block() + // filter the search + if (_search.value != scope.title) { + _search.value = scope.title + } + // clean the new filter + if (filterByNew != scope.isNew) { + filterByNew = scope.isNew + newFilter.value = newFilter.value.copy( + selected = scope.isNew == true, + closable = scope.isNew == true, + ) + } + // clean the author filter + if (filterByAuthorId != scope.authorId) { + filterByAuthorId = scope.authorId + authorFilter.value = authorFilter.value.copy( + selected = scope.authorId != null, + label = scope.author, + closable = scope.authorId != null, + openable = scope.authorId == null, + ) + } + // clean the author filter + if (filterBySeriesId != scope.seriesId) { + filterBySeriesId = scope.seriesId + seriesFilter.value = seriesFilter.value.copy( + selected = scope.seriesId != null, + label = scope.series, + closable = scope.seriesId != null, + openable = scope.seriesId == null, + ) + } + // clean the author filter + if (filterByGenreId != scope.genreId) { + filterByGenreId = scope.genreId + genreFilter.value = genreFilter.value.copy( + selected = scope.genreId != null, + label = scope.genre, + closable = scope.genreId != null, + openable = scope.genreId == null, + ) + } + // clean the author filter + if (filterByLanguageId != scope.languageId) { + filterByLanguageId = scope.languageId + languageFilter.value = languageFilter.value.copy( + selected = scope.languageId != null, + label = scope.language, + closable = scope.languageId != null, + openable = scope.languageId == null, + ) + } + // apply the sorting + if (sortBy != scope.sortBy) { + sortBy = scope.sortBy + } + // invalidate the source searchSource?.invalidate() } - fun cleanFilterThen(block: BookSearchViewModel.() -> Unit) { - // filter the search - _search.value = null - // clean the new filter - filterByNew = null - newFilter.value = newFilter.value.copy(selected = false) - // clean the author filter - filterByAuthorId = null - authorFilter.value = authorFilter.value.copy( - selected = false, label = null, closable = false, openable = true - ) - // clean the author filter - filterBySeriesId = null - seriesFilter.value = seriesFilter.value.copy( - selected = false, label = null, closable = false, openable = true - ) - // clean the author filter - filterByGenreId = null - genreFilter.value = genreFilter.value.copy( - selected = false, label = null, closable = false, openable = true - ) - // clean the author filter - filterByLanguageId = null - languageFilter.value = languageFilter.value.copy( - selected = false, label = null, closable = false, openable = true - ) - // call the filter - this.block() - } - - fun filterSearch(criteria: String?) { + fun filterByTitle(criteria: String?) { _search.value = criteria searchSource?.invalidate() } - fun filterByNew(criteria: Boolean) { - if (filterByNew != criteria) { - filterByNew = if (criteria) true else null - newFilter.value = newFilter.value.copy(selected = criteria) - searchSource?.invalidate() - } - } - - fun filterByAuthor(label: String?, criteria: Int?) { - if (filterByAuthorId != criteria) { - filterByAuthorId = criteria - authorFilter.value = if (criteria != null) { - authorFilter.value.copy( - selected = true, - label = label, - closable = true, - openable = false - ) - } else { - authorFilter.value.copy( - selected = false, - label = null, - closable = false, - openable = true - ) - } - searchSource?.invalidate() - } - } - - fun filterBySeries(label: String?, criteria: Int?) { - if (filterBySeriesId != criteria) { - filterBySeriesId = criteria - seriesFilter.value = if (criteria != null) { - seriesFilter.value.copy( - selected = true, - label = label, - closable = true, - openable = false - ) - } else { - seriesFilter.value.copy( - selected = false, - label = null, - closable = false, - openable = true - ) - } - searchSource?.invalidate() - } - } - - fun filterByGenre(label: String?, criteria: Int?) { - if (filterByGenreId != criteria) { - filterByGenreId = criteria - genreFilter.value = if (criteria != null) { - genreFilter.value.copy( - selected = true, - label = label, - closable = true, - openable = false - ) - } else { - genreFilter.value.copy( - selected = false, - label = null, - closable = false, - openable = true - ) - } - searchSource?.invalidate() - } - } - - fun filterByLanguage(label: String?, criteria: Int?) { - if (filterByLanguageId != criteria) { - filterByLanguageId = criteria - languageFilter.value = if (criteria != null) { - languageFilter.value.copy( - selected = true, - label = label, - closable = true, - openable = false - ) - } else { - languageFilter.value.copy( - selected = false, - label = null, - closable = false, - openable = true - ) - } - searchSource?.invalidate() - } - } - private fun buildBookSource(): PagingSource { return BookSearchSource( searchRepository = searchRepository, @@ -312,6 +271,79 @@ class BookSearchViewModel @Inject constructor( ) ) + class SortAndFilterScope( + sortBy: Map, + isNew: Boolean?, + title: String?, + author: String?, + authorId: Int?, + series: String?, + seriesId: Int?, + genre: String?, + genreId: Int?, + language: String?, + languageId: Int?, + ) { + var sortBy: Map = sortBy + private set + var isNew: Boolean? = isNew + private set + var title: String? = title + private set + var author: String? = author + private set + var authorId: Int? = authorId + private set + var series: String? = series + private set + var seriesId: Int? = seriesId + private set + var genre: String? = genre + private set + var genreId: Int? = genreId + private set + var language: String? = language + private set + var languageId: Int? = languageId + private set + + fun sortBy(type: SortType, value: SortValue?) { + this.sortBy = mapOf(type to value) + } + + fun sortBy(sortBy: Map) { + this.sortBy = sortBy + } + + fun filterByNew(isNew: Boolean) { + this.isNew = if (isNew) true else null + } + + fun filterByTitle(title: String?) { + this.title = title + } + + fun filterByAuthor(author: String?, authorId: Int?) { + this.author = author + this.authorId = authorId + } + + fun filterBySeries(series: String?, seriesId: Int?) { + this.series = series + this.seriesId = seriesId + } + + fun filterByGenre(genre: String?, genreId: Int?) { + this.genre = genre + this.genreId = genreId + } + + fun filterByLanguage(language: String?, languageId: Int?) { + this.language = language + this.languageId = languageId + } + } + companion object { private const val SEARCH_PAGE_SIZE = 20 } diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt index 9386dcd..2be0dc4 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt @@ -41,6 +41,8 @@ import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.pixelized.biblib.R import com.pixelized.biblib.model.search.FilterType +import com.pixelized.biblib.model.search.SortType +import com.pixelized.biblib.model.search.SortValue import com.pixelized.biblib.ui.LocalSnackHostState import com.pixelized.biblib.ui.composable.scaffold.* import com.pixelized.biblib.ui.navigation.LocalScreenNavHostController @@ -104,14 +106,16 @@ fun HomeScreen( DetailBottomSheet( bottomDetailState = detailState, onAuthor = { label: String?, id: Int? -> - bookViewModel.cleanFilterThen { - filterByAuthor(label = label, criteria = id) + bookViewModel.source(clean = true) { + filterByAuthor(author = label, authorId = id) + sortBy(type = SortType.Title, value = SortValue.ASC) } scope.launch { detailState.collapse() } }, onSeries = { label: String?, id: Int? -> - bookViewModel.cleanFilterThen { - filterBySeries(label = label, criteria = id) + bookViewModel.source(clean = true) { + filterBySeries(series = label, seriesId = id) + sortBy(type = SortType.Series, value = SortValue.ASC) } scope.launch { detailState.collapse() } }, @@ -123,7 +127,7 @@ fun HomeScreen( avatar = profileViewModel.avatar, search = bookViewModel.search, onSearch = { - bookViewModel.filterSearch(criteria = it) + bookViewModel.filterByTitle(criteria = it) }, onAvatarTap = { navigation.navigateToProfile() @@ -149,7 +153,9 @@ fun HomeScreen( scope.launch { val result = sortingState.show(sortBy = bookViewModel.sortBy) if (result is SortBottomSheetResult.ActionPerformed) { - bookViewModel.sort(sortBy = result.sortBy) + bookViewModel.source { + sortBy(sortBy = result.sortBy) + } } } }, @@ -182,6 +188,12 @@ fun HomeScreen( } } + CollapseKeyboardOnScrollHandler( + largeGridState = largeGridState, + smallListState = smallListState, + microListState = microListState + ) + LaunchedEffect(key1 = "HomeScreenLaunchedEffect") { // update launch { @@ -212,53 +224,58 @@ private fun rememberOnFilter( return remember { { if (it.id == FilterType.New) { - filterViewModel.filterByNew(criteria = it.selected.not()) + filterViewModel.source { + filterByNew(isNew = it.selected.not()) + } } else { if (it.selected.not()) { scope.launch { val result = filterState.show(type = it.id) - if (result is FilterBottomSheetResult.ActionPerformed) { - when (it.id) { - FilterType.Author -> filterViewModel.filterByAuthor( - label = result.filter.filterLabel, - criteria = result.filter.filterId, - ) - FilterType.Series -> filterViewModel.filterBySeries( - label = result.filter.filterLabel, - criteria = result.filter.filterId, - ) - FilterType.Genre -> filterViewModel.filterByGenre( - label = result.filter.filterLabel, - criteria = result.filter.filterId, - ) - FilterType.Language -> filterViewModel.filterByLanguage( - label = result.filter.filterLabel, - criteria = result.filter.filterId, - ) - else -> Unit + filterViewModel.source { + when (it.id) { + FilterType.Author -> filterByAuthor( + author = result.filter.filterLabel, + authorId = result.filter.filterId, + ) + FilterType.Series -> filterBySeries( + series = result.filter.filterLabel, + seriesId = result.filter.filterId, + ) + FilterType.Genre -> filterByGenre( + genre = result.filter.filterLabel, + genreId = result.filter.filterId, + ) + FilterType.Language -> filterByLanguage( + language = result.filter.filterLabel, + languageId = result.filter.filterId, + ) + else -> Unit + } } } } } else { - when (it.id) { - FilterType.Author -> filterViewModel.filterByAuthor( - label = null, - criteria = null, - ) - FilterType.Series -> filterViewModel.filterBySeries( - label = null, - criteria = null, - ) - FilterType.Genre -> filterViewModel.filterByGenre( - label = null, - criteria = null, - ) - FilterType.Language -> filterViewModel.filterByLanguage( - label = null, - criteria = null, - ) - else -> Unit + filterViewModel.source { + when (it.id) { + FilterType.Author -> filterByAuthor( + author = null, + authorId = null, + ) + FilterType.Series -> filterBySeries( + series = null, + seriesId = null, + ) + FilterType.Genre -> filterByGenre( + genre = null, + genreId = null, + ) + FilterType.Language -> filterByLanguage( + language = null, + languageId = null, + ) + else -> Unit + } } } } @@ -569,6 +586,26 @@ private fun HomeToolbar( } } +@OptIn(ExperimentalComposeUiApi::class) +@Composable +private fun CollapseKeyboardOnScrollHandler( + largeGridState: LazyGridState, + smallListState: LazyListState, + microListState: LazyListState, +) { + val keyboard = LocalSoftwareKeyboardController.current + val focus = LocalFocusManager.current + + if ( + largeGridState.isScrollInProgress + || smallListState.isScrollInProgress + || microListState.isScrollInProgress + ) { + focus.clearFocus() + keyboard?.hide() + } +} + @Composable @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) 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 5420a2b..f73eb01 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 @@ -91,24 +91,21 @@ fun DetailScreen( content = { if (detailState.bottomSheetState.isVisible) { DetailScreenContent( - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .padding(all = 16.dp) - .systemBarsPadding(), book = detail, - onAuthor = { onAuthor(detail.author, detail.authorId) }, - onSeries = { onSeries(detail.series, detail.seriesId) }, + onAuthor = { onAuthor(it.name, it.id) }, + onSeries = { onSeries(it.name, it.id) }, onMobi = { }, onEpub = { }, onSend = { - send( - context = context, - profileViewModel = profileViewModel, - snackBarHost = snackBarHost, - emailSheetState = emailSheetState, - user = profileViewModel.user - ) + scope.launch { + send( + context = context, + profileViewModel = profileViewModel, + snackBarHost = snackBarHost, + emailSheetState = emailSheetState, + user = profileViewModel.user + ) + } } ) DetailScreenSendLoader( diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenContent.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenContent.kt index 2268ba5..26ffe56 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenContent.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreenContent.kt @@ -11,19 +11,17 @@ 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.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.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity 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.compose.ui.unit.dp import androidx.core.text.HtmlCompat import androidx.core.text.toSpannable @@ -36,7 +34,6 @@ import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.placeholder import com.skydoves.landscapist.CircularReveal import com.skydoves.landscapist.glide.GlideImage -import kotlinx.coroutines.launch import java.io.Serializable @Stable @@ -45,25 +42,40 @@ data class BookDetailUio( val id: Int, val title: String, val authorId: Int?, - val author: String, + val authors: List, val rating: Float, val language: String, val date: String?, val seriesId: Int?, - val series: String?, + val series: SeriesUio?, val description: String, val cover: String, val placeHolder: Boolean, ) : Serializable { + @Stable + @Immutable + data class AuthorUio( + val id: Int, + val name: String, + ) + + @Stable + @Immutable + data class SeriesUio( + val id: Int, + val name: String, + val label: String, + ) + companion object { fun preview() = BookDetailUio( id = 90, title = "Foundation", authorId = null, - author = "Asimov", + authors = listOf(AuthorUio(id = 0, name = "Asimov")), date = "1951", seriesId = null, - series = "Foundation - 1", + series = SeriesUio(id = 0, "Foundation", label = "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", @@ -77,162 +89,146 @@ data class BookDetailUio( fun DetailScreenContent( modifier: Modifier = Modifier, book: BookDetailUio, - onAuthor: () -> Unit, - onSeries: () -> Unit, + onAuthor: (BookDetailUio.AuthorUio) -> Unit, + onSeries: (BookDetailUio.SeriesUio) -> Unit, onMobi: () -> Unit, onEpub: () -> Unit, - onSend: suspend () -> Unit, + onSend: () -> Unit, ) { - val scope = rememberCoroutineScope() + val density = LocalDensity.current + val fabHeight = remember { mutableStateOf(0.dp) } AnimatedDelayer( targetState = book.id, ) { - Column( - modifier = modifier, - ) { - GlideImage( + Box { + Column( modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp) - .height(MaterialTheme.bibLib.dimen.detail.cover), - previewPlaceholder = R.drawable.ic_fondatoin_cover, - circularReveal = CircularReveal(duration = 1000), - contentScale = ContentScale.FillHeight, - imageModel = book.cover, - ) - - Row(modifier = Modifier.padding(vertical = 16.dp)) { - AnimatedOffset( - modifier = Modifier.weight(1f), - ) { - Button( - modifier = Modifier.fillMaxWidth(), - onClick = onMobi, - ) { - Icon(imageVector = Icons.Default.Download, contentDescription = null) - Spacer(modifier = Modifier.width(4.dp)) - Text(text = stringResource(id = R.string.action_mobi)) - } - } - - Spacer(modifier = Modifier.padding(all = 4.dp)) - - AnimatedOffset( - modifier = Modifier.weight(1f), - ) { - Button( - modifier = Modifier.fillMaxWidth(), - onClick = onEpub, - ) { - Icon(imageVector = Icons.Default.Download, contentDescription = null) - Spacer(modifier = Modifier.width(4.dp)) - Text(text = stringResource(id = R.string.action_epub)) - } - } - - Spacer(modifier = Modifier.padding(all = 4.dp)) - - AnimatedOffset( - modifier = Modifier.weight(1f), - ) { - Button( - modifier = Modifier.fillMaxWidth(), - onClick = { scope.launch { onSend() } }, - ) { - Icon(imageVector = Icons.Default.Send, contentDescription = "") - Spacer(modifier = Modifier.width(4.dp)) - Text(text = stringResource(id = R.string.action_send)) - } - } - } - - AnimatedOffset( - modifier = Modifier - .align(alignment = Alignment.CenterHorizontally) - .padding(bottom = 4.dp), + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(all = 16.dp) + .systemBarsPadding(), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text( - style = MaterialTheme.typography.h5, - color = MaterialTheme.colors.onSurface, - text = book.title, - ) - } - - AnimatedOffset( - modifier = Modifier - .align(alignment = Alignment.CenterHorizontally) - .padding(bottom = 16.dp), - ) { - Text( - modifier = Modifier.clickable(onClick = onAuthor), - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.h6, - color = MaterialTheme.colors.onSurface, - text = book.author, - ) - } - - Row( - modifier = Modifier.padding(bottom = 8.dp), - ) { - AnimatedOffset(modifier = Modifier.weight(1f)) { - TitleLabel( - modifier = Modifier.fillMaxWidth(), - title = stringResource(id = R.string.detail_rating), - label = book.rating.toString(), - ) - } - - AnimatedOffset(modifier = Modifier.weight(1f)) { - TitleLabel( - modifier = Modifier.fillMaxWidth(), - title = stringResource(id = R.string.detail_language), - label = book.language, - ) - } - - AnimatedOffset(modifier = Modifier.weight(1f)) { - TitleLabel( - modifier = Modifier.fillMaxWidth(), - title = stringResource(id = R.string.detail_release), - label = book.date ?: "-", - ) - } - } - - AnimatedOffset( - modifier = Modifier.padding(bottom = 16.dp), - ) { - TitleLabel( + GlideImage( modifier = Modifier .fillMaxWidth() - .clickable(onClick = onSeries), - title = stringResource(id = R.string.detail_series), - label = book.series ?: "-", + .padding(vertical = 16.dp) + .height(MaterialTheme.bibLib.dimen.detail.cover), + previewPlaceholder = R.drawable.ic_fondatoin_cover, + circularReveal = CircularReveal(duration = 1000), + contentScale = ContentScale.FillHeight, + imageModel = book.cover, ) + + AnimatedOffset { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + style = MaterialTheme.typography.h5, + color = MaterialTheme.colors.onSurface, + text = book.title, + ) + book.series?.let { series -> + Text( + modifier = Modifier.clickable(onClick = { onSeries(series) }), + style = MaterialTheme.typography.body1, + color = MaterialTheme.colors.onSurface, + text = series.label, + ) + } + } + } + + AnimatedOffset { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + book.authors.forEach { author -> + Text( + modifier = Modifier.clickable(onClick = { onAuthor(author) }), + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.h6, + color = MaterialTheme.colors.onSurface, + text = author.name, + ) + } + } + } + + Row { + AnimatedOffset(modifier = Modifier.weight(1f)) { + TitleLabel( + modifier = Modifier.fillMaxWidth(), + title = stringResource(id = R.string.detail_rating), + label = book.rating.toString(), + ) + } + + AnimatedOffset(modifier = Modifier.weight(1f)) { + TitleLabel( + modifier = Modifier.fillMaxWidth(), + title = stringResource(id = R.string.detail_language), + label = book.language, + ) + } + + AnimatedOffset(modifier = Modifier.weight(1f)) { + TitleLabel( + modifier = Modifier.fillMaxWidth(), + title = stringResource(id = R.string.detail_release), + label = book.date ?: "-", + ) + } + } + + AnimatedOffset { + SpannedText( + modifier = Modifier + .fillMaxWidth() + .placeholder( + shape = MaterialTheme.bibLib.shapes.base.medium, + visible = { book.placeHolder }, + ), + style = MaterialTheme.typography.body1, + color = MaterialTheme.colors.onSurface, + text = HtmlCompat.fromHtml( + book.description, + HtmlCompat.FROM_HTML_MODE_COMPACT, + ).toSpannable(), + ) + } + + Spacer(height = fabHeight) } - AnimatedOffset { - SpannedText( - modifier = Modifier - .fillMaxWidth() - .placeholder( - shape = MaterialTheme.bibLib.shapes.base.medium, - visible = { book.placeHolder }, - ), - style = MaterialTheme.typography.body1, - color = MaterialTheme.colors.onSurface, - text = HtmlCompat.fromHtml( - book.description, - HtmlCompat.FROM_HTML_MODE_COMPACT, - ).toSpannable(), - ) + AnimatedOffset( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(all = 16.dp) + .systemBarsPadding() + .onSizeChanged { fabHeight.value = with(density) { it.height.toDp() } }, + ) { + Button( + onClick = onSend, + ) { + Icon(imageVector = Icons.Default.Send, contentDescription = "") + Spacer(modifier = Modifier.width(4.dp)) + Text(text = stringResource(id = R.string.action_send)) + } } } } } +@Composable +private fun Spacer(height: State) { + Spacer(modifier = Modifier.height(height = height.value)) +} + @Composable private fun TitleLabel( modifier: Modifier = Modifier, @@ -245,13 +241,12 @@ private fun TitleLabel( ) { Text( style = MaterialTheme.typography.body2, - fontWeight = FontWeight.Bold, color = MaterialTheme.colors.onSurface, text = title, ) Text( style = MaterialTheme.typography.body1, - textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, color = MaterialTheme.colors.onSurface, text = label, ) diff --git a/app/src/main/java/com/pixelized/biblib/utils/extention/BookEx.kt b/app/src/main/java/com/pixelized/biblib/utils/extention/BookEx.kt index b2050c1..3506d7e 100644 --- a/app/src/main/java/com/pixelized/biblib/utils/extention/BookEx.kt +++ b/app/src/main/java/com/pixelized/biblib/utils/extention/BookEx.kt @@ -24,7 +24,7 @@ fun Book.toMicroThumbnailUio( cover = "${coverBaseUrl}/$id.jpg", title = title, author = author.joinToString { it.name }, - series = toLabel(series) ?: "", + series = series?.let { toLabel(it) } ?: "", isNew = isNew, ) @@ -55,21 +55,26 @@ fun Book.toDetailUio( id = id, title = title, authorId = author.firstOrNull()?.id, - author = author.joinToString { it.name }, + authors = author.map { BookDetailUio.AuthorUio(id = it.id, it.name) }, rating = rating?.toFloat() ?: 0.0f, language = language?.displayLanguage?.capitalize() ?: "", date = releaseDate.shortDate(), seriesId = series?.id, - series = toLabel(series), + series = series?.let { + BookDetailUio.SeriesUio( + id = it.id, + name = it.name, + label = toLabel(series) + ) + }, description = synopsis ?: "", cover = "${coverBaseUrl}/$id.jpg", placeHolder = placeHolder, ) -fun Book.toLabel(series: Series?): String? { +fun Book.toLabel(series: Series): String { return when { - series?.name != null && seriesIndex != null -> "${series.name} - $seriesIndex" - series?.name != null -> series.name - else -> null + seriesIndex != null -> "${series.name} - $seriesIndex" + else -> series.name } } \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e79d661..486a0f4 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -46,7 +46,6 @@ Langue Publication Genre - Séries Envoyer cet eBook à : Format de l\'eBook : diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f51a826..f08e1a7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,7 +61,6 @@ Language Release Genre - Series Send this eBook to: eBook format: