add delta sync for dm-v11

This commit is contained in:
eric 2026-08-04 15:43:25 +08:00
parent afff839122
commit ce9c146576
3 changed files with 43 additions and 12 deletions

View file

@ -135,35 +135,52 @@ data class Message(
// --- 🚀 新增:全域媒體屬性,讓 UI 秒讀不塞車 --- // --- 🚀 新增:全域媒體屬性,讓 UI 秒讀不塞車 ---
val isImage: Boolean get() = type == "image" || attachment?.mimeType?.startsWith("image/") == true || val isImage: Boolean get() = type == "image" || attachment?.mimeType?.startsWith("image/") == true ||
content.lowercase().let { it.endsWith(".jpg") || it.endsWith(".jpeg") || it.endsWith(".png") || it.endsWith(".webp") || it.endsWith(".heic") || it.endsWith(".heif") || it.endsWith(".gif") } content.substringBefore("?").lowercase().let { it.endsWith(".jpg") || it.endsWith(".jpeg") || it.endsWith(".png") || it.endsWith(".webp") || it.endsWith(".heic") || it.endsWith(".heif") || it.endsWith(".gif") }
val isGif: Boolean get() = attachment?.mimeType == "image/gif" || content.lowercase().endsWith(".gif") val isGif: Boolean get() = attachment?.mimeType == "image/gif" || content.substringBefore("?").lowercase().endsWith(".gif")
val isVideo: Boolean get() = type == "video" || attachment?.mimeType?.startsWith("video/") == true || val isVideo: Boolean get() = type == "video" || attachment?.mimeType?.startsWith("video/") == true ||
content.lowercase().let { it.endsWith(".mp4") || it.endsWith(".mov") || it.endsWith(".mkv") || it.endsWith(".avi") || it.endsWith(".3gp") || it.endsWith(".webm") } content.substringBefore("?").lowercase().let { it.endsWith(".mp4") || it.endsWith(".mov") || it.endsWith(".mkv") || it.endsWith(".avi") || it.endsWith(".3gp") || it.endsWith(".webm") }
val isAudio: Boolean get() = type == "audio" || attachment?.mimeType?.startsWith("audio/") == true || val isAudio: Boolean get() = type == "audio" || attachment?.mimeType?.startsWith("audio/") == true ||
content.lowercase().let { it.endsWith(".mp3") || it.endsWith(".wav") || it.endsWith(".m4a") || it.endsWith(".aac") || it.endsWith(".ogg") || it.endsWith(".opus") || it.endsWith(".amr") } content.substringBefore("?").lowercase().let { it.endsWith(".mp3") || it.endsWith(".wav") || it.endsWith(".m4a") || it.endsWith(".aac") || it.endsWith(".ogg") || it.endsWith(".opus") || it.endsWith(".amr") }
val isFile: Boolean get() = attachment?.mimeType?.startsWith("application/") == true || attachment?.mimeType == "text/plain" || val isFile: Boolean get() = attachment?.mimeType?.startsWith("application/") == true || attachment?.mimeType == "text/plain" ||
content.lowercase().let { it.endsWith(".zip") || it.endsWith(".rar") || it.endsWith(".7z") || it.endsWith(".pdf") || it.endsWith(".doc") || it.endsWith(".docx") || it.endsWith(".xls") || it.endsWith(".xlsx") || it.endsWith(".ppt") || it.endsWith(".pptx") || it.endsWith(".txt") } content.substringBefore("?").lowercase().let { it.endsWith(".zip") || it.endsWith(".rar") || it.endsWith(".7z") || it.endsWith(".pdf") || it.endsWith(".doc") || it.endsWith(".docx") || it.endsWith(".xls") || it.endsWith(".xlsx") || it.endsWith(".ppt") || it.endsWith(".pptx") || it.endsWith(".txt") }
val mediaUrl: String get() { val mediaUrl: String get() {
val rawPath = attachment?.filename ?: content // 🎯 核心修正:媒體檔案必須優先使用 content,因為 content 包含正確的 Hash 路徑與專屬簽名
// attachment.filename 通常只是顯示用的原始檔名 (例如 IMG_123.jpg)
val rawPath = if (isImage || isVideo || isAudio) content else (attachment?.filename ?: content)
val baseUrl = "https://tg.technologia-tw.com" val baseUrl = "https://tg.technologia-tw.com"
return when {
// 🎯 Step 1: 基礎 URL 處理
var finalUrl = when {
rawPath.isBlank() -> "" rawPath.isBlank() -> ""
rawPath.startsWith("http") -> rawPath rawPath.startsWith("http") -> rawPath
rawPath.startsWith("/uploads/") -> "$baseUrl$rawPath"
rawPath.startsWith("uploads/") -> "$baseUrl/$rawPath"
else -> { else -> {
val cleanPath = rawPath.removePrefix("/").removePrefix("uploads/") val cleanPath = rawPath.removePrefix("/").removePrefix("uploads/")
if (cleanPath.startsWith("attachment/")) { if (cleanPath.contains("/")) "$baseUrl/uploads/$cleanPath"
"$baseUrl/uploads/$cleanPath" else "$baseUrl/uploads/attachment/$cleanPath"
} else { }
"$baseUrl/uploads/attachment/$cleanPath" }
// 🎯 Step 2: 簽名繼承 (如果 content 本身沒簽名,才去借縮圖的)
if (!finalUrl.contains("?")) {
attachment?.thumbnailUrl?.let { thumb ->
if (thumb.contains("?")) {
val signature = thumb.substringAfter("?", "")
if (signature.isNotEmpty()) {
finalUrl += "?$signature"
} }
} }
} }
} }
return finalUrl
}
} }
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)

View file

@ -64,11 +64,16 @@ fun ImageLightbox(
.clickable(enabled = scale == 1f) { onDismiss() }, .clickable(enabled = scale == 1f) { onDismiss() },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
// 1. 底層:大圖檢視元件(保持不變)
AsyncImage( AsyncImage(
model = imageUrl, model = imageUrl,
contentDescription = "放大檢視圖片", contentDescription = "放大檢視圖片",
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
onLoading = { android.util.Log.d("LightboxDebug", "Loading original image: $imageUrl") },
onSuccess = { android.util.Log.d("LightboxDebug", "Successfully loaded original image") },
onError = { error ->
android.util.Log.e("LightboxDebug", "Failed to load original image: ${error.result.throwable.message}")
error.result.throwable.printStackTrace()
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight(0.85f) .fillMaxHeight(0.85f)

View file

@ -535,6 +535,15 @@ fun MessageMessaging(
val onRowClickStable = remember(viewModel) { val onRowClickStable = remember(viewModel) {
{ message: MessageUiModel -> { message: MessageUiModel ->
// Debug Logs
Log.d("ChatDebug", "--- Clicked Message Content ---")
Log.d("ChatDebug", "Message ID: ${message.id}")
Log.d("ChatDebug", "Type: ${message.type}")
Log.d("ChatDebug", "Raw Content: ${message.content}")
Log.d("ChatDebug", "Attachment Filename: ${message.attachment?.filename}")
Log.d("ChatDebug", "Thumbnail URL: ${message.thumbnailUrl}")
Log.d("ChatDebug", "Final Media URL (OG): ${message.mediaUrl}")
//is image //is image
val isImage = message.isImage val isImage = message.isImage
//is video //is video