ui: open PM files explicitly and translate conversation mode choices

This commit is contained in:
evgeny committed 2026-11-03 13:37:39 +01:00
1 parent 25d2ac981e
commit 45f4ca0f31
3 files changed
+48 -7

No files matched your search

@@ -24,6 +24,10 @@ import com.utun.chat.ui.components.ConversationComposer
import com.utun.chat.ui.components.ConversationMessages
import com.utun.chat.viewmodel.ChatViewModel
import java.io.File
import android.content.Intent
import android.content.ClipData
import android.webkit.MimeTypeMap
import androidx.core.content.FileProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
@@ -45,6 +49,7 @@ fun DmChatScreen(conv: DmConversation, messages: List<Message>, viewModel: ChatV
val context = LocalContext.current
val scope = rememberCoroutineScope()
var pathToSave by rememberSaveable { mutableStateOf<String?>(null) }
var fileChoice by remember(target) { mutableStateOf<Message?>(null) }
var error by remember(conv.convId, conv.generation) { mutableStateOf<Int?>(null) }
val save = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("*/*")) { uri ->
val path = pathToSave
@@ -67,6 +72,40 @@ fun DmChatScreen(conv: DmConversation, messages: List<Message>, viewModel: ChatV
LaunchedEffect(messages.size) {
if (messages.lastOrNull()?.isOutgoing == true) state.scrollToItem(0)
}
fileChoice?.let { message ->
AlertDialog(onDismissRequest = { fileChoice = null }, title = { Text(message.text) },
confirmButton = { TextButton(onClick = {
fileChoice = null
viewModel.prepareDmAttachment(target, message)
scope.launch {
try {
val file = withContext(Dispatchers.IO) {
val dir = File(context.cacheDir, "member_files/dm/${File(message.filePath).nameWithoutExtension}")
check(dir.isDirectory || dir.mkdirs()) { "Cannot create export directory" }
val name = File(message.text).name.takeUnless { it.isBlank() || it == "." || it == ".." } ?: "attachment.bin"
File(message.filePath).copyTo(File(dir, name), overwrite = true)
}
val uri = FileProvider.getUriForFile(context, "${context.packageName}.memberfiles", file)
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension.lowercase()) ?: "application/octet-stream"
context.startActivity(Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, mime); clipData = ClipData.newRawUri(file.name, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
viewModel.attachmentOpened(message.filePath)
} catch (e: CancellationException) { throw e }
catch (e: Exception) {
LogManager.addLog("ERROR", "PM", "Cannot open attachment: $e"); error = R.string.file_open_failed
}
}
}) { Text(stringResource(R.string.files_open)) } },
dismissButton = { TextButton(onClick = {
fileChoice = null; pathToSave = message.filePath; error = null
try { save.launch(message.text) } catch (e: Exception) {
pathToSave = null; error = R.string.file_save_failed
LogManager.addLog("ERROR", "PM", "Cannot launch document export: $e")
}
}) { Text(stringResource(R.string.save)) } })
}
Column(Modifier.fillMaxSize().imePadding()) {
TopAppBar(title = {
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -96,13 +135,7 @@ fun DmChatScreen(conv: DmConversation, messages: List<Message>, viewModel: ChatV
viewModel.seekVoice(path, position, duration)
},
onOpenFile = {
if (it.filePath.isNotEmpty() && pathToSave == null) {
pathToSave = it.filePath; error = null
try { save.launch(it.text) } catch (e: Exception) {
pathToSave = null; error = R.string.file_save_failed
LogManager.addLog("ERROR", "PM", "Cannot launch document export: $e")
}
}
if (it.filePath.isNotEmpty() && pathToSave == null) fileChoice = it
},
onDownloadFile = { viewModel.downloadDmAttachment(target, it) },
onPlayVideo = { viewModel.prepareDmAttachment(target, it); onPlayVideo(it) },
+4
View File
@@ -8,6 +8,10 @@
<message><source>Finish pairing</source><translation>Finish pairing</translation></message>
<message><source>Link %1 to your device subgroup. The device will receive the subgroup's shared keys.&#10;&#10;Enter the first PIN on that device, then enter its return PIN here. On the other device, open Finish pairing in your own member menu. Complete both steps within two minutes.</source><translation>Link %1 to your device subgroup. The device will receive the subgroup's shared keys.&#10;&#10;Enter the first PIN on that device, then enter its return PIN here. On the other device, open Finish pairing in your own member menu. Complete both steps within two minutes.</translation></message>
<message><source>Message formatting</source><translation>Message formatting</translation></message>
<message><source>Choose which devices participate in this conversation.</source><translation>Choose which devices participate in this conversation.</translation></message>
<message><source>With device groups</source><translation>With device groups</translation></message>
<message><source>Only these devices</source><translation>Only these devices</translation></message>
<message><source>File unavailable</source><translation>File unavailable</translation></message>
<message>
<source>**text** — bold&#10;__text__ — italic&#10;~~text~~ — strikethrough&#10;&#10;Use paired tags on one line, without spaces next to the text.&#10;&#10;Code blocks: put ``` on separate lines, as shown:&#10;```cpp&#10;int value = 42;&#10;```&#10;&#10;Spaces around ``` and the language are allowed.&#10;The language after ``` is optional: the chat will try to detect it.&#10;Use ```text to disable highlighting. Copy copies the whole block.</source>
<translation>**text** — bold&#10;__text__ — italic&#10;~~text~~ — strikethrough&#10;&#10;Use paired tags on one line, without spaces next to the text.&#10;&#10;Code blocks: put ``` on separate lines, as shown:&#10;```cpp&#10;int value = 42;&#10;```&#10;&#10;Spaces around ``` and the language are allowed.&#10;The language after ``` is optional: the chat will try to detect it.&#10;Use ```text to disable highlighting. Copy copies the whole block.</translation>
+4
View File
@@ -8,6 +8,10 @@
<message><source>Finish pairing</source><translation>Завершить спаривание</translation></message>
<message><source>Link %1 to your device subgroup. The device will receive the subgroup's shared keys.&#10;&#10;Enter the first PIN on that device, then enter its return PIN here. On the other device, open Finish pairing in your own member menu. Complete both steps within two minutes.</source><translation>Свяжите %1 с подгруппой ваших устройств. Устройство получит общие ключи подгруппы.&#10;&#10;Введите первый PIN на том устройстве, затем его ответный PIN здесь. На другом устройстве откройте «Завершить спаривание» в меню своего участника. Завершите оба шага в течение двух минут.</translation></message>
<message><source>Message formatting</source><translation>Форматирование сообщений</translation></message>
<message><source>Choose which devices participate in this conversation.</source><translation>Выберите устройства для этой беседы.</translation></message>
<message><source>With device groups</source><translation>С подгруппами устройств</translation></message>
<message><source>Only these devices</source><translation>Только эти устройства</translation></message>
<message><source>File unavailable</source><translation>Файл недоступен</translation></message>
<message>
<source>**text** — bold&#10;__text__ — italic&#10;~~text~~ — strikethrough&#10;&#10;Use paired tags on one line, without spaces next to the text.&#10;&#10;Code blocks: put ``` on separate lines, as shown:&#10;```cpp&#10;int value = 42;&#10;```&#10;&#10;Spaces around ``` and the language are allowed.&#10;The language after ``` is optional: the chat will try to detect it.&#10;Use ```text to disable highlighting. Copy copies the whole block.</source>
<translation>**текст** — жирный&#10;__текст__ — курсив&#10;~~текст~~ — зачёркнутый&#10;&#10;Ставьте парные теги в одной строке, без пробелов рядом с текстом.&#10;&#10;Блок кода: ``` должны быть на отдельных строках, как в примере:&#10;```cpp&#10;int value = 42;&#10;```&#10;&#10;Пробелы вокруг ``` и названия языка допустимы.&#10;Язык после ``` необязателен: чат попробует определить его сам.&#10;Укажите ```text для кода без подсветки. Copy копирует весь блок.</translation>