Change to Toolbar layout
This commit is contained in:
parent
7af9eb8acc
commit
db7d6d8ab8
25 changed files with 544 additions and 157 deletions
|
|
@ -1,6 +1,6 @@
|
|||
package com.pixelized.biblib.model.user
|
||||
|
||||
import java.util.Date
|
||||
import java.util.*
|
||||
|
||||
data class User(
|
||||
val id: String,
|
||||
|
|
@ -10,4 +10,11 @@ data class User(
|
|||
val firstname: String?,
|
||||
val lastname: String?,
|
||||
val amazonEmails: List<String>,
|
||||
val downloadedBooks: List<DownloadedBooks>,
|
||||
)
|
||||
|
||||
data class DownloadedBooks(
|
||||
val bookId: Int,
|
||||
val date: Date,
|
||||
val format: String,
|
||||
)
|
||||
|
|
@ -4,7 +4,8 @@ import android.content.Context
|
|||
import android.content.SharedPreferences
|
||||
import androidx.room.Room
|
||||
import com.pixelized.biblib.database.BibLibDatabase
|
||||
import com.pixelized.biblib.utils.CoverCache
|
||||
import com.pixelized.biblib.utils.cache.BookCoverCache
|
||||
import com.pixelized.biblib.utils.cache.ImageCache
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -31,10 +32,22 @@ class PersistenceModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBitmapCache(
|
||||
fun provideBookCoverCache(
|
||||
imageCache: ImageCache,
|
||||
): BookCoverCache {
|
||||
return BookCoverCache(
|
||||
cache = imageCache
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideImageCache(
|
||||
@ApplicationContext context: Context,
|
||||
): CoverCache {
|
||||
return CoverCache(context)
|
||||
): ImageCache {
|
||||
return ImageCache(
|
||||
context = context
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ interface IBibLibClient {
|
|||
const val THUMBNAIL_URL = "$BASE_URL/api/book/thumbnail"
|
||||
const val COVER_URL = "$BASE_URL/api/book/cover"
|
||||
const val REGISTER_URL = "$BASE_URL/signup"
|
||||
const val EDIT_PROFILE = "$BASE_URL/profile"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.pixelized.biblib.network.factory
|
||||
|
||||
import com.pixelized.biblib.model.user.DownloadedBooks
|
||||
import com.pixelized.biblib.model.user.User
|
||||
import com.pixelized.biblib.network.data.response.UserResponse
|
||||
import com.pixelized.biblib.utils.exception.MandatoryFieldMissingException
|
||||
|
|
@ -23,6 +24,21 @@ class UserFactory {
|
|||
val lastname = response.data?.local?.lastname
|
||||
val amazonEmails = response.data?.local?.amazonEmails
|
||||
|
||||
val downloadedBooks = response.data?.history?.downloadedBooks?.mapNotNull { book ->
|
||||
val bookId = book.id
|
||||
val date = book.date?.let { parser.parse(it) }
|
||||
val format = book.data?.data_format
|
||||
if (bookId != null && date != null && format != null) {
|
||||
DownloadedBooks(
|
||||
bookId = bookId,
|
||||
date = date,
|
||||
format = format,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return User(
|
||||
id = id ?: throw error("id"),
|
||||
created = created ?: throw error("created"),
|
||||
|
|
@ -31,7 +47,7 @@ class UserFactory {
|
|||
firstname = firstname,
|
||||
lastname = lastname,
|
||||
amazonEmails = amazonEmails ?: listOf(),
|
||||
downloadedBooks = downloadedBooks ?: listOf()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.pixelized.biblib.repository.googleSignIn
|
|||
|
||||
import android.app.Application
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInClient
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||
import com.pixelized.biblib.R
|
||||
|
|
@ -19,6 +20,10 @@ class GoogleSingInRepository(application: Application) : IGoogleSingInRepository
|
|||
GoogleSignIn.getClient(application, option)
|
||||
}
|
||||
|
||||
override val account: GoogleSignInAccount? by lazy {
|
||||
GoogleSignIn.getLastSignedInAccount(application)
|
||||
}
|
||||
|
||||
override val lastGoogleToken: String? by lazy {
|
||||
GoogleSignIn.getLastSignedInAccount(application)?.idToken
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.pixelized.biblib.repository.googleSignIn
|
||||
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInClient
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||
|
||||
interface IGoogleSingInRepository {
|
||||
val option: GoogleSignInOptions
|
||||
val client: GoogleSignInClient
|
||||
val account: GoogleSignInAccount?
|
||||
val lastGoogleToken: String?
|
||||
}
|
||||
|
|
@ -15,6 +15,11 @@ sealed class Screen(
|
|||
route = "home"
|
||||
)
|
||||
|
||||
object Profile: Screen(
|
||||
route = "profile"
|
||||
)
|
||||
|
||||
@Deprecated(message = "User BottomDetailState instead.")
|
||||
class BookDetail(id: Int) : Screen(
|
||||
route = "$ROUTE/$id"
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.navigation.compose.composable
|
|||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.biblib.ui.screen.authentication.AuthenticationScreen
|
||||
import com.pixelized.biblib.ui.screen.home.HomeScreen
|
||||
import com.pixelized.biblib.ui.screen.profile.ProfileScreen
|
||||
|
||||
val LocalScreenNavHostController = compositionLocalOf<NavHostController> {
|
||||
error("LocalFullScreenNavHostController is not ready yet.")
|
||||
|
|
@ -35,6 +36,9 @@ fun ScreenNavHost(
|
|||
composable(Screen.Home.route) {
|
||||
HomeScreen()
|
||||
}
|
||||
composable(Screen.Profile.route) {
|
||||
ProfileScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,4 +48,8 @@ fun NavHostController.navigateToHome() {
|
|||
launchSingleTop = true
|
||||
popUpTo(0) { inclusive = true }
|
||||
}
|
||||
}
|
||||
|
||||
fun NavHostController.navigateToProfile() {
|
||||
navigate(Screen.Profile.route)
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ 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
|
||||
import com.pixelized.biblib.utils.CoverCache
|
||||
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
|
||||
|
|
@ -32,10 +32,10 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
class BookDetailViewModel @Inject constructor(
|
||||
application: Application,
|
||||
coverCache: CoverCache,
|
||||
bookCoverCache: BookCoverCache,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val client: IBibLibClient,
|
||||
) : ACoverViewModel(application, coverCache) {
|
||||
) : ACoverViewModel(application, bookCoverCache) {
|
||||
|
||||
private val _state = mutableStateOf<StateUio<BookUio>>(StateUio.Progress())
|
||||
val state: State<StateUio<BookUio>> get() = _state
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ import androidx.compose.foundation.Image
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.Button
|
||||
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.Close
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -39,6 +41,7 @@ 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.theme.BibLibTheme
|
||||
import com.pixelized.biblib.utils.extention.bibLib
|
||||
import com.pixelized.biblib.utils.extention.todo
|
||||
|
||||
@Composable
|
||||
fun DetailScreen(
|
||||
|
|
@ -54,9 +57,6 @@ fun DetailScreen(
|
|||
onClose = {
|
||||
bottomDetailState.collapse()
|
||||
},
|
||||
onMobi = {},
|
||||
onEpub = {},
|
||||
onSend = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -67,10 +67,10 @@ fun DetailScreen(
|
|||
private fun DetailScreenContent(
|
||||
modifier: Modifier = Modifier,
|
||||
book: BookUio,
|
||||
onClose: () -> Unit,
|
||||
onMobi: () -> Unit,
|
||||
onEpub: () -> Unit,
|
||||
onSend: () -> Unit,
|
||||
onClose: () -> Unit = todo(),
|
||||
onMobi: () -> Unit = todo(),
|
||||
onEpub: () -> Unit = todo(),
|
||||
onSend: () -> Unit = todo(),
|
||||
) {
|
||||
AnimatedDelayer(delay = Delay(300)) {
|
||||
Column(
|
||||
|
|
@ -291,12 +291,6 @@ private fun DetailScreenContentPreview() {
|
|||
cover = cover,
|
||||
)
|
||||
BibLibTheme {
|
||||
DetailScreenContent(
|
||||
book = book,
|
||||
onClose = {},
|
||||
onMobi = {},
|
||||
onEpub = {},
|
||||
onSend = {},
|
||||
)
|
||||
DetailScreenContent(book = book)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,115 +1,40 @@
|
|||
package com.pixelized.biblib.ui.screen.home
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.navigation.BottomDetailScaffold
|
||||
import com.pixelized.biblib.ui.navigation.page.Page
|
||||
import com.pixelized.biblib.ui.navigation.page.PageNavHost
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun HomeScreen() {
|
||||
val pageNavHostController = rememberNavController()
|
||||
fun HomeScreen(
|
||||
accountViewModel: HomeViewModel = hiltViewModel(),
|
||||
navHostController: NavHostController = rememberNavController(),
|
||||
) {
|
||||
|
||||
BottomDetailScaffold {
|
||||
Scaffold(
|
||||
modifier = Modifier.systemBarsPadding(),
|
||||
topBar = {
|
||||
Column {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.app_name))
|
||||
}
|
||||
)
|
||||
BottomBarNavigation(
|
||||
homePageNavController = pageNavHostController
|
||||
)
|
||||
HomeSearchBar()
|
||||
HomeTarBar(homePageNavController = navHostController)
|
||||
}
|
||||
},
|
||||
content = {
|
||||
PageNavHost(
|
||||
modifier = Modifier.padding(it),
|
||||
navHostController = pageNavHostController,
|
||||
navHostController = navHostController,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomBarNavigation(
|
||||
homePageNavController: NavHostController,
|
||||
pages: List<Page> = Page.all
|
||||
) {
|
||||
BottomNavigation(
|
||||
backgroundColor = MaterialTheme.colors.background,
|
||||
) {
|
||||
val navBackStackEntry by homePageNavController.currentBackStackEntryAsState()
|
||||
val currentDestination = navBackStackEntry?.destination
|
||||
|
||||
pages.forEach { page ->
|
||||
val selected = currentDestination?.hierarchy?.any { it.route == page.route }
|
||||
BottomNavigationIcon(
|
||||
page = page,
|
||||
selected = selected ?: false,
|
||||
onClick = {
|
||||
homePageNavController.navigate(page.route) {
|
||||
// Pop up to the start destination of the graph to avoid building up a
|
||||
// large stack of destinations on the back stack as users select items
|
||||
popUpTo(homePageNavController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
// Avoid multiple copies of the same destination when re-selecting the same item
|
||||
launchSingleTop = true
|
||||
// Restore state when re-selecting a previously selected item
|
||||
restoreState = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RowScope.BottomNavigationIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
page: Page,
|
||||
selected: Boolean,
|
||||
selectedColor: Color = MaterialTheme.colors.primary,
|
||||
defaultColor: Color = MaterialTheme.colors.onSurface,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
BottomNavigationItem(
|
||||
modifier = modifier,
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = page.icon,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = stringResource(id = page.label),
|
||||
)
|
||||
},
|
||||
selectedContentColor = selectedColor,
|
||||
unselectedContentColor = defaultColor,
|
||||
selected = selected,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package com.pixelized.biblib.ui.screen.home
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.navigation.screen.LocalScreenNavHostController
|
||||
import com.pixelized.biblib.ui.navigation.screen.navigateToProfile
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.utils.extention.todo
|
||||
|
||||
@Composable
|
||||
fun HomeSearchBar(
|
||||
accountViewModel: HomeViewModel = hiltViewModel()
|
||||
) {
|
||||
val screenNavHostController = LocalScreenNavHostController.current
|
||||
val avatar by rememberUpdatedState(accountViewModel.avatar)
|
||||
|
||||
HomeSearchBarContent(
|
||||
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
avatar = avatar,
|
||||
onAvatar = {
|
||||
screenNavHostController.navigateToProfile()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HomeSearchBarContent(
|
||||
modifier: Modifier = Modifier,
|
||||
avatar: Painter,
|
||||
onAvatar: () -> Unit = todo(),
|
||||
onSearch: () -> Unit = todo(),
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
elevation = 8.dp
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onSearch)
|
||||
.fillMaxWidth()
|
||||
.height(48.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(42.dp)
|
||||
.padding(horizontal = 8.dp),
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
text = "Rechercher sur BibLib"
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
onClick = onAvatar,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.clip(RoundedCornerShape(50)),
|
||||
painter = avatar,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun HomeSearchBarContentPreview() {
|
||||
BibLibTheme {
|
||||
HomeSearchBarContent(
|
||||
avatar = painterResource(R.drawable.ic_baseline_auto_stories_24)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.pixelized.biblib.ui.screen.home
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.biblib.ui.navigation.page.Page
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
|
||||
@Composable
|
||||
fun HomeTarBar(
|
||||
homePageNavController: NavHostController = rememberNavController(),
|
||||
pages: List<Page> = Page.all
|
||||
) {
|
||||
BottomNavigation(
|
||||
backgroundColor = MaterialTheme.colors.background,
|
||||
) {
|
||||
val navBackStackEntry by homePageNavController.currentBackStackEntryAsState()
|
||||
val currentDestination = navBackStackEntry?.destination
|
||||
|
||||
pages.forEach { page ->
|
||||
val selected = currentDestination?.hierarchy?.any { it.route == page.route }
|
||||
BottomNavigationIcon(
|
||||
page = page,
|
||||
selected = selected ?: false,
|
||||
onClick = {
|
||||
homePageNavController.navigate(page.route) {
|
||||
// Pop up to the start destination of the graph to avoid building up a
|
||||
// large stack of destinations on the back stack as users select items
|
||||
popUpTo(homePageNavController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
// Avoid multiple copies of the same destination when re-selecting the same item
|
||||
launchSingleTop = true
|
||||
// Restore state when re-selecting a previously selected item
|
||||
restoreState = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RowScope.BottomNavigationIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
page: Page,
|
||||
selected: Boolean,
|
||||
selectedColor: Color = MaterialTheme.colors.primary,
|
||||
defaultColor: Color = MaterialTheme.colors.onSurface,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
BottomNavigationItem(
|
||||
modifier = modifier,
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = page.icon,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = stringResource(id = page.label),
|
||||
)
|
||||
},
|
||||
selectedContentColor = selectedColor,
|
||||
unselectedContentColor = defaultColor,
|
||||
selected = selected,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun BottomBarNavigationPreview() {
|
||||
BibLibTheme {
|
||||
HomeTarBar()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
package com.pixelized.biblib.ui.screen.home.common.uio
|
||||
|
||||
import androidx.compose.runtime.State
|
||||
import com.pixelized.biblib.network.client.IBibLibClient.Companion.COVER_URL
|
||||
import java.net.URL
|
||||
|
||||
data class BookUio(
|
||||
val id: Int,
|
||||
|
|
|
|||
|
|
@ -6,25 +6,25 @@ 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.CoverCache
|
||||
import com.pixelized.biblib.utils.cache.BookCoverCache
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.net.URL
|
||||
|
||||
|
||||
abstract class ACoverViewModel(
|
||||
application: Application,
|
||||
private val cache: CoverCache,
|
||||
private val cacheBook: BookCoverCache,
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
fun cover(
|
||||
cache: CoverCache = this.cache,
|
||||
cacheBook: BookCoverCache = this.cacheBook,
|
||||
coroutineScope: CoroutineScope = viewModelScope,
|
||||
placeHolder: CoverUio,
|
||||
type: CoverUio.Type,
|
||||
contentScale: ContentScale = ContentScale.FillBounds,
|
||||
tint: ColorFilter? = null,
|
||||
url: URL,
|
||||
) = cache.cover(
|
||||
) = cacheBook.cover(
|
||||
placeHolder = placeHolder,
|
||||
coroutineScope = coroutineScope,
|
||||
type = type,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ 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.CoverCache
|
||||
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
|
||||
|
|
@ -27,8 +27,8 @@ import javax.inject.Inject
|
|||
class BooksViewModel @Inject constructor(
|
||||
application: Application,
|
||||
bookRepository: IBookRepository,
|
||||
cache: CoverCache,
|
||||
) : ACoverViewModel(application, cache) {
|
||||
cacheBook: BookCoverCache,
|
||||
) : ACoverViewModel(application, cacheBook) {
|
||||
|
||||
private val booksSource = Pager(
|
||||
config = PagingConfig(pageSize = PAGING_SIZE),
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ 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.CoverCache
|
||||
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
|
||||
|
|
@ -29,8 +29,8 @@ import javax.inject.Inject
|
|||
class NewsBookViewModel @Inject constructor(
|
||||
application: Application,
|
||||
bookRepository: IBookRepository,
|
||||
cache: CoverCache,
|
||||
) : ACoverViewModel(application, cache) {
|
||||
cacheBook: BookCoverCache,
|
||||
) : ACoverViewModel(application, cacheBook) {
|
||||
|
||||
private val newsSource: Flow<PagingData<BookThumbnailUio>> = Pager(
|
||||
config = PagingConfig(pageSize = PAGING_SIZE),
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class LauncherViewModel @Inject constructor(
|
|||
loadAllBooks()
|
||||
}
|
||||
}
|
||||
//
|
||||
// Update loading state.
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
package com.pixelized.biblib.ui.screen.profile
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.FloatingActionButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import com.pixelized.biblib.ui.composable.StateUio
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
|
||||
@Composable
|
||||
fun ProfileScreen(
|
||||
viewModel: ProfileViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val user by rememberUpdatedState(newValue = viewModel.user)
|
||||
|
||||
when (val unwrap = user) {
|
||||
is StateUio.Success -> ProfileScreenContent(user = unwrap.value)
|
||||
}
|
||||
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(IBibLibClient.EDIT_PROFILE)))
|
||||
},
|
||||
content = { },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProfileScreenContent(
|
||||
modifier: Modifier = Modifier,
|
||||
user: ProfileViewModel.UserUio,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.h5,
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
text = user.username,
|
||||
)
|
||||
user.firstname?.let {
|
||||
Text(
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
text = it,
|
||||
)
|
||||
}
|
||||
user.lastname?.let {
|
||||
Text(
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
text = it,
|
||||
)
|
||||
}
|
||||
user.amazonEmails.forEach {
|
||||
Text(
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
text = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun ProfileScreenContentPreview() {
|
||||
BibLibTheme {
|
||||
val user = ProfileViewModel.UserUio(
|
||||
username = "R. Daneel Olivaw",
|
||||
firstname = "R. Daneel",
|
||||
lastname = "Olivaw",
|
||||
amazonEmails = listOf("r.daneel.olivaw@biblib.com"),
|
||||
)
|
||||
ProfileScreenContent(
|
||||
user = user
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
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.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
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ProfileViewModel @Inject constructor(
|
||||
application: Application,
|
||||
cacheBook: BookCoverCache,
|
||||
private val client: IBibLibClient,
|
||||
) : ACoverViewModel(application, cacheBook) {
|
||||
|
||||
private val _user = mutableStateOf<StateUio<UserUio>>(StateUio.Progress())
|
||||
val user by _user
|
||||
|
||||
init {
|
||||
updateUser()
|
||||
}
|
||||
|
||||
private fun updateUser() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val factory = UserFactory()
|
||||
val response = client.service.user()
|
||||
val user = factory.fromUserResponseToUser(response)
|
||||
_user.value = StateUio.Success(user.toUio())
|
||||
} catch (exception: Exception) {
|
||||
Log.e("AccountViewModel", exception.message, exception)
|
||||
_user.value = StateUio.Failure(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class UserUio(
|
||||
val username: String,
|
||||
val firstname: String?,
|
||||
val lastname: String?,
|
||||
val amazonEmails: List<String>,
|
||||
)
|
||||
}
|
||||
|
||||
private fun User.toUio() = ProfileViewModel.UserUio(
|
||||
username = username,
|
||||
firstname = firstname,
|
||||
lastname = lastname,
|
||||
amazonEmails = amazonEmails.toList(),
|
||||
)
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
package com.pixelized.biblib.utils
|
||||
package com.pixelized.biblib.utils.cache
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.MutableState
|
||||
|
|
@ -14,16 +12,13 @@ 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.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
class CoverCache @Inject constructor(context: Context) {
|
||||
private var cache: File? = context.cacheDir
|
||||
|
||||
class BookCoverCache @Inject constructor(
|
||||
private val cache: ImageCache
|
||||
) {
|
||||
fun cover(
|
||||
coroutineScope: CoroutineScope,
|
||||
placeHolder: CoverUio,
|
||||
|
|
@ -33,7 +28,7 @@ class CoverCache @Inject constructor(context: Context) {
|
|||
tint: ColorFilter?,
|
||||
): State<CoverUio> {
|
||||
// read the cache a convert it to a UIO.
|
||||
val cache = readFromDisk(url)?.let {
|
||||
val cache = cache.readFromDisk(url)?.let {
|
||||
CoverUio(
|
||||
type = type,
|
||||
contentScale = contentScale,
|
||||
|
|
@ -65,7 +60,7 @@ class CoverCache @Inject constructor(context: Context) {
|
|||
try {
|
||||
val bitmap = BitmapFactory.decodeStream(url.openStream())
|
||||
val painter = BitmapPainter(bitmap.asImageBitmap())
|
||||
writeToDisk(url, bitmap)
|
||||
cache.writeToDisk(url, bitmap)
|
||||
value = CoverUio(
|
||||
type = type,
|
||||
contentScale = contentScale,
|
||||
|
|
@ -76,26 +71,4 @@ class CoverCache @Inject constructor(context: Context) {
|
|||
Log.w("CoverCache", "Fail to download: {$url}", exception)
|
||||
}
|
||||
}
|
||||
|
||||
private fun file(url: URL): File = File(cache?.absolutePath + url.file)
|
||||
|
||||
private 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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readFromDisk(url: URL): Bitmap? {
|
||||
val file = file(url)
|
||||
return try {
|
||||
if (file.exists()) BitmapFactory.decodeStream(file.inputStream()) else null
|
||||
} catch (e: IOException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
37
app/src/main/java/com/pixelized/biblib/utils/cache/ImageCache.kt
vendored
Normal file
37
app/src/main/java/com/pixelized/biblib/utils/cache/ImageCache.kt
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,18 @@ package com.pixelized.biblib.utils.extention
|
|||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) =
|
||||
Toast.makeText(this, message, duration).show()
|
||||
Toast.makeText(this, message, duration).show()
|
||||
|
||||
fun Context.notImplemented() {
|
||||
showToast("Not implemented yet")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun todo(): () -> Unit {
|
||||
val context = LocalContext.current
|
||||
return { context.notImplemented() }
|
||||
}
|
||||
|
|
@ -12,5 +12,6 @@ class UserMock {
|
|||
firstname = "R. Daneel",
|
||||
lastname = "Olivaw",
|
||||
amazonEmails = listOf("r.daneel.olivaw@biblib.com"),
|
||||
downloadedBooks = listOf(),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue