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 be.ugent.sel.studeez.common.snackbar.SnackbarManager | ||||
| 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.ui.theme.StudeezTheme | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
|  | @ -77,5 +79,11 @@ fun NavGraphBuilder.studeezGraph(appState: StudeezAppstate) { | |||
|         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 | ||||
| fun SecondaryScreenToolbar( | ||||
|     title: String, | ||||
|     content: (PaddingValues) -> Unit | ||||
|     content: @Composable (PaddingValues) -> Unit | ||||
| ) { | ||||
|     Scaffold( | ||||
|         // 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 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.screens.StudeezViewModel | ||||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||||
| import javax.inject.Inject | ||||
|  | @ -18,9 +19,10 @@ class SplashViewModel @Inject constructor( | |||
| 
 | ||||
|         showError.value = false | ||||
|         if (accountDAO.hasUser) { | ||||
|             // openAndPopUp( <homeScreen>, SPLASH_SCREEN) | ||||
|             // TODO this should go to the home page | ||||
|             openAndPopUp(StudeezDestinations.SIGN_UP_SCREEN, StudeezDestinations.SPLASH_SCREEN) | ||||
|         } else{ | ||||
|             // openAndPopUp(<login>, SPLASH_SCREEN) | ||||
|             openAndPopUp(StudeezDestinations.SIGN_UP_SCREEN, StudeezDestinations.SPLASH_SCREEN) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -19,7 +19,7 @@ | |||
|     <string name="not_already_user">Don\'t have an account yet? Sign up.</string> | ||||
|     <string name="sign_in">Sign in</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="empty_password_error">Password cannot be empty.</string> | ||||
| 
 | ||||
|  |  | |||
		Reference in a new issue
	
	 lbarraga
						lbarraga