Update the book lazy on the homepage + homepage error management.

This commit is contained in:
Thomas Andres Gomez 2022-10-21 17:54:37 +02:00
parent 58ebd970f2
commit c9049be239
11 changed files with 204 additions and 122 deletions

View file

@ -124,6 +124,7 @@ dependencies {
implementation "com.google.accompanist:accompanist-insets:0.26.5-rc"
implementation "com.google.accompanist:accompanist-pager:0.26.5-rc"
implementation "com.google.accompanist:accompanist-placeholder-material:0.26.5-rc"
implementation "com.google.accompanist:accompanist-swiperefresh:0.26.5-rc"
// Landscapist
implementation "com.github.skydoves:landscapist-glide:1.5.2"

View file

@ -26,6 +26,7 @@ import com.pixelized.biblib.ui.screen.authentication.viewModel.AuthenticationFor
import com.pixelized.biblib.ui.screen.authentication.viewModel.AuthenticationViewModel
import com.pixelized.biblib.ui.theme.color.ShadowPalette
import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.R
import kotlinx.coroutines.launch
@Stable
@ -43,22 +44,16 @@ sealed class AuthenticationUio {
@Stable
@Immutable
sealed class AuthenticationErrorUio(
@StringRes val message: Int,
@StringRes val action: Int,
@StringRes val message: Int = R.string.error_authentication_message,
@StringRes val action: Int = R.string.error_authentication_action,
) {
@Stable
@Immutable
class Login(
@StringRes message: Int,
@StringRes action: Int,
) : AuthenticationErrorUio(message, action)
class Login : AuthenticationErrorUio()
@Stable
@Immutable
class GoogleLogin(
@StringRes message: Int,
@StringRes action: Int,
) : AuthenticationErrorUio(message, action)
class GoogleLogin : AuthenticationErrorUio()
}
@Composable
@ -72,8 +67,6 @@ fun AuthenticationScreen(
val context = LocalContext.current
val scope = rememberCoroutineScope()
AuthenticationScreenContent(
modifier = Modifier.systemBarsPadding(),
login = formViewModel.form.login,

View file

@ -65,10 +65,7 @@ class AuthenticationViewModel @Inject constructor(
Log.e("AuthenticationViewModel", exception.message, exception)
_authenticationProcess.value = null
_authenticationError.emit(
AuthenticationErrorUio.Login(
message = R.string.error_authentication_message,
action = R.string.error_authentication_action,
)
AuthenticationErrorUio.Login(),
)
}
}
@ -98,12 +95,7 @@ class AuthenticationViewModel @Inject constructor(
} catch (exception: Exception) {
Log.e("AuthenticationViewModel", exception.message, exception)
_authenticationProcess.value = null
_authenticationError.emit(
AuthenticationErrorUio.GoogleLogin(
message = R.string.error_authentication_message,
action = R.string.error_authentication_action,
)
)
_authenticationError.emit(AuthenticationErrorUio.GoogleLogin())
}
}
}

View file

@ -0,0 +1,56 @@
package com.pixelized.biblib.ui.screen.home
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.network.client.IBibLibClient
import com.pixelized.biblib.repository.apiCache.IAPICacheRepository
import com.pixelized.biblib.repository.book.IBookRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
class BooksViewModel @Inject constructor(
private val repository: IBookRepository,
private val client: IBibLibClient,
private val cache: IAPICacheRepository,
) : ViewModel() {
var isLoading: Boolean by mutableStateOf(false)
private set
private val _updateError = MutableSharedFlow<BookUpdateErrorUio>()
val updateError: Flow<BookUpdateErrorUio> get() = _updateError
init {
updateBooks()
}
fun updateBooks() {
viewModelScope.launch {
isLoading = true
try {
withContext(Dispatchers.IO) {
com.pixelized.biblib.repository.book.updateBooks(
client = client,
cache = cache,
repository = repository,
)
}
} catch (exception: Exception) {
Log.e("BooksViewModel", exception.message, exception)
_updateError.emit(BookUpdateErrorUio())
} finally {
isLoading = false
}
}
}
}

View file

@ -1,14 +1,11 @@
package com.pixelized.biblib.ui.screen.home
import androidx.activity.compose.BackHandler
import androidx.compose.animation.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusManager
@ -16,27 +13,13 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.window.Dialog
import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.PagerState
import com.google.accompanist.pager.rememberPagerState
import com.pixelized.biblib.R
import com.pixelized.biblib.ui.scaffold.*
import com.pixelized.biblib.ui.screen.home.common.connectivity.ConnectivityHeader
import com.pixelized.biblib.ui.screen.home.common.connectivity.ConnectivityViewModel
import com.pixelized.biblib.ui.screen.home.page.Page
import com.pixelized.biblib.ui.screen.home.page.books.BooksPage
import com.pixelized.biblib.ui.screen.home.page.news.NewsPage
import com.pixelized.biblib.ui.screen.home.page.profile.ProfilePage
import com.pixelized.biblib.ui.screen.home.page.search.Search
import com.pixelized.biblib.ui.screen.home.page.search.SearchPage
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -44,7 +27,6 @@ import kotlinx.coroutines.launch
@Composable
fun HomeScreen(
homeViewModel: HomeViewModel = hiltViewModel(),
connectivityViewModel: ConnectivityViewModel = hiltViewModel(),
keyboard: SoftwareKeyboardController? = LocalSoftwareKeyboardController.current,
searchScaffoldState: SearchScaffoldState = rememberSearchScaffoldState(),
) {
@ -91,7 +73,6 @@ fun HomeScreen(
},
content = {
HomeScreenContent(
isNetworkAvailable = { connectivityViewModel.isNetworkAvailable },
pages = Page.all
)
},
@ -120,7 +101,7 @@ fun HomeScreen(
@Composable
fun ProfileHandler(
viewModel: HomeViewModel
viewModel: HomeViewModel = hiltViewModel()
) {
if (viewModel.shouldDisplayProfileDialog) {
Dialog(
@ -129,73 +110,4 @@ fun ProfileHandler(
ProfilePage()
}
}
}
@OptIn(ExperimentalPagerApi::class)
@Composable
fun HomeScreenContent(
isNetworkAvailable: () -> State<Boolean>,
pages: List<Page> = Page.all
) {
val scope: CoroutineScope = rememberCoroutineScope()
val pagerState: PagerState = rememberPagerState()
Column {
ScrollableTabRow(
selectedTabIndex = pagerState.currentPage,
edgePadding = MaterialTheme.bibLib.dimen.dp16,
) {
pages.forEachIndexed { index, page ->
Tab(
selected = pagerState.currentPage == index,
text = { Text(text = stringResource(id = page.label)) },
onClick = { scope.launch { pagerState.animateScrollToPage(index) } },
)
}
}
ConnectivityHeader(
isNetworkAvailable = isNetworkAvailable,
)
HorizontalPager(
modifier = Modifier.fillMaxSize(),
state = pagerState,
count = pages.size
) {
when (pages[it]) {
is Page.News -> NewsPage()
is Page.Books -> BooksPage()
is Page.Author -> NotYetImplemented()
is Page.Series -> NotYetImplemented()
is Page.Tag -> NotYetImplemented()
}
}
}
}
@Composable
private fun NotYetImplemented() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Text(
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
text = stringResource(id = R.string.not_implemented_yet)
)
}
}
@Composable
@Preview(showBackground = true)
private fun HomeScreenContentPreview() {
BibLibTheme {
val isNetworkAvailable = { mutableStateOf(true) }
HomeScreenContent(
isNetworkAvailable = isNetworkAvailable,
pages = Page.all,
)
}
}

View file

@ -0,0 +1,129 @@
package com.pixelized.biblib.ui.screen.home
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.PagerState
import com.google.accompanist.pager.rememberPagerState
import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import com.pixelized.biblib.R
import com.pixelized.biblib.ui.LocalSnackHostState
import com.pixelized.biblib.ui.screen.home.common.connectivity.ConnectivityHeader
import com.pixelized.biblib.ui.screen.home.common.connectivity.ConnectivityViewModel
import com.pixelized.biblib.ui.screen.home.page.Page
import com.pixelized.biblib.ui.screen.home.page.books.BooksPage
import com.pixelized.biblib.ui.screen.home.page.news.NewsPage
import com.pixelized.biblib.utils.extention.bibLib
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@Stable
data class BookUpdateErrorUio(
@StringRes val message: Int = R.string.error_book_update_message,
@StringRes val action: Int = R.string.error_book_update_action
)
@OptIn(ExperimentalPagerApi::class)
@Composable
fun HomeScreenContent(
connectivityViewModel: ConnectivityViewModel = hiltViewModel(),
booksViewModel: BooksViewModel = hiltViewModel(),
pages: List<Page> = Page.all
) {
val scope: CoroutineScope = rememberCoroutineScope()
val pagerState: PagerState = rememberPagerState()
val snackBarHostState = LocalSnackHostState.current
val context = LocalContext.current
LaunchedEffect(key1 = "BookUpdateError") {
booksViewModel.updateError.collect {
val result = snackBarHostState.showSnackbar(
message = context.getString(it.message),
actionLabel = context.getString(it.action),
)
if (result == SnackbarResult.ActionPerformed) {
booksViewModel.updateBooks()
}
}
}
Column {
ScrollableTabRow(
selectedTabIndex = pagerState.currentPage,
edgePadding = MaterialTheme.bibLib.dimen.dp16,
) {
pages.forEachIndexed { index, page ->
Tab(
selected = pagerState.currentPage == index,
text = { Text(text = stringResource(id = page.label)) },
onClick = { scope.launch { pagerState.animateScrollToPage(index) } },
)
}
}
ConnectivityHeader(
isNetworkAvailable = { connectivityViewModel.isNetworkAvailable },
)
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing = false),
onRefresh = { booksViewModel.updateBooks() }
) {
HorizontalPager(
modifier = Modifier.fillMaxSize(),
state = pagerState,
count = pages.size
) {
when (pages[it]) {
is Page.News -> NewsPage()
is Page.Books -> BooksPage()
is Page.Author -> Empty()
is Page.Series -> Empty()
is Page.Tag -> Empty()
}
}
AnimatedVisibility(
visible = booksViewModel.isLoading,
enter = fadeIn(),
exit = fadeOut(),
) {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
@Composable
private fun Empty() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Text(
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
text = stringResource(id = R.string.not_implemented_yet)
)
}
}

View file

@ -4,12 +4,8 @@ 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.repository.googleSignIn.IGoogleSingInRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel

View file

@ -25,7 +25,7 @@ import kotlinx.coroutines.launch
@Composable
fun BooksPage(
newsViewModel: BooksViewModel = hiltViewModel()
newsViewModel: LibraryViewModel = hiltViewModel()
) {
val books = newsViewModel.books

View file

@ -13,7 +13,7 @@ import javax.inject.Inject
@HiltViewModel
class BooksViewModel @Inject constructor(
class LibraryViewModel @Inject constructor(
bookRepository: IBookRepository,
) : ViewModel() {

View file

@ -18,6 +18,7 @@
<string name="error_action_retry">Réessayer</string>
<string name="error_authentication_message">L\'authentification à BibLib à échouée.</string>
<string name="error_book_update_message">La mise à jour de la librarie à échouée.</string>
<string name="error_get_book_detail_message">La récupération des détails a échouée.</string>
<string name="error_send_book_message">L\'envoi de l\'eBook a échoué.</string>

View file

@ -23,8 +23,10 @@
<!-- Error -->
<string name="error_action_retry">Retry</string>
<string name="error_authentication_message">Failed to connect to BibLib</string>
<string name="error_authentication_message">Failed to connect to BibLib.</string>
<string name="error_authentication_action" translatable="false">@string/error_action_retry</string>
<string name="error_book_update_message">Failed to update BibLib library.</string>
<string name="error_book_update_action" translatable="false">@string/error_action_retry</string>
<string name="error_get_book_detail_message">Failed to retrieve book details.</string>
<string name="error_get_book_detail_action" translatable="false">@string/error_action_retry</string>
<string name="error_send_book_message">Failed to send the book to your kindle.</string>