更新
This commit is contained in:
@@ -2,5 +2,5 @@ package com.aiosman.riderpro
|
|||||||
|
|
||||||
object ConstVars {
|
object ConstVars {
|
||||||
// api 地址
|
// api 地址
|
||||||
const val BASE_SERVER = "https://api.rider-pro.com"
|
const val BASE_SERVER = "https://8.137.22.101:8088"
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
try {
|
try {
|
||||||
val resp = accountService.getMyAccount()
|
val resp = accountService.getMyAccount()
|
||||||
return true
|
return true
|
||||||
} catch (e: ServiceException) {
|
} catch (e: Exception) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.aiosman.riderpro.data
|
|||||||
|
|
||||||
import com.aiosman.riderpro.AppStore
|
import com.aiosman.riderpro.AppStore
|
||||||
import com.aiosman.riderpro.data.api.ApiClient
|
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.LoginUserRequestBody
|
||||||
import com.aiosman.riderpro.data.api.RegisterRequestBody
|
import com.aiosman.riderpro.data.api.RegisterRequestBody
|
||||||
import com.aiosman.riderpro.test.TestDatabase
|
import com.aiosman.riderpro.test.TestDatabase
|
||||||
@@ -63,6 +64,7 @@ interface AccountService {
|
|||||||
suspend fun updateAvatar(uri: String)
|
suspend fun updateAvatar(uri: String)
|
||||||
suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?)
|
suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?)
|
||||||
suspend fun registerUserWithPassword(loginName: String, password: String)
|
suspend fun registerUserWithPassword(loginName: String, password: String)
|
||||||
|
suspend fun changeAccountPassword(oldPassword: String, newPassword: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestAccountServiceImpl : AccountService {
|
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())
|
val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())
|
||||||
return MultipartBody.Part.createFormData(name, filename, requestFile)
|
return MultipartBody.Part.createFormData(name, filename, requestFile)
|
||||||
}
|
}
|
||||||
@@ -112,7 +114,7 @@ class TestAccountServiceImpl : AccountService {
|
|||||||
override suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?) {
|
override suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?) {
|
||||||
val nicknameField: RequestBody? = nickName?.toRequestBody("text/plain".toMediaTypeOrNull())
|
val nicknameField: RequestBody? = nickName?.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
val avatarField: MultipartBody.Part? = avatar?.let {
|
val avatarField: MultipartBody.Part? = avatar?.let {
|
||||||
createMultipartBody(it.file,it.filename, "avatar")
|
createMultipartBody(it.file, it.filename, "avatar")
|
||||||
}
|
}
|
||||||
ApiClient.api.updateProfile(avatarField, nicknameField)
|
ApiClient.api.updateProfile(avatarField, nicknameField)
|
||||||
}
|
}
|
||||||
@@ -120,4 +122,8 @@ class TestAccountServiceImpl : AccountService {
|
|||||||
override suspend fun registerUserWithPassword(loginName: String, password: String) {
|
override suspend fun registerUserWithPassword(loginName: String, password: String) {
|
||||||
ApiClient.api.register(RegisterRequestBody(loginName, password))
|
ApiClient.api.register(RegisterRequestBody(loginName, password))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun changeAccountPassword(oldPassword: String, newPassword: String) {
|
||||||
|
ApiClient.api.changePassword(ChangePasswordRequestBody(oldPassword, newPassword))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,39 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
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<TrustManager>(object : X509TrustManager {
|
||||||
|
@Throws(CertificateException::class)
|
||||||
|
override fun checkClientTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {}
|
||||||
|
|
||||||
|
@Throws(CertificateException::class)
|
||||||
|
override fun checkServerTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {}
|
||||||
|
|
||||||
|
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> = 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 {
|
class AuthInterceptor() : Interceptor {
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val requestBuilder = chain.request().newBuilder()
|
val requestBuilder = chain.request().newBuilder()
|
||||||
@@ -21,9 +53,7 @@ object ApiClient {
|
|||||||
const val BASE_API_URL = "${BASE_SERVER}/api/v1"
|
const val BASE_API_URL = "${BASE_SERVER}/api/v1"
|
||||||
const val RETROFIT_URL = "${BASE_API_URL}/"
|
const val RETROFIT_URL = "${BASE_API_URL}/"
|
||||||
private val okHttpClient: OkHttpClient by lazy {
|
private val okHttpClient: OkHttpClient by lazy {
|
||||||
OkHttpClient.Builder()
|
getUnsafeOkHttpClient()
|
||||||
.addInterceptor(AuthInterceptor())
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
private val retrofit: Retrofit by lazy {
|
private val retrofit: Retrofit by lazy {
|
||||||
Retrofit.Builder()
|
Retrofit.Builder()
|
||||||
|
|||||||
@@ -52,6 +52,13 @@ data class CommentRequestBody(
|
|||||||
val content: String
|
val content: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class ChangePasswordRequestBody(
|
||||||
|
@SerializedName("currentPassword")
|
||||||
|
val oldPassword: String = "",
|
||||||
|
@SerializedName("newPassword")
|
||||||
|
val newPassword: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
interface RiderProAPI {
|
interface RiderProAPI {
|
||||||
@POST("register")
|
@POST("register")
|
||||||
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
|
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
|
||||||
@@ -136,6 +143,11 @@ interface RiderProAPI {
|
|||||||
@Part("nickname") nickname: RequestBody?,
|
@Part("nickname") nickname: RequestBody?,
|
||||||
): Response<Unit>
|
): Response<Unit>
|
||||||
|
|
||||||
|
@POST("account/my/password")
|
||||||
|
suspend fun changePassword(
|
||||||
|
@Body body: ChangePasswordRequestBody
|
||||||
|
): Response<Unit>
|
||||||
|
|
||||||
@GET("profile/{id}")
|
@GET("profile/{id}")
|
||||||
suspend fun getAccountProfileById(
|
suspend fun getAccountProfileById(
|
||||||
@Path("id") id: Int
|
@Path("id") id: Int
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.aiosman.riderpro.ui
|
package com.aiosman.riderpro.ui
|
||||||
|
|
||||||
|
import ChangePasswordScreen
|
||||||
import ImageViewer
|
import ImageViewer
|
||||||
import ModificationListScreen
|
import ModificationListScreen
|
||||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
@@ -64,6 +65,7 @@ sealed class NavigationRoute(
|
|||||||
data object EmailSignUp : NavigationRoute("EmailSignUp")
|
data object EmailSignUp : NavigationRoute("EmailSignUp")
|
||||||
data object AccountEdit : NavigationRoute("AccountEditScreen")
|
data object AccountEdit : NavigationRoute("AccountEditScreen")
|
||||||
data object ImageViewer : NavigationRoute("ImageViewer")
|
data object ImageViewer : NavigationRoute("ImageViewer")
|
||||||
|
data object ChangePasswordScreen : NavigationRoute("ChangePasswordScreen")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -184,6 +186,9 @@ fun NavigationController(
|
|||||||
ImageViewer()
|
ImageViewer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
composable(route = NavigationRoute.ChangePasswordScreen.route) {
|
||||||
|
ChangePasswordScreen()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -76,7 +76,6 @@ fun ProfilePage() {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Top,
|
verticalArrangement = Arrangement.Top,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -116,6 +115,13 @@ fun ProfilePage() {
|
|||||||
}, text = {
|
}, text = {
|
||||||
Text("Edit")
|
Text("Edit")
|
||||||
})
|
})
|
||||||
|
DropdownMenuItem(onClick = {
|
||||||
|
scope.launch {
|
||||||
|
navController.navigate(NavigationRoute.ChangePasswordScreen.route)
|
||||||
|
}
|
||||||
|
}, text = {
|
||||||
|
Text("Change password")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
60
app/src/main/java/com/aiosman/riderpro/ui/splash/splash.kt
Normal file
60
app/src/main/java/com/aiosman/riderpro/ui/splash/splash.kt
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user