diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 7c8380a..5c8cbf6 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -73,7 +73,8 @@ dependencies {
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.lifecycle.common.jvm)
implementation(libs.googleid)
- implementation(libs.identity.credential) // 用于媒体会话(可选)
+ implementation(libs.identity.credential)
+ implementation(libs.androidx.lifecycle.process)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
@@ -99,6 +100,7 @@ dependencies {
implementation("com.google.firebase:firebase-crashlytics")
implementation("com.google.firebase:firebase-analytics")
implementation("com.google.firebase:firebase-perf")
+ implementation("com.google.firebase:firebase-messaging-ktx")
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b69175d..4b62647 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/aiosman/riderpro/Colors.kt b/app/src/main/java/com/aiosman/riderpro/Colors.kt
new file mode 100644
index 0000000..37df191
--- /dev/null
+++ b/app/src/main/java/com/aiosman/riderpro/Colors.kt
@@ -0,0 +1,7 @@
+package com.aiosman.riderpro
+
+import androidx.compose.ui.graphics.Color
+
+object AppColors {
+ val mainColor = Color(0xffED1C24)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aiosman/riderpro/Const.kt b/app/src/main/java/com/aiosman/riderpro/Const.kt
index a7448c7..76a3018 100644
--- a/app/src/main/java/com/aiosman/riderpro/Const.kt
+++ b/app/src/main/java/com/aiosman/riderpro/Const.kt
@@ -5,4 +5,7 @@ object ConstVars {
// const val BASE_SERVER = "http://192.168.31.190:8088"
// const val BASE_SERVER = "http://192.168.31.36:8088"
const val BASE_SERVER = "https://8.137.22.101:8088"
+ const val MOMENT_LIKE_CHANNEL_ID = "moment_like"
+ const val MOMENT_LIKE_CHANNEL_NAME = "Moment Like"
+
}
\ 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 8addff3..df92ffe 100644
--- a/app/src/main/java/com/aiosman/riderpro/MainActivity.kt
+++ b/app/src/main/java/com/aiosman/riderpro/MainActivity.kt
@@ -4,6 +4,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
@@ -28,6 +29,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
+import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
@@ -44,10 +46,40 @@ import com.google.firebase.ktx.Firebase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import android.Manifest
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.util.Log
+import android.widget.Toast
+import androidx.lifecycle.ProcessLifecycleOwner
+import androidx.lifecycle.viewModelScope
+import androidx.navigation.findNavController
+import com.aiosman.riderpro.ui.post.PostViewModel
+import com.google.android.gms.tasks.OnCompleteListener
+import com.google.firebase.messaging.FirebaseMessaging
class MainActivity : ComponentActivity() {
+ // Firebase Analytics
private lateinit var analytics: FirebaseAnalytics
private val scope = CoroutineScope(Dispatchers.Main)
+ // 请求通知权限
+ private val requestPermissionLauncher = registerForActivityResult(
+ ActivityResultContracts.RequestPermission(),
+ ) { isGranted: Boolean ->
+ if (isGranted) {
+ // FCM SDK (and your app) can post notifications.
+ } else {
+ // TODO: Inform user that that your app will not show notifications.
+ }
+ }
+
+ /**
+ * 获取账号信息
+ */
suspend fun getAccount(): Boolean {
val accountService: AccountService = AccountServiceImpl()
try {
@@ -60,26 +92,90 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ // 监听应用生命周期
+ ProcessLifecycleOwner.get().lifecycle.addObserver(MainActivityLifecycleObserver())
+ // 创建通知渠道
+ createNotificationChannel()
+ // 沉浸式状态栏
WindowCompat.setDecorFitsSystemWindows(window, false)
+ // 初始化 Places SDK
if (!Places.isInitialized()) {
Places.initialize(applicationContext, "AIzaSyDpgLDH1-SECw_pdjJq_msynq1XrxwgKVI")
}
+ // 初始化 Firebase Analytics
analytics = Firebase.analytics
+ // 请求通知权限
+ askNotificationPermission()
+ // 加载一些本地化的配置
AppStore.init(this)
enableEdgeToEdge()
scope.launch {
+ // 检查是否有登录态
val isAccountValidate = getAccount()
var startDestination = NavigationRoute.Login.route
+ // 如果有登录态,且记住登录状态,且账号有效,则初始化 FCM,下一步进入首页
if (AppStore.token != null && AppStore.rememberMe && isAccountValidate) {
+ Messaging.InitFCM(scope)
startDestination = NavigationRoute.Index.route
}
setContent {
- Navigation(startDestination)
+ Navigation(startDestination) { navController ->
+ // 处理带有 postId 的通知点击
+ val postId = intent.getStringExtra("POST_ID")
+ if (postId != null) {
+ Log.d("MainActivity", "Navigation to Post$postId")
+ PostViewModel.postId = postId
+ PostViewModel.viewModelScope.launch {
+ PostViewModel.initData()
+ navController.navigate(NavigationRoute.Post.route.replace("{id}", postId))
+ }
+
+ }
+ }
+ }
+
+ }
+ }
+
+ /**
+ * 请求通知权限
+ */
+ private fun askNotificationPermission() {
+ // This is only necessary for API level >= 33 (TIRAMISU)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
+ PackageManager.PERMISSION_GRANTED
+ ) {
+ // FCM SDK (and your app) can post notifications.
+ } else if (shouldShowRequestPermissionRationale(android.Manifest.permission.POST_NOTIFICATIONS)) {
+
+ } else {
+ // Directly ask for the permission
+ requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
+
+ /**
+ * 创建通知渠道
+ */
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channelId = ConstVars.MOMENT_LIKE_CHANNEL_ID
+ val channelName = ConstVars.MOMENT_LIKE_CHANNEL_NAME
+ val channel =
+ NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT)
+ val notificationManager =
+ getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.createNotificationChannel(channel)
+ }
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ }
}
val LocalNavController = compositionLocalOf {
@@ -96,97 +192,3 @@ val LocalAnimatedContentScope = compositionLocalOf {
}
-// 用于带导航栏的路由的可复用 composable
-@Composable
-fun ScaffoldWithNavigationBar(
- navController: NavHostController,
- content: @Composable () -> Unit
-) {
- val navigationBarHeight = with(LocalDensity.current) {
- WindowInsets.navigationBars.getBottom(this).toDp()
- }
- val item = listOf(
- NavigationItem.Home,
- NavigationItem.Street,
- NavigationItem.Add,
- NavigationItem.Message,
- NavigationItem.Profile
- )
- Scaffold(
- modifier = Modifier.statusBarsPadding(),
- bottomBar = {
- NavigationBar(
- modifier = Modifier.height(56.dp + navigationBarHeight),
- containerColor = Color.Black
- ) {
- val navBackStackEntry by navController.currentBackStackEntryAsState()
- val currentRoute = navBackStackEntry?.destination?.route
- val systemUiController = rememberSystemUiController()
- item.forEach { it ->
- val isSelected = currentRoute == it.route
- val iconTint by animateColorAsState(
- targetValue = if (isSelected) Color.Red else Color.White,
- animationSpec = tween(durationMillis = 250), label = ""
- )
- NavigationBarItem(
- selected = currentRoute == it.route,
- onClick = {
- // Check if the current route is not the same as the tab's route to avoid unnecessary navigation
- if (currentRoute != it.route) {
- navController.navigate(it.route) {
- // Avoid creating a new layer on top of the navigation stack
- launchSingleTop = true
- // Attempt to pop up to the existing instance of the destination, if present
- popUpTo(navController.graph.startDestinationId) {
- saveState = true
- }
- // Restore state when navigating back to the composable
- restoreState = true
- }
- }
- // Additional logic for system UI color changes
- when (it.route) {
- NavigationItem.Add.route -> {
- systemUiController.setSystemBarsColor(color = Color.Black)
- }
-
- NavigationItem.Message.route -> {
- systemUiController.setSystemBarsColor(color = Color.Black)
- }
-
- else -> {
- systemUiController.setSystemBarsColor(color = Color.Transparent)
- }
- }
- },
- colors = NavigationBarItemColors(
- selectedTextColor = Color.Red,
- selectedIndicatorColor = Color.Black,
- unselectedTextColor = Color.Red,
- disabledIconColor = Color.Red,
- disabledTextColor = Color.Red,
- selectedIconColor = iconTint,
- unselectedIconColor = iconTint,
- ),
- icon = {
- Icon(
- modifier = Modifier.size(24.dp),
- imageVector = if (currentRoute == it.route) it.selectedIcon() else it.icon(),
- contentDescription = null,
- tint = iconTint
- )
- }
- )
- }
-
- }
- }
- ) { innerPadding ->
- Box(
- modifier = Modifier.padding(innerPadding)
- ) {
- content()
- }
- }
-}
-
diff --git a/app/src/main/java/com/aiosman/riderpro/MainActivityLifecycle.kt b/app/src/main/java/com/aiosman/riderpro/MainActivityLifecycle.kt
new file mode 100644
index 0000000..b98ebaf
--- /dev/null
+++ b/app/src/main/java/com/aiosman/riderpro/MainActivityLifecycle.kt
@@ -0,0 +1,18 @@
+package com.aiosman.riderpro
+
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+
+object MainActivityLifecycle {
+ var isForeground = false
+}
+
+class MainActivityLifecycleObserver : DefaultLifecycleObserver {
+ override fun onStart(owner: LifecycleOwner) {
+ MainActivityLifecycle.isForeground = true
+ }
+
+ override fun onPause(owner: LifecycleOwner) {
+ MainActivityLifecycle.isForeground = false
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aiosman/riderpro/Messaging.kt b/app/src/main/java/com/aiosman/riderpro/Messaging.kt
new file mode 100644
index 0000000..c514452
--- /dev/null
+++ b/app/src/main/java/com/aiosman/riderpro/Messaging.kt
@@ -0,0 +1,30 @@
+package com.aiosman.riderpro
+
+import android.util.Log
+import com.aiosman.riderpro.data.AccountService
+import com.aiosman.riderpro.data.AccountServiceImpl
+import com.google.android.gms.tasks.OnCompleteListener
+import com.google.firebase.messaging.FirebaseMessaging
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+object Messaging {
+ fun InitFCM(scope: CoroutineScope) {
+ val accountService: AccountService = AccountServiceImpl()
+ FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
+ if (!task.isSuccessful) {
+ Log.w("Pushing", "Fetching FCM registration token failed", task.exception)
+ return@OnCompleteListener
+ }
+
+ // Get new FCM registration token
+ val token = task.result
+
+ // Log and toast
+ Log.d("Pushing", token)
+ scope.launch {
+ accountService.registerMessageChannel(client = "fcm", token)
+ }
+ })
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aiosman/riderpro/MyFirebaseMessagingService.kt b/app/src/main/java/com/aiosman/riderpro/MyFirebaseMessagingService.kt
new file mode 100644
index 0000000..5ea51d6
--- /dev/null
+++ b/app/src/main/java/com/aiosman/riderpro/MyFirebaseMessagingService.kt
@@ -0,0 +1,95 @@
+package com.aiosman.riderpro
+
+import android.Manifest
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.util.Log
+import androidx.compose.material.Icon
+import androidx.core.app.ActivityCompat
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import com.google.firebase.messaging.FirebaseMessagingService
+import com.google.firebase.messaging.RemoteMessage
+
+val MessageTypeLike = "like"
+fun showLikeNotification(context: Context, title: String, message: String, postId: Int) {
+ val channelId = ConstVars.MOMENT_LIKE_CHANNEL_ID
+// Create an Intent to open the specific activity
+ val intent = Intent(context, MainActivity::class.java).apply {
+ putExtra("POST_ID", postId.toString())
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ }
+
+ // Create a PendingIntent to wrap the Intent
+ val pendingIntent = PendingIntent.getActivity(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ val notificationBuilder = NotificationCompat.Builder(context, channelId)
+ .setSmallIcon(R.drawable.rider_pro_favoriate)
+ .setContentTitle(title)
+ .setContentText(message)
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setContentIntent(pendingIntent)
+ .setAutoCancel(true)
+
+
+ with(NotificationManagerCompat.from(context)) {
+ if (ActivityCompat.checkSelfPermission(
+ context,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ // 用户没有授权,不显示通知
+ return
+ }
+ notify(System.currentTimeMillis().toInt(), notificationBuilder.build())
+ }
+}
+
+class MyFirebaseMessagingService : FirebaseMessagingService() {
+ override fun onNewToken(token: String) {
+ Log.d("Pushing", "Refreshed token: $token")
+ super.onNewToken(token)
+ }
+
+ override fun onMessageReceived(remoteMessage: RemoteMessage) {
+
+ if (remoteMessage.data.containsKey("action")) {
+ when (remoteMessage.data["action"]) {
+ MessageTypeLike -> {
+ val postId = remoteMessage.data["postId"]?.toInt() ?: return
+ showLikeNotification(
+ applicationContext,
+ remoteMessage.data["title"] ?: "FCM Message",
+ remoteMessage.data["body"] ?: "No message body",
+ postId
+ )
+ }
+
+ else -> {
+ Log.w("Pushing", "Unknown message type: ${remoteMessage.data["messageType"]}")
+ }
+ }
+ }
+ super.onMessageReceived(remoteMessage)
+ }
+
+ fun prepareIntent(clickAction: String?): Intent {
+ val intent: Intent
+ val isAppInBackground: Boolean = !MainActivityLifecycle.isForeground
+ intent = if (isAppInBackground) {
+ Intent(this, MainActivity::class.java)
+ } else {
+ Intent(clickAction)
+ }
+ return intent
+ }
+
+
+}
\ No newline at end of file
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 990df5f..78c272d 100644
--- a/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt
+++ b/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt
@@ -4,6 +4,7 @@ import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.ChangePasswordRequestBody
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.UpdateNoticeRequestBody
import com.aiosman.riderpro.entity.AccountFavouriteEntity
@@ -300,6 +301,8 @@ interface AccountService {
* @param payload 通知信息
*/
suspend fun updateNotice(payload: UpdateNoticeRequestBody)
+
+ suspend fun registerMessageChannel(client:String,identifier:String)
}
class AccountServiceImpl : AccountService {
@@ -400,4 +403,8 @@ class AccountServiceImpl : AccountService {
ApiClient.api.updateNoticeInfo(payload)
}
+ override suspend fun registerMessageChannel(client: String, identifier: String) {
+ ApiClient.api.registerMessageChannel(RegisterMessageChannelRequestBody(client,identifier))
+ }
+
}
\ No newline at end of file
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 2f2584a..d1f4eb8 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
@@ -79,6 +79,12 @@ data class UpdateNoticeRequestBody(
val lastLookFavouriteTime: String? = null
)
+data class RegisterMessageChannelRequestBody(
+ @SerializedName("client")
+ val client: String,
+ @SerializedName("identifier")
+ val identifier: String,
+)
interface RiderProAPI {
@POST("register")
suspend fun register(@Body body: RegisterRequestBody): Response
@@ -211,6 +217,11 @@ interface RiderProAPI {
@Body body: UpdateNoticeRequestBody
): Response
+ @POST("account/my/messaging")
+ suspend fun registerMessageChannel(
+ @Body body: RegisterMessageChannelRequestBody
+ ): Response
+
@GET("profile/{id}")
suspend fun getAccountProfileById(
diff --git a/app/src/main/java/com/aiosman/riderpro/exp/Date.kt b/app/src/main/java/com/aiosman/riderpro/exp/Date.kt
index bdda052..4cfc624 100644
--- a/app/src/main/java/com/aiosman/riderpro/exp/Date.kt
+++ b/app/src/main/java/com/aiosman/riderpro/exp/Date.kt
@@ -46,4 +46,18 @@ fun Date.formatPostTime(): String {
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
}
return dateFormat.format(this)
+}
+
+/**
+ * YYYY.DD.MM HH:MM
+ */
+fun Date.formatPostTime2(): String {
+ val calendar = Calendar.getInstance()
+ calendar.time = this
+ val year = calendar.get(Calendar.YEAR)
+ val month = calendar.get(Calendar.MONTH) + 1
+ val day = calendar.get(Calendar.DAY_OF_MONTH)
+ val hour = calendar.get(Calendar.HOUR_OF_DAY)
+ val minute = calendar.get(Calendar.MINUTE)
+ return "$year.$month.$day $hour:$minute"
}
\ 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 9dd0595..9ba68a7 100644
--- a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt
+++ b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt
@@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
@@ -27,6 +28,7 @@ import com.aiosman.riderpro.LocalAnimatedContentScope
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope
import com.aiosman.riderpro.ui.account.AccountEditScreen
+import com.aiosman.riderpro.ui.account.AccountEditScreen2
import com.aiosman.riderpro.ui.comment.CommentsScreen
import com.aiosman.riderpro.ui.favourite.FavouriteScreen
import com.aiosman.riderpro.ui.follower.FollowerScreen
@@ -201,7 +203,7 @@ fun NavigationController(
EmailSignupScreen()
}
composable(route = NavigationRoute.AccountEdit.route) {
- AccountEditScreen()
+ AccountEditScreen2()
}
composable(route = NavigationRoute.ImageViewer.route) {
CompositionLocalProvider(
@@ -233,8 +235,11 @@ fun NavigationController(
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
-fun Navigation(startDestination: String = NavigationRoute.Login.route) {
+fun Navigation(startDestination: String = NavigationRoute.Login.route,onLaunch: (navController: NavHostController) -> Unit) {
val navController = rememberNavController()
+ LaunchedEffect(Unit) {
+ onLaunch(navController)
+ }
SharedTransitionLayout {
CompositionLocalProvider(
LocalNavController provides navController,
diff --git a/app/src/main/java/com/aiosman/riderpro/ui/account/edit2.kt b/app/src/main/java/com/aiosman/riderpro/ui/account/edit2.kt
new file mode 100644
index 0000000..fe2d1eb
--- /dev/null
+++ b/app/src/main/java/com/aiosman/riderpro/ui/account/edit2.kt
@@ -0,0 +1,316 @@
+package com.aiosman.riderpro.ui.account
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.util.Log
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+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.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+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.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.aiosman.riderpro.AppColors
+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.data.UploadImage
+import com.aiosman.riderpro.entity.AccountProfileEntity
+import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
+import com.aiosman.riderpro.ui.composables.CustomAsyncImage
+import com.aiosman.riderpro.ui.composables.StatusBarSpacer
+import com.aiosman.riderpro.ui.modifiers.noRippleClickable
+import com.aiosman.riderpro.ui.post.NewPostViewModel.uriToFile
+import kotlinx.coroutines.launch
+
+/**
+ * 编辑用户资料界面
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AccountEditScreen2() {
+ val accountService: AccountService = AccountServiceImpl()
+ var name by remember { mutableStateOf("") }
+ var bio by remember { mutableStateOf("") }
+ var imageUrl by remember { mutableStateOf(null) }
+ var bannerImageUrl by remember { mutableStateOf(null) }
+ var profile by remember {
+ mutableStateOf(
+ null
+ )
+ }
+ val navController = LocalNavController.current
+ val scope = rememberCoroutineScope()
+ val context = LocalContext.current
+
+ /**
+ * 加载用户资料
+ */
+ suspend fun reloadProfile() {
+ accountService.getMyAccountProfile().let {
+ profile = it
+ name = it.nickName
+ bio = it.bio
+ }
+ }
+
+ fun updateUserProfile() {
+ scope.launch {
+ val newAvatar = imageUrl?.let {
+ val cursor = context.contentResolver.query(it, null, null, null, null)
+ var newAvatar: UploadImage? = null
+ cursor?.use { cur ->
+ if (cur.moveToFirst()) {
+ val displayName = cur.getString(cur.getColumnIndex("_display_name"))
+ val extension = displayName.substringAfterLast(".")
+ Log.d("NewPost", "File name: $displayName, extension: $extension")
+ // read as file
+ val file = uriToFile(context, it)
+ Log.d("NewPost", "File size: ${file.length()}")
+ newAvatar = UploadImage(file, displayName, it.toString(), extension)
+ }
+ }
+ newAvatar
+ }
+ var newBanner = bannerImageUrl?.let {
+ val cursor = context.contentResolver.query(it, null, null, null, null)
+ var newBanner: UploadImage? = null
+ cursor?.use { cur ->
+ if (cur.moveToFirst()) {
+ val displayName = cur.getString(cur.getColumnIndex("_display_name"))
+ val extension = displayName.substringAfterLast(".")
+ Log.d("NewPost", "File name: $displayName, extension: $extension")
+ // read as file
+ val file = uriToFile(context, it)
+ Log.d("NewPost", "File size: ${file.length()}")
+ newBanner = UploadImage(file, displayName, it.toString(), extension)
+ }
+ }
+ newBanner
+ }
+ val newName = if (name == profile?.nickName) null else name
+ accountService.updateProfile(
+ avatar = newAvatar,
+ banner = newBanner,
+ nickName = newName,
+ bio = bio
+ )
+ reloadProfile()
+ navController.popBackStack()
+ }
+ }
+
+ val pickImageLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ val uri = result.data?.data
+ uri?.let {
+ imageUrl = it
+ }
+ }
+ }
+ val pickBannerImageLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ val uri = result.data?.data
+ uri?.let {
+ bannerImageUrl = uri
+ }
+ }
+ }
+
+ LaunchedEffect(Unit) {
+ reloadProfile()
+ }
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ StatusBarSpacer()
+ Box(
+ modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
+ ) {
+ NoticeScreenHeader(
+ title = "编辑资料",
+ moreIcon = false
+ ) {
+
+ Icon(
+ modifier = Modifier
+ .size(24.dp)
+ .noRippleClickable {
+ updateUserProfile()
+ },
+ imageVector = Icons.Default.Check,
+ contentDescription = "保存"
+ )
+ }
+ }
+ Spacer(modifier = Modifier.height(32.dp))
+ profile?.let {
+ Box(
+ modifier = Modifier.size(width = 112.dp, height = 112.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Image(
+ modifier = Modifier.fillMaxSize(),
+ painter = painterResource(id = R.drawable.avatar_bold), contentDescription = ""
+ )
+ CustomAsyncImage(
+ context,
+ imageUrl?.toString() ?: it.avatar,
+ modifier = Modifier
+ .size(width = 88.dp, height = 88.dp)
+ .clip(
+ RoundedCornerShape(88.dp)
+ ),
+ contentDescription = "",
+ contentScale = ContentScale.Crop
+ )
+ Box(
+ modifier = Modifier
+ .size(32.dp)
+ .clip(CircleShape)
+ .background(AppColors.mainColor)
+ .align(Alignment.BottomEnd).noRippleClickable {
+ Intent(Intent.ACTION_PICK).apply {
+ type = "image/*"
+ pickImageLauncher.launch(this)
+ }
+ }
+ ,
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ Icons.Default.Add,
+ contentDescription = "Add",
+ tint = Color.White,
+ )
+ }
+
+ }
+ Spacer(modifier = Modifier.height(46.dp))
+ Column(
+ modifier = Modifier.padding(horizontal = 24.dp).border(
+ width = 1.dp,
+ color = Color(0xFFEBEBEB),
+ )
+ ) {
+ Row(
+ modifier = Modifier.padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = "Name",
+ modifier = Modifier
+ .widthIn(100.dp),
+ style = TextStyle(
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Bold,
+ color = Color(0xFF333333)
+ )
+ )
+ BasicTextField(
+ maxLines = 1,
+ value = name,
+ onValueChange = {
+ name = it
+ },
+ textStyle = TextStyle(
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal
+ ),
+ modifier = Modifier
+ .weight(1f)
+ .padding(start = 16.dp)
+ )
+ }
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .padding(horizontal = 16.dp)
+ .background(Color(0xFFEBEBEB))
+ )
+ Row(
+ modifier = Modifier.padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = "Bio",
+ modifier = Modifier
+ .widthIn(100.dp),
+ style = TextStyle(
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Bold,
+ color = Color(0xFF333333)
+ )
+ )
+ BasicTextField(
+ value = bio,
+ onValueChange = {
+ bio = it
+ },
+ textStyle = TextStyle(
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal
+ ),
+ modifier = Modifier
+ .weight(1f)
+ .padding(start = 16.dp)
+ )
+ }
+ }
+ }
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentsScreen.kt b/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentsScreen.kt
index 8e763c6..cc5c3e9 100644
--- a/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentsScreen.kt
+++ b/app/src/main/java/com/aiosman/riderpro/ui/comment/CommentsScreen.kt
@@ -63,7 +63,8 @@ fun CommentsScreen() {
@Composable
fun NoticeScreenHeader(
title:String,
- moreIcon: Boolean = true
+ moreIcon: Boolean = true,
+ rightIcon: @Composable (() -> Unit)? = null
) {
val nav = LocalNavController.current
Row(
@@ -90,8 +91,10 @@ fun NoticeScreenHeader(
modifier = Modifier.size(24.dp)
)
}
-
-
+ if (rightIcon != null) {
+ Spacer(modifier = Modifier.weight(1f))
+ rightIcon()
+ }
}
}
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 45446e0..b8a6c34 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
@@ -4,6 +4,7 @@ import android.util.Log
import androidx.annotation.DrawableRes
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.core.Animatable
+import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -17,6 +18,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -24,6 +26,7 @@ import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
@@ -32,6 +35,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -44,13 +48,17 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -64,6 +72,7 @@ import com.aiosman.riderpro.R
import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.exp.formatPostTime
import com.aiosman.riderpro.entity.MomentEntity
+import com.aiosman.riderpro.exp.formatPostTime2
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
@@ -86,7 +95,7 @@ fun ProfilePage() {
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
LazyColumn(
modifier = Modifier
- .fillMaxSize().padding(bottom = with(LocalDensity.current) {
+ .fillMaxSize().background(Color(0xFFF5F5F5)).padding(bottom = with(LocalDensity.current) {
val da = WindowInsets.navigationBars.getBottom(this).toDp() + 48.dp
da
})
@@ -133,53 +142,71 @@ fun ProfilePage() {
contentDescription = "",
modifier = Modifier.noRippleClickable {
expanded = true
- }
+ },
+ tint = Color.White
)
- DropdownMenu(
- expanded = expanded,
- onDismissRequest = { expanded = false }
- ) {
- DropdownMenuItem(onClick = {
- scope.launch {
- model.logout()
- navController.navigate(NavigationRoute.Login.route) {
- popUpTo(NavigationRoute.Index.route) {
- inclusive = true
- }
+ MaterialTheme(shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(16.dp))) {
+ DropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false },
+ modifier = Modifier.width(250.dp).background(Color.White)
+ ) {
+ Box(modifier = Modifier.padding(vertical = 14.dp, horizontal = 24.dp)) {
+ Row {
+ Text("Logout", fontWeight = FontWeight.W500)
+ Spacer(modifier = Modifier.weight(1f))
+ Icon(
+ painter = painterResource(id = R.mipmap.rider_pro_logout),
+ contentDescription = "",
+ modifier = Modifier.size(24.dp).noRippleClickable {
+ scope.launch {
+ model.logout()
+ navController.navigate(NavigationRoute.Login.route) {
+ popUpTo(NavigationRoute.Index.route) {
+ inclusive = true
+ }
+ }
+ }
+ }
+ )
}
}
- }, text = {
- Text("Logout")
- })
- DropdownMenuItem(onClick = {
- scope.launch {
- navController.navigate(NavigationRoute.AccountEdit.route)
+ Box(modifier = Modifier.padding(vertical = 14.dp, horizontal = 24.dp)) {
+ Row {
+ Text("Change password",fontWeight = FontWeight.W500)
+ Spacer(modifier = Modifier.weight(1f))
+ Icon(
+ painter = painterResource(id = R.mipmap.rider_pro_change_password),
+ contentDescription = "",
+ modifier = Modifier.size(24.dp).noRippleClickable {
+ scope.launch {
+ navController.navigate(NavigationRoute.ChangePasswordScreen.route)
+ }
+ }
+ )
+ }
}
- }, text = {
- Text("Edit")
- })
- DropdownMenuItem(onClick = {
- scope.launch {
- navController.navigate(NavigationRoute.ChangePasswordScreen.route)
- }
- }, text = {
- Text("Change password")
- })
+ }
}
}
}
-// CarGroup()
-
+ Spacer(modifier = Modifier.height(32.dp))
model.profile?.let {
- UserInformation(accountProfileEntity = it)
+ UserInformation(
+ accountProfileEntity = it,
+ onEditProfileClick = {
+ navController.navigate(NavigationRoute.AccountEdit.route)
+ }
+ )
}
-// RidingStyle()
}
items(moments.itemCount) { idx ->
val momentItem = moments[idx] ?: return@items
MomentPostUnit(momentItem)
-// MomentCard(momentItem)
+ }
+ item {
+ Spacer(modifier = Modifier.height(48.dp))
}
@@ -239,7 +266,8 @@ fun CarTopPicture() {
fun UserInformation(
isSelf: Boolean = true,
accountProfileEntity: AccountProfileEntity,
- onFollowClick: () -> Unit = {}
+ onFollowClick: () -> Unit = {},
+ onEditProfileClick: () -> Unit = {}
) {
Column(
modifier = Modifier
@@ -257,7 +285,8 @@ fun UserInformation(
CommunicationOperatorGroup(
isSelf = isSelf,
isFollowing = accountProfileEntity.isFollowing,
- onFollowClick = onFollowClick
+ onFollowClick = onFollowClick,
+ onEditProfileClick = onEditProfileClick
)
}
}
@@ -270,20 +299,26 @@ fun UserInformationFollowers(modifier: Modifier, accountProfileEntity: AccountPr
text = accountProfileEntity.followerCount.toString(),
fontSize = 24.sp,
color = Color.Black,
- style = TextStyle(fontWeight = FontWeight.Bold)
+ style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold)
)
- Spacer(
+ Canvas(
modifier = Modifier
.size(width = 88.83.dp, height = 1.dp)
- .border(width = 1.dp, color = Color.Gray)
- .padding(top = 5.dp, bottom = 5.dp)
- )
+ .padding(top = 2.dp, bottom = 5.dp)
+ ) {
+ drawLine(
+ color = Color(0xFFCCCCCC),
+ start = Offset(0f, 0f),
+ end = Offset(size.width, 0f),
+ strokeWidth = 1f,
+ pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f)
+ )
+ }
Text(
modifier = Modifier.padding(top = 5.dp),
text = stringResource(R.string.followers_upper),
fontSize = 12.sp,
color = Color.Black,
- style = TextStyle(fontWeight = FontWeight.Bold)
)
}
}
@@ -331,12 +366,6 @@ fun UserInformationBasic(modifier: Modifier, accountProfileEntity: AccountProfil
fontSize = 12.sp,
color = Color.Gray
)
-// Text(
-// modifier = Modifier.padding(top = 4.dp),
-// text = "Member since Jun 4.2019",
-// fontSize = 12.sp,
-// color = Color.Gray
-// )
}
}
@@ -351,14 +380,21 @@ fun UserInformationFollowing(modifier: Modifier, accountProfileEntity: AccountPr
text = accountProfileEntity.followingCount.toString(),
fontSize = 24.sp,
color = Color.Black,
- style = TextStyle(fontWeight = FontWeight.Bold)
+ style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold)
)
- Box(
+ Canvas(
modifier = Modifier
.size(width = 88.83.dp, height = 1.dp)
- .border(width = 1.dp, color = Color.Gray)
-
- )
+ .padding(top = 2.dp, bottom = 5.dp)
+ ) {
+ drawLine(
+ color = Color(0xFFCCCCCC),
+ start = Offset(0f, 0f),
+ end = Offset(size.width, 0f),
+ strokeWidth = 1f,
+ pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f)
+ )
+ }
Text(
modifier = Modifier.padding(top = 5.dp),
text = stringResource(R.string.following_upper),
@@ -384,7 +420,8 @@ fun UserInformationSlogan(accountProfileEntity: AccountProfileEntity) {
fun CommunicationOperatorGroup(
isSelf: Boolean = true,
isFollowing: Boolean = false,
- onFollowClick: () -> Unit
+ onFollowClick: () -> Unit = {},
+ onEditProfileClick: () -> Unit = {}
) {
val navController = LocalNavController.current
Row(
@@ -415,6 +452,30 @@ fun CommunicationOperatorGroup(
}
}
+ if (isSelf) {
+ Box(
+ modifier = Modifier
+ .size(width = 142.dp, height = 40.dp)
+ .noRippleClickable {
+ onEditProfileClick()
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ Image(
+ modifier = Modifier.fillMaxSize(),
+ painter = painterResource(id = R.mipmap.rider_pro_btn_bg_grey),
+ contentDescription = ""
+ )
+ Text(
+ text = "Edit profile",
+ fontSize = 14.sp,
+ color = Color.Black,
+ fontWeight = FontWeight.Bold,
+ style = TextStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic)
+ )
+ }
+ }
+
}
}
@@ -484,7 +545,7 @@ fun RidingStyleItem(styleContent: String) {
@Composable
fun MomentPostUnit(momentEntity: MomentEntity) {
- TimeGroup(momentEntity.time.formatPostTime())
+ TimeGroup(momentEntity.time.formatPostTime2())
ProfileMomentCard(
momentEntity.momentTextContent,
momentEntity.images[0].thumbnail,
@@ -504,13 +565,14 @@ fun TimeGroup(time: String = "2024.06.08 12:23") {
verticalAlignment = Alignment.CenterVertically
) {
Image(
- modifier = Modifier.padding(end = 12.dp),
+ modifier = Modifier.height(16.dp).width(14.dp),
painter = painterResource(id = R.drawable.rider_pro_moment_time_flag),
contentDescription = ""
)
+ Spacer(modifier = Modifier.width(12.dp))
Text(
text = time,
- fontSize = 22.sp,
+ fontSize = 16.sp,
color = Color.Black,
style = TextStyle(fontWeight = FontWeight.Bold)
)
@@ -525,15 +587,46 @@ fun ProfileMomentCard(
comment: String,
momentEntity: MomentEntity
) {
+ var columnHeight by remember { mutableStateOf(0) }
+
Column(
modifier = Modifier
.fillMaxWidth()
- .padding(start = 48.dp, top = 18.dp, end = 24.dp)
- .border(width = 1.dp, color = Color(0f, 0f, 0f, 0.1f), shape = RoundedCornerShape(6.dp))
+ .padding(start = 24.dp, top = 18.dp, end = 24.dp)
) {
- MomentCardTopContent(content)
- MomentCardPicture(imageUrl, momentEntity = momentEntity)
- MomentCardOperation(like, comment)
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Canvas(
+ modifier = Modifier
+ .height(with(LocalDensity.current) { columnHeight.toDp() })
+ .width(14.dp)
+ ) {
+ drawLine(
+ color = Color(0xff899DA9),
+ start = Offset(0f, 0f),
+ end = Offset(0f, size.height),
+ strokeWidth = 4f,
+ pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f)
+ )
+ }
+ Spacer(modifier = Modifier.width(10.dp))
+ Column(
+ modifier = Modifier
+ .background(Color.White)
+ .weight(1f)
+ .onGloballyPositioned { coordinates ->
+ columnHeight = coordinates.size.height
+ }
+ ) {
+ if (content.isNotEmpty()) {
+ MomentCardTopContent(content)
+ }
+ MomentCardPicture(imageUrl, momentEntity = momentEntity)
+ MomentCardOperation(like, comment)
+ }
+ }
}
}
@@ -561,7 +654,7 @@ fun MomentCardPicture(imageUrl: String, momentEntity: MomentEntity) {
imageUrl,
modifier = Modifier
.fillMaxSize()
- .aspectRatio(1f)
+ .aspectRatio(3f/2f)
.padding(16.dp)
.noRippleClickable {
PostViewModel.preTransit(momentEntity)
diff --git a/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt b/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt
index 1e5d0de..2a20dd4 100644
--- a/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt
+++ b/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt
@@ -26,6 +26,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.LocalNavController
+import com.aiosman.riderpro.Messaging
import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.ServiceException
@@ -101,6 +102,7 @@ fun EmailSignupScreen() {
// 获取token 信息
try {
accountService.getMyAccount()
+ Messaging.InitFCM(scope)
} catch (e: ServiceException) {
scope.launch(Dispatchers.Main) {
Toast.makeText(context, "Failed to get account", Toast.LENGTH_SHORT).show()
diff --git a/app/src/main/java/com/aiosman/riderpro/ui/login/signup.kt b/app/src/main/java/com/aiosman/riderpro/ui/login/signup.kt
index ba1d299..a463ee5 100644
--- a/app/src/main/java/com/aiosman/riderpro/ui/login/signup.kt
+++ b/app/src/main/java/com/aiosman/riderpro/ui/login/signup.kt
@@ -36,6 +36,7 @@ import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetCredentialResponse
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.LocalNavController
+import com.aiosman.riderpro.Messaging
import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.AccountServiceImpl
@@ -87,6 +88,7 @@ fun SignupScreen() {
// 获取token 信息
try {
accountService.getMyAccount()
+ Messaging.InitFCM(coroutineScope)
} catch (e: ServiceException) {
coroutineScope.launch(Dispatchers.Main) {
Toast.makeText(context, "Failed to get account", Toast.LENGTH_SHORT)
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 631ec04..c972e5c 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
@@ -1,7 +1,5 @@
package com.aiosman.riderpro.ui.login
-import android.content.ContentValues.TAG
-import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -21,8 +19,6 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CheckboxDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.SnackbarHost
-import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -42,28 +38,19 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.credentials.Credential
import androidx.credentials.CredentialManager
-import androidx.credentials.CustomCredential
-import androidx.credentials.GetCredentialRequest
-import androidx.credentials.GetCredentialResponse
-import androidx.credentials.exceptions.GetCredentialProviderConfigurationException
-import androidx.credentials.exceptions.NoCredentialException
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.LocalNavController
+import com.aiosman.riderpro.Messaging
import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountService
-import com.aiosman.riderpro.data.ServiceException
import com.aiosman.riderpro.data.AccountServiceImpl
+import com.aiosman.riderpro.data.ServiceException
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.utils.GoogleLogin
-import com.google.android.libraries.identity.googleid.GetGoogleIdOption
-import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
-import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -103,6 +90,7 @@ fun UserAuthScreen() {
this.rememberMe = rememberMe
saveData()
}
+ Messaging.InitFCM(scope)
navController.navigate(NavigationRoute.Index.route) {
popUpTo(NavigationRoute.Login.route) { inclusive = true }
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d264b51..ac8b209 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -21,6 +21,7 @@ lifecycleCommonJvm = "2.8.2"
places = "3.3.0"
googleid = "1.1.1"
identityCredential = "20231002"
+lifecycleProcess = "2.8.4"
[libraries]
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" }
@@ -53,6 +54,7 @@ androidx-lifecycle-common-jvm = { group = "androidx.lifecycle", name = "lifecycl
places = { module = "com.google.android.libraries.places:places", version.ref = "places" }
googleid = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "googleid" }
identity-credential = { group = "com.android.identity", name = "identity-credential", version.ref = "identityCredential" }
+androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleProcess" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }