Use bookRepository at application start.
This commit is contained in:
		
							parent
							
								
									b94eaba31b
								
							
						
					
					
						commit
						45f5e9023e
					
				
					 9 changed files with 256 additions and 145 deletions
				
			
		| 
						 | 
				
			
			@ -14,20 +14,11 @@ class APICacheRepository : IAPICacheRepository {
 | 
			
		|||
        get() = preferences.new?.let { gson.fromJson(it, BookListResponse::class.java) }
 | 
			
		||||
        set(value) = gson.toJson(value).let { preferences.new = it }
 | 
			
		||||
 | 
			
		||||
    override var list: BookListResponse?
 | 
			
		||||
        get() = preferences.list?.let { gson.fromJson(it, BookListResponse::class.java) }
 | 
			
		||||
        set(value) = gson.toJson(value).let { preferences.list = it }
 | 
			
		||||
 | 
			
		||||
    private var SharedPreferences.new: String?
 | 
			
		||||
        get() = getString(NEW, null)
 | 
			
		||||
        set(value) = edit { putString(NEW, value) }
 | 
			
		||||
 | 
			
		||||
    private var SharedPreferences.list: String?
 | 
			
		||||
        get() = getString(LIST, null)
 | 
			
		||||
        set(value) = edit { putString(LIST, value) }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val NEW = "NEW"
 | 
			
		||||
        const val LIST = "LIST"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,5 +4,4 @@ import com.pixelized.biblib.network.data.response.BookListResponse
 | 
			
		|||
 | 
			
		||||
interface IAPICacheRepository {
 | 
			
		||||
    var new: BookListResponse?
 | 
			
		||||
    var list: BookListResponse?
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -52,7 +52,7 @@ fun LoadingCard(
 | 
			
		|||
private fun LoadingCardLightPreview() {
 | 
			
		||||
    BibLibTheme(darkTheme = false) {
 | 
			
		||||
        LoadingCard(
 | 
			
		||||
            message = stringResource(id = R.string.loading)
 | 
			
		||||
            message = stringResource(id = R.string.loading_authentication)
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +62,7 @@ private fun LoadingCardLightPreview() {
 | 
			
		|||
private fun LoadingCardDarkPreview() {
 | 
			
		||||
    BibLibTheme(darkTheme = true) {
 | 
			
		||||
        LoadingCard(
 | 
			
		||||
            message = stringResource(id = R.string.loading)
 | 
			
		||||
            message = stringResource(id = R.string.loading_book)
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -60,7 +60,7 @@ fun SuccessCard(
 | 
			
		|||
@Composable
 | 
			
		||||
private fun SuccessLightPreview() {
 | 
			
		||||
    BibLibTheme(darkTheme = false) {
 | 
			
		||||
        SuccessCard(message = stringResource(id = R.string.authentication_success))
 | 
			
		||||
        SuccessCard(message = stringResource(id = R.string.success_authentication))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +68,6 @@ private fun SuccessLightPreview() {
 | 
			
		|||
@Composable
 | 
			
		||||
private fun SuccessDarkPreview() {
 | 
			
		||||
    BibLibTheme(darkTheme = true) {
 | 
			
		||||
        SuccessCard(message = stringResource(id = R.string.authentication_success))
 | 
			
		||||
        SuccessCard(message = stringResource(id = R.string.success_authentication))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -45,15 +45,19 @@ import com.pixelized.biblib.ui.composable.items.dialog.SuccessCard
 | 
			
		|||
import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.authentication.AuthenticationViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.authentication.IAuthentication
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.initialisation.IInitialisation
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.initialisation.InitialisationViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigation
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
private const val LE_LOAD_BOOK = "LE_LOAD_BOOK"
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun LoginScreenComposable(
 | 
			
		||||
    navigation: INavigation = viewModel<NavigationViewModel>(),
 | 
			
		||||
    authentication: IAuthentication = viewModel<AuthenticationViewModel>(),
 | 
			
		||||
    initialisation: IInitialisation = viewModel<InitialisationViewModel>()
 | 
			
		||||
) {
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
| 
						 | 
				
			
			@ -61,10 +65,10 @@ fun LoginScreenComposable(
 | 
			
		|||
            .fillMaxHeight()
 | 
			
		||||
    ) {
 | 
			
		||||
        authentication.PrepareLoginWithGoogle()
 | 
			
		||||
        LoginScreenNavigationComposable(navigation, authentication)
 | 
			
		||||
        LoginScreenNavigationComposable(navigation, authentication, initialisation)
 | 
			
		||||
 | 
			
		||||
        LoginScreenContentComposable(authentication)
 | 
			
		||||
        LoginScreenDialogComposable(authentication)
 | 
			
		||||
        LoginScreenDialogComposable(authentication, initialisation)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -91,49 +95,87 @@ private fun LoginScreenContentComposable(
 | 
			
		|||
@OptIn(ExperimentalAnimationApi::class)
 | 
			
		||||
@Composable
 | 
			
		||||
private fun LoginScreenDialogComposable(
 | 
			
		||||
    authentication: IAuthentication
 | 
			
		||||
    authentication: IAuthentication,
 | 
			
		||||
    initialisation: IInitialisation,
 | 
			
		||||
) {
 | 
			
		||||
    val state = authentication.state.observeAsState()
 | 
			
		||||
    val authenticationState = authentication.state.observeAsState()
 | 
			
		||||
    val bookLoadingState = initialisation.state.observeAsState()
 | 
			
		||||
 | 
			
		||||
    CrossFadeOverlay(
 | 
			
		||||
        modifier = Modifier.clickable {
 | 
			
		||||
            if (state.value is IAuthentication.State.Error) {
 | 
			
		||||
            if (authenticationState.value is IAuthentication.State.Error) {
 | 
			
		||||
                authentication.clearState()
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        visible = (state.value is IAuthentication.State.Initial).not()
 | 
			
		||||
        visible = (authenticationState.value is IAuthentication.State.Initial
 | 
			
		||||
                && bookLoadingState.value is IInitialisation.State.Initial).not()
 | 
			
		||||
    ) {
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = state.value is IAuthentication.State.Error,
 | 
			
		||||
            visible = authenticationState.value is IAuthentication.State.Error,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            ErrorCard(
 | 
			
		||||
                message = stringResource(id = R.string.error_generic),
 | 
			
		||||
                exception = (state.value as? IAuthentication.State.Error)?.exception
 | 
			
		||||
                exception = (authenticationState.value as? IAuthentication.State.Error)?.exception
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = state.value is IAuthentication.State.Loading,
 | 
			
		||||
            visible = authenticationState.value is IAuthentication.State.Loading,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            LoadingCard(
 | 
			
		||||
                message = stringResource(id = R.string.loading)
 | 
			
		||||
                message = stringResource(id = R.string.loading_authentication)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = state.value is IAuthentication.State.Connect,
 | 
			
		||||
            visible = authenticationState.value is IAuthentication.State.Connect,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            SuccessCard(
 | 
			
		||||
                message = stringResource(id = R.string.authentication_success)
 | 
			
		||||
                message = stringResource(id = R.string.success_authentication)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = bookLoadingState.value is IInitialisation.State.Error,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            ErrorCard(
 | 
			
		||||
                message = stringResource(id = R.string.error_generic),
 | 
			
		||||
                exception = (bookLoadingState.value as? IInitialisation.State.Error)?.exception
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = bookLoadingState.value is IInitialisation.State.Loading,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            LoadingCard(
 | 
			
		||||
                message = stringResource(id = R.string.loading_book)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = bookLoadingState.value is IInitialisation.State.Finished,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            SuccessCard(
 | 
			
		||||
                message = stringResource(id = R.string.success_book)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -142,15 +184,21 @@ private fun LoginScreenDialogComposable(
 | 
			
		|||
@Composable
 | 
			
		||||
private fun LoginScreenNavigationComposable(
 | 
			
		||||
    navigation: INavigation,
 | 
			
		||||
    authentication: IAuthentication
 | 
			
		||||
    authentication: IAuthentication,
 | 
			
		||||
    initialisation: IInitialisation,
 | 
			
		||||
) {
 | 
			
		||||
    val state = authentication.state.observeAsState()
 | 
			
		||||
    if (state.value == IAuthentication.State.Connect) {
 | 
			
		||||
        LaunchedEffect(key1 = "navigateTo(MainScreen)") {
 | 
			
		||||
            delay(1000)
 | 
			
		||||
            navigation.navigateTo(INavigation.Screen.MainScreen)
 | 
			
		||||
    val authenticationState = authentication.state.observeAsState()
 | 
			
		||||
    val bookLoadingState = initialisation.state.observeAsState()
 | 
			
		||||
 | 
			
		||||
    if (authenticationState.value == IAuthentication.State.Connect) {
 | 
			
		||||
        LaunchedEffect(LE_LOAD_BOOK) {
 | 
			
		||||
            initialisation.loadBook()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (bookLoadingState.value is IInitialisation.State.Finished) {
 | 
			
		||||
        navigation.navigateTo(INavigation.Screen.MainScreen)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
| 
						 | 
				
			
			@ -317,6 +365,7 @@ fun LoginScreenComposablePreview() {
 | 
			
		|||
    BibLibTheme {
 | 
			
		||||
        val navigationViewModel = INavigation.Mock()
 | 
			
		||||
        val authenticationViewModel = IAuthentication.Mock()
 | 
			
		||||
        LoginScreenComposable(navigationViewModel, authenticationViewModel)
 | 
			
		||||
        val initialisation = IInitialisation.Mock()
 | 
			
		||||
        LoginScreenComposable(navigationViewModel, authenticationViewModel, initialisation)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,11 +2,14 @@ package com.pixelized.biblib.ui.composable.screen
 | 
			
		|||
 | 
			
		||||
import androidx.compose.animation.*
 | 
			
		||||
import androidx.compose.animation.core.tween
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.foundation.layout.*
 | 
			
		||||
import androidx.compose.material.MaterialTheme
 | 
			
		||||
import androidx.compose.material.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.livedata.observeAsState
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +18,8 @@ import androidx.compose.ui.unit.dp
 | 
			
		|||
import androidx.lifecycle.viewmodel.compose.viewModel
 | 
			
		||||
import com.pixelized.biblib.BuildConfig
 | 
			
		||||
import com.pixelized.biblib.R
 | 
			
		||||
import com.pixelized.biblib.ui.composable.items.dialog.CrossFadeOverlay
 | 
			
		||||
import com.pixelized.biblib.ui.composable.items.dialog.ErrorCard
 | 
			
		||||
import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.initialisation.IInitialisation
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.initialisation.InitialisationViewModel
 | 
			
		||||
| 
						 | 
				
			
			@ -23,12 +28,16 @@ import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
 | 
			
		|||
import kotlinx.coroutines.delay
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
private const val ANIMATION_DURATION = 1000
 | 
			
		||||
private const val LAUNCH_EFFECT_LOAD_APPLICATION = "LoadApplication"
 | 
			
		||||
 | 
			
		||||
@Preview
 | 
			
		||||
@Composable
 | 
			
		||||
fun SplashScreenComposablePreview() {
 | 
			
		||||
    BibLibTheme {
 | 
			
		||||
        SplashScreenComposable(IInitialisation.Mock(), INavigation.Mock())
 | 
			
		||||
        val initialisation = IInitialisation.Mock(IInitialisation.State.Loading)
 | 
			
		||||
        val navigation = INavigation.Mock()
 | 
			
		||||
        SplashScreenComposable(initialisation, navigation, true)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -36,92 +45,138 @@ fun SplashScreenComposablePreview() {
 | 
			
		|||
@Composable
 | 
			
		||||
fun SplashScreenComposable(
 | 
			
		||||
    initialisation: IInitialisation = viewModel<InitialisationViewModel>(),
 | 
			
		||||
    navigation: INavigation = viewModel<NavigationViewModel>()
 | 
			
		||||
    navigation: INavigation = viewModel<NavigationViewModel>(),
 | 
			
		||||
    initiallyVisible: Boolean = false,
 | 
			
		||||
) {
 | 
			
		||||
    val state by initialisation.state.observeAsState()
 | 
			
		||||
 | 
			
		||||
    LaunchedEffect(LAUNCH_EFFECT_LOAD_APPLICATION) {
 | 
			
		||||
        initialisation.loadApplication()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ContentComposable(state = state, initiallyVisible = initiallyVisible)
 | 
			
		||||
    DialogComposable(state = state)
 | 
			
		||||
    NavigationComposable(navigation = navigation, state = state)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@OptIn(ExperimentalAnimationApi::class)
 | 
			
		||||
@Composable
 | 
			
		||||
private fun ContentComposable(
 | 
			
		||||
    state: IInitialisation.State?,
 | 
			
		||||
    duration: Int = ANIMATION_DURATION,
 | 
			
		||||
    initiallyVisible: Boolean = false,
 | 
			
		||||
) {
 | 
			
		||||
    val duration = 1000
 | 
			
		||||
    val typography = MaterialTheme.typography
 | 
			
		||||
    initialisation.LoadApplication {
 | 
			
		||||
        val visible = it !is IInitialisation.State.Finished
 | 
			
		||||
    val visible = state !is IInitialisation.State.Finished
 | 
			
		||||
 | 
			
		||||
        Box(
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .fillMaxHeight()
 | 
			
		||||
            .fillMaxWidth()
 | 
			
		||||
            .padding(16.dp),
 | 
			
		||||
    ) {
 | 
			
		||||
        Column(
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .fillMaxHeight()
 | 
			
		||||
                .fillMaxWidth()
 | 
			
		||||
                .padding(16.dp),
 | 
			
		||||
                .width(240.dp)
 | 
			
		||||
                .align(Alignment.Center)
 | 
			
		||||
        ) {
 | 
			
		||||
            Column(
 | 
			
		||||
                modifier = Modifier
 | 
			
		||||
                    .width(240.dp)
 | 
			
		||||
                    .align(Alignment.Center)
 | 
			
		||||
            ) {
 | 
			
		||||
                AnimatedVisibility(
 | 
			
		||||
                    visible = visible,
 | 
			
		||||
                    initiallyVisible = false,
 | 
			
		||||
                    enter = fadeIn(animationSpec = tween(duration))
 | 
			
		||||
                            + slideInVertically(
 | 
			
		||||
                        initialOffsetY = { height -> -height },
 | 
			
		||||
                        animationSpec = tween(duration)
 | 
			
		||||
                    ),
 | 
			
		||||
                    exit = fadeOut(animationSpec = tween(duration))
 | 
			
		||||
                            + slideOutVertically(
 | 
			
		||||
                        targetOffsetY = { height -> -height },
 | 
			
		||||
                        animationSpec = tween(duration)
 | 
			
		||||
                    ),
 | 
			
		||||
                ) {
 | 
			
		||||
                    Text(
 | 
			
		||||
                        style = typography.h4,
 | 
			
		||||
                        text = "Welcome to"
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                AnimatedVisibility(
 | 
			
		||||
                    modifier = Modifier.align(Alignment.End),
 | 
			
		||||
                    visible = visible,
 | 
			
		||||
                    initiallyVisible = false,
 | 
			
		||||
                    enter = fadeIn(animationSpec = tween(duration))
 | 
			
		||||
                            + slideInVertically(
 | 
			
		||||
                        initialOffsetY = { height -> height },
 | 
			
		||||
                        animationSpec = tween(duration)
 | 
			
		||||
                    ),
 | 
			
		||||
                    exit = fadeOut(animationSpec = tween(duration))
 | 
			
		||||
                            + slideOutVertically(
 | 
			
		||||
                        targetOffsetY = { height -> height },
 | 
			
		||||
                        animationSpec = tween(duration)
 | 
			
		||||
                    ),
 | 
			
		||||
                ) {
 | 
			
		||||
                    Text(
 | 
			
		||||
                        style = typography.h4,
 | 
			
		||||
                        text = stringResource(id = R.string.app_name)
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            AnimatedVisibility(
 | 
			
		||||
                modifier = Modifier.align(Alignment.BottomEnd),
 | 
			
		||||
                visible = visible,
 | 
			
		||||
                enter = fadeIn(animationSpec = tween(duration)),
 | 
			
		||||
                exit = fadeOut(animationSpec = tween(duration)),
 | 
			
		||||
                initiallyVisible = initiallyVisible,
 | 
			
		||||
                enter = fadeIn(animationSpec = tween(duration))
 | 
			
		||||
                        + slideInVertically(
 | 
			
		||||
                    initialOffsetY = { height -> -height },
 | 
			
		||||
                    animationSpec = tween(duration)
 | 
			
		||||
                ),
 | 
			
		||||
                exit = fadeOut(animationSpec = tween(duration))
 | 
			
		||||
                        + slideOutVertically(
 | 
			
		||||
                    targetOffsetY = { height -> -height },
 | 
			
		||||
                    animationSpec = tween(duration)
 | 
			
		||||
                ),
 | 
			
		||||
            ) {
 | 
			
		||||
                Text(
 | 
			
		||||
                    style = typography.caption,
 | 
			
		||||
                    text = stringResource(
 | 
			
		||||
                        R.string.app_version,
 | 
			
		||||
                        BuildConfig.BUILD_TYPE.toUpperCase(Locale.getDefault()),
 | 
			
		||||
                        BuildConfig.VERSION_NAME,
 | 
			
		||||
                        BuildConfig.VERSION_CODE
 | 
			
		||||
                    )
 | 
			
		||||
                    style = typography.h4,
 | 
			
		||||
                    text = "Welcome to"
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            AnimatedVisibility(
 | 
			
		||||
                modifier = Modifier.align(Alignment.End),
 | 
			
		||||
                visible = visible,
 | 
			
		||||
                initiallyVisible = initiallyVisible,
 | 
			
		||||
                enter = fadeIn(animationSpec = tween(duration))
 | 
			
		||||
                        + slideInVertically(
 | 
			
		||||
                    initialOffsetY = { height -> height },
 | 
			
		||||
                    animationSpec = tween(duration)
 | 
			
		||||
                ),
 | 
			
		||||
                exit = fadeOut(animationSpec = tween(duration))
 | 
			
		||||
                        + slideOutVertically(
 | 
			
		||||
                    targetOffsetY = { height -> height },
 | 
			
		||||
                    animationSpec = tween(duration)
 | 
			
		||||
                ),
 | 
			
		||||
            ) {
 | 
			
		||||
                Text(
 | 
			
		||||
                    style = typography.h4,
 | 
			
		||||
                    text = stringResource(id = R.string.app_name)
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (it is IInitialisation.State.Finished) {
 | 
			
		||||
            LaunchedEffect(key1 = "SplashScreen.navigateTo()") {
 | 
			
		||||
                delay(1000)
 | 
			
		||||
                if (it.needLogin) {
 | 
			
		||||
                    navigation.navigateTo(INavigation.Screen.LoginScreen)
 | 
			
		||||
                } else {
 | 
			
		||||
                    navigation.navigateTo(INavigation.Screen.MainScreen)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.BottomEnd),
 | 
			
		||||
            visible = visible,
 | 
			
		||||
            initiallyVisible = initiallyVisible,
 | 
			
		||||
            enter = fadeIn(animationSpec = tween(duration)),
 | 
			
		||||
            exit = fadeOut(animationSpec = tween(duration)),
 | 
			
		||||
        ) {
 | 
			
		||||
            Text(
 | 
			
		||||
                style = typography.caption,
 | 
			
		||||
                text = stringResource(
 | 
			
		||||
                    R.string.app_version,
 | 
			
		||||
                    BuildConfig.BUILD_TYPE.toUpperCase(Locale.getDefault()),
 | 
			
		||||
                    BuildConfig.VERSION_NAME,
 | 
			
		||||
                    BuildConfig.VERSION_CODE
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@OptIn(ExperimentalAnimationApi::class)
 | 
			
		||||
@Composable
 | 
			
		||||
private fun DialogComposable(state: IInitialisation.State?) {
 | 
			
		||||
    CrossFadeOverlay(
 | 
			
		||||
        modifier = Modifier.clickable {},
 | 
			
		||||
        visible = state is IInitialisation.State.Error
 | 
			
		||||
    ) {
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = true,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            ErrorCard(
 | 
			
		||||
                message = stringResource(id = R.string.error_generic),
 | 
			
		||||
                exception = (state as? IInitialisation.State.Error)?.exception
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun NavigationComposable(
 | 
			
		||||
    navigation: INavigation,
 | 
			
		||||
    state: IInitialisation.State?,
 | 
			
		||||
    duration: Int = ANIMATION_DURATION,
 | 
			
		||||
) {
 | 
			
		||||
    if (state is IInitialisation.State.Finished) {
 | 
			
		||||
        LaunchedEffect(key1 = "SplashScreen.navigateTo()") {
 | 
			
		||||
            delay(duration.toLong())
 | 
			
		||||
            if (state.needLogin) {
 | 
			
		||||
                navigation.navigateTo(INavigation.Screen.LoginScreen)
 | 
			
		||||
            } else {
 | 
			
		||||
                navigation.navigateTo(INavigation.Screen.MainScreen)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,25 +1,35 @@
 | 
			
		|||
package com.pixelized.biblib.ui.viewmodel.initialisation
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.Stable
 | 
			
		||||
import androidx.lifecycle.LiveData
 | 
			
		||||
import androidx.lifecycle.MutableLiveData
 | 
			
		||||
 | 
			
		||||
interface IInitialisation {
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    fun LoadApplication(content: @Composable (State) -> Unit)
 | 
			
		||||
    val state: LiveData<State>
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    fun LoadBook(content: @Composable (State) -> Unit)
 | 
			
		||||
    fun loadApplication()
 | 
			
		||||
 | 
			
		||||
    fun loadBook()
 | 
			
		||||
 | 
			
		||||
    @Stable
 | 
			
		||||
    sealed class State {
 | 
			
		||||
        @Stable
 | 
			
		||||
        object Initial : State()
 | 
			
		||||
 | 
			
		||||
        @Stable
 | 
			
		||||
        object Loading : State()
 | 
			
		||||
 | 
			
		||||
        @Stable
 | 
			
		||||
        class Finished(val needLogin: Boolean) : State()
 | 
			
		||||
 | 
			
		||||
        @Stable
 | 
			
		||||
        class Error(val exception: Exception) : State()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class Mock(private val state: State = State.Loading) : IInitialisation {
 | 
			
		||||
        @Composable
 | 
			
		||||
        override fun LoadApplication(content: (State) -> Unit) = content(state)
 | 
			
		||||
        @Composable
 | 
			
		||||
        override fun LoadBook(content: (State) -> Unit) = content(state)
 | 
			
		||||
    class Mock(private val value: State = State.Initial) : IInitialisation {
 | 
			
		||||
        override val state get() = MutableLiveData(value)
 | 
			
		||||
        override fun loadApplication() = Unit
 | 
			
		||||
        override fun loadBook() = Unit
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,9 @@
 | 
			
		|||
package com.pixelized.biblib.ui.viewmodel.initialisation
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.*
 | 
			
		||||
import androidx.lifecycle.LiveData
 | 
			
		||||
import androidx.lifecycle.MutableLiveData
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import androidx.lifecycle.viewModelScope
 | 
			
		||||
import com.pixelized.biblib.network.client.IBibLibClient
 | 
			
		||||
import com.pixelized.biblib.network.data.query.AuthLoginQuery
 | 
			
		||||
import com.pixelized.biblib.network.factory.BookFactory
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +13,10 @@ import com.pixelized.biblib.repository.credential.ICredentialRepository
 | 
			
		|||
import com.pixelized.biblib.repository.googleSignIn.IGoogleSingInRepository
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.initialisation.IInitialisation.State.*
 | 
			
		||||
import com.pixelized.biblib.utils.injection.inject
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InitialisationViewModel : ViewModel(), IInitialisation {
 | 
			
		||||
    private val credentialRepository: ICredentialRepository by inject()
 | 
			
		||||
| 
						 | 
				
			
			@ -20,33 +25,37 @@ class InitialisationViewModel : ViewModel(), IInitialisation {
 | 
			
		|||
    private val client: IBibLibClient by inject()
 | 
			
		||||
    private val apiCache: IAPICacheRepository by inject()
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    override fun LoadApplication(content: @Composable (IInitialisation.State) -> Unit) {
 | 
			
		||||
        val state: MutableState<IInitialisation.State> = remember { mutableStateOf(Initial) }
 | 
			
		||||
    private val _state = MutableLiveData<IInitialisation.State>()
 | 
			
		||||
    override val state: LiveData<IInitialisation.State> get() = _state
 | 
			
		||||
 | 
			
		||||
        LaunchedEffect(key1 = "LoadApplication") {
 | 
			
		||||
            state.value = Loading
 | 
			
		||||
    override fun loadApplication() {
 | 
			
		||||
        viewModelScope.launch(Dispatchers.IO) {
 | 
			
		||||
            _state.postValue(Initial)
 | 
			
		||||
            _state.postValue(Loading)
 | 
			
		||||
            delay(2000)
 | 
			
		||||
 | 
			
		||||
            val loggedIn = loginWithGoogle() || loginWithCredential()
 | 
			
		||||
            if (loggedIn) {
 | 
			
		||||
                loadNewBooks() && loadAllBooks()
 | 
			
		||||
            try {
 | 
			
		||||
                val loggedIn = loginWithGoogle() || loginWithCredential()
 | 
			
		||||
                if (loggedIn) {
 | 
			
		||||
                    loadNewBooks() && loadAllBooks()
 | 
			
		||||
                }
 | 
			
		||||
                _state.postValue(Finished(needLogin = loggedIn.not()))
 | 
			
		||||
            } catch (exception: Exception) {
 | 
			
		||||
                _state.postValue(Error(exception))
 | 
			
		||||
            }
 | 
			
		||||
            state.value = Finished(needLogin = loggedIn.not())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        content(state.value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    override fun LoadBook(content: (IInitialisation.State) -> Unit) {
 | 
			
		||||
        val state: MutableState<IInitialisation.State> = remember { mutableStateOf(Initial) }
 | 
			
		||||
        LaunchedEffect(key1 = "LoadBook") {
 | 
			
		||||
            state.value = Loading
 | 
			
		||||
            loadNewBooks() && loadAllBooks()
 | 
			
		||||
            state.value = Finished(needLogin = false)
 | 
			
		||||
    override fun loadBook() {
 | 
			
		||||
        viewModelScope.launch(Dispatchers.IO) {
 | 
			
		||||
            _state.postValue(Initial)
 | 
			
		||||
            _state.postValue(Loading)
 | 
			
		||||
            try {
 | 
			
		||||
                loadNewBooks() && loadAllBooks()
 | 
			
		||||
                _state.postValue(Finished(needLogin = false))
 | 
			
		||||
            } catch (exception: Exception) {
 | 
			
		||||
                _state.postValue(Error(exception))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        content(state.value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun loginWithGoogle(): Boolean {
 | 
			
		||||
| 
						 | 
				
			
			@ -104,9 +113,6 @@ class InitialisationViewModel : ViewModel(), IInitialisation {
 | 
			
		|||
 | 
			
		||||
    private suspend fun loadAllBooks(): Boolean {
 | 
			
		||||
        client.service.list().let { response ->
 | 
			
		||||
            // TODO: useless isn't it ?
 | 
			
		||||
            apiCache.list = response
 | 
			
		||||
 | 
			
		||||
            val factory = BookFactory()
 | 
			
		||||
            val books = response.data?.map { dto -> factory.fromListResponseToBook(dto, false) }
 | 
			
		||||
            books?.let { data -> bookRepository.update(data) }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,16 +9,17 @@
 | 
			
		|||
    <string name="action_send">SEND</string>
 | 
			
		||||
    <string name="action_google_sign_in">Sign in with Google</string>
 | 
			
		||||
 | 
			
		||||
    <string name="loading">Entering the Imperial Library of Trantor.</string>
 | 
			
		||||
 | 
			
		||||
    <string name="error_generic">Oops!</string>
 | 
			
		||||
    <string name="loading_authentication">Entering the Imperial Library of Trantor.</string>
 | 
			
		||||
    <string name="loading_book">Entering the Imperial Library of Trantor.</string>
 | 
			
		||||
    <string name="success_authentication">Authentication successful</string>
 | 
			
		||||
    <string name="success_book">Library successfully loaded</string>
 | 
			
		||||
 | 
			
		||||
    <string name="welcome_sign_in">Sign in to BibLib</string>
 | 
			
		||||
 | 
			
		||||
    <string name="authentication_login">Login</string>
 | 
			
		||||
    <string name="authentication_password">Password</string>
 | 
			
		||||
    <string name="authentication_credential_remember">Remember my credential</string>
 | 
			
		||||
    <string name="authentication_success">Authentication successful</string>
 | 
			
		||||
 | 
			
		||||
    <string name="detail_rating">Rating</string>
 | 
			
		||||
    <string name="detail_language">Language</string>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue