SplashScreen animation.
This commit is contained in:
		
							parent
							
								
									fa2af6dd90
								
							
						
					
					
						commit
						9cde3f6404
					
				
					 7 changed files with 181 additions and 50 deletions
				
			
		| 
						 | 
				
			
			@ -4,39 +4,47 @@ import android.app.Application
 | 
			
		|||
import android.content.Context
 | 
			
		||||
import android.content.SharedPreferences
 | 
			
		||||
import androidx.core.content.edit
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.authentication.AuthenticationViewModel
 | 
			
		||||
 | 
			
		||||
class CredentialRepository(application: Application) : ICredentialRepository {
 | 
			
		||||
    private val preferences =
 | 
			
		||||
        application.getSharedPreferences(AuthenticationViewModel.SHARED_PREF, Context.MODE_PRIVATE)
 | 
			
		||||
    private val preferences = application.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
 | 
			
		||||
 | 
			
		||||
    override var login
 | 
			
		||||
        get() = preferences.login
 | 
			
		||||
        set(value) {
 | 
			
		||||
            preferences.login = value
 | 
			
		||||
        }
 | 
			
		||||
        set(value) = value.let { preferences.login = it }
 | 
			
		||||
 | 
			
		||||
    override var password: String?
 | 
			
		||||
        get() = preferences.password
 | 
			
		||||
        set(value) {
 | 
			
		||||
            preferences.password = value
 | 
			
		||||
        }
 | 
			
		||||
        set(value) = value.let { preferences.password = it }
 | 
			
		||||
 | 
			
		||||
    override var rememberCredential: Boolean
 | 
			
		||||
        get() = preferences.rememberCredential
 | 
			
		||||
        set(value) {
 | 
			
		||||
            preferences.rememberCredential = value
 | 
			
		||||
        }
 | 
			
		||||
        set(value) = value.let { preferences.rememberCredential = it }
 | 
			
		||||
 | 
			
		||||
    override var bearer: String?
 | 
			
		||||
        get() = preferences.bearer
 | 
			
		||||
        set(value) = value.let { preferences.bearer = it }
 | 
			
		||||
 | 
			
		||||
    private var SharedPreferences.login: String?
 | 
			
		||||
        get() = getString(AuthenticationViewModel.REMEMBER_USER, null)
 | 
			
		||||
        set(value) = edit { putString(AuthenticationViewModel.REMEMBER_USER, value) }
 | 
			
		||||
        get() = getString(REMEMBER_USER, null)
 | 
			
		||||
        set(value) = edit { putString(REMEMBER_USER, value) }
 | 
			
		||||
 | 
			
		||||
    private var SharedPreferences.password: String?
 | 
			
		||||
        get() = getString(AuthenticationViewModel.REMEMBER_PASSWORD, null)
 | 
			
		||||
        set(value) = edit { putString(AuthenticationViewModel.REMEMBER_PASSWORD, value) }
 | 
			
		||||
        get() = getString(REMEMBER_PASSWORD, null)
 | 
			
		||||
        set(value) = edit { putString(REMEMBER_PASSWORD, value) }
 | 
			
		||||
 | 
			
		||||
    private var SharedPreferences.rememberCredential: Boolean
 | 
			
		||||
        get() = getBoolean(AuthenticationViewModel.REMEMBER_CREDENTIAL, false)
 | 
			
		||||
        set(value) = edit { putBoolean(AuthenticationViewModel.REMEMBER_CREDENTIAL, value) }
 | 
			
		||||
        get() = getBoolean(REMEMBER_CREDENTIAL, false)
 | 
			
		||||
        set(value) = edit { putBoolean(REMEMBER_CREDENTIAL, value) }
 | 
			
		||||
 | 
			
		||||
    private var SharedPreferences.bearer: String?
 | 
			
		||||
        get() = getString(BEARER_TOKEN, null)
 | 
			
		||||
        set(value) = edit { putString(BEARER_TOKEN, value) }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val SHARED_PREF = "BIB_LIB_SHARED_PREF"
 | 
			
		||||
        private const val REMEMBER_CREDENTIAL = "REMEMBER_CREDENTIAL"
 | 
			
		||||
        private const val REMEMBER_USER = "REMEMBER_USER"
 | 
			
		||||
        private const val REMEMBER_PASSWORD = "REMEMBER_PASSWORD"
 | 
			
		||||
        private const val BEARER_TOKEN = "BEARER_TOKEN"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,4 +4,5 @@ interface ICredentialRepository {
 | 
			
		|||
    var login: String?
 | 
			
		||||
    var password: String?
 | 
			
		||||
    var rememberCredential: Boolean
 | 
			
		||||
    var bearer: String?
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,55 +1,121 @@
 | 
			
		|||
package com.pixelized.biblib.ui.composable.screen
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxHeight
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.animation.*
 | 
			
		||||
import androidx.compose.animation.core.tween
 | 
			
		||||
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.rememberCoroutineScope
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
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.theme.BibLibTheme
 | 
			
		||||
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
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview
 | 
			
		||||
@Composable
 | 
			
		||||
fun SplashScreenComposablePreview() {
 | 
			
		||||
    BibLibTheme {
 | 
			
		||||
        SplashScreenComposable(INavigation.Mock())
 | 
			
		||||
        SplashScreenComposable(IInitialisation.Mock(), INavigation.Mock())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@OptIn(ExperimentalAnimationApi::class)
 | 
			
		||||
@Composable
 | 
			
		||||
fun SplashScreenComposable(
 | 
			
		||||
    initialisation: IInitialisation = viewModel<InitialisationViewModel>(),
 | 
			
		||||
    navigation: INavigation = viewModel<NavigationViewModel>()
 | 
			
		||||
) {
 | 
			
		||||
    val duration = 1000
 | 
			
		||||
    val typography = MaterialTheme.typography
 | 
			
		||||
    initialisation.LoadApplication {
 | 
			
		||||
        Box(
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .fillMaxHeight()
 | 
			
		||||
                .fillMaxWidth()
 | 
			
		||||
                .padding(16.dp),
 | 
			
		||||
        ) {
 | 
			
		||||
            Column(
 | 
			
		||||
                modifier = Modifier
 | 
			
		||||
                    .width(240.dp)
 | 
			
		||||
                    .align(Alignment.Center)
 | 
			
		||||
            ) {
 | 
			
		||||
                AnimatedVisibility(
 | 
			
		||||
                    visible = it != IInitialisation.State.Finished,
 | 
			
		||||
                    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 = it != IInitialisation.State.Finished,
 | 
			
		||||
                    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)
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .fillMaxHeight()
 | 
			
		||||
            .fillMaxWidth(),
 | 
			
		||||
        contentAlignment = Alignment.Center,
 | 
			
		||||
    ) {
 | 
			
		||||
        Text(
 | 
			
		||||
            style = typography.h4,
 | 
			
		||||
            text = "Welcome to BibLib"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val coroutineScope = rememberCoroutineScope()
 | 
			
		||||
    LaunchedEffect(key1 = "loading", block = {
 | 
			
		||||
        coroutineScope.launch {
 | 
			
		||||
            delay(1000)
 | 
			
		||||
            navigation.navigateTo(INavigation.Screen.LoginScreen)
 | 
			
		||||
            AnimatedVisibility(
 | 
			
		||||
                modifier = Modifier.align(Alignment.BottomEnd),
 | 
			
		||||
                visible = it != IInitialisation.State.Finished,
 | 
			
		||||
                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
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        if (it == IInitialisation.State.Finished) {
 | 
			
		||||
            LaunchedEffect(key1 = "navigateTo(INavigation.Screen.LoginScreen)") {
 | 
			
		||||
                delay(1000)
 | 
			
		||||
                navigation.navigateTo(INavigation.Screen.LoginScreen)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -124,11 +124,4 @@ class AuthenticationViewModel : ViewModel(), IAuthentication {
 | 
			
		|||
        _state.postValue(State.Loading)
 | 
			
		||||
        launcher?.launch(googleSignIn.client.signInIntent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val SHARED_PREF = "BIB_LIB_SHARED_PREF"
 | 
			
		||||
        const val REMEMBER_CREDENTIAL = "REMEMBER_CREDENTIAL"
 | 
			
		||||
        const val REMEMBER_USER = "REMEMBER_USER"
 | 
			
		||||
        const val REMEMBER_PASSWORD = "REMEMBER_PASSWORD"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
package com.pixelized.biblib.ui.viewmodel.initialisation
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
 | 
			
		||||
interface IInitialisation {
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    fun LoadApplication(content: @Composable (State) -> Unit)
 | 
			
		||||
 | 
			
		||||
    sealed class State {
 | 
			
		||||
        abstract fun proceed(): State
 | 
			
		||||
 | 
			
		||||
        object Initial : State() {
 | 
			
		||||
            override fun proceed() = Loading
 | 
			
		||||
        }
 | 
			
		||||
        object Loading : State() {
 | 
			
		||||
            override fun proceed() = Finished
 | 
			
		||||
        }
 | 
			
		||||
        object Finished : State() {
 | 
			
		||||
            override fun proceed() = Finished
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class Mock(private val state: State = State.Loading) : IInitialisation {
 | 
			
		||||
        @Composable
 | 
			
		||||
        override fun LoadApplication(content: (State) -> Unit) = content(state)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
package com.pixelized.biblib.ui.viewmodel.initialisation
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.*
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import com.pixelized.biblib.network.client.IBibLibClient
 | 
			
		||||
import com.pixelized.biblib.repository.credential.ICredentialRepository
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.initialisation.IInitialisation.State.*
 | 
			
		||||
import com.pixelized.biblib.utils.injection.inject
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
 | 
			
		||||
class InitialisationViewModel : ViewModel(), IInitialisation {
 | 
			
		||||
    private val credentialRepository: ICredentialRepository by inject()
 | 
			
		||||
    private val client: IBibLibClient by inject()
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    override fun LoadApplication(content: @Composable (IInitialisation.State) -> Unit) {
 | 
			
		||||
        val state: MutableState<IInitialisation.State> = remember { mutableStateOf(Initial) }
 | 
			
		||||
 | 
			
		||||
        LaunchedEffect(key1 = "LoadApplication") {
 | 
			
		||||
            state.value = Loading
 | 
			
		||||
            delay(2000)
 | 
			
		||||
 | 
			
		||||
            val bearerToken = credentialRepository.bearer
 | 
			
		||||
            if (bearerToken != null) {
 | 
			
		||||
                client.updateBearerToken(bearerToken)
 | 
			
		||||
                state.value = Finished
 | 
			
		||||
            } else {
 | 
			
		||||
                state.value = Finished
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        content(state.value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
<resources>
 | 
			
		||||
    <string name="app_name">BibLib</string>
 | 
			
		||||
    <string name="app_version">%1$s: %2$s - %3$d</string>
 | 
			
		||||
 | 
			
		||||
    <string name="action_register">Register</string>
 | 
			
		||||
    <string name="action_login">Login</string>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue