dm-131
This commit is contained in:
parent
b41e149095
commit
944d91e3d9
3 changed files with 170 additions and 3 deletions
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue