Fix media notifications and photo bubbles
This commit is contained in:
@@ -3,6 +3,7 @@ package xyz.kusoft.qmax
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.BitmapFactory
|
||||
import android.media.MediaRecorder
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
@@ -157,8 +158,10 @@ import java.time.OffsetDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import xyz.kusoft.qmax.core.push.ForegroundChatTracker
|
||||
import xyz.kusoft.qmax.core.model.AttachmentDto
|
||||
import xyz.kusoft.qmax.core.model.ChatDto
|
||||
@@ -2226,7 +2229,8 @@ private fun MessageRow(
|
||||
attachments = visualAttachments,
|
||||
session = session,
|
||||
vm = vm,
|
||||
imagePreviewSources = imagePreviewSources
|
||||
imagePreviewSources = imagePreviewSources,
|
||||
mediaShape = shape
|
||||
)
|
||||
} else {
|
||||
sortedAttachments.forEach { attachment ->
|
||||
@@ -2234,7 +2238,8 @@ private fun MessageRow(
|
||||
attachment = attachment,
|
||||
session = session,
|
||||
vm = vm,
|
||||
imagePreviewSources = imagePreviewSources
|
||||
imagePreviewSources = imagePreviewSources,
|
||||
mediaShape = shape
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2514,13 +2519,16 @@ private fun MediaAlbumGrid(
|
||||
attachments: List<AttachmentDto>,
|
||||
session: QMaxSession,
|
||||
vm: QMaxViewModel,
|
||||
imagePreviewSources: List<String>
|
||||
imagePreviewSources: List<String>,
|
||||
mediaShape: RoundedCornerShape
|
||||
) {
|
||||
val visible = attachments.take(4)
|
||||
val extraCount = (attachments.size - visible.size).coerceAtLeast(0)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(mediaShape),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
when (visible.size) {
|
||||
@@ -2595,7 +2603,10 @@ private fun MediaAlbumCell(
|
||||
val state by vm.state
|
||||
val cachedImagePath = state.cachedImagePaths[attachment.id]
|
||||
|
||||
Box(modifier = modifier.background(Color(0xFFE1E8ED))) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(Color(0xFFE1E8ED))
|
||||
) {
|
||||
if (attachment.isImageAttachment()) {
|
||||
LaunchedEffect(attachment.id, url) {
|
||||
vm.cacheImage(attachment)
|
||||
@@ -2663,7 +2674,8 @@ private fun AttachmentView(
|
||||
attachment: AttachmentDto,
|
||||
session: QMaxSession,
|
||||
vm: QMaxViewModel,
|
||||
imagePreviewSources: List<String>
|
||||
imagePreviewSources: List<String>,
|
||||
mediaShape: RoundedCornerShape
|
||||
) {
|
||||
val url = rememberAttachmentUrl(session, attachment)
|
||||
val state by vm.state
|
||||
@@ -2683,7 +2695,8 @@ private fun AttachmentView(
|
||||
session.accessToken,
|
||||
cachedImagePath,
|
||||
vm,
|
||||
imagePreviewSources
|
||||
imagePreviewSources,
|
||||
mediaShape
|
||||
)
|
||||
attachment.isVideoAttachment() -> VideoAttachment(attachment, url, session.accessToken)
|
||||
attachment.isVoiceAttachment() || attachment.isAudioAttachment() -> VoiceAttachment(attachment, url, session.accessToken)
|
||||
@@ -2719,26 +2732,29 @@ private fun ImageAttachment(
|
||||
token: String,
|
||||
cachedImagePath: String?,
|
||||
vm: QMaxViewModel,
|
||||
imagePreviewSources: List<String>
|
||||
imagePreviewSources: List<String>,
|
||||
mediaShape: RoundedCornerShape
|
||||
) {
|
||||
LaunchedEffect(attachment.id, url) {
|
||||
vm.cacheImage(attachment)
|
||||
}
|
||||
val model = cachedImagePath?.let(::File) ?: imageRequest(url, token)
|
||||
val previewSource = cachedImagePath ?: url
|
||||
val aspectRatio = rememberImageAspectRatio(cachedImagePath)
|
||||
var imageLoaded by remember(attachment.id, cachedImagePath, url) { mutableStateOf(false) }
|
||||
var imageFailed by remember(attachment.id, cachedImagePath, url) { mutableStateOf(false) }
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1.25f)
|
||||
.aspectRatio(aspectRatio)
|
||||
.clip(mediaShape)
|
||||
.background(Color(0xFFE1E8ED))
|
||||
.clickable { vm.openImage(previewSource, imagePreviewSources) }
|
||||
) {
|
||||
AsyncImage(
|
||||
model = model,
|
||||
contentDescription = attachment.fileName,
|
||||
contentScale = ContentScale.Crop,
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onSuccess = {
|
||||
imageLoaded = true
|
||||
@@ -2755,6 +2771,29 @@ private fun ImageAttachment(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberImageAspectRatio(cachedImagePath: String?): Float {
|
||||
var aspectRatio by remember(cachedImagePath) { mutableStateOf(1.25f) }
|
||||
LaunchedEffect(cachedImagePath) {
|
||||
val path = cachedImagePath ?: return@LaunchedEffect
|
||||
val decoded = withContext(Dispatchers.IO) {
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inJustDecodeBounds = true
|
||||
}
|
||||
BitmapFactory.decodeFile(path, options)
|
||||
if (options.outWidth > 0 && options.outHeight > 0) {
|
||||
(options.outWidth.toFloat() / options.outHeight.toFloat()).coerceIn(0.58f, 1.9f)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
if (decoded != null) {
|
||||
aspectRatio = decoded
|
||||
}
|
||||
}
|
||||
return aspectRatio
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MediaImagePlaceholder(failed: Boolean) {
|
||||
Box(
|
||||
|
||||
@@ -540,9 +540,7 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
|
||||
val replacement = findReplacementChat(staleChat, orderedFresh)
|
||||
if (replacement == null) {
|
||||
state.value = state.value.copy(
|
||||
selectedChat = null,
|
||||
chats = orderedFresh,
|
||||
messages = emptyList(),
|
||||
error = null
|
||||
)
|
||||
repository.cacheChats(session, orderedFresh)
|
||||
|
||||
Reference in New Issue
Block a user