Change to Toolbar layout

This commit is contained in:
Thomas Andres Gomez 2022-04-25 00:08:55 +02:00
parent 7af9eb8acc
commit db7d6d8ab8
25 changed files with 544 additions and 157 deletions

View file

@ -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,
)

View file

@ -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

View file

@ -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"
}
}

View file

@ -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()
)
}
}

View file

@ -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
}

View file

@ -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?
}

View file

@ -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"
) {

View file

@ -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)
}

View file

@ -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

View file

@ -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)
}
}

View file

@ -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,
)
}

View file

@ -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)
)
}
}

View file

@ -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()
}
}

View file

@ -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
}
}
}

View file

@ -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,

View file

@ -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,

View file

@ -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),

View file

@ -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),

View file

@ -42,7 +42,7 @@ class LauncherViewModel @Inject constructor(
loadAllBooks()
}
}
//
// Update loading state.
_isLoading.value = false
}
}

View file

@ -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
)
}
}

View file

@ -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(),
)

View file

@ -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
}
}
}

View 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
}
}
}

View file

@ -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() }
}

View file

@ -12,5 +12,6 @@ class UserMock {
firstname = "R. Daneel",
lastname = "Olivaw",
amazonEmails = listOf("r.daneel.olivaw@biblib.com"),
downloadedBooks = listOf(),
)
}