Fix somes loading bugs. introduce Glide.
This commit is contained in:
parent
142957d490
commit
6aca43bc5f
35 changed files with 219 additions and 565 deletions
|
|
@ -18,6 +18,7 @@
|
|||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/Theme.BibLib.Starting">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
package com.pixelized.biblib
|
||||
|
||||
import android.app.Application
|
||||
import android.os.StrictMode
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
@HiltAndroidApp
|
||||
class BibLibApplication : Application()
|
||||
class BibLibApplication : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.enableDefaults()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,6 @@ import android.content.Context
|
|||
import android.content.SharedPreferences
|
||||
import androidx.room.Room
|
||||
import com.pixelized.biblib.database.BibLibDatabase
|
||||
import com.pixelized.biblib.utils.cache.BookCoverCache
|
||||
import com.pixelized.biblib.utils.cache.ImageCache
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -30,26 +28,6 @@ class PersistenceModule {
|
|||
return builder.fallbackToDestructiveMigration().build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBookCoverCache(
|
||||
imageCache: ImageCache,
|
||||
): BookCoverCache {
|
||||
return BookCoverCache(
|
||||
cache = imageCache
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideImageCache(
|
||||
@ApplicationContext context: Context,
|
||||
): ImageCache {
|
||||
return ImageCache(
|
||||
context = context
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePreferences(
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import androidx.activity.viewModels
|
|||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.google.accompanist.insets.ProvideWindowInsets
|
||||
import com.google.accompanist.systemuicontroller.SystemUiController
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.pixelized.biblib.ui.composable.SystemThemeColor
|
||||
|
|
@ -40,16 +40,8 @@ class MainActivity : ComponentActivity() {
|
|||
// Compose
|
||||
setContent {
|
||||
BibLibTheme {
|
||||
ProvideWindowInsets {
|
||||
SystemThemeColor {
|
||||
Surface(color = MaterialTheme.colors.background) {
|
||||
// Handle the system colors
|
||||
val systemUiController: SystemUiController = rememberSystemUiController()
|
||||
SystemThemeColor(
|
||||
systemUiController = systemUiController,
|
||||
statusDarkIcons = isSystemInDarkTheme().not(),
|
||||
navigationDarkIcons = isSystemInDarkTheme().not(),
|
||||
)
|
||||
|
||||
// Handle the main Navigation
|
||||
if (launcherViewModel.isLoading.not()) {
|
||||
ScreenNavHost(
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
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,
|
||||
)
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -26,16 +27,17 @@ import com.pixelized.biblib.ui.scaffold.SearchScaffoldState
|
|||
import com.pixelized.biblib.ui.scaffold.rememberSearchScaffoldState
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.utils.extention.bibLib
|
||||
import com.pixelized.biblib.utils.extention.todo
|
||||
import com.pixelized.biblib.utils.extention.default
|
||||
import com.skydoves.landscapist.glide.GlideImage
|
||||
|
||||
|
||||
@Composable
|
||||
fun Search(
|
||||
modifier: Modifier = Modifier,
|
||||
state: SearchScaffoldState = rememberSearchScaffoldState(),
|
||||
avatar: Painter,
|
||||
onAvatar: () -> Unit = todo(),
|
||||
onSearch: () -> Unit = todo(),
|
||||
avatar: String?,
|
||||
onAvatar: () -> Unit = default(),
|
||||
onSearch: () -> Unit = default(),
|
||||
) {
|
||||
val horizontalPadding by animateDpAsState(
|
||||
targetValue = when (state.isCollapsed()) {
|
||||
|
|
@ -77,24 +79,36 @@ fun Search(
|
|||
),
|
||||
onValueChange = { search = it }
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier.padding(end = horizontalPadding),
|
||||
onClick = onAvatar,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.clip(CircleShape),
|
||||
painter = avatar,
|
||||
contentDescription = null
|
||||
)
|
||||
avatar?.let {
|
||||
IconButton(
|
||||
modifier = Modifier.padding(end = horizontalPadding),
|
||||
onClick = onAvatar,
|
||||
) {
|
||||
GlideImage(
|
||||
modifier = Modifier.clip(CircleShape).size(32.dp),
|
||||
previewPlaceholder = R.drawable.ic_google,
|
||||
contentScale = ContentScale.Fit,
|
||||
imageModel = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun SearchContentEmptyPreview() {
|
||||
BibLibTheme {
|
||||
Search(avatar = null)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun SearchContentPreview() {
|
||||
BibLibTheme {
|
||||
Search(avatar = painterResource(R.drawable.ic_google))
|
||||
Search(avatar = "")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,35 @@
|
|||
package com.pixelized.biblib.ui.composable
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.google.accompanist.systemuicontroller.SystemUiController
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
|
||||
val LocalSystemUiController = compositionLocalOf<SystemUiController> { error("") }
|
||||
|
||||
@Composable
|
||||
fun SystemThemeColor(
|
||||
systemUiController: SystemUiController,
|
||||
color: Color = Color.Transparent,
|
||||
statusDarkIcons: Boolean,
|
||||
navigationDarkIcons: Boolean,
|
||||
) = SideEffect {
|
||||
systemUiController.setStatusBarColor(
|
||||
color = color,
|
||||
darkIcons = statusDarkIcons,
|
||||
)
|
||||
systemUiController.setNavigationBarColor(
|
||||
color = color,
|
||||
darkIcons = navigationDarkIcons,
|
||||
navigationBarContrastEnforced = false,
|
||||
)
|
||||
systemUiController: SystemUiController = rememberSystemUiController(),
|
||||
color: Color = Color.Black.copy(alpha = 0.3f),
|
||||
statusDarkIcons: Boolean = false,
|
||||
navigationDarkIcons: Boolean = false,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
CompositionLocalProvider(LocalSystemUiController provides systemUiController) {
|
||||
SideEffect {
|
||||
systemUiController.setStatusBarColor(
|
||||
color = color,
|
||||
darkIcons = statusDarkIcons,
|
||||
)
|
||||
systemUiController.setNavigationBarColor(
|
||||
color = color,
|
||||
darkIcons = navigationDarkIcons,
|
||||
navigationBarContrastEnforced = false,
|
||||
)
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import androidx.compose.runtime.Composable
|
|||
|
||||
@Composable
|
||||
fun AnimatedDelayer(
|
||||
delay: Int = 0,
|
||||
delay: Int = Delay.DELTA,
|
||||
content: @Composable AnimatedDelayerScope.() -> Unit
|
||||
) {
|
||||
val scope = AnimatedDelayerScope(
|
||||
|
|
@ -18,7 +18,7 @@ fun AnimatedDelayer(
|
|||
@Composable
|
||||
fun <T> AnimatedDelayer(
|
||||
targetState: T,
|
||||
delay: Int = 0,
|
||||
delay: Int = Delay.DELTA,
|
||||
content: @Composable AnimatedDelayerScope.(T) -> Unit
|
||||
) {
|
||||
AnimatedContent(
|
||||
|
|
@ -46,6 +46,6 @@ value class Delay(
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val DELTA = 100
|
||||
const val DELTA = 100
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ fun SearchScaffold(
|
|||
search: @Composable () -> Unit,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
var initialHeight by remember { mutableStateOf(0) }
|
||||
var initialCardBoxHeight by remember { mutableStateOf(0) }
|
||||
Layout(
|
||||
modifier = modifier,
|
||||
content = {
|
||||
|
|
@ -68,13 +68,15 @@ fun SearchScaffold(
|
|||
}
|
||||
) { measurables, constraints ->
|
||||
val cardBox = measurables.first { it.layoutId == "cardBox" }.measure(constraints)
|
||||
val contentBox = measurables.first { it.layoutId == "contentBox" }.measure(constraints)
|
||||
val contentBox = measurables.first { it.layoutId == "contentBox" }.measure(constraints.copy(
|
||||
maxHeight = constraints.maxHeight - initialCardBoxHeight
|
||||
))
|
||||
|
||||
layout(constraints.maxWidth, constraints.maxHeight) {
|
||||
if (initialHeight == 0) {
|
||||
initialHeight = cardBox.measuredHeight
|
||||
if (initialCardBoxHeight == 0) {
|
||||
initialCardBoxHeight = cardBox.measuredHeight
|
||||
}
|
||||
contentBox.place(x = 0, y = initialHeight)
|
||||
contentBox.place(x = 0, y = initialCardBoxHeight)
|
||||
cardBox.place(x = 0, y = 0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import androidx.compose.ui.text.withStyle
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import com.google.accompanist.insets.systemBarsPadding
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import com.pixelized.biblib.ui.composable.StateUio
|
||||
|
|
@ -52,6 +51,7 @@ fun AuthenticationScreen(
|
|||
val navHostController = LocalScreenNavHostController.current
|
||||
|
||||
AuthenticationScreenContent(
|
||||
modifier = Modifier.systemBarsPadding(),
|
||||
login = formViewModel.form.login,
|
||||
password = formViewModel.form.password,
|
||||
rememberPassword = formViewModel.form.remember,
|
||||
|
|
@ -90,6 +90,7 @@ fun AuthenticationScreen(
|
|||
|
||||
@Composable
|
||||
private fun AuthenticationScreenContent(
|
||||
modifier: Modifier = Modifier,
|
||||
login: String,
|
||||
onLoginChange: (String) -> Unit,
|
||||
password: String,
|
||||
|
|
@ -105,10 +106,9 @@ private fun AuthenticationScreenContent(
|
|||
|
||||
AnimatedDelayer {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
.systemBarsPadding()
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +1,23 @@
|
|||
package com.pixelized.biblib.ui.screen.detail
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.pixelized.biblib.R
|
||||
import androidx.lifecycle.ViewModel
|
||||
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.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
|
||||
import com.pixelized.biblib.utils.cache.BookCoverCache
|
||||
import com.pixelized.biblib.utils.extention.capitalize
|
||||
import com.pixelized.biblib.utils.extention.context
|
||||
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.withContext
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class BookDetailViewModel @Inject constructor(
|
||||
application: Application,
|
||||
bookCoverCache: BookCoverCache,
|
||||
private val client: IBibLibClient,
|
||||
) : ACoverViewModel(application, bookCoverCache) {
|
||||
) : ViewModel() {
|
||||
|
||||
suspend fun getDetail(id: Int): StateUio<BookUio> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
|
|
@ -50,16 +39,7 @@ class BookDetailViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun Book.toUio(): BookUio {
|
||||
val thumbnailCover by cover(
|
||||
placeHolder = CoverUio(
|
||||
type = CoverUio.Type.PLACE_HOLDER,
|
||||
contentScale = ContentScale.FillBounds,
|
||||
painter = painterResource(context, R.drawable.ic_baseline_auto_stories_24),
|
||||
),
|
||||
type = CoverUio.Type.THUMBNAIL,
|
||||
contentScale = ContentScale.FillHeight,
|
||||
url = URL("${IBibLibClient.THUMBNAIL_URL}/$id.jpg"),
|
||||
)
|
||||
|
||||
return BookUio(
|
||||
id = id,
|
||||
title = title,
|
||||
|
|
@ -69,12 +49,7 @@ class BookDetailViewModel @Inject constructor(
|
|||
date = releaseDate.shortDate(),
|
||||
series = series?.name,
|
||||
description = synopsis ?: "",
|
||||
coverState = cover(
|
||||
placeHolder = thumbnailCover,
|
||||
type = CoverUio.Type.DETAIL,
|
||||
contentScale = ContentScale.FillHeight,
|
||||
url = URL("${IBibLibClient.COVER_URL}/$id.jpg"),
|
||||
)
|
||||
cover = "${IBibLibClient.COVER_URL}/$id.jpg",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
|
@ -28,7 +27,6 @@ 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.StateUio
|
||||
import com.pixelized.biblib.ui.composable.animation.AnimatedDelayer
|
||||
|
|
@ -37,12 +35,13 @@ import com.pixelized.biblib.ui.composable.isSuccessful
|
|||
import com.pixelized.biblib.ui.scaffold.BottomDetailState
|
||||
import com.pixelized.biblib.ui.scaffold.LocalBottomDetailState
|
||||
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.profile.ProfileViewModel
|
||||
import com.pixelized.biblib.ui.screen.profile.UserUio
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.utils.extention.bibLib
|
||||
import com.pixelized.biblib.utils.extention.todo
|
||||
import com.pixelized.biblib.utils.extention.default
|
||||
import com.skydoves.landscapist.CircularReveal
|
||||
import com.skydoves.landscapist.glide.GlideImage
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
|
|
@ -103,9 +102,9 @@ fun DetailScreen(
|
|||
private fun DetailScreenContent(
|
||||
modifier: Modifier = Modifier,
|
||||
book: BookUio,
|
||||
onMobi: () -> Unit = todo(),
|
||||
onEpub: () -> Unit = todo(),
|
||||
onSend: () -> Unit = todo(),
|
||||
onMobi: () -> Unit = default(),
|
||||
onEpub: () -> Unit = default(),
|
||||
onSend: () -> Unit = default(),
|
||||
) {
|
||||
AnimatedDelayer(
|
||||
targetState = book,
|
||||
|
|
@ -117,23 +116,16 @@ private fun DetailScreenContent(
|
|||
.padding(all = MaterialTheme.bibLib.dimen.dp16)
|
||||
.then(modifier)
|
||||
) {
|
||||
AnimatedOffset(
|
||||
GlideImage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = MaterialTheme.bibLib.dimen.dp16)
|
||||
.height(MaterialTheme.bibLib.dimen.detail.cover),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
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,
|
||||
)
|
||||
}
|
||||
previewPlaceholder = R.drawable.ic_launcher_foreground,
|
||||
circularReveal = CircularReveal(duration = 1000),
|
||||
contentScale = ContentScale.FillHeight,
|
||||
imageModel = book.cover,
|
||||
)
|
||||
|
||||
Row(modifier = Modifier.padding(vertical = MaterialTheme.bibLib.dimen.dp16)) {
|
||||
AnimatedOffset(
|
||||
|
|
@ -318,16 +310,6 @@ private fun DetailScreenSendContent(
|
|||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun DetailScreenContentPreview() {
|
||||
val painter = painterResource(id = R.drawable.ic_baseline_auto_stories_24)
|
||||
val cover = remember {
|
||||
mutableStateOf(
|
||||
CoverUio(
|
||||
type = CoverUio.Type.PLACE_HOLDER,
|
||||
contentScale = ContentScale.FillBounds,
|
||||
painter = painter,
|
||||
)
|
||||
)
|
||||
}
|
||||
val book = BookUio(
|
||||
id = 90,
|
||||
title = "Foundation",
|
||||
|
|
@ -337,7 +319,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",
|
||||
coverState = cover,
|
||||
cover = "",
|
||||
)
|
||||
BibLibTheme {
|
||||
DetailScreenContent(book = book)
|
||||
|
|
|
|||
|
|
@ -44,10 +44,8 @@ fun HomeScreen(
|
|||
|
||||
BottomDetailScaffold {
|
||||
SearchScaffold(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
state = searchScaffoldState,
|
||||
modifier = Modifier
|
||||
.systemBarsPadding()
|
||||
.clipToBounds(),
|
||||
topBar = {
|
||||
Search(
|
||||
state = searchScaffoldState,
|
||||
|
|
@ -98,7 +96,7 @@ fun HomeScreenContent(
|
|||
val scope: CoroutineScope = rememberCoroutineScope()
|
||||
val pagerState: PagerState = rememberPagerState()
|
||||
|
||||
Column(modifier = Modifier.navigationBarsPadding()) {
|
||||
Column {
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
edgePadding = MaterialTheme.bibLib.dimen.dp16,
|
||||
|
|
@ -112,15 +110,16 @@ fun HomeScreenContent(
|
|||
}
|
||||
}
|
||||
HorizontalPager(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = pagerState,
|
||||
count = pages.size
|
||||
) {
|
||||
when (pages[it]) {
|
||||
is Page.News -> NewsPage()
|
||||
is Page.Books -> BooksPage()
|
||||
is Page.Author -> Box(modifier = Modifier.fillMaxSize())
|
||||
is Page.Series -> Box(modifier = Modifier.fillMaxSize())
|
||||
is Page.Tag -> Box(modifier = Modifier.fillMaxSize())
|
||||
is Page.Author -> Box(modifier = Modifier)
|
||||
is Page.Series -> Box(modifier = Modifier)
|
||||
is Page.Tag -> Box(modifier = Modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,15 @@
|
|||
package com.pixelized.biblib.ui.screen.home
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.pixelized.biblib.R
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.biblib.repository.googleSignIn.IGoogleSingInRepository
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.CoverUio
|
||||
import com.pixelized.biblib.ui.screen.home.common.viewModel.ACoverViewModel
|
||||
import com.pixelized.biblib.utils.cache.BookCoverCache
|
||||
import com.pixelized.biblib.utils.extention.context
|
||||
import com.pixelized.biblib.utils.painterResource
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class HomeViewModel @Inject constructor(
|
||||
application: Application,
|
||||
cacheBook: BookCoverCache,
|
||||
private val account: IGoogleSingInRepository,
|
||||
) : ACoverViewModel(application, cacheBook) {
|
||||
|
||||
private val _avatar = mutableStateOf(
|
||||
painterResource(context, R.drawable.ic_launcher_foreground)
|
||||
)
|
||||
val avatar by _avatar
|
||||
|
||||
init {
|
||||
updateAvatar()
|
||||
}
|
||||
|
||||
private fun updateAvatar() {
|
||||
account.account?.photoUrl?.let {
|
||||
_avatar.value = cover(
|
||||
placeHolder = CoverUio(
|
||||
type = CoverUio.Type.PLACE_HOLDER,
|
||||
painter = painterResource(context, R.drawable.ic_launcher_foreground),
|
||||
),
|
||||
type = CoverUio.Type.DETAIL,
|
||||
contentScale = ContentScale.None,
|
||||
url = URL("$it"),
|
||||
).value.painter
|
||||
}
|
||||
}
|
||||
account: IGoogleSingInRepository,
|
||||
) : ViewModel() {
|
||||
val avatar by mutableStateOf(account.account?.photoUrl?.toString())
|
||||
}
|
||||
|
|
@ -1,26 +1,15 @@
|
|||
package com.pixelized.biblib.ui.screen.home.common.composable
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.BookThumbnailUio
|
||||
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.CoverUio
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
@Composable
|
||||
fun bookPreviewResources(): LazyPagingItems<BookThumbnailUio> {
|
||||
val cover = CoverUio(
|
||||
type = CoverUio.Type.PLACE_HOLDER,
|
||||
painter = painterResource(id = R.drawable.ic_baseline_auto_stories_24),
|
||||
contentScale = ContentScale.None,
|
||||
)
|
||||
val thumbnails = listOf(
|
||||
BookThumbnailUio(
|
||||
id = 112,
|
||||
|
|
@ -29,7 +18,7 @@ fun bookPreviewResources(): LazyPagingItems<BookThumbnailUio> {
|
|||
date = "1988",
|
||||
genre = "Sci-Fi",
|
||||
isNew = false,
|
||||
cover = remember { mutableStateOf(cover) },
|
||||
cover = "",
|
||||
),
|
||||
BookThumbnailUio(
|
||||
id = 78,
|
||||
|
|
@ -38,7 +27,7 @@ fun bookPreviewResources(): LazyPagingItems<BookThumbnailUio> {
|
|||
date = "1993",
|
||||
genre = "Sci-Fi",
|
||||
isNew = false,
|
||||
cover = remember { mutableStateOf(cover) },
|
||||
cover = "",
|
||||
),
|
||||
BookThumbnailUio(
|
||||
id = 90,
|
||||
|
|
@ -47,7 +36,7 @@ fun bookPreviewResources(): LazyPagingItems<BookThumbnailUio> {
|
|||
date = "1951",
|
||||
genre = "Sci-Fi",
|
||||
isNew = false,
|
||||
cover = remember { mutableStateOf(cover) },
|
||||
cover = "",
|
||||
),
|
||||
BookThumbnailUio(
|
||||
id = 184,
|
||||
|
|
@ -56,7 +45,7 @@ fun bookPreviewResources(): LazyPagingItems<BookThumbnailUio> {
|
|||
date = "1952",
|
||||
genre = "Sci-Fi",
|
||||
isNew = false,
|
||||
cover = remember { mutableStateOf(cover) },
|
||||
cover = "",
|
||||
),
|
||||
BookThumbnailUio(
|
||||
id = 185,
|
||||
|
|
@ -65,7 +54,7 @@ fun bookPreviewResources(): LazyPagingItems<BookThumbnailUio> {
|
|||
date = "1953",
|
||||
genre = "Sci-Fi",
|
||||
isNew = false,
|
||||
cover = remember { mutableStateOf(cover) },
|
||||
cover = "",
|
||||
),
|
||||
BookThumbnailUio(
|
||||
id = 119,
|
||||
|
|
@ -74,7 +63,7 @@ fun bookPreviewResources(): LazyPagingItems<BookThumbnailUio> {
|
|||
date = "1982",
|
||||
genre = "Sci-Fi",
|
||||
isNew = false,
|
||||
cover = remember { mutableStateOf(cover) },
|
||||
cover = "",
|
||||
),
|
||||
BookThumbnailUio(
|
||||
id = 163,
|
||||
|
|
@ -83,7 +72,7 @@ fun bookPreviewResources(): LazyPagingItems<BookThumbnailUio> {
|
|||
date = "1986",
|
||||
genre = "Sci-Fi",
|
||||
isNew = false,
|
||||
cover = remember { mutableStateOf(cover) },
|
||||
cover = "",
|
||||
),
|
||||
)
|
||||
return flowOf(PagingData.from(thumbnails)).collectAsLazyPagingItems()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package com.pixelized.biblib.ui.screen.home.common.uio
|
||||
|
||||
import androidx.compose.runtime.State
|
||||
|
||||
class BookThumbnailUio(
|
||||
val id: Int,
|
||||
val genre: String,
|
||||
|
|
@ -9,5 +7,5 @@ class BookThumbnailUio(
|
|||
val author: String,
|
||||
val date: String?,
|
||||
val isNew: Boolean,
|
||||
val cover: State<CoverUio>,
|
||||
val cover: String,
|
||||
)
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
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,
|
||||
val title: String,
|
||||
|
|
@ -12,7 +9,5 @@ data class BookUio(
|
|||
val date: String?,
|
||||
val series: String?,
|
||||
val description: String,
|
||||
private val coverState: State<CoverUio>,
|
||||
) {
|
||||
val cover by coverState
|
||||
}
|
||||
val cover: String,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
package com.pixelized.biblib.ui.screen.home.common.uio
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
|
||||
@Stable
|
||||
@Immutable
|
||||
data class CoverUio(
|
||||
val type: Type,
|
||||
val contentScale: ContentScale = ContentScale.FillBounds,
|
||||
val colorFilter: ColorFilter? = null,
|
||||
val painter: Painter,
|
||||
) {
|
||||
enum class Type {
|
||||
PLACE_HOLDER,
|
||||
THUMBNAIL,
|
||||
DETAIL,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package com.pixelized.biblib.ui.screen.home.common.viewModel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.CoverUio
|
||||
import com.pixelized.biblib.utils.cache.BookCoverCache
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.net.URL
|
||||
|
||||
|
||||
abstract class ACoverViewModel(
|
||||
application: Application,
|
||||
private val cacheBook: BookCoverCache,
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
fun cover(
|
||||
cacheBook: BookCoverCache = this.cacheBook,
|
||||
coroutineScope: CoroutineScope = viewModelScope,
|
||||
placeHolder: CoverUio,
|
||||
type: CoverUio.Type,
|
||||
contentScale: ContentScale = ContentScale.FillBounds,
|
||||
tint: ColorFilter? = null,
|
||||
url: URL,
|
||||
) = cacheBook.cover(
|
||||
placeHolder = placeHolder,
|
||||
coroutineScope = coroutineScope,
|
||||
type = type,
|
||||
contentScale = contentScale,
|
||||
tint = tint,
|
||||
url = url,
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val PAGING_SIZE = 30
|
||||
}
|
||||
}
|
||||
|
|
@ -8,18 +8,18 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.CoverUio
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.utils.extention.bibLib
|
||||
import com.skydoves.landscapist.glide.GlideImage
|
||||
|
||||
@Composable
|
||||
fun BookThumbnail(
|
||||
|
|
@ -40,9 +40,10 @@ fun BookThumbnail(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
thumbnail?.cover?.let { it ->
|
||||
Cover(
|
||||
GlideImage(
|
||||
modifier = Modifier.size(MaterialTheme.bibLib.dimen.thumbnail.cover),
|
||||
image = it,
|
||||
previewPlaceholder = R.drawable.ic_launcher_foreground,
|
||||
imageModel = it,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
|
|
@ -51,16 +52,19 @@ fun BookThumbnail(
|
|||
.padding(MaterialTheme.bibLib.dimen.dp8)
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.body1,
|
||||
modifier = Modifier.padding(bottom = MaterialTheme.bibLib.dimen.dp4),
|
||||
style = MaterialTheme.typography.h6,
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
text = thumbnail?.title ?: "",
|
||||
maxLines = 1,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
Text(
|
||||
style = MaterialTheme.typography.caption,
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
text = thumbnail?.author ?: ""
|
||||
)
|
||||
|
||||
|
|
@ -86,33 +90,10 @@ fun BookThumbnail(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun Cover(
|
||||
modifier: Modifier = Modifier,
|
||||
image: State<CoverUio>
|
||||
) {
|
||||
val cover by image
|
||||
AnimatedContent(
|
||||
targetState = cover.painter,
|
||||
transitionSpec = { fadeIn() with fadeOut() }
|
||||
) {
|
||||
Image(
|
||||
modifier = modifier,
|
||||
alignment = Alignment.Center,
|
||||
contentScale = cover.contentScale,
|
||||
colorFilter = cover.colorFilter,
|
||||
painter = cover.painter,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun BookThumbnailPreview() {
|
||||
val painter = painterResource(id = R.drawable.ic_baseline_auto_stories_24)
|
||||
val thumbnail = BookThumbnailUio(
|
||||
id = 0,
|
||||
genre = "Sci-Fi",
|
||||
|
|
@ -120,15 +101,7 @@ private fun BookThumbnailPreview() {
|
|||
author = "Asimov",
|
||||
date = "1951",
|
||||
isNew = false,
|
||||
cover = remember {
|
||||
mutableStateOf(
|
||||
CoverUio(
|
||||
type = CoverUio.Type.PLACE_HOLDER,
|
||||
contentScale = ContentScale.None,
|
||||
painter = painter,
|
||||
)
|
||||
)
|
||||
},
|
||||
cover = "",
|
||||
)
|
||||
BibLibTheme {
|
||||
BookThumbnail(thumbnail = thumbnail)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.pixelized.biblib.ui.screen.home.page.books
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
|
|
@ -23,6 +22,7 @@ import com.pixelized.biblib.ui.screen.home.common.composable.bookPreviewResource
|
|||
import com.pixelized.biblib.ui.screen.home.common.uio.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.utils.extention.bibLib
|
||||
import com.pixelized.biblib.utils.extention.navigationBarsHeight
|
||||
|
||||
|
||||
@Composable
|
||||
|
|
@ -38,7 +38,6 @@ fun BooksPage(
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun BooksPageContent(
|
||||
isNetworkAvailable: () -> State<Boolean>,
|
||||
|
|
@ -51,7 +50,10 @@ private fun BooksPageContent(
|
|||
isNetworkAvailable = isNetworkAvailable,
|
||||
)
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(vertical = MaterialTheme.bibLib.dimen.thumbnail.padding),
|
||||
contentPadding = PaddingValues(
|
||||
top = MaterialTheme.bibLib.dimen.thumbnail.padding,
|
||||
bottom = MaterialTheme.bibLib.dimen.thumbnail.padding + navigationBarsHeight(),
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement),
|
||||
) {
|
||||
items(
|
||||
|
|
|
|||
|
|
@ -1,34 +1,24 @@
|
|||
package com.pixelized.biblib.ui.screen.home.page.books
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.model.book.Book
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import com.pixelized.biblib.repository.book.IBookRepository
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.CoverUio
|
||||
import com.pixelized.biblib.ui.screen.home.common.viewModel.ACoverViewModel
|
||||
import com.pixelized.biblib.utils.cache.BookCoverCache
|
||||
import com.pixelized.biblib.utils.extention.context
|
||||
import com.pixelized.biblib.utils.extention.longDate
|
||||
import com.pixelized.biblib.utils.painterResource
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@HiltViewModel
|
||||
class BooksViewModel @Inject constructor(
|
||||
application: Application,
|
||||
bookRepository: IBookRepository,
|
||||
cacheBook: BookCoverCache,
|
||||
) : ACoverViewModel(application, cacheBook) {
|
||||
) : ViewModel() {
|
||||
|
||||
private val booksSource = Pager(
|
||||
config = PagingConfig(pageSize = PAGING_SIZE),
|
||||
|
|
@ -46,14 +36,10 @@ class BooksViewModel @Inject constructor(
|
|||
author = author.joinToString { it.name },
|
||||
date = releaseDate.longDate(),
|
||||
isNew = isNew,
|
||||
cover = cover(
|
||||
placeHolder = CoverUio(
|
||||
type = CoverUio.Type.PLACE_HOLDER,
|
||||
contentScale = ContentScale.None,
|
||||
painter = painterResource(context, R.drawable.ic_baseline_auto_stories_24),
|
||||
),
|
||||
type = CoverUio.Type.THUMBNAIL,
|
||||
url = URL("${IBibLibClient.THUMBNAIL_URL}/$id.jpg"),
|
||||
)
|
||||
cover = "${IBibLibClient.THUMBNAIL_URL}/$id.jpg"
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val PAGING_SIZE = 16
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.pixelized.biblib.ui.screen.home.page.news
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
|
|
@ -10,20 +9,17 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.CoverUio
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.utils.extention.bibLib
|
||||
import com.skydoves.landscapist.glide.GlideImage
|
||||
import com.pixelized.biblib.R
|
||||
|
||||
@Composable
|
||||
fun NewThumbnail(
|
||||
|
|
@ -35,12 +31,13 @@ fun NewThumbnail(
|
|||
modifier = modifier.clickable { thumbnail?.let(onClick) }
|
||||
) {
|
||||
thumbnail?.cover?.let { it ->
|
||||
Cover(
|
||||
GlideImage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(MaterialTheme.bibLib.shape.base.medium)
|
||||
.aspectRatio(64f / 102f),
|
||||
image = it,
|
||||
previewPlaceholder = R.drawable.ic_baseline_auto_stories_24,
|
||||
imageModel = it,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -66,33 +63,10 @@ fun NewThumbnail(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun Cover(
|
||||
modifier: Modifier = Modifier,
|
||||
image: State<CoverUio>
|
||||
) {
|
||||
val cover by image
|
||||
AnimatedContent(
|
||||
targetState = cover.painter,
|
||||
transitionSpec = { fadeIn() with fadeOut() }
|
||||
) {
|
||||
Image(
|
||||
modifier = modifier,
|
||||
alignment = Alignment.Center,
|
||||
contentScale = cover.contentScale,
|
||||
colorFilter = cover.colorFilter,
|
||||
painter = cover.painter,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun BookThumbnailPreview() {
|
||||
val painter = painterResource(id = R.drawable.ic_baseline_auto_stories_24)
|
||||
val thumbnail = BookThumbnailUio(
|
||||
id = 0,
|
||||
genre = "Sci-Fi",
|
||||
|
|
@ -100,15 +74,7 @@ private fun BookThumbnailPreview() {
|
|||
author = "Asimov",
|
||||
date = "1951",
|
||||
isNew = false,
|
||||
cover = remember {
|
||||
mutableStateOf(
|
||||
CoverUio(
|
||||
type = CoverUio.Type.PLACE_HOLDER,
|
||||
contentScale = ContentScale.None,
|
||||
painter = painter,
|
||||
)
|
||||
)
|
||||
},
|
||||
cover = "",
|
||||
)
|
||||
BibLibTheme {
|
||||
NewThumbnail(
|
||||
|
|
|
|||
|
|
@ -1,37 +1,26 @@
|
|||
package com.pixelized.biblib.ui.screen.home.page.news
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.model.book.Book
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import com.pixelized.biblib.repository.book.IBookRepository
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.CoverUio
|
||||
import com.pixelized.biblib.ui.screen.home.common.viewModel.ACoverViewModel
|
||||
import com.pixelized.biblib.utils.cache.BookCoverCache
|
||||
import com.pixelized.biblib.utils.extention.context
|
||||
import com.pixelized.biblib.utils.extention.longDate
|
||||
import com.pixelized.biblib.utils.painterResource
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@HiltViewModel
|
||||
class NewsBookViewModel @Inject constructor(
|
||||
application: Application,
|
||||
bookRepository: IBookRepository,
|
||||
cacheBook: BookCoverCache,
|
||||
) : ACoverViewModel(application, cacheBook) {
|
||||
) : ViewModel() {
|
||||
|
||||
private val newsSource: Flow<PagingData<BookThumbnailUio>> = Pager(
|
||||
config = PagingConfig(pageSize = PAGING_SIZE),
|
||||
|
|
@ -42,16 +31,7 @@ class NewsBookViewModel @Inject constructor(
|
|||
|
||||
val news @Composable get() = newsSource.collectAsLazyPagingItems()
|
||||
|
||||
private fun Book.toThumbnail() : BookThumbnailUio{
|
||||
val thumbnail by cover(
|
||||
placeHolder = CoverUio(
|
||||
type = CoverUio.Type.PLACE_HOLDER,
|
||||
contentScale = ContentScale.None,
|
||||
painter = painterResource(context, R.drawable.ic_baseline_auto_stories_24),
|
||||
),
|
||||
type = CoverUio.Type.THUMBNAIL,
|
||||
url = URL("${IBibLibClient.THUMBNAIL_URL}/$id.jpg"),
|
||||
)
|
||||
private fun Book.toThumbnail(): BookThumbnailUio {
|
||||
return BookThumbnailUio(
|
||||
id = id,
|
||||
genre = genre?.joinToString { it.name } ?: "",
|
||||
|
|
@ -59,11 +39,11 @@ class NewsBookViewModel @Inject constructor(
|
|||
author = author.joinToString { it.name },
|
||||
date = releaseDate.longDate(),
|
||||
isNew = isNew,
|
||||
cover = cover(
|
||||
placeHolder = thumbnail,
|
||||
type = CoverUio.Type.DETAIL,
|
||||
url = URL("${IBibLibClient.COVER_URL}/$id.jpg"),
|
||||
)
|
||||
cover = "${IBibLibClient.COVER_URL}/$id.jpg"
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PAGING_SIZE = 8
|
||||
}
|
||||
}
|
||||
|
|
@ -2,16 +2,9 @@ package com.pixelized.biblib.ui.screen.home.page.news
|
|||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridScope
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
|
|
@ -21,7 +14,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.items
|
||||
import com.pixelized.biblib.ui.scaffold.BottomDetailState
|
||||
import com.pixelized.biblib.ui.scaffold.LocalBottomDetailState
|
||||
import com.pixelized.biblib.ui.screen.connectivity.ConnectivityViewModel
|
||||
|
|
@ -30,7 +22,7 @@ import com.pixelized.biblib.ui.screen.home.common.composable.bookPreviewResource
|
|||
import com.pixelized.biblib.ui.screen.home.common.uio.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.utils.extention.bibLib
|
||||
import java.util.*
|
||||
import com.pixelized.biblib.utils.extention.navigationBarsHeight
|
||||
|
||||
@Composable
|
||||
fun NewsPage(
|
||||
|
|
@ -58,10 +50,16 @@ private fun NewsPageContent(
|
|||
isNetworkAvailable = isNetworkAvailable,
|
||||
)
|
||||
LazyVerticalGrid(
|
||||
modifier = Modifier.weight(1f),
|
||||
columns = GridCells.Fixed(2),
|
||||
contentPadding = PaddingValues(all = MaterialTheme.bibLib.dimen.thumbnail.padding),
|
||||
contentPadding = PaddingValues(
|
||||
start = MaterialTheme.bibLib.dimen.thumbnail.padding,
|
||||
end = MaterialTheme.bibLib.dimen.thumbnail.padding,
|
||||
top = MaterialTheme.bibLib.dimen.thumbnail.padding,
|
||||
bottom = MaterialTheme.bibLib.dimen.thumbnail.padding + navigationBarsHeight(),
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement)
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement),
|
||||
) {
|
||||
items(
|
||||
count = books.itemCount
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import com.pixelized.biblib.network.client.IBibLibClient
|
|||
import com.pixelized.biblib.ui.composable.StateUio
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.utils.extention.bibLib
|
||||
import com.pixelized.biblib.utils.extention.todo
|
||||
import com.pixelized.biblib.utils.extention.default
|
||||
|
||||
@Composable
|
||||
fun ProfileScreen(
|
||||
|
|
@ -33,7 +33,7 @@ fun ProfileScreen(
|
|||
onEditClick = {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(IBibLibClient.EDIT_PROFILE))
|
||||
context.startActivity(intent)
|
||||
}
|
||||
},
|
||||
)
|
||||
is StateUio.Failure -> Unit
|
||||
}
|
||||
|
|
@ -43,7 +43,8 @@ fun ProfileScreen(
|
|||
private fun ProfileScreenContent(
|
||||
modifier: Modifier = Modifier,
|
||||
user: UserUio,
|
||||
onEditClick: () -> Unit = todo()
|
||||
onEditClick: () -> Unit = default(),
|
||||
onLogoutClick: () -> Unit = default(),
|
||||
) {
|
||||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
|
|
@ -107,8 +108,19 @@ private fun ProfileScreenContent(
|
|||
text = "Edit profile"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.padding(top = MaterialTheme.bibLib.dimen.dp8)
|
||||
.align(Alignment.End),
|
||||
colors = ButtonDefaults.outlinedButtonColors(),
|
||||
onClick = onLogoutClick,
|
||||
) {
|
||||
Text(
|
||||
text = "Logout"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
package com.pixelized.biblib.ui.screen.profile
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.biblib.model.user.User
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import com.pixelized.biblib.network.factory.UserFactory
|
||||
import com.pixelized.biblib.ui.composable.StateUio
|
||||
import com.pixelized.biblib.ui.screen.home.common.viewModel.ACoverViewModel
|
||||
import com.pixelized.biblib.utils.cache.BookCoverCache
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -19,10 +17,8 @@ import javax.inject.Inject
|
|||
|
||||
@HiltViewModel
|
||||
class ProfileViewModel @Inject constructor(
|
||||
application: Application,
|
||||
cacheBook: BookCoverCache,
|
||||
private val client: IBibLibClient,
|
||||
) : ACoverViewModel(application, cacheBook) {
|
||||
) : ViewModel() {
|
||||
|
||||
var user by mutableStateOf<StateUio<UserUio>>(StateUio.Progress())
|
||||
private set
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ data class BibLibDimen(
|
|||
data class BookThumbnail(
|
||||
val padding: Dp = 16.dp,
|
||||
val arrangement: Dp = 16.dp,
|
||||
val cover: DpSize = DpSize(64.dp, 102.dp), // ratio 1.6
|
||||
val cover: DpSize = DpSize(width = 72.dp, height = 115.dp), // ratio 1.6
|
||||
val corner: Dp = 8.dp,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
package com.pixelized.biblib.utils.cache
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.pixelized.biblib.ui.screen.home.common.uio.CoverUio
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
class BookCoverCache @Inject constructor(
|
||||
private val cache: ImageCache
|
||||
) {
|
||||
fun cover(
|
||||
coroutineScope: CoroutineScope,
|
||||
placeHolder: CoverUio,
|
||||
url: URL,
|
||||
type: CoverUio.Type,
|
||||
contentScale: ContentScale,
|
||||
tint: ColorFilter?,
|
||||
): State<CoverUio> {
|
||||
// read the cache a convert it to a UIO.
|
||||
val cache = cache.readFromDisk(url)?.let {
|
||||
CoverUio(
|
||||
type = type,
|
||||
contentScale = contentScale,
|
||||
colorFilter = tint,
|
||||
painter = BitmapPainter(it.asImageBitmap()),
|
||||
)
|
||||
}
|
||||
// publish the cache and stop there, or publish the placeHolder and download the proper file.
|
||||
return mutableStateOf(cache ?: placeHolder).also {
|
||||
if (cache == null) {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
it.download(
|
||||
type = type,
|
||||
contentScale = contentScale,
|
||||
tint = tint,
|
||||
url = url,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableState<CoverUio>.download(
|
||||
type: CoverUio.Type,
|
||||
contentScale: ContentScale,
|
||||
tint: ColorFilter?,
|
||||
url: URL,
|
||||
) {
|
||||
try {
|
||||
val bitmap = BitmapFactory.decodeStream(url.openStream())
|
||||
val painter = BitmapPainter(bitmap.asImageBitmap())
|
||||
cache.writeToDisk(url, bitmap)
|
||||
value = CoverUio(
|
||||
type = type,
|
||||
contentScale = contentScale,
|
||||
colorFilter = tint,
|
||||
painter = painter,
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
Log.w("CoverCache", "Fail to download: {$url}", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package com.pixelized.biblib.utils.cache
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
class ImageCache @Inject constructor(context: Context) {
|
||||
|
||||
private var cache: File? = context.cacheDir
|
||||
|
||||
private fun file(url: URL): File = File(cache?.absolutePath + url.file)
|
||||
|
||||
fun writeToDisk(url: URL, bitmap: Bitmap) {
|
||||
val file = file(url)
|
||||
try {
|
||||
file.mkdirs()
|
||||
file.delete()
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, file.outputStream())
|
||||
} catch (e: Exception) {
|
||||
Log.e("BitmapCache", "bitmap?.compress() FAILED !", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun readFromDisk(url: URL): Bitmap? {
|
||||
val file = file(url)
|
||||
return try {
|
||||
if (file.exists()) BitmapFactory.decodeStream(file.inputStream()) else null
|
||||
} catch (e: IOException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ fun Context.notImplemented() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun todo(): () -> Unit {
|
||||
fun default(): () -> Unit {
|
||||
val context = LocalContext.current
|
||||
return { context.notImplemented() }
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package com.pixelized.biblib.utils.extention
|
||||
|
||||
import android.util.Log
|
||||
|
||||
fun logC(message: String): Int = Log.v("Composition", message)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.pixelized.biblib.utils.extention
|
||||
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
@Composable
|
||||
fun navigationBarsHeight(): Dp =
|
||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
||||
|
|
@ -1,7 +1,18 @@
|
|||
<vector android:height="24dp" android:viewportHeight="512"
|
||||
android:viewportWidth="512" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FBBB00" android:pathData="M113.47,309.408L95.648,375.94l-65.139,1.378C11.042,341.211 0,299.9 0,256c0,-42.451 10.324,-82.483 28.624,-117.732h0.014l57.992,10.632l25.404,57.644c-5.317,15.501 -8.215,32.141 -8.215,49.456C103.821,274.792 107.225,292.797 113.47,309.408z"/>
|
||||
<path android:fillColor="#518EF8" android:pathData="M507.527,208.176C510.467,223.662 512,239.655 512,256c0,18.328 -1.927,36.206 -5.598,53.451c-12.462,58.683 -45.025,109.925 -90.134,146.187l-0.014,-0.014l-73.044,-3.727l-10.338,-64.535c29.932,-17.554 53.324,-45.025 65.646,-77.911h-136.89V208.176h138.887L507.527,208.176L507.527,208.176z"/>
|
||||
<path android:fillColor="#28B446" android:pathData="M416.253,455.624l0.014,0.014C372.396,490.901 316.666,512 256,512c-97.491,0 -182.252,-54.491 -225.491,-134.681l82.961,-67.91c21.619,57.698 77.278,98.771 142.53,98.771c28.047,0 54.323,-7.582 76.87,-20.818L416.253,455.624z"/>
|
||||
<path android:fillColor="#F14336" android:pathData="M419.404,58.936l-82.933,67.896c-23.335,-14.586 -50.919,-23.012 -80.471,-23.012c-66.729,0 -123.429,42.957 -143.965,102.724l-83.397,-68.276h-0.014C71.23,56.123 157.06,0 256,0C318.115,0 375.068,22.126 419.404,58.936z"/>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:fillColor="#FBBB00"
|
||||
android:pathData="M113.47,309.408L95.648,375.94l-65.139,1.378C11.042,341.211 0,299.9 0,256c0,-42.451 10.324,-82.483 28.624,-117.732h0.014l57.992,10.632l25.404,57.644c-5.317,15.501 -8.215,32.141 -8.215,49.456C103.821,274.792 107.225,292.797 113.47,309.408z" />
|
||||
<path
|
||||
android:fillColor="#518EF8"
|
||||
android:pathData="M507.527,208.176C510.467,223.662 512,239.655 512,256c0,18.328 -1.927,36.206 -5.598,53.451c-12.462,58.683 -45.025,109.925 -90.134,146.187l-0.014,-0.014l-73.044,-3.727l-10.338,-64.535c29.932,-17.554 53.324,-45.025 65.646,-77.911h-136.89V208.176h138.887L507.527,208.176L507.527,208.176z" />
|
||||
<path
|
||||
android:fillColor="#28B446"
|
||||
android:pathData="M416.253,455.624l0.014,0.014C372.396,490.901 316.666,512 256,512c-97.491,0 -182.252,-54.491 -225.491,-134.681l82.961,-67.91c21.619,57.698 77.278,98.771 142.53,98.771c28.047,0 54.323,-7.582 76.87,-20.818L416.253,455.624z" />
|
||||
<path
|
||||
android:fillColor="#F14336"
|
||||
android:pathData="M419.404,58.936l-82.933,67.896c-23.335,-14.586 -50.919,-23.012 -80.471,-23.012c-66.729,0 -123.429,42.957 -143.965,102.724l-83.397,-68.276h-0.014C71.23,56.123 157.06,0 256,0C318.115,0 375.068,22.126 419.404,58.936z" />
|
||||
</vector>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue