From cfce2b3045109fdb31597c89767407bb392edd6d Mon Sep 17 00:00:00 2001 From: Eric85710 Date: Wed, 24 Jun 2026 16:22:43 +0800 Subject: [PATCH] dm-156 --- app/src/main/AndroidManifest.xml | 7 +- .../Message_Component/Attachment_Bar.kt | 154 ++++++++++++++++-- .../Message/UI/Detail/Message_Messaging.kt | 9 +- 3 files changed, 147 insertions(+), 23 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c0fefd4..35fb034 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,12 @@ + + + + + + diff --git a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Component/Attachment_Bar.kt b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Component/Attachment_Bar.kt index 2ae3b3f..a394337 100644 --- a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Component/Attachment_Bar.kt +++ b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Component/Attachment_Bar.kt @@ -1,10 +1,13 @@ package com.example.login_v3.home.Message.UI.Detail.Message_Component +import android.content.ContentUris +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -13,7 +16,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Image import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -23,19 +25,95 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.blur import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import android.provider.MediaStore +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Text +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import coil.compose.AsyncImage // 建議使用 Coil 來載入本地圖片 Uri,效能極佳 +import android.Manifest +import androidx.compose.foundation.layout.Column +import androidx.compose.material.icons.filled.Add @Composable fun MessageAttachmentBar( - onImageClick: () -> Unit, + onImageSelected: (Uri) -> Unit, // 🎯 改為直接回傳選中的圖片 Uri onCancel: () -> Unit ) { + val context = LocalContext.current + + val isPartialAccess by remember { + derivedStateOf { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + ContextCompat.checkSelfPermission(context, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission(context, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED + } else { + false + } + } + } + + // 處理 Android 14 重新調整選取照片的 Launcher + val permissionLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestPermission() + ) { _ -> + // 重新讀取或觸發相簿狀態更新(這裡會觸發 localImages 重新 query) + } + + // 💡 非同步讀取手機內最新的數張圖片 Uri + val localImages by remember { + derivedStateOf { + val imageUris = mutableListOf() + val projection = arrayOf( + MediaStore.Images.Media._ID, + MediaStore.Images.Media.DATE_TAKEN + ) + // 依時間倒序排序,讓最新的照片排在最前面 + val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC" + + context.contentResolver.query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + projection, + null, + null, + sortOrder + )?.use { cursor -> + val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID) + // 這裡限制讀取前 20 張,避免一次載入太多有效能隱憂 + var count = 0 + while (cursor.moveToNext() && count < 20) { + val id = cursor.getLong(idColumn) + val contentUri = ContentUris.withAppendedId( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + id + ) + imageUris.add(contentUri) + count++ + } + } + imageUris + } + } + Box( modifier = Modifier .fillMaxWidth() .navigationBarsPadding() .padding(horizontal = 16.dp, vertical = 8.dp) ) { - // 毛玻璃背景與邊框 (與 InputBar 一致) + // 毛玻璃背景與邊框 (保持與 InputBar 一致) Box(modifier = Modifier.matchParentSize().blur(10.dp)) Box( modifier = Modifier @@ -47,29 +125,69 @@ fun MessageAttachmentBar( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(24.dp) + .padding(horizontal = 12.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically ) { - // 圖片選擇按鈕 - IconButton(onClick = onImageClick) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Icon( - imageVector = Icons.Default.Image, // 記得引入對應的 Icon - contentDescription = "選擇圖片", - tint = Color.White - ) + + // 🎯 橫向媒體檢視器 + LazyRow( + modifier = Modifier.weight(1f).height(70.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // 🎯 核心優化:如果是 Android 14 部分允許,在最左邊固定顯示一個「編輯存取範圍」的按鈕 + if (isPartialAccess) { + item { + Box( + modifier = Modifier + .size(70.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Color.White.copy(alpha = 0.1f)) + .clickable { + // 再次要求 READ_MEDIA_IMAGES,系統會自動彈出「管理已選取的照片」視窗 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + permissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES) + } + }, + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Icon(imageVector = Icons.Default.Add, contentDescription = "管理照片", tint = Color.White) + Text("管理", color = Color.White, style = MaterialTheme.typography.bodySmall) + } + } + } + } + + if (localImages.isEmpty()) { + item { + Text(text = "無媒體檔案", color = Color.White.copy(alpha = 0.6f), modifier = Modifier.padding(start = 4.dp)) + } + } else { + items(localImages) { uri -> + AsyncImage( + model = uri, + contentDescription = "媒體預覽", + modifier = Modifier.size(70.dp).clip(RoundedCornerShape(8.dp)).clickable { onImageSelected(uri) }, + contentScale = androidx.compose.ui.layout.ContentScale.Crop + ) + } } } - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.width(12.dp)) // 關閉/返回輸入框按鈕 - IconButton(onClick = onCancel) { + IconButton( + onClick = onCancel, + modifier = Modifier + .size(36.dp) + .background(Color.White.copy(alpha = 0.15f), CircleShape) + ) { Icon( imageVector = Icons.Default.Close, contentDescription = "返回輸入", - tint = Color.White.copy(alpha = 0.7f) + tint = Color.White ) } } diff --git a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt index b2d7fe9..71a70b8 100644 --- a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt +++ b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt @@ -427,13 +427,14 @@ fun MessageMessaging( is BottomBarState.AttachmentMenu -> { MessageAttachmentBar( - onImageClick = { - imagePickerLauncher.launch("image/*") - // 🎯 選完圖片後,將本地狀態重置,自動回歸 Input 輸入框 + onImageSelected = { uri -> + // 🎯 使用者在預覽列中點選了某張圖片,直接觸發上傳 + viewModel.uploadAttachment(roomId, uri) + // 上傳後自動退回輸入框 localBottomBarState = null }, onCancel = { - // 🎯 點擊關閉,同樣回歸 Input 輸入框 + // 點擊關閉退回輸入框 localBottomBarState = null } )