feat(android): make chat info entries clickable and open from header
This commit is contained in:
@@ -2,12 +2,14 @@ package ru.daemonlord.messenger.ui.chat
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
|
import android.widget.Toast
|
||||||
import android.widget.VideoView
|
import android.widget.VideoView
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
@@ -297,6 +299,7 @@ fun ChatScreen(
|
|||||||
onJumpInlineSearch: (Boolean) -> Unit,
|
onJumpInlineSearch: (Boolean) -> Unit,
|
||||||
onVisibleIncomingMessageId: (Long?) -> Unit,
|
onVisibleIncomingMessageId: (Long?) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val allImageUrls = remember(state.messages) {
|
val allImageUrls = remember(state.messages) {
|
||||||
@@ -473,6 +476,15 @@ fun ChatScreen(
|
|||||||
contentDescription = "Back",
|
contentDescription = "Back",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
.clickable { showChatInfoSheet = true }
|
||||||
|
.padding(vertical = 2.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
if (!state.chatAvatarUrl.isNullOrBlank()) {
|
if (!state.chatAvatarUrl.isNullOrBlank()) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = state.chatAvatarUrl,
|
model = state.chatAvatarUrl,
|
||||||
@@ -511,6 +523,7 @@ fun ChatScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { showInlineSearch = !showInlineSearch },
|
onClick = { showInlineSearch = !showInlineSearch },
|
||||||
enabled = !state.isLoadingMore,
|
enabled = !state.isLoadingMore,
|
||||||
@@ -1056,6 +1069,32 @@ fun ChatScreen(
|
|||||||
ChatInfoTabContent(
|
ChatInfoTabContent(
|
||||||
tab = chatInfoTab,
|
tab = chatInfoTab,
|
||||||
entries = chatInfoEntries,
|
entries = chatInfoEntries,
|
||||||
|
onEntryClick = { entry ->
|
||||||
|
when (entry.type) {
|
||||||
|
ChatInfoEntryType.Media -> {
|
||||||
|
val url = entry.resourceUrl.orEmpty()
|
||||||
|
if (url.isBlank()) return@ChatInfoTabContent
|
||||||
|
if (entry.previewIsVideo) {
|
||||||
|
viewerVideoUrl = url
|
||||||
|
} else {
|
||||||
|
val index = allImageUrls.indexOf(url)
|
||||||
|
if (index >= 0) {
|
||||||
|
viewerImageIndex = index
|
||||||
|
} else {
|
||||||
|
openUrlExternally(context, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ChatInfoEntryType.Link -> {
|
||||||
|
entry.resourceUrl?.let { openUrlExternally(context, it) }
|
||||||
|
}
|
||||||
|
ChatInfoEntryType.File,
|
||||||
|
ChatInfoEntryType.Voice,
|
||||||
|
-> {
|
||||||
|
entry.resourceUrl?.let { openUrlExternally(context, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2787,6 +2826,18 @@ private fun formatBytes(value: Long): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openUrlExternally(context: Context, url: String) {
|
||||||
|
if (url.isBlank()) return
|
||||||
|
runCatching {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
}.onFailure {
|
||||||
|
Toast.makeText(context, "Unable to open item", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private enum class ChatInfoTab(val title: String) {
|
private enum class ChatInfoTab(val title: String) {
|
||||||
Media("Media"),
|
Media("Media"),
|
||||||
Files("Files"),
|
Files("Files"),
|
||||||
@@ -2805,6 +2856,8 @@ private data class ChatInfoEntry(
|
|||||||
val type: ChatInfoEntryType,
|
val type: ChatInfoEntryType,
|
||||||
val title: String,
|
val title: String,
|
||||||
val subtitle: String,
|
val subtitle: String,
|
||||||
|
val resourceUrl: String? = null,
|
||||||
|
val sourceMessageId: Long? = null,
|
||||||
val previewImageUrl: String? = null,
|
val previewImageUrl: String? = null,
|
||||||
val previewIsVideo: Boolean = false,
|
val previewIsVideo: Boolean = false,
|
||||||
)
|
)
|
||||||
@@ -2813,6 +2866,7 @@ private data class ChatInfoEntry(
|
|||||||
private fun ChatInfoTabContent(
|
private fun ChatInfoTabContent(
|
||||||
tab: ChatInfoTab,
|
tab: ChatInfoTab,
|
||||||
entries: List<ChatInfoEntry>,
|
entries: List<ChatInfoEntry>,
|
||||||
|
onEntryClick: (ChatInfoEntry) -> Unit,
|
||||||
) {
|
) {
|
||||||
val filtered = remember(tab, entries) {
|
val filtered = remember(tab, entries) {
|
||||||
entries.filter {
|
entries.filter {
|
||||||
@@ -2854,7 +2908,8 @@ private fun ChatInfoTabContent(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.clip(RoundedCornerShape(4.dp))
|
.clip(RoundedCornerShape(4.dp))
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f)),
|
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f))
|
||||||
|
.clickable { onEntryClick(entry) },
|
||||||
) {
|
) {
|
||||||
if (!entry.previewImageUrl.isNullOrBlank()) {
|
if (!entry.previewImageUrl.isNullOrBlank()) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
@@ -2906,6 +2961,7 @@ private fun ChatInfoTabContent(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(12.dp))
|
.clip(RoundedCornerShape(12.dp))
|
||||||
.background(MaterialTheme.colorScheme.surface.copy(alpha = 0.55f))
|
.background(MaterialTheme.colorScheme.surface.copy(alpha = 0.55f))
|
||||||
|
.clickable { onEntryClick(entry) }
|
||||||
.padding(horizontal = 10.dp, vertical = 9.dp),
|
.padding(horizontal = 10.dp, vertical = 9.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
@@ -2982,6 +3038,8 @@ private fun buildChatInfoEntries(messages: List<MessageItem>): List<ChatInfoEntr
|
|||||||
type = ChatInfoEntryType.Media,
|
type = ChatInfoEntryType.Media,
|
||||||
title = extractFileName(attachment.fileUrl),
|
title = extractFileName(attachment.fileUrl),
|
||||||
subtitle = "$time • ${attachment.fileType}",
|
subtitle = "$time • ${attachment.fileType}",
|
||||||
|
resourceUrl = attachment.fileUrl,
|
||||||
|
sourceMessageId = message.id,
|
||||||
previewImageUrl = if (normalized.startsWith("image/")) attachment.fileUrl else null,
|
previewImageUrl = if (normalized.startsWith("image/")) attachment.fileUrl else null,
|
||||||
previewIsVideo = normalized.startsWith("video/"),
|
previewIsVideo = normalized.startsWith("video/"),
|
||||||
)
|
)
|
||||||
@@ -2992,6 +3050,8 @@ private fun buildChatInfoEntries(messages: List<MessageItem>): List<ChatInfoEntr
|
|||||||
type = ChatInfoEntryType.Voice,
|
type = ChatInfoEntryType.Voice,
|
||||||
title = extractFileName(attachment.fileUrl),
|
title = extractFileName(attachment.fileUrl),
|
||||||
subtitle = "$time • ${formatBytes(attachment.fileSize)}",
|
subtitle = "$time • ${formatBytes(attachment.fileSize)}",
|
||||||
|
resourceUrl = attachment.fileUrl,
|
||||||
|
sourceMessageId = message.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3000,6 +3060,8 @@ private fun buildChatInfoEntries(messages: List<MessageItem>): List<ChatInfoEntr
|
|||||||
type = ChatInfoEntryType.File,
|
type = ChatInfoEntryType.File,
|
||||||
title = extractFileName(attachment.fileUrl),
|
title = extractFileName(attachment.fileUrl),
|
||||||
subtitle = "$time • ${formatBytes(attachment.fileSize)}",
|
subtitle = "$time • ${formatBytes(attachment.fileSize)}",
|
||||||
|
resourceUrl = attachment.fileUrl,
|
||||||
|
sourceMessageId = message.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3009,6 +3071,8 @@ private fun buildChatInfoEntries(messages: List<MessageItem>): List<ChatInfoEntr
|
|||||||
type = ChatInfoEntryType.Link,
|
type = ChatInfoEntryType.Link,
|
||||||
title = match.value,
|
title = match.value,
|
||||||
subtitle = "${message.senderDisplayName ?: "Unknown"} • $time",
|
subtitle = "${message.senderDisplayName ?: "Unknown"} • $time",
|
||||||
|
resourceUrl = match.value,
|
||||||
|
sourceMessageId = message.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user