|
|
|
|
@ -2,8 +2,14 @@ package com.utun.chat.ui.components
|
|
|
|
|
|
|
|
|
|
import androidx.compose.foundation.layout.* |
|
|
|
|
import androidx.compose.foundation.lazy.* |
|
|
|
|
import androidx.compose.runtime.Composable |
|
|
|
|
import androidx.compose.runtime.LaunchedEffect |
|
|
|
|
import androidx.compose.runtime.* |
|
|
|
|
import android.content.Intent |
|
|
|
|
import android.content.ActivityNotFoundException |
|
|
|
|
import android.net.Uri |
|
|
|
|
import androidx.compose.material3.AlertDialog |
|
|
|
|
import androidx.compose.material3.Text |
|
|
|
|
import androidx.compose.material3.TextButton |
|
|
|
|
import androidx.compose.ui.platform.LocalContext |
|
|
|
|
import androidx.compose.ui.Modifier |
|
|
|
|
import androidx.compose.ui.draw.drawWithCache |
|
|
|
|
import androidx.compose.ui.geometry.Offset |
|
|
|
|
@ -14,11 +20,13 @@ import androidx.compose.ui.graphics.ShaderBrush
|
|
|
|
|
import androidx.compose.ui.graphics.TileMode |
|
|
|
|
import androidx.compose.ui.graphics.drawscope.scale |
|
|
|
|
import androidx.compose.ui.res.imageResource |
|
|
|
|
import androidx.compose.ui.res.stringResource |
|
|
|
|
import androidx.compose.ui.unit.dp |
|
|
|
|
import com.utun.chat.R |
|
|
|
|
import com.utun.chat.data.ConversationTarget |
|
|
|
|
import com.utun.chat.data.LogManager |
|
|
|
|
import com.utun.chat.data.Message |
|
|
|
|
import com.utun.chat.data.MessageLinks |
|
|
|
|
import com.utun.chat.viewmodel.ChatViewModel |
|
|
|
|
|
|
|
|
|
/** Общий список; направление включено в ключ, поскольку PM имеет два потока seq. */ |
|
|
|
|
@ -29,6 +37,30 @@ fun ConversationMessages(target: ConversationTarget, messages: List<Message>, st
|
|
|
|
|
onOpenFile: (Message) -> Unit, onPlayVideo: (Message) -> Unit, |
|
|
|
|
onDownloadFile: (Message) -> Unit = {}, onOpenImage: (Message) -> Unit = {}, |
|
|
|
|
onInviteLink: (String) -> Unit = {}) { |
|
|
|
|
val context = LocalContext.current |
|
|
|
|
var openFailed by remember { mutableStateOf(false) } |
|
|
|
|
val onLink: (String) -> Unit = { link -> |
|
|
|
|
if (link.startsWith("utun://")) onInviteLink(link) |
|
|
|
|
else if (!MessageLinks.isWebUrl(link)) { |
|
|
|
|
LogManager.addLog("ERROR", "LINK", "invalid external message link: bytes=${link.length}") |
|
|
|
|
openFailed = true |
|
|
|
|
} else { |
|
|
|
|
val uri = Uri.parse(link) |
|
|
|
|
try { |
|
|
|
|
context.startActivity(Intent(Intent.ACTION_VIEW, uri)) |
|
|
|
|
LogManager.addLog("INFO", "LINK", "external open requested: scheme=${uri.scheme} host=${uri.host}") |
|
|
|
|
} catch (e: ActivityNotFoundException) { |
|
|
|
|
LogManager.addLog("ERROR", "LINK", "no URL handler: scheme=${uri.scheme} host=${uri.host} error=${e.message}") |
|
|
|
|
openFailed = true |
|
|
|
|
} catch (e: SecurityException) { |
|
|
|
|
LogManager.addLog("ERROR", "LINK", "URL handler denied: scheme=${uri.scheme} host=${uri.host} error=${e.message}") |
|
|
|
|
openFailed = true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (openFailed) AlertDialog(onDismissRequest = { openFailed = false }, |
|
|
|
|
text = { Text(stringResource(R.string.link_open_failed)) }, |
|
|
|
|
confirmButton = { TextButton(onClick = { openFailed = false }) { Text(stringResource(R.string.close)) } }) |
|
|
|
|
val background = ImageBitmap.imageResource(R.drawable.chat_background) |
|
|
|
|
LaunchedEffect(background) { |
|
|
|
|
LogManager.addLog("DEBUG", "UI", "Chat background ready: desktop bg.png tile=${background.width}x${background.height}") |
|
|
|
|
@ -45,7 +77,7 @@ fun ConversationMessages(target: ConversationTarget, messages: List<Message>, st
|
|
|
|
|
LazyColumn(modifier = modifier.then(backgroundModifier), state = state, reverseLayout = true) { |
|
|
|
|
item { Spacer(Modifier.height(8.dp)) } |
|
|
|
|
items(messages.reversed(), key = { "${target.nativeId}:${it.isOutgoing}:${it.id}" }) { message -> |
|
|
|
|
MessageBubble(message, playbackState, onPlayVoice, onSeekVoice, onDownloadFile, onOpenFile, onOpenImage, onPlayVideo, onInviteLink) |
|
|
|
|
MessageBubble(message, playbackState, onPlayVoice, onSeekVoice, onDownloadFile, onOpenFile, onOpenImage, onPlayVideo, onLink) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|