diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/DmChatScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/DmChatScreen.kt index bbf332e0c..ec19e745f 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/DmChatScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/DmChatScreen.kt @@ -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, viewModel: ChatV val context = LocalContext.current val scope = rememberCoroutineScope() var pathToSave by rememberSaveable { mutableStateOf(null) } + var fileChoice by remember(target) { mutableStateOf(null) } var error by remember(conv.convId, conv.generation) { mutableStateOf(null) } val save = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("*/*")) { uri -> val path = pathToSave @@ -67,6 +72,40 @@ fun DmChatScreen(conv: DmConversation, messages: List, 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, 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) }, diff --git a/tools/chatgui/translations/chatgui_en.ts b/tools/chatgui/translations/chatgui_en.ts index bf73745b5..75674e7e8 100644 --- a/tools/chatgui/translations/chatgui_en.ts +++ b/tools/chatgui/translations/chatgui_en.ts @@ -8,6 +8,10 @@ Finish pairingFinish pairing Link %1 to your device subgroup. The device will receive the subgroup's shared keys. 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.Link %1 to your device subgroup. The device will receive the subgroup's shared keys. 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. Message formattingMessage formatting + Choose which devices participate in this conversation.Choose which devices participate in this conversation. + With device groupsWith device groups + Only these devicesOnly these devices + File unavailableFile unavailable **text** — bold __text__ — italic ~~text~~ — strikethrough Use paired tags on one line, without spaces next to the text. Code blocks: put ``` on separate lines, as shown: ```cpp int value = 42; ``` Spaces around ``` and the language are allowed. The language after ``` is optional: the chat will try to detect it. Use ```text to disable highlighting. Copy copies the whole block. **text** — bold __text__ — italic ~~text~~ — strikethrough Use paired tags on one line, without spaces next to the text. Code blocks: put ``` on separate lines, as shown: ```cpp int value = 42; ``` Spaces around ``` and the language are allowed. The language after ``` is optional: the chat will try to detect it. Use ```text to disable highlighting. Copy copies the whole block. diff --git a/tools/chatgui/translations/chatgui_ru.ts b/tools/chatgui/translations/chatgui_ru.ts index 8fbaf35ed..a2671163b 100644 --- a/tools/chatgui/translations/chatgui_ru.ts +++ b/tools/chatgui/translations/chatgui_ru.ts @@ -8,6 +8,10 @@ Finish pairingЗавершить спаривание Link %1 to your device subgroup. The device will receive the subgroup's shared keys. 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.Свяжите %1 с подгруппой ваших устройств. Устройство получит общие ключи подгруппы. Введите первый PIN на том устройстве, затем его ответный PIN здесь. На другом устройстве откройте «Завершить спаривание» в меню своего участника. Завершите оба шага в течение двух минут. Message formattingФорматирование сообщений + Choose which devices participate in this conversation.Выберите устройства для этой беседы. + With device groupsС подгруппами устройств + Only these devicesТолько эти устройства + File unavailableФайл недоступен **text** — bold __text__ — italic ~~text~~ — strikethrough Use paired tags on one line, without spaces next to the text. Code blocks: put ``` on separate lines, as shown: ```cpp int value = 42; ``` Spaces around ``` and the language are allowed. The language after ``` is optional: the chat will try to detect it. Use ```text to disable highlighting. Copy copies the whole block. **текст** — жирный __текст__ — курсив ~~текст~~ — зачёркнутый Ставьте парные теги в одной строке, без пробелов рядом с текстом. Блок кода: ``` должны быть на отдельных строках, как в примере: ```cpp int value = 42; ``` Пробелы вокруг ``` и названия языка допустимы. Язык после ``` необязателен: чат попробует определить его сам. Укажите ```text для кода без подсветки. Copy копирует весь блок.