mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-22 13:11:55 +00:00
desktop: paste files/images to attach to message (#3165)
* desktop: paste files/images to attach to message * Windows * copy files inside the app * change * encrypted files support --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
1 parent
38be27271f
commit
cc95fa6b30
10 files changed
+97
-19
No files matched your search
+37
-2
@@ -12,7 +12,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.input.key.*
|
||||
import androidx.compose.ui.platform.*
|
||||
@@ -27,6 +26,9 @@ import chat.simplex.common.views.helpers.generalGetString
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import kotlinx.coroutines.delay
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import kotlin.io.path.*
|
||||
import kotlin.math.min
|
||||
import kotlin.text.substring
|
||||
|
||||
@@ -39,6 +41,7 @@ actual fun PlatformTextField(
|
||||
userIsObserver: Boolean,
|
||||
onMessageChange: (String) -> Unit,
|
||||
onUpArrow: () -> Unit,
|
||||
onFilesPasted: (List<URI>) -> Unit,
|
||||
onDone: () -> Unit,
|
||||
) {
|
||||
val cs = composeState.value
|
||||
@@ -63,10 +66,20 @@ actual fun PlatformTextField(
|
||||
val isRtl = remember(cs.message) { isRtl(cs.message.subSequence(0, min(50, cs.message.length))) }
|
||||
var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = cs.message)) }
|
||||
val textFieldValue = textFieldValueState.copy(text = cs.message)
|
||||
val clipboard = LocalClipboardManager.current
|
||||
BasicTextField(
|
||||
value = textFieldValue,
|
||||
onValueChange = {
|
||||
onValueChange = onValueChange@ {
|
||||
if (!composeState.value.inProgress && !(composeState.value.preview is ComposePreview.VoicePreview && it.text != "")) {
|
||||
val diff = textFieldValueState.selection.length + (it.text.length - textFieldValueState.text.length)
|
||||
if (diff > 1 && it.text != textFieldValueState.text && it.selection.max - diff >= 0) {
|
||||
val pasted = it.text.substring(it.selection.max - diff, it.selection.max)
|
||||
val files = parseToFiles(AnnotatedString(pasted))
|
||||
if (files.isNotEmpty()) {
|
||||
onFilesPasted(files)
|
||||
return@onValueChange
|
||||
}
|
||||
}
|
||||
textFieldValueState = it
|
||||
onMessageChange(it.text)
|
||||
}
|
||||
@@ -98,6 +111,12 @@ actual fun PlatformTextField(
|
||||
} else if (it.key == Key.DirectionUp && it.type == KeyEventType.KeyDown && cs.message.isEmpty()) {
|
||||
onUpArrow()
|
||||
true
|
||||
} else if (it.key == Key.V &&
|
||||
it.type == KeyEventType.KeyDown &&
|
||||
((it.isCtrlPressed && !desktopPlatform.isMac()) || (it.isMetaPressed && desktopPlatform.isMac())) &&
|
||||
parseToFiles(clipboard.getText()).isNotEmpty()) {
|
||||
onFilesPasted(parseToFiles(clipboard.getText()))
|
||||
true
|
||||
}
|
||||
else false
|
||||
},
|
||||
@@ -142,3 +161,19 @@ private fun ComposeOverlay(textId: StringResource, textStyle: MutableState<TextS
|
||||
style = textStyle.value.copy(fontStyle = FontStyle.Italic)
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseToFiles(text: AnnotatedString?): List<URI> {
|
||||
text ?: return emptyList()
|
||||
val files = ArrayList<URI>()
|
||||
text.lines().forEach {
|
||||
try {
|
||||
val uri = File(it.removePrefix("\"").removeSuffix("\"")).toURI()
|
||||
val path = uri.toPath()
|
||||
if (!path.exists() || !path.isAbsolute || path.isDirectory()) return emptyList()
|
||||
files.add(uri)
|
||||
} catch (e: Exception) {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
+26
-4
@@ -7,17 +7,19 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.ui.platform.ClipboardManager
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.model.ChatItem
|
||||
import chat.simplex.common.model.MsgContent
|
||||
import chat.simplex.common.platform.FileChooserLauncher
|
||||
import chat.simplex.common.platform.desktopPlatform
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.EmojiFont
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
actual fun ReactionIcon(text: String, fontSize: TextUnit) {
|
||||
@@ -39,3 +41,23 @@ actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserL
|
||||
showMenu.value = false
|
||||
})
|
||||
}
|
||||
|
||||
actual fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) {
|
||||
val fileSource = getLoadedFileSource(cItem.file)
|
||||
if (fileSource != null) {
|
||||
val filePath: String = if (fileSource.cryptoArgs != null) {
|
||||
val tmpFile = File(tmpDir, fileSource.filePath)
|
||||
tmpFile.deleteOnExit()
|
||||
decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, tmpFile.absolutePath)
|
||||
tmpFile.absolutePath
|
||||
} else {
|
||||
getAppFilePath(fileSource.filePath)
|
||||
}
|
||||
when {
|
||||
desktopPlatform.isWindows() -> clipboard.setText(AnnotatedString("\"${File(filePath).absolutePath}\""))
|
||||
else -> clipboard.setText(AnnotatedString(filePath))
|
||||
}
|
||||
} else {
|
||||
clipboard.setText(AnnotatedString(cItem.content.text))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user