更新代码
This commit is contained in:
@@ -73,7 +73,8 @@ dependencies {
|
|||||||
implementation(libs.androidx.activity.ktx)
|
implementation(libs.androidx.activity.ktx)
|
||||||
implementation(libs.androidx.lifecycle.common.jvm)
|
implementation(libs.androidx.lifecycle.common.jvm)
|
||||||
implementation(libs.googleid)
|
implementation(libs.googleid)
|
||||||
implementation(libs.identity.credential) // 用于媒体会话(可选)
|
implementation(libs.identity.credential)
|
||||||
|
implementation(libs.androidx.lifecycle.process)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
@@ -99,6 +100,7 @@ dependencies {
|
|||||||
implementation("com.google.firebase:firebase-crashlytics")
|
implementation("com.google.firebase:firebase-crashlytics")
|
||||||
implementation("com.google.firebase:firebase-analytics")
|
implementation("com.google.firebase:firebase-analytics")
|
||||||
implementation("com.google.firebase:firebase-perf")
|
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.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
@@ -17,6 +18,19 @@
|
|||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<meta-data android:name="com.google.android.geo.API_KEY"
|
<meta-data android:name="com.google.android.geo.API_KEY"
|
||||||
android:value="AIzaSyBM9xMcybq9IbFSFVneZ4nAqQ0ZmTnHGO4"/>
|
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
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -28,6 +42,13 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<service
|
||||||
|
android:name=".MyFirebaseMessagingService"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</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.190:8088"
|
||||||
// const val BASE_SERVER = "http://192.168.31.36:8088"
|
// const val BASE_SERVER = "http://192.168.31.36:8088"
|
||||||
const val BASE_SERVER = "https://8.137.22.101: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.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.AnimatedContentScope
|
import androidx.compose.animation.AnimatedContentScope
|
||||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
import androidx.compose.animation.SharedTransitionScope
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
@@ -28,6 +29,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
@@ -44,10 +46,40 @@ import com.google.firebase.ktx.Firebase
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
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() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
// Firebase Analytics
|
||||||
private lateinit var analytics: FirebaseAnalytics
|
private lateinit var analytics: FirebaseAnalytics
|
||||||
private val scope = CoroutineScope(Dispatchers.Main)
|
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 {
|
suspend fun getAccount(): Boolean {
|
||||||
val accountService: AccountService = AccountServiceImpl()
|
val accountService: AccountService = AccountServiceImpl()
|
||||||
try {
|
try {
|
||||||
@@ -60,26 +92,90 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
// 监听应用生命周期
|
||||||
|
ProcessLifecycleOwner.get().lifecycle.addObserver(MainActivityLifecycleObserver())
|
||||||
|
// 创建通知渠道
|
||||||
|
createNotificationChannel()
|
||||||
|
// 沉浸式状态栏
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
// 初始化 Places SDK
|
||||||
if (!Places.isInitialized()) {
|
if (!Places.isInitialized()) {
|
||||||
Places.initialize(applicationContext, "AIzaSyDpgLDH1-SECw_pdjJq_msynq1XrxwgKVI")
|
Places.initialize(applicationContext, "AIzaSyDpgLDH1-SECw_pdjJq_msynq1XrxwgKVI")
|
||||||
}
|
}
|
||||||
|
// 初始化 Firebase Analytics
|
||||||
analytics = Firebase.analytics
|
analytics = Firebase.analytics
|
||||||
|
// 请求通知权限
|
||||||
|
askNotificationPermission()
|
||||||
|
// 加载一些本地化的配置
|
||||||
AppStore.init(this)
|
AppStore.init(this)
|
||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
// 检查是否有登录态
|
||||||
val isAccountValidate = getAccount()
|
val isAccountValidate = getAccount()
|
||||||
var startDestination = NavigationRoute.Login.route
|
var startDestination = NavigationRoute.Login.route
|
||||||
|
// 如果有登录态,且记住登录状态,且账号有效,则初始化 FCM,下一步进入首页
|
||||||
if (AppStore.token != null && AppStore.rememberMe && isAccountValidate) {
|
if (AppStore.token != null && AppStore.rememberMe && isAccountValidate) {
|
||||||
|
Messaging.InitFCM(scope)
|
||||||
startDestination = NavigationRoute.Index.route
|
startDestination = NavigationRoute.Index.route
|
||||||
}
|
}
|
||||||
setContent {
|
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> {
|
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.ChangePasswordRequestBody
|
||||||
import com.aiosman.riderpro.data.api.GoogleRegisterRequestBody
|
import com.aiosman.riderpro.data.api.GoogleRegisterRequestBody
|
||||||
import com.aiosman.riderpro.data.api.LoginUserRequestBody
|
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.RegisterRequestBody
|
||||||
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
|
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
|
||||||
import com.aiosman.riderpro.entity.AccountFavouriteEntity
|
import com.aiosman.riderpro.entity.AccountFavouriteEntity
|
||||||
@@ -300,6 +301,8 @@ interface AccountService {
|
|||||||
* @param payload 通知信息
|
* @param payload 通知信息
|
||||||
*/
|
*/
|
||||||
suspend fun updateNotice(payload: UpdateNoticeRequestBody)
|
suspend fun updateNotice(payload: UpdateNoticeRequestBody)
|
||||||
|
|
||||||
|
suspend fun registerMessageChannel(client:String,identifier:String)
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccountServiceImpl : AccountService {
|
class AccountServiceImpl : AccountService {
|
||||||
@@ -400,4 +403,8 @@ class AccountServiceImpl : AccountService {
|
|||||||
ApiClient.api.updateNoticeInfo(payload)
|
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
|
val lastLookFavouriteTime: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class RegisterMessageChannelRequestBody(
|
||||||
|
@SerializedName("client")
|
||||||
|
val client: String,
|
||||||
|
@SerializedName("identifier")
|
||||||
|
val identifier: String,
|
||||||
|
)
|
||||||
interface RiderProAPI {
|
interface RiderProAPI {
|
||||||
@POST("register")
|
@POST("register")
|
||||||
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
|
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
|
||||||
@@ -211,6 +217,11 @@ interface RiderProAPI {
|
|||||||
@Body body: UpdateNoticeRequestBody
|
@Body body: UpdateNoticeRequestBody
|
||||||
): Response<Unit>
|
): Response<Unit>
|
||||||
|
|
||||||
|
@POST("account/my/messaging")
|
||||||
|
suspend fun registerMessageChannel(
|
||||||
|
@Body body: RegisterMessageChannelRequestBody
|
||||||
|
): Response<Unit>
|
||||||
|
|
||||||
|
|
||||||
@GET("profile/{id}")
|
@GET("profile/{id}")
|
||||||
suspend fun getAccountProfileById(
|
suspend fun getAccountProfileById(
|
||||||
|
|||||||
@@ -46,4 +46,18 @@ fun Date.formatPostTime(): String {
|
|||||||
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||||
}
|
}
|
||||||
return dateFormat.format(this)
|
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.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -27,6 +28,7 @@ import com.aiosman.riderpro.LocalAnimatedContentScope
|
|||||||
import com.aiosman.riderpro.LocalNavController
|
import com.aiosman.riderpro.LocalNavController
|
||||||
import com.aiosman.riderpro.LocalSharedTransitionScope
|
import com.aiosman.riderpro.LocalSharedTransitionScope
|
||||||
import com.aiosman.riderpro.ui.account.AccountEditScreen
|
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.comment.CommentsScreen
|
||||||
import com.aiosman.riderpro.ui.favourite.FavouriteScreen
|
import com.aiosman.riderpro.ui.favourite.FavouriteScreen
|
||||||
import com.aiosman.riderpro.ui.follower.FollowerScreen
|
import com.aiosman.riderpro.ui.follower.FollowerScreen
|
||||||
@@ -201,7 +203,7 @@ fun NavigationController(
|
|||||||
EmailSignupScreen()
|
EmailSignupScreen()
|
||||||
}
|
}
|
||||||
composable(route = NavigationRoute.AccountEdit.route) {
|
composable(route = NavigationRoute.AccountEdit.route) {
|
||||||
AccountEditScreen()
|
AccountEditScreen2()
|
||||||
}
|
}
|
||||||
composable(route = NavigationRoute.ImageViewer.route) {
|
composable(route = NavigationRoute.ImageViewer.route) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
@@ -233,8 +235,11 @@ fun NavigationController(
|
|||||||
|
|
||||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Navigation(startDestination: String = NavigationRoute.Login.route) {
|
fun Navigation(startDestination: String = NavigationRoute.Login.route,onLaunch: (navController: NavHostController) -> Unit) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
onLaunch(navController)
|
||||||
|
}
|
||||||
SharedTransitionLayout {
|
SharedTransitionLayout {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalNavController provides navController,
|
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
|
@Composable
|
||||||
fun NoticeScreenHeader(
|
fun NoticeScreenHeader(
|
||||||
title:String,
|
title:String,
|
||||||
moreIcon: Boolean = true
|
moreIcon: Boolean = true,
|
||||||
|
rightIcon: @Composable (() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val nav = LocalNavController.current
|
val nav = LocalNavController.current
|
||||||
Row(
|
Row(
|
||||||
@@ -90,8 +91,10 @@ fun NoticeScreenHeader(
|
|||||||
modifier = Modifier.size(24.dp)
|
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.annotation.DrawableRes
|
||||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
import androidx.compose.animation.core.Animatable
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
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.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
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.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
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.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -44,13 +48,17 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.PathEffect
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
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.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
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.entity.AccountProfileEntity
|
||||||
import com.aiosman.riderpro.exp.formatPostTime
|
import com.aiosman.riderpro.exp.formatPostTime
|
||||||
import com.aiosman.riderpro.entity.MomentEntity
|
import com.aiosman.riderpro.entity.MomentEntity
|
||||||
|
import com.aiosman.riderpro.exp.formatPostTime2
|
||||||
import com.aiosman.riderpro.ui.NavigationRoute
|
import com.aiosman.riderpro.ui.NavigationRoute
|
||||||
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
||||||
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
|
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
|
||||||
@@ -86,7 +95,7 @@ fun ProfilePage() {
|
|||||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
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
|
val da = WindowInsets.navigationBars.getBottom(this).toDp() + 48.dp
|
||||||
da
|
da
|
||||||
})
|
})
|
||||||
@@ -133,53 +142,71 @@ fun ProfilePage() {
|
|||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
modifier = Modifier.noRippleClickable {
|
modifier = Modifier.noRippleClickable {
|
||||||
expanded = true
|
expanded = true
|
||||||
}
|
},
|
||||||
|
tint = Color.White
|
||||||
)
|
)
|
||||||
DropdownMenu(
|
MaterialTheme(shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(16.dp))) {
|
||||||
expanded = expanded,
|
DropdownMenu(
|
||||||
onDismissRequest = { expanded = false }
|
expanded = expanded,
|
||||||
) {
|
onDismissRequest = { expanded = false },
|
||||||
DropdownMenuItem(onClick = {
|
modifier = Modifier.width(250.dp).background(Color.White)
|
||||||
scope.launch {
|
) {
|
||||||
model.logout()
|
Box(modifier = Modifier.padding(vertical = 14.dp, horizontal = 24.dp)) {
|
||||||
navController.navigate(NavigationRoute.Login.route) {
|
Row {
|
||||||
popUpTo(NavigationRoute.Index.route) {
|
Text("Logout", fontWeight = FontWeight.W500)
|
||||||
inclusive = true
|
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 = {
|
Box(modifier = Modifier.padding(vertical = 14.dp, horizontal = 24.dp)) {
|
||||||
Text("Logout")
|
Row {
|
||||||
})
|
Text("Change password",fontWeight = FontWeight.W500)
|
||||||
DropdownMenuItem(onClick = {
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
scope.launch {
|
Icon(
|
||||||
navController.navigate(NavigationRoute.AccountEdit.route)
|
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 {
|
model.profile?.let {
|
||||||
UserInformation(accountProfileEntity = it)
|
UserInformation(
|
||||||
|
accountProfileEntity = it,
|
||||||
|
onEditProfileClick = {
|
||||||
|
navController.navigate(NavigationRoute.AccountEdit.route)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// RidingStyle()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
items(moments.itemCount) { idx ->
|
items(moments.itemCount) { idx ->
|
||||||
val momentItem = moments[idx] ?: return@items
|
val momentItem = moments[idx] ?: return@items
|
||||||
MomentPostUnit(momentItem)
|
MomentPostUnit(momentItem)
|
||||||
// MomentCard(momentItem)
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(48.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -239,7 +266,8 @@ fun CarTopPicture() {
|
|||||||
fun UserInformation(
|
fun UserInformation(
|
||||||
isSelf: Boolean = true,
|
isSelf: Boolean = true,
|
||||||
accountProfileEntity: AccountProfileEntity,
|
accountProfileEntity: AccountProfileEntity,
|
||||||
onFollowClick: () -> Unit = {}
|
onFollowClick: () -> Unit = {},
|
||||||
|
onEditProfileClick: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -257,7 +285,8 @@ fun UserInformation(
|
|||||||
CommunicationOperatorGroup(
|
CommunicationOperatorGroup(
|
||||||
isSelf = isSelf,
|
isSelf = isSelf,
|
||||||
isFollowing = accountProfileEntity.isFollowing,
|
isFollowing = accountProfileEntity.isFollowing,
|
||||||
onFollowClick = onFollowClick
|
onFollowClick = onFollowClick,
|
||||||
|
onEditProfileClick = onEditProfileClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,20 +299,26 @@ fun UserInformationFollowers(modifier: Modifier, accountProfileEntity: AccountPr
|
|||||||
text = accountProfileEntity.followerCount.toString(),
|
text = accountProfileEntity.followerCount.toString(),
|
||||||
fontSize = 24.sp,
|
fontSize = 24.sp,
|
||||||
color = Color.Black,
|
color = Color.Black,
|
||||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold)
|
||||||
)
|
)
|
||||||
Spacer(
|
Canvas(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = 88.83.dp, height = 1.dp)
|
.size(width = 88.83.dp, height = 1.dp)
|
||||||
.border(width = 1.dp, color = Color.Gray)
|
.padding(top = 2.dp, bottom = 5.dp)
|
||||||
.padding(top = 5.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(
|
Text(
|
||||||
modifier = Modifier.padding(top = 5.dp),
|
modifier = Modifier.padding(top = 5.dp),
|
||||||
text = stringResource(R.string.followers_upper),
|
text = stringResource(R.string.followers_upper),
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
color = Color.Black,
|
color = Color.Black,
|
||||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,12 +366,6 @@ fun UserInformationBasic(modifier: Modifier, accountProfileEntity: AccountProfil
|
|||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
color = Color.Gray
|
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(),
|
text = accountProfileEntity.followingCount.toString(),
|
||||||
fontSize = 24.sp,
|
fontSize = 24.sp,
|
||||||
color = Color.Black,
|
color = Color.Black,
|
||||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold)
|
||||||
)
|
)
|
||||||
Box(
|
Canvas(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = 88.83.dp, height = 1.dp)
|
.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(
|
Text(
|
||||||
modifier = Modifier.padding(top = 5.dp),
|
modifier = Modifier.padding(top = 5.dp),
|
||||||
text = stringResource(R.string.following_upper),
|
text = stringResource(R.string.following_upper),
|
||||||
@@ -384,7 +420,8 @@ fun UserInformationSlogan(accountProfileEntity: AccountProfileEntity) {
|
|||||||
fun CommunicationOperatorGroup(
|
fun CommunicationOperatorGroup(
|
||||||
isSelf: Boolean = true,
|
isSelf: Boolean = true,
|
||||||
isFollowing: Boolean = false,
|
isFollowing: Boolean = false,
|
||||||
onFollowClick: () -> Unit
|
onFollowClick: () -> Unit = {},
|
||||||
|
onEditProfileClick: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
Row(
|
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
|
@Composable
|
||||||
fun MomentPostUnit(momentEntity: MomentEntity) {
|
fun MomentPostUnit(momentEntity: MomentEntity) {
|
||||||
TimeGroup(momentEntity.time.formatPostTime())
|
TimeGroup(momentEntity.time.formatPostTime2())
|
||||||
ProfileMomentCard(
|
ProfileMomentCard(
|
||||||
momentEntity.momentTextContent,
|
momentEntity.momentTextContent,
|
||||||
momentEntity.images[0].thumbnail,
|
momentEntity.images[0].thumbnail,
|
||||||
@@ -504,13 +565,14 @@ fun TimeGroup(time: String = "2024.06.08 12:23") {
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Image(
|
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),
|
painter = painterResource(id = R.drawable.rider_pro_moment_time_flag),
|
||||||
contentDescription = ""
|
contentDescription = ""
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
Text(
|
Text(
|
||||||
text = time,
|
text = time,
|
||||||
fontSize = 22.sp,
|
fontSize = 16.sp,
|
||||||
color = Color.Black,
|
color = Color.Black,
|
||||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||||
)
|
)
|
||||||
@@ -525,15 +587,46 @@ fun ProfileMomentCard(
|
|||||||
comment: String,
|
comment: String,
|
||||||
momentEntity: MomentEntity
|
momentEntity: MomentEntity
|
||||||
) {
|
) {
|
||||||
|
var columnHeight by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 48.dp, top = 18.dp, end = 24.dp)
|
.padding(start = 24.dp, top = 18.dp, end = 24.dp)
|
||||||
.border(width = 1.dp, color = Color(0f, 0f, 0f, 0.1f), shape = RoundedCornerShape(6.dp))
|
|
||||||
) {
|
) {
|
||||||
MomentCardTopContent(content)
|
Row(
|
||||||
MomentCardPicture(imageUrl, momentEntity = momentEntity)
|
modifier = Modifier
|
||||||
MomentCardOperation(like, comment)
|
.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,
|
imageUrl,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.aspectRatio(1f)
|
.aspectRatio(3f/2f)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
PostViewModel.preTransit(momentEntity)
|
PostViewModel.preTransit(momentEntity)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.aiosman.riderpro.AppStore
|
import com.aiosman.riderpro.AppStore
|
||||||
import com.aiosman.riderpro.LocalNavController
|
import com.aiosman.riderpro.LocalNavController
|
||||||
|
import com.aiosman.riderpro.Messaging
|
||||||
import com.aiosman.riderpro.R
|
import com.aiosman.riderpro.R
|
||||||
import com.aiosman.riderpro.data.AccountService
|
import com.aiosman.riderpro.data.AccountService
|
||||||
import com.aiosman.riderpro.data.ServiceException
|
import com.aiosman.riderpro.data.ServiceException
|
||||||
@@ -101,6 +102,7 @@ fun EmailSignupScreen() {
|
|||||||
// 获取token 信息
|
// 获取token 信息
|
||||||
try {
|
try {
|
||||||
accountService.getMyAccount()
|
accountService.getMyAccount()
|
||||||
|
Messaging.InitFCM(scope)
|
||||||
} catch (e: ServiceException) {
|
} catch (e: ServiceException) {
|
||||||
scope.launch(Dispatchers.Main) {
|
scope.launch(Dispatchers.Main) {
|
||||||
Toast.makeText(context, "Failed to get account", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "Failed to get account", Toast.LENGTH_SHORT).show()
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import androidx.credentials.GetCredentialRequest
|
|||||||
import androidx.credentials.GetCredentialResponse
|
import androidx.credentials.GetCredentialResponse
|
||||||
import com.aiosman.riderpro.AppStore
|
import com.aiosman.riderpro.AppStore
|
||||||
import com.aiosman.riderpro.LocalNavController
|
import com.aiosman.riderpro.LocalNavController
|
||||||
|
import com.aiosman.riderpro.Messaging
|
||||||
import com.aiosman.riderpro.R
|
import com.aiosman.riderpro.R
|
||||||
import com.aiosman.riderpro.data.AccountService
|
import com.aiosman.riderpro.data.AccountService
|
||||||
import com.aiosman.riderpro.data.AccountServiceImpl
|
import com.aiosman.riderpro.data.AccountServiceImpl
|
||||||
@@ -87,6 +88,7 @@ fun SignupScreen() {
|
|||||||
// 获取token 信息
|
// 获取token 信息
|
||||||
try {
|
try {
|
||||||
accountService.getMyAccount()
|
accountService.getMyAccount()
|
||||||
|
Messaging.InitFCM(coroutineScope)
|
||||||
} catch (e: ServiceException) {
|
} catch (e: ServiceException) {
|
||||||
coroutineScope.launch(Dispatchers.Main) {
|
coroutineScope.launch(Dispatchers.Main) {
|
||||||
Toast.makeText(context, "Failed to get account", Toast.LENGTH_SHORT)
|
Toast.makeText(context, "Failed to get account", Toast.LENGTH_SHORT)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.aiosman.riderpro.ui.login
|
package com.aiosman.riderpro.ui.login
|
||||||
|
|
||||||
import android.content.ContentValues.TAG
|
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -21,8 +19,6 @@ import androidx.compose.foundation.text.KeyboardOptions
|
|||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.CheckboxDefaults
|
import androidx.compose.material3.CheckboxDefaults
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
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.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.credentials.Credential
|
|
||||||
import androidx.credentials.CredentialManager
|
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.AppStore
|
||||||
import com.aiosman.riderpro.LocalNavController
|
import com.aiosman.riderpro.LocalNavController
|
||||||
|
import com.aiosman.riderpro.Messaging
|
||||||
import com.aiosman.riderpro.R
|
import com.aiosman.riderpro.R
|
||||||
import com.aiosman.riderpro.data.AccountService
|
import com.aiosman.riderpro.data.AccountService
|
||||||
import com.aiosman.riderpro.data.ServiceException
|
|
||||||
import com.aiosman.riderpro.data.AccountServiceImpl
|
import com.aiosman.riderpro.data.AccountServiceImpl
|
||||||
|
import com.aiosman.riderpro.data.ServiceException
|
||||||
import com.aiosman.riderpro.ui.NavigationRoute
|
import com.aiosman.riderpro.ui.NavigationRoute
|
||||||
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
|
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
|
||||||
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
|
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
|
||||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||||
import com.aiosman.riderpro.utils.GoogleLogin
|
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
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
@@ -103,6 +90,7 @@ fun UserAuthScreen() {
|
|||||||
this.rememberMe = rememberMe
|
this.rememberMe = rememberMe
|
||||||
saveData()
|
saveData()
|
||||||
}
|
}
|
||||||
|
Messaging.InitFCM(scope)
|
||||||
navController.navigate(NavigationRoute.Index.route) {
|
navController.navigate(NavigationRoute.Index.route) {
|
||||||
popUpTo(NavigationRoute.Login.route) { inclusive = true }
|
popUpTo(NavigationRoute.Login.route) { inclusive = true }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ lifecycleCommonJvm = "2.8.2"
|
|||||||
places = "3.3.0"
|
places = "3.3.0"
|
||||||
googleid = "1.1.1"
|
googleid = "1.1.1"
|
||||||
identityCredential = "20231002"
|
identityCredential = "20231002"
|
||||||
|
lifecycleProcess = "2.8.4"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" }
|
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" }
|
places = { module = "com.google.android.libraries.places:places", version.ref = "places" }
|
||||||
googleid = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "googleid" }
|
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" }
|
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]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
|||||||
Reference in New Issue
Block a user