diff --git a/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt b/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt index 2706a9a..0467234 100644 --- a/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt +++ b/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt @@ -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) }) + } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/ToolbarComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/ToolbarComposable.kt index 2e3d635..9002bf9 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/ToolbarComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/ToolbarComposable.kt @@ -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 diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/log_in/LoginScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/log_in/LoginScreen.kt new file mode 100644 index 0000000..067011b --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/log_in/LoginScreen.kt @@ -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() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/log_in/LoginUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/log_in/LoginUiState.kt new file mode 100644 index 0000000..e1cc98a --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/log_in/LoginUiState.kt @@ -0,0 +1,6 @@ +package be.ugent.sel.studeez.screens.sign_in + +data class LoginUiState( + val email: String = "", + val password: String = "" +) diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/log_in/LoginViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/log_in/LoginViewModel.kt new file mode 100644 index 0000000..075be2f --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/log_in/LoginViewModel.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/sign_up/SignUpScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/sign_up/SignUpScreen.kt new file mode 100644 index 0000000..4d07f59 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/sign_up/SignUpScreen.kt @@ -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) + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/sign_up/SignUpUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/sign_up/SignUpUiState.kt new file mode 100644 index 0000000..081eb42 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/sign_up/SignUpUiState.kt @@ -0,0 +1,7 @@ +package be.ugent.sel.studeez.screens.sign_up + +data class SignUpUiState( + val email: String = "", + val password: String = "", + val repeatPassword: String = "" +) diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/sign_up/SignUpViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/sign_up/SignUpViewModel.kt new file mode 100644 index 0000000..a22fe68 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/sign_up/SignUpViewModel.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/splash/SplashViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/splash/SplashViewModel.kt index f019c8c..e617fff 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/splash/SplashViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/splash/SplashViewModel.kt @@ -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( , SPLASH_SCREEN) + // TODO this should go to the home page + openAndPopUp(StudeezDestinations.SIGN_UP_SCREEN, StudeezDestinations.SPLASH_SCREEN) } else{ - // openAndPopUp(, SPLASH_SCREEN) + openAndPopUp(StudeezDestinations.SIGN_UP_SCREEN, StudeezDestinations.SPLASH_SCREEN) } } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79d0a41..6daa9bf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,8 +19,8 @@ Don\'t have an account yet? Sign up. Sign in Enter your login details - Forgot password? Click to get recovery email + Forgot password? Click to get recovery email. Check your inbox for the recovery email. Password cannot be empty. - \ No newline at end of file +