Simplify the detail page. & small code clean up
This commit is contained in:
parent
100f8d32bd
commit
9c8d05e578
9 changed files with 428 additions and 356 deletions
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
if (haveBeenDismissed) {
|
||||
LaunchedEffect(bottomSheetState.currentValue) {
|
||||
if (skipLaunchEffect.not()) {
|
||||
if (bottomSheetState.currentValue == ModalBottomSheetValue.Hidden && bottomSheetState.targetValue == ModalBottomSheetValue.Hidden) {
|
||||
currentOnDismiss()
|
||||
skipLaunchEffect = true
|
||||
}
|
||||
} else {
|
||||
skipLaunchEffect = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SortType, SortValue?>) {
|
||||
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<Int, Book> {
|
||||
return BookSearchSource(
|
||||
searchRepository = searchRepository,
|
||||
|
|
@ -312,6 +271,79 @@ class BookSearchViewModel @Inject constructor(
|
|||
)
|
||||
)
|
||||
|
||||
class SortAndFilterScope(
|
||||
sortBy: Map<SortType, SortValue?>,
|
||||
isNew: Boolean?,
|
||||
title: String?,
|
||||
author: String?,
|
||||
authorId: Int?,
|
||||
series: String?,
|
||||
seriesId: Int?,
|
||||
genre: String?,
|
||||
genreId: Int?,
|
||||
language: String?,
|
||||
languageId: Int?,
|
||||
) {
|
||||
var sortBy: Map<SortType, SortValue?> = 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<SortType, SortValue?>) {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,51 +224,55 @@ 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) {
|
||||
filterViewModel.source {
|
||||
when (it.id) {
|
||||
FilterType.Author -> filterViewModel.filterByAuthor(
|
||||
label = result.filter.filterLabel,
|
||||
criteria = result.filter.filterId,
|
||||
FilterType.Author -> filterByAuthor(
|
||||
author = result.filter.filterLabel,
|
||||
authorId = result.filter.filterId,
|
||||
)
|
||||
FilterType.Series -> filterViewModel.filterBySeries(
|
||||
label = result.filter.filterLabel,
|
||||
criteria = result.filter.filterId,
|
||||
FilterType.Series -> filterBySeries(
|
||||
series = result.filter.filterLabel,
|
||||
seriesId = result.filter.filterId,
|
||||
)
|
||||
FilterType.Genre -> filterViewModel.filterByGenre(
|
||||
label = result.filter.filterLabel,
|
||||
criteria = result.filter.filterId,
|
||||
FilterType.Genre -> filterByGenre(
|
||||
genre = result.filter.filterLabel,
|
||||
genreId = result.filter.filterId,
|
||||
)
|
||||
FilterType.Language -> filterViewModel.filterByLanguage(
|
||||
label = result.filter.filterLabel,
|
||||
criteria = result.filter.filterId,
|
||||
FilterType.Language -> filterByLanguage(
|
||||
language = result.filter.filterLabel,
|
||||
languageId = result.filter.filterId,
|
||||
)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filterViewModel.source {
|
||||
when (it.id) {
|
||||
FilterType.Author -> filterViewModel.filterByAuthor(
|
||||
label = null,
|
||||
criteria = null,
|
||||
FilterType.Author -> filterByAuthor(
|
||||
author = null,
|
||||
authorId = null,
|
||||
)
|
||||
FilterType.Series -> filterViewModel.filterBySeries(
|
||||
label = null,
|
||||
criteria = null,
|
||||
FilterType.Series -> filterBySeries(
|
||||
series = null,
|
||||
seriesId = null,
|
||||
)
|
||||
FilterType.Genre -> filterViewModel.filterByGenre(
|
||||
label = null,
|
||||
criteria = null,
|
||||
FilterType.Genre -> filterByGenre(
|
||||
genre = null,
|
||||
genreId = null,
|
||||
)
|
||||
FilterType.Language -> filterViewModel.filterByLanguage(
|
||||
label = null,
|
||||
criteria = null,
|
||||
FilterType.Language -> filterByLanguage(
|
||||
language = null,
|
||||
languageId = null,
|
||||
)
|
||||
else -> Unit
|
||||
}
|
||||
|
|
@ -264,6 +280,7 @@ private fun rememberOnFilter(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -91,17 +91,13 @@ 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 = {
|
||||
scope.launch {
|
||||
send(
|
||||
context = context,
|
||||
profileViewModel = profileViewModel,
|
||||
|
|
@ -110,6 +106,7 @@ fun DetailScreen(
|
|||
user = profileViewModel.user
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
DetailScreenSendLoader(
|
||||
loadingVisibility = detailViewModel.sendStatus,
|
||||
|
|
|
|||
|
|
@ -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<AuthorUio>,
|
||||
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,19 +89,26 @@ 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,
|
||||
) {
|
||||
Box {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(all = 16.dp)
|
||||
.systemBarsPadding(),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
GlideImage(
|
||||
modifier = Modifier
|
||||
|
|
@ -102,80 +121,45 @@ fun DetailScreenContent(
|
|||
imageModel = book.cover,
|
||||
)
|
||||
|
||||
Row(modifier = Modifier.padding(vertical = 16.dp)) {
|
||||
AnimatedOffset(
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
Button(
|
||||
AnimatedOffset {
|
||||
Column(
|
||||
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),
|
||||
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(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.CenterHorizontally)
|
||||
.padding(bottom = 16.dp),
|
||||
AnimatedOffset {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
book.authors.forEach { author ->
|
||||
Text(
|
||||
modifier = Modifier.clickable(onClick = onAuthor),
|
||||
modifier = Modifier.clickable(onClick = { onAuthor(author) }),
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.h6,
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
text = book.author,
|
||||
text = author.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
) {
|
||||
Row {
|
||||
AnimatedOffset(modifier = Modifier.weight(1f)) {
|
||||
TitleLabel(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
@ -201,18 +185,6 @@ fun DetailScreenContent(
|
|||
}
|
||||
}
|
||||
|
||||
AnimatedOffset(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
) {
|
||||
TitleLabel(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onSeries),
|
||||
title = stringResource(id = R.string.detail_series),
|
||||
label = book.series ?: "-",
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedOffset {
|
||||
SpannedText(
|
||||
modifier = Modifier
|
||||
|
|
@ -229,8 +201,32 @@ fun DetailScreenContent(
|
|||
).toSpannable(),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(height = fabHeight)
|
||||
}
|
||||
|
||||
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<Dp>) {
|
||||
Spacer(modifier = Modifier.height(height = height.value))
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +46,6 @@
|
|||
<string name="detail_language">Langue</string>
|
||||
<string name="detail_release">Publication</string>
|
||||
<string name="detail_genre">Genre</string>
|
||||
<string name="detail_series">Séries</string>
|
||||
|
||||
<string name="detail_option_mail">Envoyer cet eBook à :</string>
|
||||
<string name="detail_option_format">Format de l\'eBook :</string>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@
|
|||
<string name="detail_language">Language</string>
|
||||
<string name="detail_release">Release</string>
|
||||
<string name="detail_genre">Genre</string>
|
||||
<string name="detail_series">Series</string>
|
||||
|
||||
<string name="detail_option_mail">Send this eBook to:</string>
|
||||
<string name="detail_option_format">eBook format:</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue