From 5c3c3111ae0163c86141fda4b192798b2827cebe Mon Sep 17 00:00:00 2001 From: AllenTom Date: Thu, 12 Sep 2024 23:13:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=87=8D=E7=BD=AE=E5=AF=86?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiosman/riderpro/data/AccountService.kt | 12 ++ .../aiosman/riderpro/data/api/ApiClient.kt | 1 - .../aiosman/riderpro/data/api/RiderProAPI.kt | 9 +- .../main/java/com/aiosman/riderpro/ui/Navi.kt | 9 + .../riderpro/ui/account/ResetPassword.kt | 160 ++++++++++++++++++ .../riderpro/ui/composables/TextInputField.kt | 4 +- .../com/aiosman/riderpro/ui/login/userauth.kt | 5 +- 7 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/aiosman/riderpro/ui/account/ResetPassword.kt diff --git a/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt b/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt index 67d3274..8226e4a 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt @@ -6,6 +6,7 @@ import com.aiosman.riderpro.data.api.GoogleRegisterRequestBody import com.aiosman.riderpro.data.api.LoginUserRequestBody import com.aiosman.riderpro.data.api.RegisterMessageChannelRequestBody import com.aiosman.riderpro.data.api.RegisterRequestBody +import com.aiosman.riderpro.data.api.ResetPasswordRequestBody import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody import com.aiosman.riderpro.entity.AccountFavouriteEntity import com.aiosman.riderpro.entity.AccountLikeEntity @@ -339,6 +340,9 @@ interface AccountService { suspend fun updateNotice(payload: UpdateNoticeRequestBody) suspend fun registerMessageChannel(client: String, identifier: String) + + suspend fun resetPassword(email: String) + } class AccountServiceImpl : AccountService { @@ -455,4 +459,12 @@ class AccountServiceImpl : AccountService { ApiClient.api.registerMessageChannel(RegisterMessageChannelRequestBody(client, identifier)) } + override suspend fun resetPassword(email: String) { + ApiClient.api.resetPassword( + ResetPasswordRequestBody( + username = email + ) + ) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/data/api/ApiClient.kt b/app/src/main/java/com/aiosman/riderpro/data/api/ApiClient.kt index 368b045..42c0153 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/api/ApiClient.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/api/ApiClient.kt @@ -4,7 +4,6 @@ import android.icu.text.SimpleDateFormat import android.icu.util.TimeZone import com.aiosman.riderpro.AppStore import com.aiosman.riderpro.ConstVars -import com.aiosman.riderpro.data.ServiceException import com.auth0.android.jwt.JWT import kotlinx.coroutines.runBlocking import okhttp3.Interceptor diff --git a/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt b/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt index 53141d8..473c54f 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt @@ -92,7 +92,10 @@ data class RegisterMessageChannelRequestBody( @SerializedName("identifier") val identifier: String, ) - +data class ResetPasswordRequestBody( + @SerializedName("username") + val username: String, +) interface RiderProAPI { @POST("register") suspend fun register(@Body body: RegisterRequestBody): Response @@ -270,4 +273,8 @@ interface RiderProAPI { @Path("id") id: Int ): Response + @POST("account/my/password/reset") + suspend fun resetPassword( + @Body body: ResetPasswordRequestBody + ): Response } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt index c73e941..b21919c 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt @@ -28,6 +28,7 @@ import com.aiosman.riderpro.LocalAnimatedContentScope import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalSharedTransitionScope import com.aiosman.riderpro.ui.account.AccountEditScreen2 +import com.aiosman.riderpro.ui.account.ResetPasswordScreen import com.aiosman.riderpro.ui.comment.CommentsScreen import com.aiosman.riderpro.ui.favourite.FavouriteNoticeScreen import com.aiosman.riderpro.ui.follower.FollowerListScreen @@ -80,6 +81,7 @@ sealed class NavigationRoute( data object Search : NavigationRoute("Search") data object FollowerList : NavigationRoute("FollowerList/{id}") data object FollowingList : NavigationRoute("FollowingList/{id}") + data object ResetPassword : NavigationRoute("ResetPassword") } @@ -262,6 +264,13 @@ fun NavigationController( FollowingListScreen(it.arguments?.getInt("id")!!) } } + composable(route = NavigationRoute.ResetPassword.route) { + CompositionLocalProvider( + LocalAnimatedContentScope provides this, + ) { + ResetPasswordScreen() + } + } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/account/ResetPassword.kt b/app/src/main/java/com/aiosman/riderpro/ui/account/ResetPassword.kt new file mode 100644 index 0000000..82eef66 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/account/ResetPassword.kt @@ -0,0 +1,160 @@ +package com.aiosman.riderpro.ui.account + +import android.widget.Toast +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +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.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.aiosman.riderpro.LocalNavController +import com.aiosman.riderpro.R +import com.aiosman.riderpro.data.AccountService +import com.aiosman.riderpro.data.AccountServiceImpl +import com.aiosman.riderpro.ui.comment.NoticeScreenHeader +import com.aiosman.riderpro.ui.composables.ActionButton +import com.aiosman.riderpro.ui.composables.StatusBarSpacer +import com.aiosman.riderpro.ui.composables.TextInputField +import com.aiosman.riderpro.ui.modifiers.noRippleClickable +import kotlinx.coroutines.launch + +@Composable +fun ResetPasswordScreen() { + var username by remember { mutableStateOf("") } + val accountService: AccountService = AccountServiceImpl() + val scope = rememberCoroutineScope() + val context = LocalContext.current + var isSendSuccess by remember { mutableStateOf(null) } + var isLoading by remember { mutableStateOf(false) } + val navController = LocalNavController.current + + fun resetPassword() { + scope.launch { + isLoading = true + try { + accountService.resetPassword(username) + isSendSuccess = true + } catch (e: Exception) { + Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() + isSendSuccess = false + } finally { + isLoading = false + } + } + } + + Column( + modifier = Modifier.fillMaxSize() + ) { + StatusBarSpacer() + Box( + modifier = Modifier.padding( + start = 16.dp, + end = 16.dp, + top = 16.dp, + bottom = 0.dp + ) + ) { + NoticeScreenHeader( + "RECOVER ACCOUNT", + moreIcon = false + ) + } + + Column( + modifier = Modifier.padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(36.dp)) + + if (isSendSuccess != null) { + if (isSendSuccess!!) { + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "Reset password email has been sent to your email address", + style = TextStyle( + color = Color(0xFF333333), + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + ) + } else { + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "Failed to send reset password email", + style = TextStyle( + color = Color(0xFF333333), + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + ) + } + Spacer(modifier = Modifier.height(40.dp)) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.noRippleClickable { + navController.popBackStack() + } + ) { + Image( + painter = painterResource(id = R.drawable.rider_pro_nav_back), + contentDescription = "Back", + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + androidx.compose.material3.Text( + stringResource(R.string.back_upper), + color = Color.Black, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + } else { + Spacer(modifier = Modifier.height(120.dp)) + TextInputField( + text = username, + onValueChange = { username = it }, + label = stringResource(R.string.login_email_label), + hint = stringResource(R.string.text_hint_email), + enabled = !isLoading + ) + Spacer(modifier = Modifier.height(72.dp)) + if (isLoading) { + CircularProgressIndicator() + } else { + ActionButton( + modifier = Modifier + .width(345.dp) + .height(48.dp), + text = "Recover Account", + backgroundImage = R.mipmap.rider_pro_signup_red_bg + ) { + resetPassword() + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/TextInputField.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/TextInputField.kt index b738685..de547dc 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/composables/TextInputField.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/TextInputField.kt @@ -43,7 +43,8 @@ fun TextInputField( password: Boolean = false, label: String? = null, hint: String? = null, - error: String? = null + error: String? = null, + enabled: Boolean = true ) { var showPassword by remember { mutableStateOf(!password) } var isFocused by remember { mutableStateOf(false) } @@ -74,6 +75,7 @@ fun TextInputField( ), visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(), singleLine = true, + enabled = enabled ) if (password) { Image( diff --git a/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt b/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt index 2de5a13..6015312 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt @@ -197,7 +197,9 @@ fun UserAuthScreen() { fontSize = 12.sp ) Spacer(modifier = Modifier.weight(1f)) - Text(stringResource(R.string.forgot_password), fontSize = 12.sp) + Text(stringResource(R.string.forgot_password), fontSize = 12.sp, modifier = Modifier.noRippleClickable { + navController.navigate(NavigationRoute.ResetPassword.route) + }) } } Spacer(modifier = Modifier.height(64.dp)) @@ -210,7 +212,6 @@ fun UserAuthScreen() { ) { onLogin() } - Spacer(modifier = Modifier.height(48.dp)) Text(stringResource(R.string.or_login_with), color = Color(0xFF999999)) Spacer(modifier = Modifier.height(16.dp))