diff --git a/app/build.gradle b/app/build.gradle index a94c25d..9adcecd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreen.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreen.kt index a5b821e..bdb237a 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/AuthenticationScreen.kt @@ -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, diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/viewModel/AuthenticationViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/viewModel/AuthenticationViewModel.kt index 91b33b7..a48a339 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/viewModel/AuthenticationViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/authentication/viewModel/AuthenticationViewModel.kt @@ -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()) } } } diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/BooksViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/BooksViewModel.kt new file mode 100644 index 0000000..a44ea22 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/BooksViewModel.kt @@ -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() + val updateError: Flow 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 + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt index 3ca7b00..f35f4f7 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt @@ -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, - pages: List = 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, - ) - } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreenContent.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreenContent.kt new file mode 100644 index 0000000..5067750 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreenContent.kt @@ -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.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) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeViewModel.kt index d9770e2..4d367fe 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeViewModel.kt @@ -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 diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/BooksPage.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/LibraryPage.kt similarity index 97% rename from app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/BooksPage.kt rename to app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/LibraryPage.kt index 297fd02..1dca481 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/BooksPage.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/LibraryPage.kt @@ -25,7 +25,7 @@ import kotlinx.coroutines.launch @Composable fun BooksPage( - newsViewModel: BooksViewModel = hiltViewModel() + newsViewModel: LibraryViewModel = hiltViewModel() ) { val books = newsViewModel.books diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/BooksViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/LibraryViewModel.kt similarity index 95% rename from app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/BooksViewModel.kt rename to app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/LibraryViewModel.kt index a6a8fd6..b63a5f0 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/BooksViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/LibraryViewModel.kt @@ -13,7 +13,7 @@ import javax.inject.Inject @HiltViewModel -class BooksViewModel @Inject constructor( +class LibraryViewModel @Inject constructor( bookRepository: IBookRepository, ) : ViewModel() { diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c1640f2..3eb74d2 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -18,6 +18,7 @@ Réessayer L\'authentification à BibLib à échouée. + La mise à jour de la librarie à échouée. La récupération des détails a échouée. L\'envoi de l\'eBook a échoué. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1d45f98..f3044ab 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -23,8 +23,10 @@ Retry - Failed to connect to BibLib + Failed to connect to BibLib. @string/error_action_retry + Failed to update BibLib library. + @string/error_action_retry Failed to retrieve book details. @string/error_action_retry Failed to send the book to your kindle.