Update the book lazy on the homepage + homepage error management.
This commit is contained in:
parent
58ebd970f2
commit
c9049be239
11 changed files with 204 additions and 122 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import kotlinx.coroutines.launch
|
|||
|
||||
@Composable
|
||||
fun BooksPage(
|
||||
newsViewModel: BooksViewModel = hiltViewModel()
|
||||
newsViewModel: LibraryViewModel = hiltViewModel()
|
||||
) {
|
||||
val books = newsViewModel.books
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ import javax.inject.Inject
|
|||
|
||||
|
||||
@HiltViewModel
|
||||
class BooksViewModel @Inject constructor(
|
||||
class LibraryViewModel @Inject constructor(
|
||||
bookRepository: IBookRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue