Fix lazy detail loading.
This commit is contained in:
parent
98f0e94766
commit
4f3fabc4d6
10 changed files with 106 additions and 107 deletions
|
|
@ -83,13 +83,13 @@ dependencies {
|
|||
// Android core
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation 'com.google.android.material:material:1.6.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
|
||||
implementation 'androidx.activity:activity-compose:1.4.0'
|
||||
|
||||
// Android Compose
|
||||
implementation "androidx.compose.ui:ui:1.2.0-alpha08"
|
||||
implementation "androidx.compose.ui:ui:1.2.0-beta01"
|
||||
implementation "androidx.compose.material:material:1.1.1"
|
||||
implementation "androidx.compose.runtime:runtime-livedata:1.1.1"
|
||||
implementation "androidx.compose.ui:ui-tooling-preview:1.1.1"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.pixelized.biblib.ui.composable
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.CoverUio
|
||||
|
||||
@Composable
|
||||
fun Cover(
|
||||
modifier: Modifier = Modifier,
|
||||
cover: CoverUio,
|
||||
contentDescription: String? = null,
|
||||
) {
|
||||
Image(
|
||||
modifier = modifier,
|
||||
painter = cover.painter,
|
||||
contentScale = cover.contentScale,
|
||||
colorFilter = cover.colorFilter,
|
||||
contentDescription = contentDescription,
|
||||
)
|
||||
}
|
||||
|
|
@ -46,7 +46,11 @@ fun ScreenNavHost(
|
|||
fun NavHostController.navigateToHome() {
|
||||
navigate(Screen.Home.route) {
|
||||
launchSingleTop = true
|
||||
popUpTo(0) { inclusive = true }
|
||||
restoreState = true
|
||||
popUpTo(0) {
|
||||
saveState = true
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
package com.pixelized.biblib.ui.scaffold
|
||||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.BottomSheetScaffold
|
||||
import androidx.compose.material.BottomSheetScaffoldState
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.rememberBottomSheetScaffoldState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.composable.StateUio
|
||||
import com.pixelized.biblib.ui.screen.detail.BookDetailViewModel
|
||||
import com.pixelized.biblib.ui.screen.detail.DetailScreen
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.BookUio
|
||||
import com.pixelized.biblib.utils.extention.showToast
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -15,7 +23,7 @@ val LocalBottomDetailController = staticCompositionLocalOf<BottomDetailStateCont
|
|||
error("LocalBottomDetailController is not ready yet")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalAnimationApi::class)
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun BottomDetailScaffold(
|
||||
bottomStateController: BottomDetailStateController = rememberBottomDetailStateController(),
|
||||
|
|
@ -29,16 +37,8 @@ fun BottomDetailScaffold(
|
|||
sheetPeekHeight = 0.dp,
|
||||
sheetGesturesEnabled = false,
|
||||
sheetContent = {
|
||||
var detail by remember { bottomStateController.detail }
|
||||
val state = bottomStateController.scaffoldState.bottomSheetState
|
||||
if (state.currentValue == BottomSheetValue.Collapsed) {
|
||||
detail = null
|
||||
}
|
||||
detail?.let {
|
||||
val viewModel: BookDetailViewModel = hiltViewModel()
|
||||
viewModel.getDetail(id = it)
|
||||
DetailScreen(viewModel)
|
||||
}
|
||||
val detail by remember { bottomStateController.bookDetail }
|
||||
DetailScreen(detail = detail)
|
||||
},
|
||||
content = content
|
||||
)
|
||||
|
|
@ -48,35 +48,49 @@ fun BottomDetailScaffold(
|
|||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun rememberBottomDetailStateController(
|
||||
viewModel: BookDetailViewModel = hiltViewModel(),
|
||||
scope: CoroutineScope = rememberCoroutineScope(),
|
||||
scaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(),
|
||||
): BottomDetailStateController {
|
||||
val context: Context = LocalContext.current
|
||||
val controller = BottomDetailStateController(
|
||||
context = context,
|
||||
viewModel = viewModel,
|
||||
scope = scope,
|
||||
scaffoldState = scaffoldState
|
||||
)
|
||||
return remember(scope, scaffoldState) { controller }
|
||||
return remember(scope, viewModel, scaffoldState) { controller }
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Stable
|
||||
class BottomDetailStateController constructor(
|
||||
private val context: Context,
|
||||
private val viewModel: BookDetailViewModel,
|
||||
private val scope: CoroutineScope,
|
||||
val scaffoldState: BottomSheetScaffoldState,
|
||||
) {
|
||||
val detail = mutableStateOf<Int?>(null)
|
||||
var bookDetail = mutableStateOf<BookUio?>(null)
|
||||
private set
|
||||
|
||||
fun expandBookDetail(bookId: Int) {
|
||||
fun expandBookDetail(id: Int) {
|
||||
scope.launch {
|
||||
detail.value = bookId
|
||||
scaffoldState.bottomSheetState.expand()
|
||||
when (val book = viewModel.getDetail(id)) {
|
||||
is StateUio.Failure -> {
|
||||
context.showToast(message = context.getString(R.string.error_generic))
|
||||
}
|
||||
is StateUio.Success -> {
|
||||
bookDetail.value = book.value
|
||||
scaffoldState.bottomSheetState.expand()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun collapse() {
|
||||
scope.launch {
|
||||
scaffoldState.bottomSheetState.collapse()
|
||||
detail.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,19 +2,13 @@ package com.pixelized.biblib.ui.screen.detail
|
|||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.model.book.Book
|
||||
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.ui.navigation.screen.Screen
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.BookUio
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.CoverUio
|
||||
import com.pixelized.biblib.ui.screen.home.common.viewModel.ACoverViewModel
|
||||
|
|
@ -25,7 +19,7 @@ import com.pixelized.biblib.utils.extention.shortDate
|
|||
import com.pixelized.biblib.utils.painterResource
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -33,25 +27,17 @@ import javax.inject.Inject
|
|||
class BookDetailViewModel @Inject constructor(
|
||||
application: Application,
|
||||
bookCoverCache: BookCoverCache,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val client: IBibLibClient,
|
||||
) : ACoverViewModel(application, bookCoverCache) {
|
||||
|
||||
private val _state = mutableStateOf<StateUio<BookUio>>(StateUio.Progress())
|
||||
val state: State<StateUio<BookUio>> get() = _state
|
||||
|
||||
val book: State<BookUio?> = derivedStateOf {
|
||||
state.value.let { if (it is StateUio.Success<BookUio>) it.value else null }
|
||||
}
|
||||
|
||||
fun getDetail(id: Int) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
suspend fun getDetail(id: Int): StateUio<BookUio> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val book = getBookDetail(id = id)
|
||||
_state.value = StateUio.Success(book)
|
||||
StateUio.Success(book)
|
||||
} catch (exception: Exception) {
|
||||
Log.e("BookDetailViewModel", exception.message, exception)
|
||||
_state.value = StateUio.Failure(exception)
|
||||
StateUio.Failure(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -83,7 +69,7 @@ class BookDetailViewModel @Inject constructor(
|
|||
date = releaseDate.shortDate(),
|
||||
series = series?.name,
|
||||
description = synopsis ?: "",
|
||||
cover = cover(
|
||||
coverState = cover(
|
||||
placeHolder = thumbnailCover,
|
||||
type = CoverUio.Type.DETAIL,
|
||||
contentScale = ContentScale.FillHeight,
|
||||
|
|
@ -91,7 +77,4 @@ class BookDetailViewModel @Inject constructor(
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
private val SavedStateHandle.bookId: Int
|
||||
get() = get<Int>(Screen.BookDetail.ARG_BOOK_ID) ?: error("")
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.pixelized.biblib.ui.screen.detail
|
|||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
|
@ -16,7 +15,6 @@ 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.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -30,8 +28,8 @@ 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.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.composable.Cover
|
||||
import com.pixelized.biblib.ui.composable.SpannedText
|
||||
import com.pixelized.biblib.ui.composable.animation.AnimatedDelayer
|
||||
import com.pixelized.biblib.ui.composable.animation.AnimatedOffset
|
||||
|
|
@ -45,29 +43,27 @@ import com.pixelized.biblib.utils.extention.todo
|
|||
|
||||
@Composable
|
||||
fun DetailScreen(
|
||||
viewModel: BookDetailViewModel = hiltViewModel()
|
||||
detail: BookUio? = null,
|
||||
) {
|
||||
val bottomDetailState = LocalBottomDetailController.current
|
||||
if (detail != null) {
|
||||
DetailScreenContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
book = detail,
|
||||
)
|
||||
} else {
|
||||
Box(modifier = Modifier.fillMaxSize())
|
||||
}
|
||||
|
||||
Box {
|
||||
val book by viewModel.book
|
||||
book?.let {
|
||||
DetailScreenContent(
|
||||
book = it,
|
||||
onClose = {
|
||||
bottomDetailState.collapse()
|
||||
},
|
||||
)
|
||||
}
|
||||
val bottomDetailState = LocalBottomDetailController.current
|
||||
BackHandler {
|
||||
bottomDetailState.collapse()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun DetailScreenContent(
|
||||
modifier: Modifier = Modifier,
|
||||
book: BookUio,
|
||||
onClose: () -> Unit = todo(),
|
||||
onMobi: () -> Unit = todo(),
|
||||
onEpub: () -> Unit = todo(),
|
||||
onSend: () -> Unit = todo(),
|
||||
|
|
@ -87,36 +83,15 @@ private fun DetailScreenContent(
|
|||
.height(MaterialTheme.bibLib.dimen.detail.cover),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val cover by book.cover
|
||||
when (cover.type) {
|
||||
CoverUio.Type.PLACE_HOLDER -> {
|
||||
Image(
|
||||
modifier = Modifier.size(MaterialTheme.bibLib.dimen.detail.placeHolder),
|
||||
painter = cover.painter,
|
||||
contentScale = cover.contentScale,
|
||||
colorFilter = cover.colorFilter,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
CoverUio.Type.THUMBNAIL -> {
|
||||
Image(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
painter = cover.painter,
|
||||
contentScale = cover.contentScale,
|
||||
colorFilter = cover.colorFilter,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
CoverUio.Type.DETAIL -> {
|
||||
Image(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
painter = cover.painter,
|
||||
contentScale = cover.contentScale,
|
||||
colorFilter = cover.colorFilter,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
val sizeModifier = if (book.cover.type == CoverUio.Type.PLACE_HOLDER) {
|
||||
Modifier.size(MaterialTheme.bibLib.dimen.detail.placeHolder)
|
||||
} else {
|
||||
Modifier.fillMaxSize()
|
||||
}
|
||||
Cover(
|
||||
modifier = sizeModifier,
|
||||
cover = book.cover,
|
||||
)
|
||||
}
|
||||
|
||||
Row(modifier = Modifier.padding(vertical = MaterialTheme.bibLib.dimen.medium)) {
|
||||
|
|
@ -195,7 +170,7 @@ private fun DetailScreenContent(
|
|||
AnimatedOffset(modifier = Modifier.weight(1f)) {
|
||||
TitleLabel(
|
||||
title = stringResource(id = R.string.detail_rating),
|
||||
label = book.rating.toString(),
|
||||
label = book.rating?.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -206,13 +181,11 @@ private fun DetailScreenContent(
|
|||
)
|
||||
}
|
||||
|
||||
book.date?.let {
|
||||
AnimatedOffset(modifier = Modifier.weight(1f)) {
|
||||
TitleLabel(
|
||||
title = stringResource(id = R.string.detail_release),
|
||||
label = it,
|
||||
)
|
||||
}
|
||||
AnimatedOffset(modifier = Modifier.weight(1f)) {
|
||||
TitleLabel(
|
||||
title = stringResource(id = R.string.detail_release),
|
||||
label = book.date ?: "-" ,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -237,8 +210,6 @@ private fun DetailScreenContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(onBack = onClose)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -288,7 +259,7 @@ private fun DetailScreenContentPreview() {
|
|||
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 = cover,
|
||||
coverState = cover,
|
||||
)
|
||||
BibLibTheme {
|
||||
DetailScreenContent(book = book)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,10 @@ fun LazyBookThumbnailColumn(
|
|||
contentPadding = contentPadding,
|
||||
state = state,
|
||||
) {
|
||||
items(books) { thumbnail ->
|
||||
items(
|
||||
items = books,
|
||||
key = { it.id },
|
||||
) { thumbnail ->
|
||||
BookThumbnail(
|
||||
thumbnail = thumbnail,
|
||||
onClick = currentOnItemClick,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pixelized.biblib.ui.screen.home.common.uio
|
||||
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
|
||||
data class BookUio(
|
||||
val id: Int,
|
||||
|
|
@ -11,5 +12,7 @@ data class BookUio(
|
|||
val date: String?,
|
||||
val series: String?,
|
||||
val description: String,
|
||||
val cover: State<CoverUio>,
|
||||
)
|
||||
private val coverState: State<CoverUio>,
|
||||
) {
|
||||
val cover by coverState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ fun BooksPage(
|
|||
contentPadding = PaddingValues(all = MaterialTheme.bibLib.dimen.thumbnail.padding),
|
||||
books = booksViewModel.books,
|
||||
onItemClick = {
|
||||
bottomDetailState.expandBookDetail(bookId = it.id)
|
||||
bottomDetailState.expandBookDetail(id = it.id)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ fun NewsPage(
|
|||
contentPadding = PaddingValues(all = MaterialTheme.bibLib.dimen.thumbnail.padding),
|
||||
books = booksViewModel.news,
|
||||
onItemClick = {
|
||||
bottomDetail.expandBookDetail(bookId = it.id)
|
||||
bottomDetail.expandBookDetail(id = it.id)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue