desktop: sending and receiving files via connected mobile (#3365)

* desktop: support remote files (WIP)

* working with remote files locally

* better

* working with remote file downloads

* sending files

* fixes of loading files in some situations

* image compression

* constant for remote hosts

* refactor

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
This commit is contained in:
Evgeny Poberezkin
2023-11-18 20:11:30 +00:00
committed by GitHub
parent e95d9d0b49
commit ca8833c0c1
20 changed files with 334 additions and 97 deletions
@@ -113,7 +113,7 @@ object NtfManager {
private fun prepareIconPath(icon: ImageBitmap?): String? = if (icon != null) {
tmpDir.mkdir()
val newFile = File(tmpDir.absolutePath + File.separator + generateNewFileName("IMG", "png"))
val newFile = File(tmpDir.absolutePath + File.separator + generateNewFileName("IMG", "png", tmpDir))
try {
ImageIO.write(icon.toAwtImage(), "PNG", newFile.outputStream())
newFile.absolutePath
@@ -21,6 +21,8 @@ actual val agentDatabaseFileName: String = "simplex_v1_agent.db"
actual val databaseExportDir: File = tmpDir
actual val remoteHostsDir: File = File(dataDir.absolutePath + File.separator + "remote_hosts")
actual fun desktopOpenDatabaseDir() {
if (Desktop.isDesktopSupported()) {
try {
@@ -2,12 +2,10 @@ package chat.simplex.common.views.chat.item
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import chat.simplex.common.model.CIFile
import chat.simplex.common.platform.*
import chat.simplex.common.views.helpers.ModalManager
import java.net.URI
@Composable
actual fun SimpleAndAnimatedImageView(
@@ -34,35 +34,51 @@ actual fun ReactionIcon(text: String, fontSize: TextUnit) {
@Composable
actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserLauncher, showMenu: MutableState<Boolean>) {
ItemAction(stringResource(MR.strings.save_verb), painterResource(if (cItem.file?.fileSource?.cryptoArgs == null) MR.images.ic_download else MR.images.ic_lock_open_right), onClick = {
when (cItem.content.msgContent) {
is MsgContent.MCImage, is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> withApi { saveFileLauncher.launch(cItem.file?.fileName ?: "") }
else -> {}
val saveIfExists = {
when (cItem.content.msgContent) {
is MsgContent.MCImage, is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> withApi { saveFileLauncher.launch(cItem.file?.fileName ?: "") }
else -> {}
}
showMenu.value = false
}
showMenu.value = false
var fileSource = getLoadedFileSource(cItem.file)
if (chatModel.connectedToRemote() && fileSource == null) {
withBGApi {
cItem.file?.loadRemoteFile(true)
fileSource = getLoadedFileSource(cItem.file)
saveIfExists()
}
} else saveIfExists()
})
}
actual fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) {
val fileSource = getLoadedFileSource(cItem.file)
actual fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) = withBGApi {
var fileSource = getLoadedFileSource(cItem.file)
if (chatModel.connectedToRemote() && fileSource == null) {
cItem.file?.loadRemoteFile(true)
fileSource = getLoadedFileSource(cItem.file)
}
if (fileSource != null) {
val filePath: String = if (fileSource.cryptoArgs != null) {
val tmpFile = File(tmpDir, fileSource.filePath)
tmpFile.deleteOnExit()
try {
decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, tmpFile.absolutePath)
decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs ?: return@withBGApi, tmpFile.absolutePath)
} catch (e: Exception) {
Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString())
return
return@withBGApi
}
tmpFile.absolutePath
} else {
getAppFilePath(fileSource.filePath)
}
when {
when {
desktopPlatform.isWindows() -> clipboard.setText(AnnotatedString("\"${File(filePath).absolutePath}\""))
else -> clipboard.setText(AnnotatedString(filePath))
}
} else {
clipboard.setText(AnnotatedString(cItem.content.text))
}
}
showToast(MR.strings.copied.localized())
}.run {}
@@ -5,8 +5,7 @@ import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Density
import chat.simplex.common.model.CIFile
import chat.simplex.common.model.readCryptoFile
import chat.simplex.common.model.*
import chat.simplex.common.platform.*
import chat.simplex.common.simplexWindowState
import chat.simplex.res.MR
@@ -88,11 +87,21 @@ actual fun escapedHtmlToAnnotatedString(text: String, density: Density): Annotat
AnnotatedString(text)
}
actual fun getAppFileUri(fileName: String): URI =
URI(appFilesDir.toURI().toString() + "/" + fileName)
actual fun getAppFileUri(fileName: String): URI {
val rh = chatModel.currentRemoteHost.value
return if (rh == null) {
URI(appFilesDir.toURI().toString() + "/" + fileName)
} else {
URI(dataDir.absolutePath + "/remote_hosts/" + rh.storePath + "/simplex_v1_files/" + fileName)
}
}
actual fun getLoadedImage(file: CIFile?): Pair<ImageBitmap, ByteArray>? {
val filePath = getLoadedFilePath(file)
actual suspend fun getLoadedImage(file: CIFile?): Pair<ImageBitmap, ByteArray>? {
var filePath = getLoadedFilePath(file)
if (chatModel.connectedToRemote() && filePath == null) {
file?.loadRemoteFile(false)
filePath = getLoadedFilePath(file)
}
return if (filePath != null) {
try {
val data = if (file?.fileSource?.cryptoArgs != null) readCryptoFile(filePath, file.fileSource.cryptoArgs) else File(filePath).readBytes()
@@ -141,7 +150,7 @@ actual suspend fun saveTempImageUncompressed(image: ImageBitmap, asPng: Boolean)
return if (file != null) {
try {
val ext = if (asPng) "png" else "jpg"
val newFile = File(file.absolutePath + File.separator + generateNewFileName("IMG", ext))
val newFile = File(file.absolutePath + File.separator + generateNewFileName("IMG", ext, File(getAppFilePath(""))))
// LALAL FILE IS EMPTY
ImageIO.write(image.toAwtImage(), ext.uppercase(), newFile.outputStream())
newFile