更新代码
This commit is contained in:
@@ -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")
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
@@ -17,6 +18,19 @@
|
||||
tools:targetApi="31">
|
||||
<meta-data android:name="com.google.android.geo.API_KEY"
|
||||
android:value="AIzaSyBM9xMcybq9IbFSFVneZ4nAqQ0ZmTnHGO4"/>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||
android:resource="@drawable/googleg_standard_color_18" />
|
||||
<!-- Set color used with incoming notification messages. This is used when no color is set for the incoming
|
||||
notification message. See README(https://goo.gl/6BKBk7) for more. -->
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_color"
|
||||
android:resource="@color/black" />
|
||||
<!-- [END fcm_default_icon] -->
|
||||
<!-- [START fcm_default_channel] -->
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||
android:value="Default Message" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
@@ -28,6 +42,13 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name=".MyFirebaseMessagingService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
7
app/src/main/java/com/aiosman/riderpro/Colors.kt
Normal file
7
app/src/main/java/com/aiosman/riderpro/Colors.kt
Normal file
@@ -0,0 +1,7 @@
|
||||
package com.aiosman.riderpro
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
object AppColors {
|
||||
val mainColor = Color(0xffED1C24)
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
}
|
||||
@@ -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<NavHostController> {
|
||||
@@ -96,97 +192,3 @@ val LocalAnimatedContentScope = compositionLocalOf<AnimatedContentScope> {
|
||||
}
|
||||
|
||||
|
||||
// 用于带导航栏的路由的可复用 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
30
app/src/main/java/com/aiosman/riderpro/Messaging.kt
Normal file
30
app/src/main/java/com/aiosman/riderpro/Messaging.kt
Normal file
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Unit>
|
||||
@@ -211,6 +217,11 @@ interface RiderProAPI {
|
||||
@Body body: UpdateNoticeRequestBody
|
||||
): Response<Unit>
|
||||
|
||||
@POST("account/my/messaging")
|
||||
suspend fun registerMessageChannel(
|
||||
@Body body: RegisterMessageChannelRequestBody
|
||||
): Response<Unit>
|
||||
|
||||
|
||||
@GET("profile/{id}")
|
||||
suspend fun getAccountProfileById(
|
||||
|
||||
@@ -47,3 +47,17 @@ fun Date.formatPostTime(): String {
|
||||
}
|
||||
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"
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
316
app/src/main/java/com/aiosman/riderpro/ui/account/edit2.kt
Normal file
316
app/src/main/java/com/aiosman/riderpro/ui/account/edit2.kt
Normal file
@@ -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<Uri?>(null) }
|
||||
var bannerImageUrl by remember { mutableStateOf<Uri?>(null) }
|
||||
var profile by remember {
|
||||
mutableStateOf<AccountProfileEntity?>(
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user