dm-150
This commit is contained in:
parent
4733812a9c
commit
e054c014ad
2 changed files with 133 additions and 1 deletions
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue