新增对话
This commit is contained in:
@@ -110,5 +110,7 @@ dependencies {
|
|||||||
implementation("com.google.firebase:firebase-perf")
|
implementation("com.google.firebase:firebase-perf")
|
||||||
implementation("com.google.firebase:firebase-messaging-ktx")
|
implementation("com.google.firebase:firebase-messaging-ktx")
|
||||||
implementation ("cn.jiguang.sdk:jpush-google:5.4.0")
|
implementation ("cn.jiguang.sdk:jpush-google:5.4.0")
|
||||||
|
api ("com.tencent.imsdk:imsdk-plus:8.1.6116")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
app/proguard-rules.pro
vendored
2
app/proguard-rules.pro
vendored
@@ -19,3 +19,5 @@
|
|||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
-keep class com.tencent.imsdk.** { *; }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
@@ -7,7 +8,7 @@
|
|||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="false"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/rider_pro_log"
|
android:icon="@mipmap/rider_pro_log"
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.RiderPro">
|
android:theme="@style/Theme.RiderPro">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
package com.aiosman.riderpro
|
package com.aiosman.riderpro
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.icu.util.Calendar
|
||||||
|
import android.icu.util.TimeZone
|
||||||
|
import android.util.Log
|
||||||
|
import com.aiosman.riderpro.data.AccountService
|
||||||
|
import com.aiosman.riderpro.data.AccountServiceImpl
|
||||||
import com.aiosman.riderpro.ui.favourite.FavouriteListViewModel
|
import com.aiosman.riderpro.ui.favourite.FavouriteListViewModel
|
||||||
import com.aiosman.riderpro.ui.favourite.FavouriteNoticeViewModel
|
import com.aiosman.riderpro.ui.favourite.FavouriteNoticeViewModel
|
||||||
import com.aiosman.riderpro.ui.follower.FollowerNoticeViewModel
|
import com.aiosman.riderpro.ui.follower.FollowerNoticeViewModel
|
||||||
@@ -11,10 +17,68 @@ import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel
|
|||||||
import com.aiosman.riderpro.ui.index.tabs.search.DiscoverViewModel
|
import com.aiosman.riderpro.ui.index.tabs.search.DiscoverViewModel
|
||||||
import com.aiosman.riderpro.ui.index.tabs.search.SearchViewModel
|
import com.aiosman.riderpro.ui.index.tabs.search.SearchViewModel
|
||||||
import com.aiosman.riderpro.ui.like.LikeNoticeViewModel
|
import com.aiosman.riderpro.ui.like.LikeNoticeViewModel
|
||||||
|
import com.aiosman.riderpro.utils.Utils
|
||||||
|
import com.tencent.imsdk.v2.V2TIMCallback
|
||||||
|
import com.tencent.imsdk.v2.V2TIMLogListener
|
||||||
|
import com.tencent.imsdk.v2.V2TIMManager
|
||||||
|
import com.tencent.imsdk.v2.V2TIMSDKConfig
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
object AppState {
|
object AppState {
|
||||||
var UserId: Int? = null
|
var UserId: Int? = null
|
||||||
|
|
||||||
|
suspend fun initWithAccount(scope: CoroutineScope, context: Context) {
|
||||||
|
val accountService: AccountService = AccountServiceImpl()
|
||||||
|
// 获取用户认证信息
|
||||||
|
val resp = accountService.getMyAccount()
|
||||||
|
// 更新必要的用户信息
|
||||||
|
val calendar: Calendar = Calendar.getInstance()
|
||||||
|
val tz: TimeZone = calendar.timeZone
|
||||||
|
val offsetInMillis: Int = tz.rawOffset
|
||||||
|
accountService.updateUserExtra(
|
||||||
|
Utils.getCurrentLanguage(),
|
||||||
|
// 时区偏移量单位是秒
|
||||||
|
offsetInMillis / 1000,
|
||||||
|
tz.displayName
|
||||||
|
)
|
||||||
|
// 设置当前登录用户 ID
|
||||||
|
UserId = resp.id
|
||||||
|
|
||||||
|
// 注册 JPush
|
||||||
|
Messaging.RegistDevice(scope, context)
|
||||||
|
// 注册 Trtc
|
||||||
|
val config = V2TIMSDKConfig()
|
||||||
|
|
||||||
|
config.logLevel = V2TIMSDKConfig.V2TIM_LOG_INFO
|
||||||
|
|
||||||
|
config.logListener = object : V2TIMLogListener() {
|
||||||
|
override fun onLog(logLevel: Int, logContent: String) {
|
||||||
|
Log.d("V2TIMLogListener", logContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val appConfig = accountService.getAppConfig()
|
||||||
|
V2TIMManager.getInstance().initSDK(context, appConfig.trtcAppId, config)
|
||||||
|
try {
|
||||||
|
val sign = accountService.getMyTrtcSign()
|
||||||
|
V2TIMManager.getInstance().login(
|
||||||
|
sign.userId,
|
||||||
|
sign.sig,
|
||||||
|
object : V2TIMCallback {
|
||||||
|
override fun onError(code: Int, desc: String?) {
|
||||||
|
Log.e("V2TIMManager", "login failed: $code, $desc")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess() {
|
||||||
|
Log.d("V2TIMManager", "login success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun ReloadAppState() {
|
fun ReloadAppState() {
|
||||||
// 重置动态列表页面
|
// 重置动态列表页面
|
||||||
MomentViewModel.ResetModel()
|
MomentViewModel.ResetModel()
|
||||||
|
|||||||
@@ -34,10 +34,15 @@ import com.aiosman.riderpro.ui.navigateToPost
|
|||||||
import com.aiosman.riderpro.ui.post.NewPostViewModel
|
import com.aiosman.riderpro.ui.post.NewPostViewModel
|
||||||
import com.aiosman.riderpro.utils.Utils
|
import com.aiosman.riderpro.utils.Utils
|
||||||
import com.google.firebase.analytics.FirebaseAnalytics
|
import com.google.firebase.analytics.FirebaseAnalytics
|
||||||
|
import com.tencent.imsdk.v2.V2TIMCallback
|
||||||
|
import com.tencent.imsdk.v2.V2TIMLogListener
|
||||||
|
import com.tencent.imsdk.v2.V2TIMManager
|
||||||
|
import com.tencent.imsdk.v2.V2TIMSDKConfig
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
// Firebase Analytics
|
// Firebase Analytics
|
||||||
private lateinit var analytics: FirebaseAnalytics
|
private lateinit var analytics: FirebaseAnalytics
|
||||||
@@ -50,7 +55,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
if (isGranted) {
|
if (isGranted) {
|
||||||
// FCM SDK (and your app) can post notifications.
|
// FCM SDK (and your app) can post notifications.
|
||||||
} else {
|
} else {
|
||||||
// TODO: Inform user that that your app will not show notifications.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,17 +66,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
val accountService: AccountService = AccountServiceImpl()
|
val accountService: AccountService = AccountServiceImpl()
|
||||||
try {
|
try {
|
||||||
val resp = accountService.getMyAccount()
|
val resp = accountService.getMyAccount()
|
||||||
val calendar: Calendar = Calendar.getInstance()
|
|
||||||
val tz: TimeZone = calendar.timeZone
|
|
||||||
val offsetInMillis: Int = tz.rawOffset
|
|
||||||
accountService.updateUserExtra(
|
|
||||||
Utils.getCurrentLanguage(),
|
|
||||||
// 时区偏移量单位是秒
|
|
||||||
offsetInMillis / 1000,
|
|
||||||
tz.displayName
|
|
||||||
)
|
|
||||||
// 设置当前登录用户 ID
|
|
||||||
AppState.UserId = resp.id
|
|
||||||
return true
|
return true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return false
|
return false
|
||||||
@@ -102,11 +96,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
JPushInterface.init(this)
|
JPushInterface.init(this)
|
||||||
|
|
||||||
// JPushInterface.setAlias(this, 0, "myTest")
|
|
||||||
|
|
||||||
// Log.d("MainActivity", "pushId: $pushId")
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
// 初始化腾讯云通信 SDK
|
||||||
|
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -115,9 +107,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
var startDestination = NavigationRoute.Login.route
|
var startDestination = NavigationRoute.Login.route
|
||||||
// 如果有登录态,且记住登录状态,且账号有效,则初始化 FCM,下一步进入首页
|
// 如果有登录态,且记住登录状态,且账号有效,则初始化 FCM,下一步进入首页
|
||||||
if (AppStore.token != null && AppStore.rememberMe && isAccountValidate) {
|
if (AppStore.token != null && AppStore.rememberMe && isAccountValidate) {
|
||||||
Messaging.RegistDevice(scope, this@MainActivity)
|
AppState.initWithAccount(scope, this@MainActivity)
|
||||||
startDestination = NavigationRoute.Index.route
|
startDestination = NavigationRoute.Index.route
|
||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
Navigation(startDestination) { navController ->
|
Navigation(startDestination) { navController ->
|
||||||
// 处理带有 postId 的通知点击
|
// 处理带有 postId 的通知点击
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package com.aiosman.riderpro.data
|
|||||||
|
|
||||||
import com.aiosman.riderpro.AppState
|
import com.aiosman.riderpro.AppState
|
||||||
import com.aiosman.riderpro.data.api.ApiClient
|
import com.aiosman.riderpro.data.api.ApiClient
|
||||||
|
import com.aiosman.riderpro.data.api.AppConfig
|
||||||
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.RegisterMessageChannelRequestBody
|
||||||
import com.aiosman.riderpro.data.api.RegisterRequestBody
|
import com.aiosman.riderpro.data.api.RegisterRequestBody
|
||||||
import com.aiosman.riderpro.data.api.ResetPasswordRequestBody
|
import com.aiosman.riderpro.data.api.ResetPasswordRequestBody
|
||||||
|
import com.aiosman.riderpro.data.api.TrtcSignResponseBody
|
||||||
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
|
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
|
||||||
import com.aiosman.riderpro.data.api.UpdateUserLangRequestBody
|
import com.aiosman.riderpro.data.api.UpdateUserLangRequestBody
|
||||||
import com.aiosman.riderpro.entity.AccountFavouriteEntity
|
import com.aiosman.riderpro.entity.AccountFavouriteEntity
|
||||||
@@ -46,6 +48,8 @@ data class AccountProfile(
|
|||||||
val bio: String,
|
val bio: String,
|
||||||
// 主页背景图
|
// 主页背景图
|
||||||
val banner: String?,
|
val banner: String?,
|
||||||
|
// trtcUserId
|
||||||
|
val trtcUserId: String,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* 转换为Entity
|
* 转换为Entity
|
||||||
@@ -65,7 +69,8 @@ data class AccountProfile(
|
|||||||
return@let "${ApiClient.BASE_SERVER}$it"
|
return@let "${ApiClient.BASE_SERVER}$it"
|
||||||
}
|
}
|
||||||
null
|
null
|
||||||
}
|
},
|
||||||
|
trtcUserId = trtcUserId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,6 +360,13 @@ interface AccountService {
|
|||||||
* 更新用户额外信息
|
* 更新用户额外信息
|
||||||
*/
|
*/
|
||||||
suspend fun updateUserExtra(language: String, timeOffset: Int, timezone: String)
|
suspend fun updateUserExtra(language: String, timeOffset: Int, timezone: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取腾讯云TRTC签名
|
||||||
|
*/
|
||||||
|
suspend fun getMyTrtcSign(): TrtcSignResponseBody
|
||||||
|
|
||||||
|
suspend fun getAppConfig(): AppConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccountServiceImpl : AccountService {
|
class AccountServiceImpl : AccountService {
|
||||||
@@ -490,4 +502,15 @@ class AccountServiceImpl : AccountService {
|
|||||||
ApiClient.api.updateUserExtra(UpdateUserLangRequestBody(language, timeOffset, timezone))
|
ApiClient.api.updateUserExtra(UpdateUserLangRequestBody(language, timeOffset, timezone))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMyTrtcSign(): TrtcSignResponseBody {
|
||||||
|
val resp = ApiClient.api.getChatSign()
|
||||||
|
val body = resp.body() ?: throw ServiceException("Failed to get trtc sign")
|
||||||
|
return body.data
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAppConfig(): AppConfig {
|
||||||
|
val resp = ApiClient.api.getAppConfig()
|
||||||
|
val body = resp.body() ?: throw ServiceException("Failed to get app config")
|
||||||
|
return body.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -107,6 +107,18 @@ data class UpdateUserLangRequestBody(
|
|||||||
val timezone: String,
|
val timezone: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class TrtcSignResponseBody(
|
||||||
|
@SerializedName("sig")
|
||||||
|
val sig: String,
|
||||||
|
@SerializedName("userId")
|
||||||
|
val userId: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AppConfig(
|
||||||
|
@SerializedName("trtcAppId")
|
||||||
|
val trtcAppId: Int,
|
||||||
|
)
|
||||||
|
|
||||||
interface RiderProAPI {
|
interface RiderProAPI {
|
||||||
@POST("register")
|
@POST("register")
|
||||||
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
|
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
|
||||||
@@ -300,4 +312,9 @@ interface RiderProAPI {
|
|||||||
@Body body: UpdateUserLangRequestBody
|
@Body body: UpdateUserLangRequestBody
|
||||||
): Response<Unit>
|
): Response<Unit>
|
||||||
|
|
||||||
|
@GET("account/my/chat/sign")
|
||||||
|
suspend fun getChatSign(): Response<DataContainer<TrtcSignResponseBody>>
|
||||||
|
|
||||||
|
@GET("app/info")
|
||||||
|
suspend fun getAppConfig(): Response<DataContainer<AppConfig>>
|
||||||
}
|
}
|
||||||
@@ -59,6 +59,8 @@ data class AccountProfileEntity(
|
|||||||
val isFollowing: Boolean,
|
val isFollowing: Boolean,
|
||||||
// 主页背景图
|
// 主页背景图
|
||||||
val banner: String?,
|
val banner: String?,
|
||||||
|
// trtcUserId
|
||||||
|
val trtcUserId: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.aiosman.riderpro.R
|
|||||||
import com.aiosman.riderpro.data.api.ApiClient
|
import com.aiosman.riderpro.data.api.ApiClient
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化时间为 xx 前
|
* 格式化时间为 xx 前
|
||||||
@@ -61,3 +62,22 @@ fun Date.formatPostTime2(): String {
|
|||||||
val minute = calendar.get(Calendar.MINUTE)
|
val minute = calendar.get(Calendar.MINUTE)
|
||||||
return "$year.$month.$day $hour:$minute"
|
return "$year.$month.$day $hour:$minute"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Date.formatChatTime(context: Context): String {
|
||||||
|
val now = Date()
|
||||||
|
val diffInMillis = now.time - this.time
|
||||||
|
|
||||||
|
val seconds = TimeUnit.MILLISECONDS.toSeconds(diffInMillis)
|
||||||
|
val minutes = TimeUnit.MILLISECONDS.toMinutes(diffInMillis)
|
||||||
|
val hours = TimeUnit.MILLISECONDS.toHours(diffInMillis)
|
||||||
|
val days = TimeUnit.MILLISECONDS.toDays(diffInMillis)
|
||||||
|
val years = days / 365
|
||||||
|
|
||||||
|
return when {
|
||||||
|
seconds < 60 -> context.getString(R.string.seconds_ago, seconds)
|
||||||
|
minutes < 60 -> context.getString(R.string.minutes_ago, minutes)
|
||||||
|
hours < 24 -> SimpleDateFormat("HH:mm", Locale.getDefault()).format(this)
|
||||||
|
days < 365 -> SimpleDateFormat("MM-dd HH:mm", Locale.getDefault()).format(this)
|
||||||
|
else -> SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import com.aiosman.riderpro.LocalNavController
|
|||||||
import com.aiosman.riderpro.LocalSharedTransitionScope
|
import com.aiosman.riderpro.LocalSharedTransitionScope
|
||||||
import com.aiosman.riderpro.ui.account.AccountEditScreen2
|
import com.aiosman.riderpro.ui.account.AccountEditScreen2
|
||||||
import com.aiosman.riderpro.ui.account.ResetPasswordScreen
|
import com.aiosman.riderpro.ui.account.ResetPasswordScreen
|
||||||
|
import com.aiosman.riderpro.ui.chat.ChatScreen
|
||||||
import com.aiosman.riderpro.ui.comment.CommentsScreen
|
import com.aiosman.riderpro.ui.comment.CommentsScreen
|
||||||
import com.aiosman.riderpro.ui.favourite.FavouriteListPage
|
import com.aiosman.riderpro.ui.favourite.FavouriteListPage
|
||||||
import com.aiosman.riderpro.ui.favourite.FavouriteNoticeScreen
|
import com.aiosman.riderpro.ui.favourite.FavouriteNoticeScreen
|
||||||
@@ -84,6 +85,7 @@ sealed class NavigationRoute(
|
|||||||
data object FollowingList : NavigationRoute("FollowingList/{id}")
|
data object FollowingList : NavigationRoute("FollowingList/{id}")
|
||||||
data object ResetPassword : NavigationRoute("ResetPassword")
|
data object ResetPassword : NavigationRoute("ResetPassword")
|
||||||
data object FavouriteList : NavigationRoute("FavouriteList")
|
data object FavouriteList : NavigationRoute("FavouriteList")
|
||||||
|
data object Chat : NavigationRoute("Chat/{id}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -340,6 +342,16 @@ fun NavigationController(
|
|||||||
FavouriteListPage()
|
FavouriteListPage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
composable(
|
||||||
|
route = NavigationRoute.Chat.route,
|
||||||
|
arguments = listOf(navArgument("id") { type = NavType.StringType })
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalAnimatedContentScope provides this,
|
||||||
|
) {
|
||||||
|
ChatScreen(it.arguments?.getString("id")!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -382,3 +394,10 @@ fun NavHostController.navigateToPost(
|
|||||||
.replace("{initImagePagerIndex}", initImagePagerIndex.toString())
|
.replace("{initImagePagerIndex}", initImagePagerIndex.toString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun NavHostController.navigateToChat(id: String) {
|
||||||
|
navigate(
|
||||||
|
route = NavigationRoute.Chat.route
|
||||||
|
.replace("{id}", id)
|
||||||
|
)
|
||||||
|
}
|
||||||
373
app/src/main/java/com/aiosman/riderpro/ui/chat/ChatScreen.kt
Normal file
373
app/src/main/java/com/aiosman/riderpro/ui/chat/ChatScreen.kt
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
package com.aiosman.riderpro.ui.chat
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
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.rememberUpdatedState
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.aiosman.riderpro.LocalNavController
|
||||||
|
import com.aiosman.riderpro.R
|
||||||
|
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
||||||
|
import com.aiosman.riderpro.ui.composables.StatusBarSpacer
|
||||||
|
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChatScreen(userId: String) {
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
val context = LocalNavController.current.context
|
||||||
|
val viewModel = viewModel<ChatViewModel>(
|
||||||
|
key = "ChatViewModel_$userId",
|
||||||
|
factory = object : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return ChatViewModel(userId) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.init(context = context)
|
||||||
|
}
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
viewModel.UnRegistListener()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val navigationBarHeight = with(LocalDensity.current) {
|
||||||
|
WindowInsets.navigationBars.getBottom(this).toDp()
|
||||||
|
}
|
||||||
|
LaunchedEffect(listState) {
|
||||||
|
snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index }
|
||||||
|
.collect { index ->
|
||||||
|
if (index == listState.layoutInfo.totalItemsCount - 1) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.onLoadMore(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
topBar = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.border(1.dp, Color(0xffe5e5e5))
|
||||||
|
.background(Color.White)
|
||||||
|
) {
|
||||||
|
StatusBarSpacer()
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.Start,
|
||||||
|
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.rider_pro_nav_back),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(36.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Text(
|
||||||
|
text = viewModel.userProfile?.nickName ?: "",
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.imePadding()
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(1.dp)
|
||||||
|
.background(Color(0xfff7f7f7))
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
ChatInput() {
|
||||||
|
viewModel.sendMessage(it, context)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(navigationBarHeight)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.background(Color(0xfff7f7f7))
|
||||||
|
.fillMaxSize(),
|
||||||
|
reverseLayout = true,
|
||||||
|
verticalArrangement = Arrangement.Top
|
||||||
|
) {
|
||||||
|
val chatList = viewModel.getDisplayChatList()
|
||||||
|
items(chatList.size) { index ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
ChatItem(item = chatList[index], viewModel.myProfile?.trtcUserId!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChatSelfItem(item: ChatItem) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = androidx.compose.ui.Alignment.End,
|
||||||
|
) {
|
||||||
|
Row() {
|
||||||
|
Text(
|
||||||
|
text = item.time,
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = item.nickname,
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(Color(0xFF000000))
|
||||||
|
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = item.message,
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
),
|
||||||
|
textAlign = TextAlign.End,
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
) {
|
||||||
|
CustomAsyncImage(
|
||||||
|
imageUrl = item.avatar,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentDescription = "avatar"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChatOtherItem(item: ChatItem) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.Start,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
) {
|
||||||
|
CustomAsyncImage(
|
||||||
|
imageUrl = item.avatar,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentDescription = "avatar"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Column {
|
||||||
|
Row() {
|
||||||
|
Text(
|
||||||
|
text = item.nickname,
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = item.time,
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(Color(0xffFFFFFF))
|
||||||
|
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = item.message,
|
||||||
|
style = TextStyle(
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChatItem(item: ChatItem, currentUserId: String) {
|
||||||
|
val isCurrentUser = item.userId == currentUserId
|
||||||
|
if (isCurrentUser) {
|
||||||
|
ChatSelfItem(item)
|
||||||
|
} else {
|
||||||
|
ChatOtherItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChatInput(
|
||||||
|
onSend: (String) -> Unit = {}
|
||||||
|
) {
|
||||||
|
var text by remember { mutableStateOf("") }
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clip(androidx.compose.foundation.shape.RoundedCornerShape(16.dp))
|
||||||
|
.background(Color(0xffe5e5e5))
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
contentAlignment = androidx.compose.ui.Alignment.CenterStart
|
||||||
|
|
||||||
|
) {
|
||||||
|
BasicTextField(
|
||||||
|
value = text,
|
||||||
|
onValueChange = {
|
||||||
|
text = it
|
||||||
|
},
|
||||||
|
textStyle = TextStyle(
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 16.sp
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Crossfade(targetState = text.isNotEmpty(), animationSpec = tween(500)) { isNotEmpty ->
|
||||||
|
Image(
|
||||||
|
painter = rememberUpdatedState(
|
||||||
|
if (isNotEmpty) painterResource(id = R.drawable.rider_pro_send) else painterResource(
|
||||||
|
id = R.drawable.rider_pro_send_disable
|
||||||
|
)
|
||||||
|
).value,
|
||||||
|
contentDescription = "Send",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
if (text.isNotEmpty()) {
|
||||||
|
onSend(text)
|
||||||
|
text = ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
173
app/src/main/java/com/aiosman/riderpro/ui/chat/ChatViewModel.kt
Normal file
173
app/src/main/java/com/aiosman/riderpro/ui/chat/ChatViewModel.kt
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package com.aiosman.riderpro.ui.chat
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.icu.util.Calendar
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.aiosman.riderpro.data.AccountService
|
||||||
|
import com.aiosman.riderpro.data.AccountServiceImpl
|
||||||
|
import com.aiosman.riderpro.data.UserService
|
||||||
|
import com.aiosman.riderpro.data.UserServiceImpl
|
||||||
|
import com.aiosman.riderpro.entity.AccountProfileEntity
|
||||||
|
import com.aiosman.riderpro.exp.formatChatTime
|
||||||
|
import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener
|
||||||
|
import com.tencent.imsdk.v2.V2TIMManager
|
||||||
|
import com.tencent.imsdk.v2.V2TIMMessage
|
||||||
|
import com.tencent.imsdk.v2.V2TIMSendCallback
|
||||||
|
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
data class ChatItem(
|
||||||
|
val message: String,
|
||||||
|
val avatar: String,
|
||||||
|
val time: String,
|
||||||
|
val userId: String,
|
||||||
|
val nickname: String
|
||||||
|
)
|
||||||
|
|
||||||
|
class ChatViewModel(
|
||||||
|
val userId: String,
|
||||||
|
) : ViewModel() {
|
||||||
|
var chatData by mutableStateOf<List<ChatItem>>(emptyList())
|
||||||
|
var userProfile by mutableStateOf<AccountProfileEntity?>(null)
|
||||||
|
var myProfile by mutableStateOf<AccountProfileEntity?>(null)
|
||||||
|
val userService: UserService = UserServiceImpl()
|
||||||
|
val accountService: AccountService = AccountServiceImpl()
|
||||||
|
var textMessageListener: V2TIMAdvancedMsgListener? = null
|
||||||
|
var hasMore by mutableStateOf(true)
|
||||||
|
var isLoading by mutableStateOf(false)
|
||||||
|
var lastMessage : V2TIMMessage? = null
|
||||||
|
fun init(context: Context) {
|
||||||
|
// 获取用户信息
|
||||||
|
viewModelScope.launch {
|
||||||
|
val resp = userService.getUserProfile(userId)
|
||||||
|
userProfile = resp
|
||||||
|
myProfile = accountService.getMyAccountProfile()
|
||||||
|
|
||||||
|
RegistListener(context)
|
||||||
|
fetchHistoryMessage(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RegistListener(context: Context) {
|
||||||
|
textMessageListener = object : V2TIMAdvancedMsgListener() {
|
||||||
|
override fun onRecvNewMessage(msg: V2TIMMessage?) {
|
||||||
|
super.onRecvNewMessage(msg)
|
||||||
|
chatData = listOf(convertToChatItem(msg!!, context)) + chatData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
V2TIMManager.getMessageManager().addAdvancedMsgListener(textMessageListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
fun UnRegistListener() {
|
||||||
|
V2TIMManager.getMessageManager().removeAdvancedMsgListener(textMessageListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun convertToChatItem(message: V2TIMMessage, context: Context): ChatItem {
|
||||||
|
val avatar = if (message.sender == userProfile?.trtcUserId) {
|
||||||
|
userProfile?.avatar ?: ""
|
||||||
|
} else {
|
||||||
|
myProfile?.avatar ?: ""
|
||||||
|
}
|
||||||
|
val nickname = if (message.sender == userProfile?.trtcUserId) {
|
||||||
|
userProfile?.nickName ?: ""
|
||||||
|
} else {
|
||||||
|
myProfile?.nickName ?: ""
|
||||||
|
}
|
||||||
|
val timestamp = message.timestamp
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.timeInMillis = timestamp * 1000
|
||||||
|
|
||||||
|
return ChatItem(
|
||||||
|
message = message.textElem.text,
|
||||||
|
avatar = avatar,
|
||||||
|
time = calendar.time.formatChatTime(context),
|
||||||
|
userId = message.sender,
|
||||||
|
nickname = nickname
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onLoadMore(context: Context) {
|
||||||
|
if (!hasMore || isLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isLoading = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
V2TIMManager.getMessageManager().getC2CHistoryMessageList(
|
||||||
|
userProfile?.trtcUserId!!,
|
||||||
|
20,
|
||||||
|
lastMessage,
|
||||||
|
object : V2TIMValueCallback<List<V2TIMMessage>> {
|
||||||
|
override fun onSuccess(p0: List<V2TIMMessage>?) {
|
||||||
|
chatData = chatData + (p0 ?: emptyList()).map {
|
||||||
|
convertToChatItem(it, context)
|
||||||
|
}
|
||||||
|
if ((p0?.size ?: 0) < 20) {
|
||||||
|
hasMore = false
|
||||||
|
}
|
||||||
|
lastMessage = p0?.lastOrNull()
|
||||||
|
isLoading = false
|
||||||
|
Log.d("ChatViewModel", "fetch history message success")
|
||||||
|
}
|
||||||
|
override fun onError(p0: Int, p1: String?) {
|
||||||
|
Log.e("ChatViewModel", "fetch history message error: $p1")
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendMessage(message: String, context: Context) {
|
||||||
|
V2TIMManager.getInstance().sendC2CTextMessage(
|
||||||
|
message,
|
||||||
|
userProfile?.trtcUserId!!,
|
||||||
|
object : V2TIMSendCallback<V2TIMMessage> {
|
||||||
|
override fun onProgress(p0: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
override fun onError(p0: Int, p1: String?) {
|
||||||
|
Log.e("ChatViewModel", "send message error: $p1")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(p0: V2TIMMessage?) {
|
||||||
|
Log.d("ChatViewModel", "send message success")
|
||||||
|
chatData = listOf(convertToChatItem(p0!!, context)) + chatData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchHistoryMessage(context: Context) {
|
||||||
|
V2TIMManager.getMessageManager().getC2CHistoryMessageList(
|
||||||
|
userProfile?.trtcUserId!!,
|
||||||
|
20,
|
||||||
|
null,
|
||||||
|
object : V2TIMValueCallback<List<V2TIMMessage>> {
|
||||||
|
override fun onSuccess(p0: List<V2TIMMessage>?) {
|
||||||
|
chatData = (p0 ?: emptyList()).map {
|
||||||
|
convertToChatItem(it, context)
|
||||||
|
}
|
||||||
|
if ((p0?.size ?: 0) < 20) {
|
||||||
|
hasMore = false
|
||||||
|
}
|
||||||
|
lastMessage = p0?.lastOrNull()
|
||||||
|
Log.d("ChatViewModel", "fetch history message success")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(p0: Int, p1: String?) {
|
||||||
|
Log.e("ChatViewModel", "fetch history message error: $p1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDisplayChatList(): List<ChatItem> {
|
||||||
|
return chatData
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.widthIn
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Button
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
@@ -78,6 +79,7 @@ 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.MenuItem
|
import com.aiosman.riderpro.ui.composables.MenuItem
|
||||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||||
|
import com.aiosman.riderpro.ui.navigateToChat
|
||||||
import com.aiosman.riderpro.ui.navigateToPost
|
import com.aiosman.riderpro.ui.navigateToPost
|
||||||
import com.aiosman.riderpro.ui.post.NewPostViewModel
|
import com.aiosman.riderpro.ui.post.NewPostViewModel
|
||||||
import com.aiosman.riderpro.ui.post.PostViewModel
|
import com.aiosman.riderpro.ui.post.PostViewModel
|
||||||
@@ -526,6 +528,28 @@ fun CommunicationOperatorGroup(
|
|||||||
style = TextStyle(fontWeight = FontWeight.W600, fontStyle = FontStyle.Italic),
|
style = TextStyle(fontWeight = FontWeight.W600, fontStyle = FontStyle.Italic),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(width = 142.dp, height = 40.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
navController.navigateToChat(accountProfileEntity.id.toString())
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
painter = painterResource(id = R.mipmap.rider_pro_btn_bg_grey),
|
||||||
|
contentDescription = ""
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Chat",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = Color.Black,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
style = TextStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSelf) {
|
if (isSelf) {
|
||||||
|
|||||||
@@ -156,17 +156,7 @@ fun EmailSignupScreen() {
|
|||||||
}
|
}
|
||||||
// 获取token 信息
|
// 获取token 信息
|
||||||
try {
|
try {
|
||||||
val resp = accountService.getMyAccount()
|
AppState.initWithAccount(scope, context)
|
||||||
AppState.UserId = resp.id
|
|
||||||
val calendar: Calendar = Calendar.getInstance()
|
|
||||||
val tz: TimeZone = calendar.timeZone
|
|
||||||
val offsetInMillis: Int = tz.rawOffset
|
|
||||||
accountService.updateUserExtra(
|
|
||||||
Utils.getCurrentLanguage(),
|
|
||||||
offsetInMillis / 1000,
|
|
||||||
tz.displayName
|
|
||||||
)
|
|
||||||
Messaging.RegistDevice(scope, context)
|
|
||||||
} 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()
|
||||||
|
|||||||
@@ -86,17 +86,9 @@ fun SignupScreen() {
|
|||||||
}
|
}
|
||||||
// 获取token 信息
|
// 获取token 信息
|
||||||
try {
|
try {
|
||||||
val resp = accountService.getMyAccount()
|
AppState.initWithAccount(coroutineScope, context)
|
||||||
val calendar: Calendar = Calendar.getInstance()
|
} catch (e: Exception) {
|
||||||
val tz: TimeZone = calendar.timeZone
|
Log.e(TAG, "Failed to init with account", e)
|
||||||
val offsetInMillis: Int = tz.rawOffset
|
|
||||||
accountService.updateUserExtra(
|
|
||||||
Utils.getCurrentLanguage(),
|
|
||||||
offsetInMillis / 1000,
|
|
||||||
tz.displayName
|
|
||||||
)
|
|
||||||
AppState.UserId = resp.id
|
|
||||||
Messaging.RegistDevice(coroutineScope, context)
|
|
||||||
} 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)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import androidx.compose.ui.res.painterResource
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
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 com.aiosman.riderpro.AppState
|
||||||
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.Messaging
|
||||||
@@ -83,16 +84,7 @@ fun UserAuthScreen() {
|
|||||||
this.rememberMe = rememberMe
|
this.rememberMe = rememberMe
|
||||||
saveData()
|
saveData()
|
||||||
}
|
}
|
||||||
accountService.getMyAccount()
|
AppState.initWithAccount(scope, context)
|
||||||
val calendar: Calendar = Calendar.getInstance()
|
|
||||||
val tz: TimeZone = calendar.timeZone
|
|
||||||
val offsetInMillis: Int = tz.rawOffset
|
|
||||||
accountService.updateUserExtra(
|
|
||||||
Utils.getCurrentLanguage(),
|
|
||||||
offsetInMillis / 1000,
|
|
||||||
tz.displayName
|
|
||||||
)
|
|
||||||
Messaging.RegistDevice(scope, context)
|
|
||||||
navController.navigate(NavigationRoute.Index.route) {
|
navController.navigate(NavigationRoute.Index.route) {
|
||||||
popUpTo(NavigationRoute.Login.route) { inclusive = true }
|
popUpTo(NavigationRoute.Login.route) { inclusive = true }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,4 +70,6 @@
|
|||||||
<string name="recover">找回</string>
|
<string name="recover">找回</string>
|
||||||
<string name="reset_mail_send_success">邮件已发送!请查收您的邮箱,按照邮件中的指示重置密码。</string>
|
<string name="reset_mail_send_success">邮件已发送!请查收您的邮箱,按照邮件中的指示重置密码。</string>
|
||||||
<string name="reset_mail_send_failed">邮件发送失败,请检查您的网络连接或稍后重试。</string>
|
<string name="reset_mail_send_failed">邮件发送失败,请检查您的网络连接或稍后重试。</string>
|
||||||
|
<string name="seconds_ago">%1d秒前</string>
|
||||||
|
<string name="minutes_ago">%1d分钟前</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -69,4 +69,6 @@
|
|||||||
<string name="recover">Recover</string>
|
<string name="recover">Recover</string>
|
||||||
<string name="reset_mail_send_success">An email has been sent to your registered email address. Please check your inbox and follow the instructions to reset your password.</string>
|
<string name="reset_mail_send_success">An email has been sent to your registered email address. Please check your inbox and follow the instructions to reset your password.</string>
|
||||||
<string name="reset_mail_send_failed">Failed to send email. Please check your network connection or try again later.</string>
|
<string name="reset_mail_send_failed">Failed to send email. Please check your network connection or try again later.</string>
|
||||||
|
<string name="seconds_ago">%1d seconds ago</string>
|
||||||
|
<string name="minutes_ago">%1d minutes ago</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user