dm-132
This commit is contained in:
parent
944d91e3d9
commit
e98eb1706d
3 changed files with 73 additions and 59 deletions
|
|
@ -112,8 +112,18 @@ data class Message(
|
||||||
|
|
||||||
// 🌟 核心新增:讓 UI 可以辨識訊息發送狀態!
|
// 🌟 核心新增:讓 UI 可以辨識訊息發送狀態!
|
||||||
@kotlin.jvm.Transient // 👈 告訴 Moshi 這個欄位不需要解析 JSON,這是我們本地維護的
|
@kotlin.jvm.Transient // 👈 告訴 Moshi 這個欄位不需要解析 JSON,這是我們本地維護的
|
||||||
val status: MessageStatus = MessageStatus.SUCCESS
|
val status: MessageStatus = MessageStatus.SUCCESS,
|
||||||
)
|
|
||||||
|
@kotlin.jvm.Transient
|
||||||
|
var callLogInfo: CallLogInfo? = null
|
||||||
|
|
||||||
|
){
|
||||||
|
init {
|
||||||
|
if (type == "system" || type.startsWith("call")) {
|
||||||
|
callLogInfo = CallLogInfo.parseContent(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//send message
|
//send message
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
|
@ -134,26 +144,24 @@ data class MessageReactionUsersResponse(
|
||||||
//voice call
|
//voice call
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class CallLogInfo(
|
data class CallLogInfo(
|
||||||
@Json(name = "type") val type: String, // 例如: call, call_log, call_missed, call_rejected 等
|
@Json(name = "type") val type: String, // 例如: call_missed, call_rejected
|
||||||
@Json(name = "initiator_id") val initiator_id: String,
|
@Json(name = "initiator_id") val initiatorId: String, // 順便改成 Kotlin 的駝峰命名規範
|
||||||
@Json(name = "has_video") val has_video: Boolean
|
@Json(name = "has_video") val hasVideo: Boolean
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private val moshi = Moshi.Builder()
|
private val moshi = Moshi.Builder()
|
||||||
.addLast(KotlinJsonAdapterFactory())
|
.addLast(com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val adapter = moshi.adapter(CallLogInfo::class.java)
|
private val adapter = moshi.adapter(CallLogInfo::class.java)
|
||||||
|
|
||||||
fun parse(message: Message): CallLogInfo? {
|
// 🌟 改成只單純解析 String
|
||||||
// 🌟 修正:只要是 call 開頭的 type 都允許解析,避免 call_missed 被擋掉
|
fun parseContent(content: String?): CallLogInfo? {
|
||||||
if (message.type == null || !message.type.startsWith("call")) return null
|
val json = content?.trim() ?: return null
|
||||||
|
if (!json.startsWith("{") || !json.endsWith("}")) return null
|
||||||
val content = message.content?.trim() ?: return null
|
|
||||||
if (!content.startsWith("{") || !content.endsWith("}")) return null
|
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
adapter.fromJson(content)
|
adapter.fromJson(json)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("CallLogInfo", "解析通話 JSON 失敗: ${e.message}")
|
android.util.Log.e("CallLogInfo", "解析通話 JSON 失敗: ${e.message}")
|
||||||
null
|
null
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,14 @@ import androidx.compose.foundation.layout.Row
|
||||||
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.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Call
|
import androidx.compose.material.icons.filled.Call
|
||||||
import androidx.compose.material.icons.filled.CallMissed
|
import androidx.compose.material.icons.filled.CallMissed
|
||||||
import androidx.compose.material.icons.filled.VideoCall
|
import androidx.compose.material.icons.filled.VideoCall
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -27,13 +29,13 @@ import com.example.login_v3.data.api.api_class.CallLogInfo
|
||||||
@Composable
|
@Composable
|
||||||
fun CallLogBubble(
|
fun CallLogBubble(
|
||||||
callLog: CallLogInfo,
|
callLog: CallLogInfo,
|
||||||
isMe: Boolean,
|
isMe: Boolean, // 這一則訊息是不是「我」發送的
|
||||||
currentUserId: String,
|
currentUserId: String,
|
||||||
partnerDisplayName: String,
|
partnerDisplayName: String,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
// 判斷這通電話是不是「我」撥打的
|
// 🎯 改造 1:配合方案 A 的變數名稱修正 (initiator_id -> initiatorId)
|
||||||
val isIInitiated = callLog.initiator_id == currentUserId
|
val isIInitiated = callLog.initiatorId == currentUserId
|
||||||
|
|
||||||
// 根據通話狀態決定圖示、顏色與顯示主標題
|
// 根據通話狀態決定圖示、顏色與顯示主標題
|
||||||
val (icon, iconColor, titleText) = when (callLog.type) {
|
val (icon, iconColor, titleText) = when (callLog.type) {
|
||||||
|
|
@ -45,61 +47,66 @@ fun CallLogBubble(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val callTypeStr = if (callLog.has_video) "視訊通話" else "語音通話"
|
// 🎯 改造 2:配合方案 A 的變數名稱修正 (has_video -> hasVideo)
|
||||||
|
val callTypeStr = if (callLog.hasVideo) "視訊通話" else "語音通話"
|
||||||
Triple(
|
Triple(
|
||||||
if (callLog.has_video) Icons.Default.VideoCall else Icons.Default.Call,
|
if (callLog.hasVideo) Icons.Default.VideoCall else Icons.Default.Call,
|
||||||
if (isMe) Color(0xFF4CAF50) else Color(0xFF1976D2),
|
if (isMe) Color(0xFF4CAF50) else Color(0xFF1976D2),
|
||||||
if (isIInitiated) "撥出$callTypeStr" else "已接聽$callTypeStr"
|
if (isIInitiated) "撥出$callTypeStr" else "已接聽$callTypeStr"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🌟 新增:來電顯示副標題邏輯
|
// 來電顯示副標題邏輯
|
||||||
val callTypeLabel = if (callLog.has_video) "視訊" else "語音"
|
val callTypeLabel = if (callLog.hasVideo) "視訊" else "語音"
|
||||||
val subText = when {
|
val subText = when {
|
||||||
// 我撥出去的 -> 顯示「撥給 某某」
|
|
||||||
isIInitiated -> "$callTypeLabel • 撥給 $partnerDisplayName"
|
isIInitiated -> "$callTypeLabel • 撥給 $partnerDisplayName"
|
||||||
// 對方撥進來的 -> 顯示「來自 某某」
|
|
||||||
else -> "$callTypeLabel • 來自 $partnerDisplayName"
|
else -> "$callTypeLabel • 來自 $partnerDisplayName"
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
// 🎯 改造 3:外層加上氣泡背景與剪裁,讓它看起來更像聊天氣泡
|
||||||
modifier = modifier.padding(vertical = 2.dp),
|
Surface(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
modifier = modifier.padding(vertical = 4.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) // 給予淡淡的灰色氣泡背景
|
||||||
) {
|
) {
|
||||||
// 左側:圓形背景與狀態圖示
|
Row(
|
||||||
Box(
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||||
modifier = Modifier
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
.size(38.dp)
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
.background(iconColor.copy(alpha = 0.12f), shape = CircleShape),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
// 左側:圓形背景與狀態圖示
|
||||||
imageVector = icon,
|
Box(
|
||||||
contentDescription = titleText,
|
modifier = Modifier
|
||||||
tint = iconColor,
|
.size(38.dp)
|
||||||
modifier = Modifier.size(22.dp)
|
.background(iconColor.copy(alpha = 0.12f), shape = CircleShape),
|
||||||
)
|
contentAlignment = Alignment.Center
|
||||||
}
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = titleText,
|
||||||
|
tint = iconColor,
|
||||||
|
modifier = Modifier.size(22.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 右側:通話狀態與來電顯示文字
|
// 右側:通話狀態與來電顯示文字
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = titleText,
|
text = titleText,
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
fontSize = 15.sp,
|
fontSize = 15.sp,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
Text(
|
||||||
Text(
|
text = subText,
|
||||||
// 🌟 改用動態組合的來電顯示文字
|
style = MaterialTheme.typography.bodySmall.copy(
|
||||||
text = subText,
|
fontSize = 12.sp,
|
||||||
style = MaterialTheme.typography.bodySmall.copy(
|
color = Color.Gray
|
||||||
fontSize = 12.sp,
|
)
|
||||||
color = Color.Gray
|
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -517,7 +517,7 @@ fun MessageRow(
|
||||||
val isImage = isAttachmentImage || isLegacyImage
|
val isImage = isAttachmentImage || isLegacyImage
|
||||||
val rawContent = message.content ?: ""
|
val rawContent = message.content ?: ""
|
||||||
|
|
||||||
val callLog = remember(message.id, message.content) { CallLogInfo.parse(message) }
|
val callLog = message.callLogInfo
|
||||||
|
|
||||||
val finalImageModel = remember(message) {
|
val finalImageModel = remember(message) {
|
||||||
val rawPath = if (isAttachmentImage && !message.attachment?.filename.isNullOrBlank()) {
|
val rawPath = if (isAttachmentImage && !message.attachment?.filename.isNullOrBlank()) {
|
||||||
|
|
@ -625,17 +625,16 @@ fun MessageRow(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (callLog != null) {
|
} else if (callLog != null) {
|
||||||
// 🌟 通話紀錄渲染
|
// 🌟 🎯 改造 2:通話紀錄渲染優化
|
||||||
|
// 移除原本包裹在外層、帶有背景與 Padding 的 Box,改由長按/點擊事件的容器包住 CallLogBubble
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.widthIn(max = 280.dp)
|
.widthIn(max = 280.dp)
|
||||||
.clip(RoundedCornerShape(12.dp))
|
// 讓通話紀錄一樣能響應點擊與長按(如刪除、回覆)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onLongClick = { onReplyClick(message) },
|
onLongClick = { onReplyClick(message) },
|
||||||
onClick = { onRowClick(message) }
|
onClick = { onRowClick(message) }
|
||||||
)
|
)
|
||||||
.background(color = finalBubbleColor)
|
|
||||||
.padding(horizontal = 14.dp, vertical = 10.dp)
|
|
||||||
) {
|
) {
|
||||||
CallLogBubble(
|
CallLogBubble(
|
||||||
callLog = callLog,
|
callLog = callLog,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue