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 a394337..c7a9a10 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 @@ -46,6 +46,10 @@ import coil.compose.AsyncImage // 建議使用 Coil 來載入本地圖片 Uri, import android.Manifest import androidx.compose.foundation.layout.Column import androidx.compose.material.icons.filled.Add +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue @Composable fun MessageAttachmentBar( @@ -54,7 +58,13 @@ fun MessageAttachmentBar( ) { val context = LocalContext.current - val isPartialAccess by remember { + val localImages = remember { mutableStateListOf() } + + // 🎯 2. 用於手動觸發刷新的計數器(主要應對 Android 14+ 管理照片回來後的即時刷新) + var refreshTrigger by remember { mutableStateOf(0) } + + // 檢查目前是不是處於「部分照片允許」的狀態 (Android 14+) + val isPartialAccess by remember(refreshTrigger) { 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 && @@ -65,24 +75,25 @@ fun MessageAttachmentBar( } } - // 處理 Android 14 重新調整選取照片的 Launcher + // 處理 Android 14 重新調整選取照片的回呼 val permissionLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission() ) { _ -> - // 重新讀取或觸發相簿狀態更新(這裡會觸發 localImages 重新 query) + // 使用者調整完照片回來,變更計數器觸發 LaunchedEffect 重新查詢 + refreshTrigger++ } - // 💡 非同步讀取手機內最新的數張圖片 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" + // 🎯 3. 核心修正:利用 LaunchedEffect 監聽生命週期與刷新狀態 + // 當元件首次載入、或者 refreshTrigger 改變時,會重跑裡面的非同步查詢 + LaunchedEffect(refreshTrigger) { + val imageUris = mutableListOf() + val projection = arrayOf( + MediaStore.Images.Media._ID, + MediaStore.Images.Media.DATE_TAKEN + ) + val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC" + try { context.contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, @@ -91,7 +102,6 @@ fun MessageAttachmentBar( 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) @@ -103,7 +113,12 @@ fun MessageAttachmentBar( count++ } } - imageUris + // 查詢成功後安全地更新 UI 狀態 + localImages.clear() + localImages.addAll(imageUris) + } catch (e: SecurityException) { + // 如果此時完全沒權限,捕獲異常不崩潰,保持列表為空 + localImages.clear() } } 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 71a70b8..4ecaa6c 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 @@ -1,6 +1,9 @@ package com.example.login_v3.home.Message.UI.Detail +import android.Manifest +import android.content.pm.PackageManager import android.net.Uri +import android.os.Build import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -80,6 +83,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.filled.Call import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.PlayArrow +import androidx.core.content.ContextCompat import coil.ImageLoader import coil.compose.LocalImageLoader import coil.decode.GifDecoder @@ -177,6 +181,24 @@ fun MessageMessaging( } } + //media permision required + val mainPermissionLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestPermission() + ) { isGranted -> + if (isGranted) { + // 關鍵:只有當使用者真的按下了「允許」,才在 Callback 裡開啟附件面板! + localBottomBarState = BottomBarState.AttachmentMenu + } else { + Toast.makeText(context, "需要相簿權限才能預覽照片喔!", Toast.LENGTH_SHORT).show() + } + } + + val requiredPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Manifest.permission.READ_MEDIA_IMAGES // 🎯 Android 13+ 必須是這個 + } else { + Manifest.permission.READ_EXTERNAL_STORAGE // Android 12 以下是這個 + } + val deleteState by viewModel.deleteMessageState.collectAsState() LaunchedEffect(deleteState) { @@ -374,7 +396,15 @@ fun MessageMessaging( onSendClick = { content -> viewModel.sendMessage(roomId, content) }, // 🎯 改為直接修改本地狀態,切換至附件選單 onAttachmentClick = { - localBottomBarState = BottomBarState.AttachmentMenu + val hasPermission = ContextCompat.checkSelfPermission(context, requiredPermission) == PackageManager.PERMISSION_GRANTED + + if (hasPermission) { + // 已經有權限了,直接大方開啟 + localBottomBarState = BottomBarState.AttachmentMenu + } else { + // 沒權限,只管叫起權限請求視窗,千萬不要在這裡改 localBottomBarState! + mainPermissionLauncher.launch(requiredPermission) + } } ) }