dm-94
This commit is contained in:
parent
ab10357042
commit
bf006e6c7e
3 changed files with 128 additions and 99 deletions
|
|
@ -0,0 +1,11 @@
|
|||
package com.example.login_v3.data.di
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.example.login_v3.home.Message.ViewModel.Detail.MessageDao
|
||||
import com.example.login_v3.home.Message.ViewModel.Detail.MessageEntity
|
||||
|
||||
@Database(entities = [MessageEntity::class], version = 1, exportSchema = false)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun messageDao(): MessageDao
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.example.login_v3.data.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import com.example.login_v3.home.Message.ViewModel.Detail.MessageDao
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class) // 💡 讓整個 App 生命週期都能拿到
|
||||
object DatabaseModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppDatabase(
|
||||
@ApplicationContext context: Context // 💡 自動注入 ApplicationContext
|
||||
): AppDatabase {
|
||||
return Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java,
|
||||
"tecnologia_chat_db" // 資料庫檔案名稱
|
||||
)
|
||||
// .fallbackToDestructiveMigration() // 💡 提示:未來如果修改 Entity 欄位導致崩潰,可以先取消這行註解來清除舊資料庫
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMessageDao(database: AppDatabase): MessageDao {
|
||||
// 💡 告訴 Hilt:當有人要 MessageDao 時,就從 database 裡面把 messageDao() 拿出來提供給他!
|
||||
return database.messageDao()
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Photo
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
|
|
@ -52,6 +53,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
|
|
@ -75,6 +77,7 @@ import com.example.login_v3.home.Message.UI.Detail.Message_Messaging_detail.Mess
|
|||
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.ViewModel.Detail.ChatViewModel
|
||||
import com.example.login_v3.home.Message.ViewModel.Detail.MessageStatus
|
||||
import com.example.login_v3.home.Message.ViewModel.Detail.MessagesUiState
|
||||
import com.example.login_v3.home.Message.ViewModel.Detail.SendMessageState
|
||||
import com.example.login_v3.home.Message.ViewModel.UserStatus
|
||||
|
|
@ -422,6 +425,11 @@ fun MessageRow(
|
|||
else -> Color(0xFFECEFF1) // 對方發的:平常的淺灰色
|
||||
}
|
||||
|
||||
// 💡 核心優化:根據樂觀更新狀態決定氣泡透明度
|
||||
val bubbleAlpha = when (message.status) {
|
||||
MessageStatus.SENDING -> 0.6f // ⏳ 發送中:氣泡變半透明(類似 LINE/Telegram 的未送出狀態)
|
||||
else -> 1.0f // ✅ 成功或失敗:正常亮度
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -443,115 +451,88 @@ fun MessageRow(
|
|||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = if (isMe) Alignment.End else Alignment.Start
|
||||
// 💡 核心優化:如果是「我發的」且「發送失敗」,我們要把驚嘆號跟氣泡橫向並排
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 280.dp)
|
||||
// 💡 重點修正:將點擊/長按事件移到這裡,範圍只侷限在氣泡內
|
||||
// 注意:剪裁形狀(clip)建議放在點擊事件前或與形狀同步,確保點擊時的水波紋(Indication)不會超出圓角
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = 12.dp, topEnd = 12.dp,
|
||||
bottomStart = if (isMe) 12.dp else 0.dp,
|
||||
bottomEnd = if (isMe) 0.dp else 12.dp
|
||||
)
|
||||
)
|
||||
//long press action short press react
|
||||
.combinedClickable(
|
||||
onLongClick = { onReplyClick(message) },
|
||||
onClick = { onRowClick(message) }
|
||||
)
|
||||
.background(color = bubbleColor)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
) {
|
||||
Column {
|
||||
// 💡 【核心:渲染被回覆的訊息內容】
|
||||
message.repliedMessage?.let { replied ->
|
||||
val repliedSenderName = if (replied.senderId == currentUserId) "你" else partnerDisplayName
|
||||
|
||||
// 被回覆的小氣泡外框
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 6.dp)
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
// 💡 修正這裡:直接根據 isMe 決定傳入哪一個 Color 物件即可
|
||||
.background(if (isMe) Color(0xFFC7EBB2) else Color(0xFFE0E0E0))
|
||||
.drawBehind {
|
||||
// 在左側畫一條精緻的提示線 (類似 LINE / Telegram)
|
||||
drawLine(
|
||||
color = Color.Gray,
|
||||
start = Offset(0f, 0f),
|
||||
end = Offset(0f, size.height),
|
||||
strokeWidth = 6f
|
||||
)
|
||||
}
|
||||
.padding(start = 8.dp, top = 4.dp, bottom = 4.dp, end = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = repliedSenderName,
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontWeight = FontWeight.Bold),
|
||||
color = Color(0xFF1E88E5),
|
||||
maxLines = 1
|
||||
)
|
||||
Text(
|
||||
// 如果被回覆的是圖片,顯示 [圖片],否則顯示文字內容
|
||||
text = if (replied.attachment?.mimeType?.startsWith("image/") == true) "[圖片]" else replied.content,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.DarkGray,
|
||||
maxLines = 1, // 最多一行,避免太長
|
||||
overflow = TextOverflow.Ellipsis
|
||||
// 🛑 如果是我發的訊息,且發送失敗(FAILED),就把紅色驚嘆號顯示在氣泡「左邊」
|
||||
if (isMe && message.status == MessageStatus.FAILED) {
|
||||
Icon(
|
||||
imageVector = androidx.compose.material.icons.Icons.Default.Warning, // 需確保有 import
|
||||
contentDescription = "發送失敗",
|
||||
tint = Color.Red,
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.clickable {
|
||||
// 💡 加分功能:未來可以在這裡綁定「點擊重發」的 viewModel.sendMessage(...) 邏輯
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = if (isMe) Alignment.End else Alignment.Start
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 280.dp)
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = 12.dp, topEnd = 12.dp,
|
||||
bottomStart = if (isMe) 12.dp else 0.dp,
|
||||
bottomEnd = if (isMe) 0.dp else 12.dp
|
||||
)
|
||||
)
|
||||
.combinedClickable(
|
||||
onLongClick = { onReplyClick(message) },
|
||||
onClick = { onRowClick(message) }
|
||||
)
|
||||
.background(color = bubbleColor)
|
||||
.alpha(bubbleAlpha) // 👈 🌟 這裡套用透明度控制!
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
) {
|
||||
Column {
|
||||
// ... 保持你原本的「被回覆訊息內容渲染(message.repliedMessage)」邏輯 ...
|
||||
|
||||
// 訊息主體內容(文字或圖片)
|
||||
if (isImage) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
.widthIn(max = 240.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.LightGray)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = finalImageModel,
|
||||
contentDescription = "聊天圖片",
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier.fillMaxWidth().heightIn(max = 300.dp),
|
||||
onError = { errorState ->
|
||||
android.util.Log.e("ChatImageError", "原因: ${errorState.result.throwable}")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 原本的訊息主體內容(文字或圖片)
|
||||
if (isImage) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
.widthIn(max = 240.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.LightGray)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = finalImageModel,
|
||||
contentDescription = "聊天圖片",
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 300.dp),
|
||||
// 🛠️ 移除了 unresolved 的 debugPlaceholder
|
||||
onError = { errorState ->
|
||||
// 這樣就能在 Logcat 裡過濾 "ChatImageError" 關鍵字,看看到底是哪個網址拼錯了!
|
||||
android.util.Log.e(
|
||||
"ChatImageError",
|
||||
"圖片載入失敗, 網址為: $finalImageModel, 原因: ${errorState.result.throwable}"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = rawContent,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = rawContent,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!message.reactions.isNullOrEmpty()) {
|
||||
ReactionRow(
|
||||
reactions = message.reactions,
|
||||
onReactionClick = { emoji -> onReactionClick(message, emoji) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 💡 ✨ 新增:如果這條訊息有 Reaction,就顯示在氣泡的正下方
|
||||
if (!message.reactions.isNullOrEmpty()) {
|
||||
ReactionRow(
|
||||
reactions = message.reactions,
|
||||
onReactionClick = { emoji -> onReactionClick(message, emoji) }
|
||||
)
|
||||
}
|
||||
// 🛑 如果是對方發的訊息(雖然對方理論上不會有 FAILED 狀態,安全起見做對稱),或是想放右邊的提示
|
||||
// 可以依此類推。通常聊天軟體只會處理自己發送失敗的提示。
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue