From d5067ca05b6ba3c16f61e4f3b6bb52b364453389 Mon Sep 17 00:00:00 2001 From: AllenTom Date: Tue, 13 Aug 2024 22:32:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/aiosman/riderpro/Const.kt | 2 +- .../java/com/aiosman/riderpro/MainActivity.kt | 2 +- .../aiosman/riderpro/data/AccountService.kt | 10 ++- .../aiosman/riderpro/data/api/ApiClient.kt | 36 +++++++- .../aiosman/riderpro/data/api/RiderProAPI.kt | 12 +++ .../main/java/com/aiosman/riderpro/ui/Navi.kt | 5 ++ .../riderpro/ui/account/changepassword.kt | 85 +++++++++++++++++++ .../riderpro/ui/index/tabs/profile/Profile.kt | 8 +- .../com/aiosman/riderpro/ui/splash/splash.kt | 60 +++++++++++++ 9 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/aiosman/riderpro/ui/account/changepassword.kt create mode 100644 app/src/main/java/com/aiosman/riderpro/ui/splash/splash.kt diff --git a/app/src/main/java/com/aiosman/riderpro/Const.kt b/app/src/main/java/com/aiosman/riderpro/Const.kt index 3abb4da..3e16227 100644 --- a/app/src/main/java/com/aiosman/riderpro/Const.kt +++ b/app/src/main/java/com/aiosman/riderpro/Const.kt @@ -2,5 +2,5 @@ package com.aiosman.riderpro object ConstVars { // api 地址 - const val BASE_SERVER = "https://api.rider-pro.com" + const val BASE_SERVER = "https://8.137.22.101:8088" } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/MainActivity.kt b/app/src/main/java/com/aiosman/riderpro/MainActivity.kt index 26b6de6..a6638e2 100644 --- a/app/src/main/java/com/aiosman/riderpro/MainActivity.kt +++ b/app/src/main/java/com/aiosman/riderpro/MainActivity.kt @@ -53,7 +53,7 @@ class MainActivity : ComponentActivity() { try { val resp = accountService.getMyAccount() return true - } catch (e: ServiceException) { + } catch (e: Exception) { return false } } 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 1dca9f7..9e45875 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt @@ -2,6 +2,7 @@ package com.aiosman.riderpro.data import com.aiosman.riderpro.AppStore import com.aiosman.riderpro.data.api.ApiClient +import com.aiosman.riderpro.data.api.ChangePasswordRequestBody import com.aiosman.riderpro.data.api.LoginUserRequestBody import com.aiosman.riderpro.data.api.RegisterRequestBody import com.aiosman.riderpro.test.TestDatabase @@ -63,6 +64,7 @@ interface AccountService { suspend fun updateAvatar(uri: String) suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?) suspend fun registerUserWithPassword(loginName: String, password: String) + suspend fun changeAccountPassword(oldPassword: String, newPassword: String) } class TestAccountServiceImpl : AccountService { @@ -104,7 +106,7 @@ class TestAccountServiceImpl : AccountService { } } - fun createMultipartBody(file: File, filename:String,name: String): MultipartBody.Part { + fun createMultipartBody(file: File, filename: String, name: String): MultipartBody.Part { val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull()) return MultipartBody.Part.createFormData(name, filename, requestFile) } @@ -112,7 +114,7 @@ class TestAccountServiceImpl : AccountService { override suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?) { val nicknameField: RequestBody? = nickName?.toRequestBody("text/plain".toMediaTypeOrNull()) val avatarField: MultipartBody.Part? = avatar?.let { - createMultipartBody(it.file,it.filename, "avatar") + createMultipartBody(it.file, it.filename, "avatar") } ApiClient.api.updateProfile(avatarField, nicknameField) } @@ -120,4 +122,8 @@ class TestAccountServiceImpl : AccountService { override suspend fun registerUserWithPassword(loginName: String, password: String) { ApiClient.api.register(RegisterRequestBody(loginName, password)) } + + override suspend fun changeAccountPassword(oldPassword: String, newPassword: String) { + ApiClient.api.changePassword(ChangePasswordRequestBody(oldPassword, newPassword)) + } } \ 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 25fb525..2eebf3f 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 @@ -7,7 +7,39 @@ import okhttp3.OkHttpClient import okhttp3.Response import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import java.security.cert.CertificateException +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager +fun getUnsafeOkHttpClient(): OkHttpClient { + return try { + // Create a trust manager that does not validate certificate chains + val trustAllCerts = arrayOf(object : X509TrustManager { + @Throws(CertificateException::class) + override fun checkClientTrusted(chain: Array, authType: String) {} + + @Throws(CertificateException::class) + override fun checkServerTrusted(chain: Array, authType: String) {} + + override fun getAcceptedIssuers(): Array = arrayOf() + }) + + // Install the all-trusting trust manager + val sslContext = SSLContext.getInstance("SSL") + sslContext.init(null, trustAllCerts, java.security.SecureRandom()) + // Create an ssl socket factory with our all-trusting manager + val sslSocketFactory = sslContext.socketFactory + + OkHttpClient.Builder() + .sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager) + .hostnameVerifier { _, _ -> true } + .addInterceptor(AuthInterceptor()) + .build() + } catch (e: Exception) { + throw RuntimeException(e) + } +} class AuthInterceptor() : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val requestBuilder = chain.request().newBuilder() @@ -21,9 +53,7 @@ object ApiClient { const val BASE_API_URL = "${BASE_SERVER}/api/v1" const val RETROFIT_URL = "${BASE_API_URL}/" private val okHttpClient: OkHttpClient by lazy { - OkHttpClient.Builder() - .addInterceptor(AuthInterceptor()) - .build() + getUnsafeOkHttpClient() } private val retrofit: Retrofit by lazy { Retrofit.Builder() 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 74ae1ba..51b4248 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 @@ -52,6 +52,13 @@ data class CommentRequestBody( val content: String ) +data class ChangePasswordRequestBody( + @SerializedName("currentPassword") + val oldPassword: String = "", + @SerializedName("newPassword") + val newPassword: String = "" +) + interface RiderProAPI { @POST("register") suspend fun register(@Body body: RegisterRequestBody): Response @@ -136,6 +143,11 @@ interface RiderProAPI { @Part("nickname") nickname: RequestBody?, ): Response + @POST("account/my/password") + suspend fun changePassword( + @Body body: ChangePasswordRequestBody + ): Response + @GET("profile/{id}") suspend fun getAccountProfileById( @Path("id") id: Int 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 dab71a3..30a85f8 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt @@ -1,5 +1,6 @@ package com.aiosman.riderpro.ui +import ChangePasswordScreen import ImageViewer import ModificationListScreen import androidx.compose.animation.ExperimentalSharedTransitionApi @@ -64,6 +65,7 @@ sealed class NavigationRoute( data object EmailSignUp : NavigationRoute("EmailSignUp") data object AccountEdit : NavigationRoute("AccountEditScreen") data object ImageViewer : NavigationRoute("ImageViewer") + data object ChangePasswordScreen : NavigationRoute("ChangePasswordScreen") } @@ -184,6 +186,9 @@ fun NavigationController( ImageViewer() } } + composable(route = NavigationRoute.ChangePasswordScreen.route) { + ChangePasswordScreen() + } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/account/changepassword.kt b/app/src/main/java/com/aiosman/riderpro/ui/account/changepassword.kt new file mode 100644 index 0000000..5a91370 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/account/changepassword.kt @@ -0,0 +1,85 @@ +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import com.aiosman.riderpro.LocalNavController +import com.aiosman.riderpro.data.AccountService +import com.aiosman.riderpro.data.TestAccountServiceImpl +import kotlinx.coroutines.launch + +class ChangePasswordViewModel { + val accountService :AccountService = TestAccountServiceImpl() + suspend fun changePassword(currentPassword: String, newPassword: String) { + accountService.changeAccountPassword(currentPassword, newPassword) + } +} +@Composable +fun ChangePasswordScreen( + +) { + val viewModel = remember { ChangePasswordViewModel() } + var currentPassword by remember { mutableStateOf("") } + var newPassword by remember { mutableStateOf("") } + var confirmPassword by remember { mutableStateOf("") } + var errorMessage by remember { mutableStateOf("") } + val scope = rememberCoroutineScope() + val navController = LocalNavController.current + Scaffold { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + verticalArrangement = Arrangement.Center + ) { + TextField( + value = currentPassword, + onValueChange = { currentPassword = it }, + label = { Text("Current Password") }, + visualTransformation = PasswordVisualTransformation(), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(16.dp)) + TextField( + value = newPassword, + onValueChange = { newPassword = it }, + label = { Text("New Password") }, + visualTransformation = PasswordVisualTransformation(), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(16.dp)) + TextField( + value = confirmPassword, + onValueChange = { confirmPassword = it }, + label = { Text("Confirm New Password") }, + visualTransformation = PasswordVisualTransformation(), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(16.dp)) + if (errorMessage.isNotEmpty()) { + Text( + text = errorMessage, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + Button( + onClick = { + if (newPassword == confirmPassword) { + scope.launch { + viewModel.changePassword(currentPassword, newPassword) + navController.popBackStack() + } + } else { + errorMessage = "Passwords do not match" + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Change Password") + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt index 9980eec..a147777 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt @@ -76,7 +76,6 @@ fun ProfilePage() { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Top, ) { - item { Box( modifier = Modifier @@ -116,6 +115,13 @@ fun ProfilePage() { }, text = { Text("Edit") }) + DropdownMenuItem(onClick = { + scope.launch { + navController.navigate(NavigationRoute.ChangePasswordScreen.route) + } + }, text = { + Text("Change password") + }) } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/splash/splash.kt b/app/src/main/java/com/aiosman/riderpro/ui/splash/splash.kt new file mode 100644 index 0000000..1f75f84 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/splash/splash.kt @@ -0,0 +1,60 @@ +package com.aiosman.riderpro.ui.splash + +import android.window.SplashScreen +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +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.foundation.layout.width +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.aiosman.riderpro.R + +@Composable +fun SplashScreen() { + Scaffold { + it + Box( + modifier = Modifier.fillMaxSize() + ) { + // to bottom + Box( + contentAlignment = Alignment.TopCenter, + modifier = Modifier.padding(top = 211.dp) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + Image( + painter = painterResource(id = R.mipmap.rider_pro_logo), + contentDescription = "Rider Pro", + modifier = Modifier + .width(108.dp) + .height(45.dp) + ) + Spacer(modifier = Modifier.height(32.dp)) + Text( + "Connecting Riders".uppercase(), + fontSize = 28.sp, + fontWeight = FontWeight.Bold + ) + Text("Worldwide".uppercase(), fontSize = 28.sp, fontWeight = FontWeight.Bold) + } + + } + } + + } +} \ No newline at end of file