This commit is contained in:
Eric85710 2026-06-23 17:12:33 +08:00
parent 4733812a9c
commit e054c014ad
2 changed files with 133 additions and 1 deletions

View file

@ -1,2 +1,102 @@
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.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Pause
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.login_v3.data.api.api_class.Message
import com.example.login_v3.home.Message.ViewModel.Detail.ChatViewModel
import okhttp3.internal.concurrent.formatDuration
@Composable
fun AudioMessageBubble(
message: Message,
audioUrl: String,
isMe: Boolean,
bubbleColor: Color,
viewModel: ChatViewModel // 🎯 傳入 ViewModel
) {
// 訂閱全域的播放狀態
val playbackState by viewModel.audioState.collectAsState()
// 判斷當前這顆氣泡是不是正在被操作的音訊
val isCurrentAudio = playbackState.currentPlayingMessageId == message.id
val isPlaying = isCurrentAudio && playbackState.isPlaying
// 計算當前進度 (0.0 ~ 1.0)
val currentPos = if (isCurrentAudio) playbackState.currentPosition else 0L
val totalDuration = if (isCurrentAudio) playbackState.duration else 0L
val sliderProgress = if (totalDuration > 0) currentPos.toFloat() / totalDuration.toFloat() else 0f
Row(modifier = Modifier
.clip(
RoundedCornerShape(
topStart = 12.dp, topEnd = 12.dp,
bottomStart = if (isMe) 12.dp else 0.dp,
bottomEnd = if (isMe) 0.dp else 12.dp
)
)
.background(bubbleColor)
.padding(horizontal = 8.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
// 播放 / 暫停按鈕
IconButton(
onClick = { viewModel.toggleAudioPlayback(message.id, audioUrl) }, // 🎯 觸發全域控制
modifier = Modifier.size(36.dp)
) {
Icon(
imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
contentDescription = if (isPlaying) "暫停" else "播放",
tint = if (isMe) Color.White else Color.Black,
modifier = Modifier.size(24.dp)
)
}
// 進度條
Slider(
value = sliderProgress,
onValueChange = { ratio ->
if (isCurrentAudio) viewModel.seekAudioTo(ratio) // 🎯 允許使用者拖拉進度
},
modifier = Modifier.weight(1f),
colors = SliderDefaults.colors(
thumbColor = if (isMe) Color.White else MaterialTheme.colorScheme.primary,
activeTrackColor = if (isMe) Color.White else MaterialTheme.colorScheme.primary,
inactiveTrackColor = (if (isMe) Color.White else Color.Gray).copy(alpha = 0.3f)
)
)
// 時間格式化 (00:00)
val displayTime = remember(currentPos, totalDuration) {
val timeToFormat = if (currentPos > 0) currentPos else totalDuration
formatDuration(timeToFormat)
}
Text(
text = displayTime,
style = MaterialTheme.typography.bodySmall.copy(fontSize = 12.sp),
color = if (isMe) Color.White.copy(alpha = 0.8f) else Color.Gray,
modifier = Modifier.padding(end = 4.dp)
)
}
}

View file

@ -80,6 +80,7 @@ import androidx.compose.material.icons.filled.PlayArrow
import coil.ImageLoader
import coil.decode.VideoFrameDecoder
import com.example.login_v3.data.api.api_class.CallLogInfo
import com.example.login_v3.home.Message.UI.Detail.Message_Component.AudioMessageBubble
import com.example.login_v3.home.Message.UI.Detail.Message_Component.CallLogBubble
import com.example.login_v3.home.Message.UI.Detail.Message_Component.ImageLightbox
import com.example.login_v3.home.Message.UI.Detail.Message_Component.UserStatusDot
@ -431,6 +432,7 @@ fun MessageMessaging(
partnerDisplayName = state.roomTitle,
activeEmojiMessageId = emojiTargetMessage?.id, // ✨ 新增短按目標 ID
activeActionMessageId = actionMessage?.id,
viewModel = viewModel,
onReplyClick = { message ->
emojiTargetMessage = null
viewModel.setActionMessage(message)
@ -536,6 +538,7 @@ fun MessageList(
messages: List<Message>,
activeEmojiMessageId: String?,
activeActionMessageId: String?,
viewModel: ChatViewModel,
onReplyClick: (Message) -> Unit,
onReactionClick: (Message, String) -> Unit,
onRowClick: (Message) -> Unit,
@ -564,7 +567,8 @@ fun MessageList(
currentUserId = currentUserId,
onReplyClick = onReplyClick,
onReactionClick = onReactionClick,
onRowClick = onRowClick
onRowClick = onRowClick,
viewModel = viewModel,
)
}
}
@ -579,6 +583,7 @@ fun MessageRow(
partnerDisplayName: String,
isHighlight: Boolean,
currentUserId: String,
viewModel: ChatViewModel,
onReplyClick: (Message) -> Unit,
onReactionClick: (Message, String) -> Unit,
onRowClick: (Message) -> Unit,
@ -785,6 +790,33 @@ fun MessageRow(
}
}
//isAudio
isAudio -> {
Column(
modifier = Modifier
.widthIn(max = 250.dp) // 音訊氣泡設定稍窄,更像一般通訊軟體
.combinedClickable(
onLongClick = { onReplyClick(message) },
onClick = { /* 整塊氣泡點擊邏輯,可留空 */ }
),
horizontalAlignment = if (isMe) Alignment.End else Alignment.Start
) {
// 如果有回覆別人的訊息,一樣顯示回覆預覽
message.repliedMessage?.let { replied ->
RepliedMessagePreview(replied, isMe, partnerDisplayName, currentUserId)
}
// 呼叫你的音訊播放器元件
AudioMessageBubble(
message = message,
audioUrl = finalMediaUrl,
isMe = isMe,
bubbleColor = finalBubbleColor,
viewModel = viewModel
)
}
}
// 核心分流 3:通話紀錄
callLog != null -> {
Box(