diff --git a/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt b/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt index aff99ce..1e520a4 100644 --- a/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt +++ b/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt @@ -4,7 +4,8 @@ import com.example.login_v3.R import com.example.login_v3.home.Message.ViewModel.Detail.MessageStatus import com.squareup.moshi.Json import com.squareup.moshi.JsonClass - +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory //Get DM list @@ -125,4 +126,38 @@ data class SendMessageRequest( @JsonClass(generateAdapter = true) data class MessageReactionUsersResponse( @Json(name = "users") val users: List -) \ No newline at end of file +) + + + + +//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 + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Component/CallLogBubble.kt b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Component/CallLogBubble.kt new file mode 100644 index 0000000..91abcae --- /dev/null +++ b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Component/CallLogBubble.kt @@ -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 + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt index b836800..e5684f4 100644 --- a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt +++ b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt @@ -75,6 +75,8 @@ import androidx.compose.foundation.layout.windowInsetsTopHeight import androidx.compose.foundation.layout.statusBars // 如果要使用 statusBars 也需要這個 import androidx.compose.material.icons.filled.Call 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 @@ -515,6 +517,8 @@ fun MessageRow( val isImage = isAttachmentImage || isLegacyImage val rawContent = message.content ?: "" + val callLog = remember(message.id, message.content) { CallLogInfo.parse(message) } + val finalImageModel = remember(message) { val rawPath = if (isAttachmentImage && !message.attachment?.filename.isNullOrBlank()) { 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 { + callLog != null -> { + if (callLog.type == "call_missed") Color(0xFFFCE8E6) else Color(0xFFF1F3F4) + } isMe && isHighlight -> Color(0xFFB4E197) isMe -> Color(0xFFDCF8C6) !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 { // 🌟 文字訊息:維持原本的 Box 氣泡樣式 Box(