Add login screen.
This commit is contained in:
parent
e9f5c30e01
commit
080d2e2b49
8 changed files with 257 additions and 17 deletions
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue