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,7 +19,7 @@ | ||||||
|     <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> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 lbarraga
						lbarraga