dm-156
This commit is contained in:
parent
18065034b7
commit
cfce2b3045
3 changed files with 147 additions and 23 deletions
|
|
@ -3,6 +3,12 @@
|
|||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
|
||||
<application
|
||||
android:name=".MyApplication"
|
||||
android:allowBackup="true"
|
||||
|
|
@ -18,7 +24,6 @@
|
|||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.MyApp.Splash">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
|
|
|||
|
|
@ -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<Uri>()
|
||||
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,31 +125,71 @@ 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
|
||||
) {
|
||||
|
||||
// 🎯 橫向媒體檢視器
|
||||
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
|
||||
) {
|
||||
// 圖片選擇按鈕
|
||||
IconButton(onClick = onImageClick) {
|
||||
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.width(12.dp))
|
||||
|
||||
// 關閉/返回輸入框按鈕
|
||||
IconButton(
|
||||
onClick = onCancel,
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.background(Color.White.copy(alpha = 0.15f), CircleShape)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Image, // 記得引入對應的 Icon
|
||||
contentDescription = "選擇圖片",
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = "返回輸入",
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// 關閉/返回輸入框按鈕
|
||||
IconButton(onClick = onCancel) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = "返回輸入",
|
||||
tint = Color.White.copy(alpha = 0.7f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue