diff --git a/.idea/misc.xml b/.idea/misc.xml index 4015527..9079288 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -70,8 +70,15 @@ + + + + + + + diff --git a/app/src/main/java/com/pixelized/biblib/ui/MainActivity.kt b/app/src/main/java/com/pixelized/biblib/ui/MainActivity.kt index 788bfa9..4ffd291 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/MainActivity.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/MainActivity.kt @@ -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 + ) } } } diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/screen/LoginScreenComposable.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/screen/LoginScreenComposable.kt new file mode 100644 index 0000000..36edc22 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/screen/LoginScreenComposable.kt @@ -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 = 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) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/screen/SplashScreenComposable.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/screen/SplashScreenComposable.kt index 1a5e410..4bd79cd 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/screen/SplashScreenComposable.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/screen/SplashScreenComposable.kt @@ -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) + } + }) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/viewmodel/AuthenticationViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/AuthenticationViewModel.kt new file mode 100644 index 0000000..1c2b63d --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/AuthenticationViewModel.kt @@ -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() + val login: LiveData get() = _login + + private val _password = MutableLiveData() + val password: LiveData get() = _password + + private val _rememberCredential = MutableLiveData(false) + val rememberCredential: LiveData get() = _rememberCredential + + fun updateLogin(login: String) { + _login.postValue(login) + } + + fun updatePassword(password: String) { + _password.postValue(password) + } + + fun updateRememberCredential(credential: Boolean) { + _rememberCredential.postValue(credential) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/viewmodel/NavigationViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/NavigationViewModel.kt index f177b27..c3e544b 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/viewmodel/NavigationViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/NavigationViewModel.kt @@ -13,21 +13,12 @@ class NavigationViewModel : ViewModel() { private val stack = Stack() - private val _screen = MutableLiveData() + private val _screen = MutableLiveData(Screen.SplashScreen) val screen: LiveData get() = _screen - private val _page = MutableLiveData() + private val _page = MutableLiveData(Page.HomePage) val page: LiveData 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 { diff --git a/app/src/main/res/drawable/ic_google.xml b/app/src/main/res/drawable/ic_google.xml new file mode 100644 index 0000000..5c1b1b1 --- /dev/null +++ b/app/src/main/res/drawable/ic_google.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4d36642..45c5ab1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,16 @@ BibLib + Register + Login + Sign in with Google + + Sign in to BibLib + + Login + Password + Remember my credential + Rating Language Release