From 0d53fc6097c4806d50dea5626d3103fdf657b32d Mon Sep 17 00:00:00 2001 From: aritra Date: Sat, 25 May 2024 17:50:36 +0530 Subject: [PATCH] Created the Forgot Password flow --- .../presentation/auth/AuthViewModel.kt | 10 ++ .../forgotPassword/ForgotPasswordScreen.kt | 128 ++++++++++++++++++ .../presentation/auth/login/LoginScreens.kt | 19 +++ .../uncrack/util/ModifierExtension.kt | 57 ++++++++ app/src/main/res/values/strings.xml | 3 + 5 files changed, 217 insertions(+) create mode 100644 app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/forgotPassword/ForgotPasswordScreen.kt create mode 100644 app/src/main/java/com/geekymusketeers/uncrack/util/ModifierExtension.kt diff --git a/app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/AuthViewModel.kt b/app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/AuthViewModel.kt index 35db59e..56e5f12 100644 --- a/app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/AuthViewModel.kt +++ b/app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/AuthViewModel.kt @@ -9,6 +9,7 @@ import com.geekymusketeers.uncrack.util.UtilsKt.validateEmail import com.geekymusketeers.uncrack.util.UtilsKt.validateName import com.geekymusketeers.uncrack.util.UtilsKt.validatePassword import com.geekymusketeers.uncrack.util.runIO +import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseUser import com.google.firebase.auth.ktx.auth import com.google.firebase.firestore.FirebaseFirestore @@ -33,6 +34,8 @@ class AuthViewModel : ViewModel() { private val _password = MutableLiveData() val password: LiveData = _password + val resetPassword = MutableLiveData() + fun setUserName(username: String) { _username.value = username checkIfAdded() @@ -48,6 +51,13 @@ class AuthViewModel : ViewModel() { checkIfAdded() } + fun resetPassword(email: String) = runIO { + FirebaseAuth.getInstance().sendPasswordResetEmail(email) + .addOnSuccessListener { + resetPassword.postValue(true) + }.addOnFailureListener { } + } + fun logIn( email: String, password: String, diff --git a/app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/forgotPassword/ForgotPasswordScreen.kt b/app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/forgotPassword/ForgotPasswordScreen.kt new file mode 100644 index 0000000..8fef6c5 --- /dev/null +++ b/app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/forgotPassword/ForgotPasswordScreen.kt @@ -0,0 +1,128 @@ +package com.geekymusketeers.uncrack.presentation.auth.forgotPassword + +import android.os.Bundle +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import com.geekymusketeers.uncrack.R +import com.geekymusketeers.uncrack.components.UCButton +import com.geekymusketeers.uncrack.components.UCTextField +import com.geekymusketeers.uncrack.presentation.auth.AuthViewModel +import com.geekymusketeers.uncrack.ui.theme.DMSansFontFamily +import com.geekymusketeers.uncrack.ui.theme.UnCrackTheme +import com.geekymusketeers.uncrack.util.UtilsKt +import com.geekymusketeers.uncrack.util.UtilsKt.findActivity +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class ForgotPasswordScreen : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + + enableEdgeToEdge( + statusBarStyle = SystemBarStyle.light( + Color.White.toArgb(), Color.White.toArgb() + ), + navigationBarStyle = SystemBarStyle.light( + Color.White.toArgb(), Color.White.toArgb() + ) + ) + super.onCreate(savedInstanceState) + + setContent { + UnCrackTheme { + ForgotPasswordContent() + } + } + } +} + +@Composable +fun ForgotPasswordContent( + modifier: Modifier = Modifier, + viewModel: AuthViewModel = hiltViewModel() +) { + val context = LocalContext.current + var email by rememberSaveable { mutableStateOf("") } + val enableSendBtn by remember { derivedStateOf { UtilsKt.validateEmail(email) } } + val resetPasswordRequestLiveData by viewModel.resetPassword.observeAsState() + + LaunchedEffect(resetPasswordRequestLiveData) { + if (resetPasswordRequestLiveData == true) { + context.findActivity()?.let { + Toast.makeText(it, "Email sent", Toast.LENGTH_SHORT).show() + it.finish() + } + } + } + + Scaffold( + modifier = modifier.fillMaxSize() + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp) + ) { + + Text( + text = stringResource(R.string.forgot_password), + fontSize = 40.sp, + fontWeight = FontWeight.Bold, + fontFamily = DMSansFontFamily, + color = Color.Black + ) + + Spacer(modifier = Modifier.height(60.dp)) + + UCTextField( + modifier = Modifier + .fillMaxWidth(), + headerText = stringResource(R.string.enter_your_registered_mail), + value = email, + onValueChange = { email = it } + ) + + Spacer(modifier = Modifier.weight(1f)) + + UCButton( + modifier = Modifier + .fillMaxWidth(), + text = stringResource(R.string.send_reset_link), + onClick = { + viewModel.resetPassword(email) + }, + enabled = enableSendBtn + ) + } + } +} diff --git a/app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/login/LoginScreens.kt b/app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/login/LoginScreens.kt index dfe3fc1..c8e42fa 100644 --- a/app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/login/LoginScreens.kt +++ b/app/src/main/java/com/geekymusketeers/uncrack/presentation/auth/login/LoginScreens.kt @@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -28,6 +29,7 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb @@ -45,6 +47,7 @@ import com.geekymusketeers.uncrack.R import com.geekymusketeers.uncrack.components.UCButton import com.geekymusketeers.uncrack.components.UCTextField import com.geekymusketeers.uncrack.presentation.auth.AuthViewModel +import com.geekymusketeers.uncrack.presentation.auth.forgotPassword.ForgotPasswordScreen import com.geekymusketeers.uncrack.presentation.auth.signup.SignupScreen import com.geekymusketeers.uncrack.ui.theme.DMSansFontFamily import com.geekymusketeers.uncrack.ui.theme.OnPrimaryContainerLight @@ -52,6 +55,7 @@ import com.geekymusketeers.uncrack.ui.theme.PrimaryLight import com.geekymusketeers.uncrack.ui.theme.UnCrackTheme import com.geekymusketeers.uncrack.ui.theme.medium16 import com.geekymusketeers.uncrack.util.UtilsKt.findActivity +import com.geekymusketeers.uncrack.util.onClick import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseUser import com.google.firebase.auth.ktx.auth @@ -165,6 +169,21 @@ fun LoginContent( } ) + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = stringResource(R.string.forgot_password), modifier = Modifier + .align(Alignment.End) + .onClick { + context + .findActivity() + ?.let { + it.startActivity(Intent(it, ForgotPasswordScreen::class.java)) + } + }, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.weight(1f)) UCButton( diff --git a/app/src/main/java/com/geekymusketeers/uncrack/util/ModifierExtension.kt b/app/src/main/java/com/geekymusketeers/uncrack/util/ModifierExtension.kt new file mode 100644 index 0000000..54f7df2 --- /dev/null +++ b/app/src/main/java/com/geekymusketeers/uncrack/util/ModifierExtension.kt @@ -0,0 +1,57 @@ +package com.geekymusketeers.uncrack.util + +import androidx.compose.foundation.Indication +import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.platform.debugInspectorInfo +import androidx.compose.ui.semantics.Role + +/** + * A custom [Modifier.clickable] with no ripple effect. + * + * Configure component to receive clicks via input or accessibility "click" event. + * + * Add this modifier to the element to make it clickable within its bounds and show an indication + * as specified in [indication] parameter. + * + * If you need to support double click or long click alongside the single click, consider + * using [combinedClickable]. + * + * @sample androidx.compose.foundation.samples.ClickableSample + * + * @param indication indication to be shown when modified element is pressed. By default, + * indication from [LocalIndication] will be used. Pass `null` to show no indication, or + * current value from [LocalIndication] to show theme default + * @param enabled Controls the enabled state. When `false`, [onClick], and this modifier will + * appear disabled for accessibility services + * @param onClickLabel semantic / accessibility label for the [onClick] action + * @param role the type of user interface element. Accessibility services might use this + * to describe the element or do customizations + * @param onClick will be called when user clicks on the element + */ +fun Modifier.onClick( + indication: Indication? = null, + enabled: Boolean = true, + onClickLabel: String? = null, + role: Role? = null, + onClick: () -> Unit +) = this.composed(inspectorInfo = debugInspectorInfo { + name = "onClickModifier" + value = enabled +}) { + val interactionSource = remember { MutableInteractionSource() } + clickable( + indication = indication, + interactionSource = interactionSource, + enabled = enabled, + onClickLabel = onClickLabel, + role = role + ) { + onClick.invoke() + } +} \ 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 2572051..b3fbffe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -222,6 +222,9 @@ 9 or more characters Master Password must include: + Forgot Password + Enter your registered mail + Send reset link Others PayPal