This commit is contained in:
Eric85710 2026-06-15 07:07:51 +08:00
parent b41e149095
commit 944d91e3d9
3 changed files with 170 additions and 3 deletions

View file

@ -4,7 +4,8 @@ import com.example.login_v3.R
import com.example.login_v3.home.Message.ViewModel.Detail.MessageStatus import com.example.login_v3.home.Message.ViewModel.Detail.MessageStatus
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
//Get DM list //Get DM list
@ -125,4 +126,38 @@ data class SendMessageRequest(
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageReactionUsersResponse( data class MessageReactionUsersResponse(
@Json(name = "users") val users: List<String> @Json(name = "users") val users: List<String>
) )
//voice call
@JsonClass(generateAdapter = true)
data class CallLogInfo(
@Json(name = "type") val type: String, // 例如: call, call_log, call_missed, call_rejected 等
@Json(name = "initiator_id") val initiator_id: String,
@Json(name = "has_video") val has_video: Boolean
) {
companion object {
private val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
private val adapter = moshi.adapter(CallLogInfo::class.java)
fun parse(message: Message): CallLogInfo? {
// 🌟 修正:只要是 call 開頭的 type 都允許解析,避免 call_missed 被擋掉
if (message.type == null || !message.type.startsWith("call")) return null
val content = message.content?.trim() ?: return null
if (!content.startsWith("{") || !content.endsWith("}")) return null
return try {
adapter.fromJson(content)
} catch (e: Exception) {
android.util.Log.e("CallLogInfo", "解析通話 JSON 失敗: ${e.message}")
null
}
}
}
}

View file

@ -0,0 +1,105 @@
package com.example.login_v3.home.Message.UI.Detail.Message_Component
import androidx.compose.foundation.background
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Call
import androidx.compose.material.icons.filled.CallMissed
import androidx.compose.material.icons.filled.VideoCall
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.login_v3.data.api.api_class.CallLogInfo
@Composable
fun CallLogBubble(
callLog: CallLogInfo,
isMe: Boolean,
currentUserId: String,
partnerDisplayName: String,
modifier: Modifier = Modifier
) {
// 判斷這通電話是不是「我」撥打的
val isIInitiated = callLog.initiator_id == currentUserId
// 根據通話狀態決定圖示、顏色與顯示主標題
val (icon, iconColor, titleText) = when (callLog.type) {
"call_missed" -> {
Triple(
Icons.Default.CallMissed,
Color(0xFFEC1C24), // 未接來電用紅色
if (isIInitiated) "對方未接聽" else "未接來電"
)
}
else -> {
val callTypeStr = if (callLog.has_video) "視訊通話" else "語音通話"
Triple(
if (callLog.has_video) Icons.Default.VideoCall else Icons.Default.Call,
if (isMe) Color(0xFF4CAF50) else Color(0xFF1976D2),
if (isIInitiated) "撥出$callTypeStr" else "已接聽$callTypeStr"
)
}
}
// 🌟 新增:來電顯示副標題邏輯
val callTypeLabel = if (callLog.has_video) "視訊" else "語音"
val subText = when {
// 我撥出去的 -> 顯示「撥給 某某」
isIInitiated -> "$callTypeLabel • 撥給 $partnerDisplayName"
// 對方撥進來的 -> 顯示「來自 某某」
else -> "$callTypeLabel • 來自 $partnerDisplayName"
}
Row(
modifier = modifier.padding(vertical = 2.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
// 左側:圓形背景與狀態圖示
Box(
modifier = Modifier
.size(38.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 {
Text(
text = titleText,
style = MaterialTheme.typography.bodyMedium.copy(
fontSize = 15.sp,
fontWeight = FontWeight.SemiBold
)
)
Text(
// 🌟 改用動態組合的來電顯示文字
text = subText,
style = MaterialTheme.typography.bodySmall.copy(
fontSize = 12.sp,
color = Color.Gray
)
)
}
}
}

View file

@ -75,6 +75,8 @@ import androidx.compose.foundation.layout.windowInsetsTopHeight
import androidx.compose.foundation.layout.statusBars // 如果要使用 statusBars 也需要這個 import androidx.compose.foundation.layout.statusBars // 如果要使用 statusBars 也需要這個
import androidx.compose.material.icons.filled.Call import androidx.compose.material.icons.filled.Call
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import com.example.login_v3.data.api.api_class.CallLogInfo
import com.example.login_v3.home.Message.UI.Detail.Message_Component.CallLogBubble
import com.example.login_v3.home.Message.UI.Detail.Message_Component.UserStatusDot import com.example.login_v3.home.Message.UI.Detail.Message_Component.UserStatusDot
@ -515,6 +517,8 @@ 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 finalImageModel = remember(message) { val finalImageModel = remember(message) {
val rawPath = if (isAttachmentImage && !message.attachment?.filename.isNullOrBlank()) { val rawPath = if (isAttachmentImage && !message.attachment?.filename.isNullOrBlank()) {
message.attachment!!.filename message.attachment!!.filename
@ -536,8 +540,11 @@ fun MessageRow(
} }
} }
val finalBubbleColor = remember(message.status, isHighlight) { val finalBubbleColor = remember(message.status, isHighlight, callLog) {
val baseColor = when { val baseColor = when {
callLog != null -> {
if (callLog.type == "call_missed") Color(0xFFFCE8E6) else Color(0xFFF1F3F4)
}
isMe && isHighlight -> Color(0xFFB4E197) isMe && isHighlight -> Color(0xFFB4E197)
isMe -> Color(0xFFDCF8C6) isMe -> Color(0xFFDCF8C6)
!isMe && isHighlight -> Color(0xFFCFD8DC) !isMe && isHighlight -> Color(0xFFCFD8DC)
@ -617,6 +624,26 @@ fun MessageRow(
} }
) )
} }
} else if (callLog != null) {
// 🌟 通話紀錄渲染
Box(
modifier = Modifier
.widthIn(max = 280.dp)
.clip(RoundedCornerShape(12.dp))
.combinedClickable(
onLongClick = { onReplyClick(message) },
onClick = { onRowClick(message) }
)
.background(color = finalBubbleColor)
.padding(horizontal = 14.dp, vertical = 10.dp)
) {
CallLogBubble(
callLog = callLog,
isMe = isMe,
currentUserId = currentUserId,
partnerDisplayName = partnerDisplayName
)
}
} else { } else {
// 🌟 文字訊息:維持原本的 Box 氣泡樣式 // 🌟 文字訊息:維持原本的 Box 氣泡樣式
Box( Box(