commit
8ac37d7e7b
10 changed files with 270 additions and 5 deletions
|
@ -18,6 +18,8 @@ import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
|
import be.ugent.sel.studeez.screens.sign_in.LoginScreen
|
||||||
|
import be.ugent.sel.studeez.screens.sign_up.SignUpScreen
|
||||||
import be.ugent.sel.studeez.screens.splash.SplashScreen
|
import be.ugent.sel.studeez.screens.splash.SplashScreen
|
||||||
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
@ -77,5 +79,11 @@ fun NavGraphBuilder.studeezGraph(appState: StudeezAppstate) {
|
||||||
SplashScreen(openAndPopUp = { route, popUp -> appState.navigateAndPopUp(route, popUp) })
|
SplashScreen(openAndPopUp = { route, popUp -> appState.navigateAndPopUp(route, popUp) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable(StudeezDestinations.LOGIN_SCREEN) {
|
||||||
|
LoginScreen(openAndPopUp = { route, popUp -> appState.navigateAndPopUp(route, popUp) })
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(StudeezDestinations.SIGN_UP_SCREEN) {
|
||||||
|
SignUpScreen(openAndPopUp = { route, popUp -> appState.navigateAndPopUp(route, popUp) })
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -41,7 +41,7 @@ fun PrimaryScreenToolbar(
|
||||||
// Does not contain floatingActionButton and bottom bar, used in all the other screens
|
// Does not contain floatingActionButton and bottom bar, used in all the other screens
|
||||||
fun SecondaryScreenToolbar(
|
fun SecondaryScreenToolbar(
|
||||||
title: String,
|
title: String,
|
||||||
content: (PaddingValues) -> Unit
|
content: @Composable (PaddingValues) -> Unit
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
// Everything at the top of the screen
|
// Everything at the top of the screen
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package be.ugent.sel.studeez.screens.sign_in
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import be.ugent.sel.studeez.common.composable.*
|
||||||
|
import be.ugent.sel.studeez.common.ext.basicButton
|
||||||
|
import be.ugent.sel.studeez.common.ext.fieldModifier
|
||||||
|
import be.ugent.sel.studeez.common.ext.textButton
|
||||||
|
import be.ugent.sel.studeez.resources
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoginScreen(
|
||||||
|
openAndPopUp: (String, String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
viewModel: LoginViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
val uiState by viewModel.uiState
|
||||||
|
|
||||||
|
SecondaryScreenToolbar(title = resources().getString(AppText.sign_in)) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
EmailField(uiState.email, viewModel::onEmailChange, Modifier.fieldModifier())
|
||||||
|
PasswordField(uiState.password, viewModel::onPasswordChange, Modifier.fieldModifier())
|
||||||
|
|
||||||
|
BasicButton(AppText.sign_in, Modifier.basicButton()) { viewModel.onSignInClick(openAndPopUp) }
|
||||||
|
|
||||||
|
BasicTextButton(AppText.not_already_user, Modifier.textButton()) {
|
||||||
|
viewModel.onNotAlreadyUser(openAndPopUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicTextButton(AppText.forgot_password, Modifier.textButton()) {
|
||||||
|
viewModel.onForgotPasswordClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package be.ugent.sel.studeez.screens.sign_in
|
||||||
|
|
||||||
|
data class LoginUiState(
|
||||||
|
val email: String = "",
|
||||||
|
val password: String = ""
|
||||||
|
)
|
|
@ -0,0 +1,69 @@
|
||||||
|
package be.ugent.sel.studeez.screens.sign_in
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import be.ugent.sel.studeez.common.ext.isValidEmail
|
||||||
|
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
||||||
|
import be.ugent.sel.studeez.domain.AccountDAO
|
||||||
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
|
import be.ugent.sel.studeez.navigation.StudeezDestinations.LOGIN_SCREEN
|
||||||
|
import be.ugent.sel.studeez.navigation.StudeezDestinations.SIGN_UP_SCREEN
|
||||||
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class LoginViewModel @Inject constructor(
|
||||||
|
private val accountDAO: AccountDAO,
|
||||||
|
logService: LogService
|
||||||
|
) : StudeezViewModel(logService) {
|
||||||
|
var uiState = mutableStateOf(LoginUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val email
|
||||||
|
get() = uiState.value.email
|
||||||
|
private val password
|
||||||
|
get() = uiState.value.password
|
||||||
|
|
||||||
|
fun onEmailChange(newValue: String) {
|
||||||
|
uiState.value = uiState.value.copy(email = newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPasswordChange(newValue: String) {
|
||||||
|
uiState.value = uiState.value.copy(password = newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSignInClick(openAndPopUp: (String, String) -> Unit) {
|
||||||
|
if (!email.isValidEmail()) {
|
||||||
|
SnackbarManager.showMessage(AppText.email_error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.isBlank()) {
|
||||||
|
SnackbarManager.showMessage(AppText.empty_password_error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
launchCatching {
|
||||||
|
accountDAO.signInWithEmailAndPassword(email, password)
|
||||||
|
openAndPopUp(SIGN_UP_SCREEN, LOGIN_SCREEN) // Is not reached when error occurs.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onForgotPasswordClick() {
|
||||||
|
if (!email.isValidEmail()) {
|
||||||
|
SnackbarManager.showMessage(AppText.email_error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
launchCatching {
|
||||||
|
accountDAO.sendRecoveryEmail(email)
|
||||||
|
SnackbarManager.showMessage(AppText.recovery_email_sent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onNotAlreadyUser(openAndPopUp: (String, String) -> Unit) {
|
||||||
|
openAndPopUp(SIGN_UP_SCREEN, LOGIN_SCREEN)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package be.ugent.sel.studeez.screens.sign_up
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import be.ugent.sel.studeez.common.composable.*
|
||||||
|
import be.ugent.sel.studeez.common.ext.basicButton
|
||||||
|
import be.ugent.sel.studeez.common.ext.fieldModifier
|
||||||
|
import be.ugent.sel.studeez.common.ext.textButton
|
||||||
|
import be.ugent.sel.studeez.resources
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SignUpScreen(
|
||||||
|
openAndPopUp: (String, String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
viewModel: SignUpViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
val uiState by viewModel.uiState
|
||||||
|
val fieldModifier = Modifier.fieldModifier()
|
||||||
|
|
||||||
|
SecondaryScreenToolbar(title = resources().getString(AppText.create_account)) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
EmailField(uiState.email, viewModel::onEmailChange, fieldModifier)
|
||||||
|
PasswordField(uiState.password, viewModel::onPasswordChange, fieldModifier)
|
||||||
|
RepeatPasswordField(uiState.repeatPassword, viewModel::onRepeatPasswordChange, fieldModifier)
|
||||||
|
|
||||||
|
BasicButton(AppText.create_account, Modifier.basicButton()) {
|
||||||
|
viewModel.onSignUpClick(openAndPopUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicTextButton(AppText.already_user, Modifier.textButton()) {
|
||||||
|
viewModel.onLoginScreenClick(openAndPopUp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package be.ugent.sel.studeez.screens.sign_up
|
||||||
|
|
||||||
|
data class SignUpUiState(
|
||||||
|
val email: String = "",
|
||||||
|
val password: String = "",
|
||||||
|
val repeatPassword: String = ""
|
||||||
|
)
|
|
@ -0,0 +1,67 @@
|
||||||
|
package be.ugent.sel.studeez.screens.sign_up
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import be.ugent.sel.studeez.common.ext.isValidEmail
|
||||||
|
import be.ugent.sel.studeez.common.ext.isValidPassword
|
||||||
|
import be.ugent.sel.studeez.common.ext.passwordMatches
|
||||||
|
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
||||||
|
import be.ugent.sel.studeez.domain.AccountDAO
|
||||||
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
|
import be.ugent.sel.studeez.navigation.StudeezDestinations.LOGIN_SCREEN
|
||||||
|
import be.ugent.sel.studeez.navigation.StudeezDestinations.SIGN_UP_SCREEN
|
||||||
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class SignUpViewModel @Inject constructor(
|
||||||
|
private val accountService: AccountDAO,
|
||||||
|
logService: LogService
|
||||||
|
) : StudeezViewModel(logService) {
|
||||||
|
var uiState = mutableStateOf(SignUpUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val email
|
||||||
|
get() = uiState.value.email
|
||||||
|
private val password
|
||||||
|
get() = uiState.value.password
|
||||||
|
|
||||||
|
fun onEmailChange(newValue: String) {
|
||||||
|
uiState.value = uiState.value.copy(email = newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPasswordChange(newValue: String) {
|
||||||
|
uiState.value = uiState.value.copy(password = newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRepeatPasswordChange(newValue: String) {
|
||||||
|
uiState.value = uiState.value.copy(repeatPassword = newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSignUpClick(openAndPopUp: (String, String) -> Unit) {
|
||||||
|
if (!email.isValidEmail()) {
|
||||||
|
SnackbarManager.showMessage(AppText.email_error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password.isValidPassword()) {
|
||||||
|
SnackbarManager.showMessage(AppText.password_error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password.passwordMatches(uiState.value.repeatPassword)) {
|
||||||
|
SnackbarManager.showMessage(AppText.password_match_error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
launchCatching {
|
||||||
|
accountService.signUpWithEmailAndPassword(email, password)
|
||||||
|
openAndPopUp(LOGIN_SCREEN, SIGN_UP_SCREEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onLoginScreenClick(openAndPopUp: (String, String) -> Unit) {
|
||||||
|
openAndPopUp(LOGIN_SCREEN, SIGN_UP_SCREEN)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package be.ugent.sel.studeez.screens.splash
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import be.ugent.sel.studeez.domain.AccountDAO
|
import be.ugent.sel.studeez.domain.AccountDAO
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -18,9 +19,10 @@ class SplashViewModel @Inject constructor(
|
||||||
|
|
||||||
showError.value = false
|
showError.value = false
|
||||||
if (accountDAO.hasUser) {
|
if (accountDAO.hasUser) {
|
||||||
// openAndPopUp( <homeScreen>, SPLASH_SCREEN)
|
// TODO this should go to the home page
|
||||||
|
openAndPopUp(StudeezDestinations.SIGN_UP_SCREEN, StudeezDestinations.SPLASH_SCREEN)
|
||||||
} else{
|
} else{
|
||||||
// openAndPopUp(<login>, SPLASH_SCREEN)
|
openAndPopUp(StudeezDestinations.SIGN_UP_SCREEN, StudeezDestinations.SPLASH_SCREEN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,8 +19,8 @@
|
||||||
<string name="not_already_user">Don\'t have an account yet? Sign up.</string>
|
<string name="not_already_user">Don\'t have an account yet? Sign up.</string>
|
||||||
<string name="sign_in">Sign in</string>
|
<string name="sign_in">Sign in</string>
|
||||||
<string name="login_details">Enter your login details</string>
|
<string name="login_details">Enter your login details</string>
|
||||||
<string name="forgot_password">Forgot password? Click to get recovery email</string>
|
<string name="forgot_password">Forgot password? Click to get recovery email.</string>
|
||||||
<string name="recovery_email_sent">Check your inbox for the recovery email.</string>
|
<string name="recovery_email_sent">Check your inbox for the recovery email.</string>
|
||||||
<string name="empty_password_error">Password cannot be empty.</string>
|
<string name="empty_password_error">Password cannot be empty.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Reference in a new issue