Add login screen.

This commit is contained in:
Thomas Andres Gomez 2021-05-07 17:07:06 +02:00
parent e9f5c30e01
commit 080d2e2b49
8 changed files with 257 additions and 17 deletions

View file

@ -6,20 +6,21 @@ import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import com.pixelized.biblib.ui.composable.screen.LoginScreenComposable
import com.pixelized.biblib.ui.composable.screen.MainScreenComposable
import com.pixelized.biblib.ui.composable.screen.SplashScreenComposable
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel.Screen
import com.pixelized.biblib.ui.composable.screen.SplashScreenComposable
import com.pixelized.biblib.ui.composable.screen.MainScreenComposable
import com.pixelized.biblib.utils.BitmapCache
class MainActivity : ComponentActivity() {
private val navigationViewModel: NavigationViewModel by viewModels()
private val authenticationViewModel: AuthenticationViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -28,7 +29,7 @@ class MainActivity : ComponentActivity() {
setContent {
BibLibTheme {
ContentComposable(navigationViewModel)
ContentComposable(navigationViewModel, authenticationViewModel)
}
}
}
@ -41,13 +42,20 @@ class MainActivity : ComponentActivity() {
}
@Composable
fun ContentComposable(navigationViewModel: NavigationViewModel) {
fun ContentComposable(
navigationViewModel: NavigationViewModel,
authenticationViewModel: AuthenticationViewModel
) {
val main by navigationViewModel.screen.observeAsState()
Crossfade(targetState = main, animationSpec = tween(1000)) {
when (it) {
is Screen.SplashScreen -> SplashScreenComposable(navigationViewModel)
is Screen.MainScreen -> MainScreenComposable(navigationViewModel)
is Screen.LoginScreen -> LoginScreenComposable(
navigationViewModel = navigationViewModel,
authenticationViewModel = authenticationViewModel
)
}
}
}

View file

@ -0,0 +1,176 @@
package com.pixelized.biblib.ui.composable.screen
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.ButtonDefaults.textButtonColors
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.sharp.Visibility
import androidx.compose.material.icons.sharp.VisibilityOff
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.pixelized.biblib.R
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
@Preview
@Composable
fun LoginScreenComposablePreview() {
BibLibTheme {
val navigationViewModel = NavigationViewModel()
val authenticationViewModel = AuthenticationViewModel()
LoginScreenComposable(navigationViewModel, authenticationViewModel)
}
}
@Composable
fun LoginScreenComposable(
navigationViewModel: NavigationViewModel,
authenticationViewModel: AuthenticationViewModel
) {
val typography = MaterialTheme.typography
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
Spacer(modifier = Modifier.weight(1f))
Text(
modifier = Modifier
.padding(vertical = 16.dp)
.align(alignment = Alignment.CenterHorizontally),
style = typography.h4,
text = stringResource(id = R.string.welcome_sign_in)
)
Spacer(modifier = Modifier.weight(1f))
LoginField(
viewModel = authenticationViewModel,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
)
PasswordField(
viewModel = authenticationViewModel,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
)
CredentialRemember(
viewModel = authenticationViewModel,
modifier = Modifier
.height(48.dp)
.padding(bottom = 16.dp)
)
Row(
modifier = Modifier
.padding(bottom = 16.dp)
.align(Alignment.End)
) {
Button(
modifier = Modifier.padding(end = 8.dp),
colors = textButtonColors(),
onClick = {
// TODO:
navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
}) {
Text(text = stringResource(id = R.string.action_register))
}
Button(onClick = {
// TODO:
navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
}) {
Text(text = stringResource(id = R.string.action_login))
}
}
Spacer(modifier = Modifier.weight(2f))
Button(
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.outlinedButtonColors(),
onClick = {
// TODO:
navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
}) {
Image(
modifier = Modifier.padding(end = 8.dp),
painter = painterResource(id = R.drawable.ic_google), contentDescription = ""
)
Text(text = stringResource(id = R.string.action_google_sign_in))
}
}
}
@Composable
private fun LoginField(viewModel: AuthenticationViewModel, modifier: Modifier = Modifier) {
val login: State<String?> = viewModel.login.observeAsState()
TextField(
modifier = modifier,
value = login.value ?: "",
label = { Text(text = stringResource(id = R.string.authentication_login)) },
maxLines = 1,
singleLine = true,
onValueChange = { viewModel.updateLogin(it) },
)
}
@Composable
private fun PasswordField(viewModel: AuthenticationViewModel, modifier: Modifier = Modifier) {
val password = viewModel.password.observeAsState()
var passwordVisibility by remember { mutableStateOf(false) }
TextField(
modifier = modifier,
value = password.value ?: "",
maxLines = 1,
singleLine = true,
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { passwordVisibility = passwordVisibility.not() }) {
Icon(
imageVector = if (passwordVisibility) Icons.Sharp.VisibilityOff else Icons.Sharp.Visibility,
contentDescription = "password visibility"
)
}
},
label = { Text(text = stringResource(id = R.string.authentication_password)) },
onValueChange = { viewModel.updatePassword(it) }
)
}
@Composable
private fun CredentialRemember(viewModel: AuthenticationViewModel, modifier: Modifier = Modifier) {
val credential = viewModel.rememberCredential.observeAsState()
Row(modifier = modifier.clickable {
viewModel.updateRememberCredential(credential = credential.value?.not() ?: false)
}) {
Checkbox(
modifier = Modifier.align(Alignment.CenterVertically),
checked = credential.value ?: false,
onCheckedChange = null
)
Text(
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(start = 8.dp),
text = stringResource(id = R.string.authentication_credential_remember)
)
}
}

View file

@ -6,11 +6,14 @@ import androidx.compose.foundation.layout.fillMaxWidth
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.tooling.preview.Preview
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
import kotlinx.coroutines.*
@Preview
@ -36,4 +39,12 @@ fun SplashScreenComposable(navigationViewModel: NavigationViewModel) {
text = "Welcome to BibLib"
)
}
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(key1 = "loading", block = {
coroutineScope.launch {
delay(1000)
navigationViewModel.navigateTo(NavigationViewModel.Screen.LoginScreen)
}
})
}

View file

@ -0,0 +1,29 @@
package com.pixelized.biblib.ui.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class AuthenticationViewModel: ViewModel() {
private val _login = MutableLiveData<String>()
val login: LiveData<String> get() = _login
private val _password = MutableLiveData<String>()
val password: LiveData<String> get() = _password
private val _rememberCredential = MutableLiveData<Boolean>(false)
val rememberCredential: LiveData<Boolean> get() = _rememberCredential
fun updateLogin(login: String) {
_login.postValue(login)
}
fun updatePassword(password: String) {
_password.postValue(password)
}
fun updateRememberCredential(credential: Boolean) {
_rememberCredential.postValue(credential)
}
}

View file

@ -13,21 +13,12 @@ class NavigationViewModel : ViewModel() {
private val stack = Stack<Page>()
private val _screen = MutableLiveData<Screen>()
private val _screen = MutableLiveData<Screen>(Screen.SplashScreen)
val screen: LiveData<Screen> get() = _screen
private val _page = MutableLiveData<Page>()
private val _page = MutableLiveData<Page>(Page.HomePage)
val page: LiveData<Page> get() = _page
init {
_screen.value = Screen.SplashScreen
viewModelScope.launch {
delay(3000)
navigateTo(Page.HomePage)
navigateTo(Screen.MainScreen)
}
}
fun navigateTo(screen: Screen): Boolean {
_screen.postValue(screen)
return true
@ -52,6 +43,7 @@ class NavigationViewModel : ViewModel() {
sealed class Screen {
object SplashScreen : Screen()
object MainScreen : Screen()
object LoginScreen : Screen()
}
sealed class Page {