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

7
.idea/misc.xml generated
View file

@ -70,8 +70,15 @@
<entry key="../../../../../layout/compose-model-1620317783141.xml" value="0.25462962962962965" /> <entry key="../../../../../layout/compose-model-1620317783141.xml" value="0.25462962962962965" />
<entry key="../../../../../layout/compose-model-1620317894833.xml" value="0.25462962962962965" /> <entry key="../../../../../layout/compose-model-1620317894833.xml" value="0.25462962962962965" />
<entry key="../../../../../layout/compose-model-1620374189600.xml" value="0.23514851485148514" /> <entry key="../../../../../layout/compose-model-1620374189600.xml" value="0.23514851485148514" />
<entry key="../../../../../layout/compose-model-1620388426698.xml" value="0.26296296296296295" />
<entry key="../../../../../layout/compose-model-1620389580469.xml" value="0.23514851485148514" />
<entry key="../../../../../layout/compose-model-1620389588794.xml" value="0.23514851485148514" />
<entry key="../../../../../layout/compose-model-1620391384718.xml" value="0.26296296296296295" />
<entry key="../../../../../layout/compose-model-1620397702847.xml" value="0.28125" />
<entry key="../../../../../layout/compose-model-1620399131186.xml" value="0.1" />
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2898148148148148" /> <entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2898148148148148" />
<entry key="app/src/main/res/drawable/ic_baseline_local_library_24.xml" value="0.25462962962962965" /> <entry key="app/src/main/res/drawable/ic_baseline_local_library_24.xml" value="0.25462962962962965" />
<entry key="app/src/main/res/drawable/ic_google.xml" value="0.2962962962962963" />
</map> </map>
</option> </option>
</component> </component>

View file

@ -6,20 +6,21 @@ import androidx.activity.compose.setContent
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween 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.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState 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.theme.BibLibTheme
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel.Screen 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 import com.pixelized.biblib.utils.BitmapCache
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val navigationViewModel: NavigationViewModel by viewModels() private val navigationViewModel: NavigationViewModel by viewModels()
private val authenticationViewModel: AuthenticationViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -28,7 +29,7 @@ class MainActivity : ComponentActivity() {
setContent { setContent {
BibLibTheme { BibLibTheme {
ContentComposable(navigationViewModel) ContentComposable(navigationViewModel, authenticationViewModel)
} }
} }
} }
@ -41,13 +42,20 @@ class MainActivity : ComponentActivity() {
} }
@Composable @Composable
fun ContentComposable(navigationViewModel: NavigationViewModel) { fun ContentComposable(
navigationViewModel: NavigationViewModel,
authenticationViewModel: AuthenticationViewModel
) {
val main by navigationViewModel.screen.observeAsState() val main by navigationViewModel.screen.observeAsState()
Crossfade(targetState = main, animationSpec = tween(1000)) { Crossfade(targetState = main, animationSpec = tween(1000)) {
when (it) { when (it) {
is Screen.SplashScreen -> SplashScreenComposable(navigationViewModel) is Screen.SplashScreen -> SplashScreenComposable(navigationViewModel)
is Screen.MainScreen -> MainScreenComposable(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.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
import kotlinx.coroutines.*
@Preview @Preview
@ -36,4 +39,12 @@ fun SplashScreenComposable(navigationViewModel: NavigationViewModel) {
text = "Welcome to BibLib" 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 stack = Stack<Page>()
private val _screen = MutableLiveData<Screen>() private val _screen = MutableLiveData<Screen>(Screen.SplashScreen)
val screen: LiveData<Screen> get() = _screen val screen: LiveData<Screen> get() = _screen
private val _page = MutableLiveData<Page>() private val _page = MutableLiveData<Page>(Page.HomePage)
val page: LiveData<Page> get() = _page 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 { fun navigateTo(screen: Screen): Boolean {
_screen.postValue(screen) _screen.postValue(screen)
return true return true
@ -52,6 +43,7 @@ class NavigationViewModel : ViewModel() {
sealed class Screen { sealed class Screen {
object SplashScreen : Screen() object SplashScreen : Screen()
object MainScreen : Screen() object MainScreen : Screen()
object LoginScreen : Screen()
} }
sealed class Page { sealed class Page {

View file

@ -0,0 +1,7 @@
<vector android:height="24dp" android:viewportHeight="512"
android:viewportWidth="512" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FBBB00" android:pathData="M113.47,309.408L95.648,375.94l-65.139,1.378C11.042,341.211 0,299.9 0,256c0,-42.451 10.324,-82.483 28.624,-117.732h0.014l57.992,10.632l25.404,57.644c-5.317,15.501 -8.215,32.141 -8.215,49.456C103.821,274.792 107.225,292.797 113.47,309.408z"/>
<path android:fillColor="#518EF8" android:pathData="M507.527,208.176C510.467,223.662 512,239.655 512,256c0,18.328 -1.927,36.206 -5.598,53.451c-12.462,58.683 -45.025,109.925 -90.134,146.187l-0.014,-0.014l-73.044,-3.727l-10.338,-64.535c29.932,-17.554 53.324,-45.025 65.646,-77.911h-136.89V208.176h138.887L507.527,208.176L507.527,208.176z"/>
<path android:fillColor="#28B446" android:pathData="M416.253,455.624l0.014,0.014C372.396,490.901 316.666,512 256,512c-97.491,0 -182.252,-54.491 -225.491,-134.681l82.961,-67.91c21.619,57.698 77.278,98.771 142.53,98.771c28.047,0 54.323,-7.582 76.87,-20.818L416.253,455.624z"/>
<path android:fillColor="#F14336" android:pathData="M419.404,58.936l-82.933,67.896c-23.335,-14.586 -50.919,-23.012 -80.471,-23.012c-66.729,0 -123.429,42.957 -143.965,102.724l-83.397,-68.276h-0.014C71.23,56.123 157.06,0 256,0C318.115,0 375.068,22.126 419.404,58.936z"/>
</vector>

View file

@ -1,6 +1,16 @@
<resources> <resources>
<string name="app_name">BibLib</string> <string name="app_name">BibLib</string>
<string name="action_register">Register</string>
<string name="action_login">Login</string>
<string name="action_google_sign_in">Sign in with Google</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="detail_rating">Rating</string> <string name="detail_rating">Rating</string>
<string name="detail_language">Language</string> <string name="detail_language">Language</string>
<string name="detail_Release">Release</string> <string name="detail_Release">Release</string>