dm-78
This commit is contained in:
parent
9fda848948
commit
cce155057d
2 changed files with 161 additions and 66 deletions
|
|
@ -44,6 +44,7 @@ import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -68,6 +69,7 @@ import coil.compose.AsyncImage
|
||||||
import com.example.login_v3.R
|
import com.example.login_v3.R
|
||||||
import com.example.login_v3.data.api.api_class.Message
|
import com.example.login_v3.data.api.api_class.Message
|
||||||
import com.example.login_v3.home.Message.UI.Detail.Message_Messaging_detail.MessageActionMenuRow
|
import com.example.login_v3.home.Message.UI.Detail.Message_Messaging_detail.MessageActionMenuRow
|
||||||
|
import com.example.login_v3.home.Message.UI.Detail.Message_Messaging_detail.MessageEmojiBar
|
||||||
import com.example.login_v3.home.Message.UI.Detail.Message_Messaging_detail.MessageInputBar
|
import com.example.login_v3.home.Message.UI.Detail.Message_Messaging_detail.MessageInputBar
|
||||||
import com.example.login_v3.home.Message.UI.Detail.Message_Messaging_detail.ReactionRow
|
import com.example.login_v3.home.Message.UI.Detail.Message_Messaging_detail.ReactionRow
|
||||||
import com.example.login_v3.home.Message.UI.Detail.Message_Messaging_detail.UserAvatar
|
import com.example.login_v3.home.Message.UI.Detail.Message_Messaging_detail.UserAvatar
|
||||||
|
|
@ -111,6 +113,18 @@ fun MessageMessaging(
|
||||||
//message that selected
|
//message that selected
|
||||||
val actionMessage by viewModel.actionMessage.collectAsStateWithLifecycle()
|
val actionMessage by viewModel.actionMessage.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
var emojiTargetMessage by remember { mutableStateOf<Message?>(null) }
|
||||||
|
|
||||||
|
// 🌟 核心:使用 derivedStateOf 統一控管狀態權限(長按優先權通常大於單擊)
|
||||||
|
val bottomBarState by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
when {
|
||||||
|
actionMessage != null -> BottomBarState.ActionMenu(actionMessage!!)
|
||||||
|
emojiTargetMessage != null -> BottomBarState.EmojiMenu(emojiTargetMessage!!)
|
||||||
|
else -> BottomBarState.Input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//進入時重整
|
//進入時重整
|
||||||
LaunchedEffect(roomId) {
|
LaunchedEffect(roomId) {
|
||||||
|
|
@ -194,21 +208,30 @@ fun MessageMessaging(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
// 👈 直接將 actionMessage 作為 targetState
|
// 👈 使用我們定義的 sealed interface 狀態機
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = actionMessage,
|
targetState = bottomBarState,
|
||||||
label = "BottomBarSwitchAnimation"
|
label = "BottomBarSwitchAnimation"
|
||||||
) { currentActionMessage ->
|
) { currentState ->
|
||||||
if (currentActionMessage != null) {
|
when (currentState) {
|
||||||
|
is BottomBarState.Input -> {
|
||||||
|
MessageInputBar(
|
||||||
|
isLoading = sendStatus is SendMessageState.Loading,
|
||||||
|
replyingMessage = replyingMessage,
|
||||||
|
onCancelReply = { viewModel.setReplyingMessage(null) },
|
||||||
|
onSendClick = { content -> viewModel.sendMessage(roomId, content) },
|
||||||
|
onImageSelected = { uri -> viewModel.uploadAttachment(roomId, uri) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 💡 核心修正:從目前的 uiState 中安全地取出當前使用者的 ID 來做比較
|
is BottomBarState.ActionMenu -> {
|
||||||
|
val currentActionMessage = currentState.message
|
||||||
val currentUserId = (uiState as? MessagesUiState.Success)?.currentUserId
|
val currentUserId = (uiState as? MessagesUiState.Success)?.currentUserId
|
||||||
val isOwnMessage = currentActionMessage.senderId == currentUserId
|
val isOwnMessage = currentActionMessage.senderId == currentUserId
|
||||||
|
|
||||||
// 顯示動作選單
|
|
||||||
MessageActionMenuRow(
|
MessageActionMenuRow(
|
||||||
message = currentActionMessage,
|
message = currentActionMessage,
|
||||||
isOwnMessage = isOwnMessage, // 👈 記得要把這個新參數帶進去!
|
isOwnMessage = isOwnMessage,
|
||||||
onCancel = { viewModel.clearActionMessage() },
|
onCancel = { viewModel.clearActionMessage() },
|
||||||
onReplyClick = { msg ->
|
onReplyClick = { msg ->
|
||||||
viewModel.setReplyingMessage(msg)
|
viewModel.setReplyingMessage(msg)
|
||||||
|
|
@ -218,18 +241,30 @@ fun MessageMessaging(
|
||||||
viewModel.deleteMessage(roomId, msg.id)
|
viewModel.deleteMessage(roomId, msg.id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BottomBarState.EmojiMenu -> {
|
||||||
|
// 👈 渲染全新設計的獨立 Emoji Bar
|
||||||
|
val currentEmojiMessage = currentState.message
|
||||||
|
MessageEmojiBar(
|
||||||
|
// 🌟 移除了 commonEmojis 參數,變得非常清爽!
|
||||||
|
onEmojiClick = { emoji ->
|
||||||
|
val targetReaction = currentEmojiMessage.reactions?.find { it.emoji == emoji }
|
||||||
|
if (targetReaction?.meReacted == true) {
|
||||||
|
viewModel.removeMessageReaction(roomId, currentEmojiMessage.id, emoji)
|
||||||
} else {
|
} else {
|
||||||
// 顯示原本的輸入框
|
viewModel.addMessageReaction(roomId, currentEmojiMessage.id, emoji)
|
||||||
MessageInputBar(
|
}
|
||||||
isLoading = sendStatus is SendMessageState.Loading,
|
emojiTargetMessage = null // 點完後自動關閉
|
||||||
replyingMessage = replyingMessage,
|
},
|
||||||
onCancelReply = { viewModel.setReplyingMessage(null) },
|
onCancel = {
|
||||||
onSendClick = { content -> viewModel.sendMessage(roomId, content) },
|
emojiTargetMessage = null // 點擊關閉
|
||||||
onImageSelected = { uri -> viewModel.uploadAttachment(roomId, uri) }
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -252,35 +287,23 @@ fun MessageMessaging(
|
||||||
messages = state.messages,
|
messages = state.messages,
|
||||||
partnerAvatarUrl = state.partnerAvatarUrl,
|
partnerAvatarUrl = state.partnerAvatarUrl,
|
||||||
partnerDisplayName = state.roomTitle,
|
partnerDisplayName = state.roomTitle,
|
||||||
onReplyClick = { message -> viewModel.setActionMessage(message) },
|
onReplyClick = { message ->
|
||||||
onReactionClick = { message, emoji ->
|
emojiTargetMessage = null
|
||||||
// 尋找該訊息內,對應點擊到的 Reaction 物件
|
viewModel.setActionMessage(message)
|
||||||
val targetReaction = message.reactions?.find { it.emoji == emoji }
|
|
||||||
|
|
||||||
if (targetReaction?.meReacted == true) {
|
|
||||||
// 我自己已經點過了 ➡️ 觸發「移除/收回」
|
|
||||||
viewModel.removeMessageReaction(
|
|
||||||
roomId = roomId,
|
|
||||||
messageId = message.id,
|
|
||||||
emoji = emoji
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// 我沒點過(別人點的) ➡️ 觸發「新增/一起點」
|
|
||||||
viewModel.addMessageReaction(
|
|
||||||
roomId = roomId,
|
|
||||||
messageId = message.id,
|
|
||||||
emoji = emoji
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
// 💡 單擊事件:開啟 Emoji 選單,同時清空長按選單
|
||||||
// 點選彈出面板(Panel)的新 Emoji
|
onRowClick = { message ->
|
||||||
onAddReaction = { message, emoji ->
|
viewModel.clearActionMessage()
|
||||||
viewModel.addMessageReaction(
|
emojiTargetMessage = message
|
||||||
roomId = roomId,
|
},
|
||||||
messageId = message.id,
|
onReactionClick = { message, emoji ->
|
||||||
emoji = emoji
|
// 這是訊息氣泡下方既有 Reaction 小標籤的點擊事件,保持原樣即可
|
||||||
)
|
val targetReaction = message.reactions?.find { it.emoji == emoji }
|
||||||
|
if (targetReaction?.meReacted == true) {
|
||||||
|
viewModel.removeMessageReaction(roomId, message.id, emoji)
|
||||||
|
} else {
|
||||||
|
viewModel.addMessageReaction(roomId, message.id, emoji)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -304,7 +327,7 @@ fun MessageList(
|
||||||
messages: List<Message>,
|
messages: List<Message>,
|
||||||
onReplyClick: (Message) -> Unit,
|
onReplyClick: (Message) -> Unit,
|
||||||
onReactionClick: (Message, String) -> Unit,
|
onReactionClick: (Message, String) -> Unit,
|
||||||
onAddReaction: (Message, String) -> Unit,
|
onRowClick: (Message) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
|
@ -323,7 +346,7 @@ fun MessageList(
|
||||||
currentUserId = currentUserId,
|
currentUserId = currentUserId,
|
||||||
onReplyClick = onReplyClick,
|
onReplyClick = onReplyClick,
|
||||||
onReactionClick = onReactionClick,
|
onReactionClick = onReactionClick,
|
||||||
onAddReaction = onAddReaction
|
onRowClick = onRowClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -339,7 +362,7 @@ fun MessageRow(
|
||||||
currentUserId: String,
|
currentUserId: String,
|
||||||
onReplyClick: (Message) -> Unit,
|
onReplyClick: (Message) -> Unit,
|
||||||
onReactionClick: (Message, String) -> Unit,
|
onReactionClick: (Message, String) -> Unit,
|
||||||
onAddReaction: (Message, String) -> Unit
|
onRowClick: (Message) -> Unit,
|
||||||
) {
|
) {
|
||||||
val isAttachmentImage = message.attachment?.mimeType?.startsWith("image/") == true
|
val isAttachmentImage = message.attachment?.mimeType?.startsWith("image/") == true
|
||||||
val contentLowerCase = (message.content ?: "").lowercase()
|
val contentLowerCase = (message.content ?: "").lowercase()
|
||||||
|
|
@ -390,8 +413,8 @@ fun MessageRow(
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
// 💡 讓使用者長按整條訊息就能觸發回覆
|
// 💡 讓使用者長按整條訊息就能觸發回覆
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onLongClick = { onReplyClick(message) },
|
onLongClick = { onReplyClick(message) }, // 長按 -> 喚起 ActionMenu
|
||||||
onClick = { showEmojiPanel = true } // 💡 點一下開啟 Emoji 面板
|
onClick = { onRowClick(message) } // 單擊 -> 喚起 EmojiMenu
|
||||||
),
|
),
|
||||||
horizontalArrangement = if (isMe) Arrangement.End else Arrangement.Start
|
horizontalArrangement = if (isMe) Arrangement.End else Arrangement.Start
|
||||||
) {
|
) {
|
||||||
|
|
@ -511,10 +534,6 @@ fun MessageRow(
|
||||||
fontSize = 24.sp,
|
fontSize = 24.sp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.clickable {
|
|
||||||
onAddReaction(message, emoji) // 觸發新增 Reaction API
|
|
||||||
showEmojiPanel = false // 關閉面板
|
|
||||||
}
|
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.example.login_v3.home.Message.UI.Detail.Message_Messaging_detail
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MessageEmojiBar(
|
||||||
|
onEmojiClick: (String) -> Unit,
|
||||||
|
onCancel: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
val emojiList = remember {
|
||||||
|
listOf(
|
||||||
|
"👍", "❤️", "😂", "😮", "😢", "🙏",
|
||||||
|
"🔥", "🎉", "👏", "👀", "✨", "💯",
|
||||||
|
"🤔", "🥳", "😎", "🚀", "💔", "💩"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Surface(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
color = Color(0xFFF5F5F5), // 稍微與純白底色做點區隔,可自行調整
|
||||||
|
tonalElevation = 4.dp
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.navigationBarsPadding() // 避免被手機底部的導覽橫條遮擋
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
// 左側:Emoji 點擊區
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
emojiList.forEach { emoji ->
|
||||||
|
Text(
|
||||||
|
text = emoji,
|
||||||
|
fontSize = 26.sp,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable { onEmojiClick(emoji) }
|
||||||
|
.padding(4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右側:關閉按鈕 (Icon 可以換成你專案內現有的)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable { onCancel() }
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
Text(text = "✕", color = Color.Gray, fontSize = 18.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue