diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index b573a1375..58238b48b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -1674,15 +1674,15 @@ class CIFile( val fileStatus: CIFileStatus ) { val loaded: Boolean = when (fileStatus) { - CIFileStatus.SndStored -> true - CIFileStatus.SndTransfer -> true - CIFileStatus.SndComplete -> true - CIFileStatus.SndCancelled -> true - CIFileStatus.RcvInvitation -> false - CIFileStatus.RcvAccepted -> false - CIFileStatus.RcvTransfer -> false - CIFileStatus.RcvCancelled -> false - CIFileStatus.RcvComplete -> true + is CIFileStatus.SndStored -> true + is CIFileStatus.SndTransfer -> true + is CIFileStatus.SndComplete -> true + is CIFileStatus.SndCancelled -> true + is CIFileStatus.RcvInvitation -> false + is CIFileStatus.RcvAccepted -> false + is CIFileStatus.RcvTransfer -> false + is CIFileStatus.RcvCancelled -> false + is CIFileStatus.RcvComplete -> true } companion object { @@ -1698,16 +1698,16 @@ class CIFile( } @Serializable -enum class CIFileStatus { - @SerialName("snd_stored") SndStored, - @SerialName("snd_transfer") SndTransfer, - @SerialName("snd_complete") SndComplete, - @SerialName("snd_cancelled") SndCancelled, - @SerialName("rcv_invitation") RcvInvitation, - @SerialName("rcv_accepted") RcvAccepted, - @SerialName("rcv_transfer") RcvTransfer, - @SerialName("rcv_complete") RcvComplete, - @SerialName("rcv_cancelled") RcvCancelled; +sealed class CIFileStatus { + @Serializable @SerialName("sndStored") object SndStored: CIFileStatus() + @Serializable @SerialName("sndTransfer") class SndTransfer(val sndProgress: Int, val sndTotal: Int): CIFileStatus() + @Serializable @SerialName("sndComplete") object SndComplete: CIFileStatus() + @Serializable @SerialName("sndCancelled") object SndCancelled: CIFileStatus() + @Serializable @SerialName("rcvInvitation") object RcvInvitation: CIFileStatus() + @Serializable @SerialName("rcvAccepted") object RcvAccepted: CIFileStatus() + @Serializable @SerialName("rcvTransfer") class RcvTransfer(val rcvProgress: Int, val rcvTotal: Int): CIFileStatus() + @Serializable @SerialName("rcvComplete") object RcvComplete: CIFileStatus() + @Serializable @SerialName("rcvCancelled") object RcvCancelled: CIFileStatus() } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index 35b918776..58d41984b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -146,6 +146,8 @@ class AppPreferences(val context: Context) { val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null) + val xftpSendEnabled = mkBoolPreference(SHARED_PREFS_XFTP_SEND_ENABLED, false) + private fun mkIntPreference(prefName: String, default: Int) = SharedPreference( get = fun() = sharedPreferences.getInt(prefName, default), @@ -241,6 +243,7 @@ class AppPreferences(val context: Context) { private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme" private const val SHARED_PREFS_PRIMARY_COLOR = "PrimaryColor" private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion" + private const val SHARED_PREFS_XFTP_SEND_ENABLED = "XFTPSendEnabled" } } @@ -266,6 +269,9 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a try { if (chatModel.chatRunning.value == true) return apiSetNetworkConfig(getNetCfg()) + apiSetTempFolder(getTempFilesDirectory(appContext)) + apiSetFilesFolder(getAppFilesDirectory(appContext)) + apiSetXFTPConfig(getXFTPCfg()) val justStarted = apiStartChat() val users = listUsers() chatModel.users.clear() @@ -273,7 +279,6 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a if (justStarted) { chatModel.currentUser.value = user chatModel.userCreated.value = true - apiSetFilesFolder(getAppFilesDirectory(appContext)) apiSetIncognito(chatModel.incognito.value) getUserChatData() chatModel.onboardingStage.value = OnboardingStage.OnboardingComplete @@ -434,12 +439,24 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } } + private suspend fun apiSetTempFolder(tempFolder: String) { + val r = sendCmd(CC.SetTempFolder(tempFolder)) + if (r is CR.CmdOk) return + throw Error("failed to set temp folder: ${r.responseType} ${r.details}") + } + private suspend fun apiSetFilesFolder(filesFolder: String) { val r = sendCmd(CC.SetFilesFolder(filesFolder)) if (r is CR.CmdOk) return throw Error("failed to set files folder: ${r.responseType} ${r.details}") } + suspend fun apiSetXFTPConfig(cfg: XFTPFileConfig?) { + val r = sendCmd(CC.ApiSetXFTPConfig(cfg)) + if (r is CR.CmdOk) return + throw Error("apiSetXFTPConfig bad response: ${r.responseType} ${r.details}") + } + suspend fun apiSetIncognito(incognito: Boolean) { val r = sendCmd(CC.SetIncognito(incognito)) if (r is CR.CmdOk) return @@ -1683,6 +1700,11 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } } + fun getXFTPCfg(): XFTPFileConfig? { + val prefXFTPSendEnabled = appPrefs.xftpSendEnabled.get() + return if (prefXFTPSendEnabled) XFTPFileConfig(minFileSize = 0) else null + } + fun getNetCfg(): NetCfg { val useSocksProxy = appPrefs.networkUseSocksProxy.get() val socksProxy = if (useSocksProxy) ":9050" else null @@ -1758,7 +1780,9 @@ sealed class CC { class ApiDeleteUser(val userId: Long, val delSMPQueues: Boolean): CC() class StartChat(val expire: Boolean): CC() class ApiStopChat: CC() + class SetTempFolder(val tempFolder: String): CC() class SetFilesFolder(val filesFolder: String): CC() + class ApiSetXFTPConfig(val config: XFTPFileConfig?): CC() class SetIncognito(val incognito: Boolean): CC() class ApiExportArchive(val config: ArchiveConfig): CC() class ApiImportArchive(val config: ArchiveConfig): CC() @@ -1835,7 +1859,9 @@ sealed class CC { is ApiDeleteUser -> "/_delete user $userId del_smp=${onOff(delSMPQueues)}" is StartChat -> "/_start subscribe=on expire=${onOff(expire)}" is ApiStopChat -> "/_stop" + is SetTempFolder -> "/_temp_folder $tempFolder" is SetFilesFolder -> "/_files_folder $filesFolder" + is ApiSetXFTPConfig -> if (config != null) "/_xftp on ${json.encodeToString(config)}" else "/_xftp off" is SetIncognito -> "/incognito ${onOff(incognito)}" is ApiExportArchive -> "/_db export ${json.encodeToString(config)}" is ApiImportArchive -> "/_db import ${json.encodeToString(config)}" @@ -1913,7 +1939,9 @@ sealed class CC { is ApiDeleteUser -> "apiDeleteUser" is StartChat -> "startChat" is ApiStopChat -> "apiStopChat" + is SetTempFolder -> "setTempFolder" is SetFilesFolder -> "setFilesFolder" + is ApiSetXFTPConfig -> "apiSetXFTPConfig" is SetIncognito -> "setIncognito" is ApiExportArchive -> "apiExportArchive" is ApiImportArchive -> "apiImportArchive" @@ -2027,6 +2055,9 @@ sealed class ChatPagination { @Serializable class ComposedMessage(val filePath: String?, val quotedItemId: Long?, val msgContent: MsgContent) +@Serializable +class XFTPFileConfig(val minFileSize: Long) + @Serializable class ArchiveConfig(val archivePath: String, val disableCompression: Boolean? = null, val parentTempDirectory: String? = null) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt index b9e3bbc62..0df2c1518 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt @@ -72,7 +72,7 @@ fun CIFileView( fun fileAction() { if (file != null) { when (file.fileStatus) { - CIFileStatus.RcvInvitation -> { + is CIFileStatus.RcvInvitation -> { if (fileSizeValid()) { receiveFile(file.fileId) } else { @@ -82,12 +82,12 @@ fun CIFileView( ) } } - CIFileStatus.RcvAccepted -> + is CIFileStatus.RcvAccepted -> AlertManager.shared.showAlertMsg( generalGetString(R.string.waiting_for_file), String.format(generalGetString(R.string.file_will_be_received_when_contact_is_online), MAX_FILE_SIZE) ) - CIFileStatus.RcvComplete -> { + is CIFileStatus.RcvComplete -> { val filePath = getLoadedFilePath(context, file) if (filePath != null) { saveFileLauncher.launch(file.fileName) @@ -120,19 +120,19 @@ fun CIFileView( ) { if (file != null) { when (file.fileStatus) { - CIFileStatus.SndStored -> fileIcon() - CIFileStatus.SndTransfer -> progressIndicator() - CIFileStatus.SndComplete -> fileIcon(innerIcon = Icons.Filled.Check) - CIFileStatus.SndCancelled -> fileIcon(innerIcon = Icons.Outlined.Close) - CIFileStatus.RcvInvitation -> + is CIFileStatus.SndStored -> fileIcon() + is CIFileStatus.SndTransfer -> progressIndicator() + is CIFileStatus.SndComplete -> fileIcon(innerIcon = Icons.Filled.Check) + is CIFileStatus.SndCancelled -> fileIcon(innerIcon = Icons.Outlined.Close) + is CIFileStatus.RcvInvitation -> if (fileSizeValid()) fileIcon(innerIcon = Icons.Outlined.ArrowDownward, color = MaterialTheme.colors.primary) else fileIcon(innerIcon = Icons.Outlined.PriorityHigh, color = WarningOrange) - CIFileStatus.RcvAccepted -> fileIcon(innerIcon = Icons.Outlined.MoreHoriz) - CIFileStatus.RcvTransfer -> progressIndicator() - CIFileStatus.RcvComplete -> fileIcon() - CIFileStatus.RcvCancelled -> fileIcon(innerIcon = Icons.Outlined.Close) + is CIFileStatus.RcvAccepted -> fileIcon(innerIcon = Icons.Outlined.MoreHoriz) + is CIFileStatus.RcvTransfer -> progressIndicator() + is CIFileStatus.RcvComplete -> fileIcon() + is CIFileStatus.RcvCancelled -> fileIcon(innerIcon = Icons.Outlined.Close) } } else { fileIcon() @@ -191,7 +191,7 @@ class ChatItemProvider: PreviewParameterProvider { ChatItem.getFileMsgContentSample(), ChatItem.getFileMsgContentSample(fileName = "some_long_file_name_here", fileStatus = CIFileStatus.RcvInvitation), ChatItem.getFileMsgContentSample(fileStatus = CIFileStatus.RcvAccepted), - ChatItem.getFileMsgContentSample(fileStatus = CIFileStatus.RcvTransfer), + ChatItem.getFileMsgContentSample(fileStatus = CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10)), ChatItem.getFileMsgContentSample(fileStatus = CIFileStatus.RcvCancelled), ChatItem.getFileMsgContentSample(fileSize = 1_000_000_000, fileStatus = CIFileStatus.RcvInvitation), ChatItem.getFileMsgContentSample(text = "Hello there", fileStatus = CIFileStatus.RcvInvitation), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt index 644bacfe6..7fb2e92f9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt @@ -55,33 +55,33 @@ fun CIImageView( contentAlignment = Alignment.Center ) { when (file.fileStatus) { - CIFileStatus.SndTransfer -> + is CIFileStatus.SndTransfer -> CircularProgressIndicator( Modifier.size(16.dp), color = Color.White, strokeWidth = 2.dp ) - CIFileStatus.SndComplete -> + is CIFileStatus.SndComplete -> Icon( Icons.Filled.Check, stringResource(R.string.icon_descr_image_snd_complete), Modifier.fillMaxSize(), tint = Color.White ) - CIFileStatus.RcvAccepted -> + is CIFileStatus.RcvAccepted -> Icon( Icons.Outlined.MoreHoriz, stringResource(R.string.icon_descr_waiting_for_image), Modifier.fillMaxSize(), tint = Color.White ) - CIFileStatus.RcvTransfer -> + is CIFileStatus.RcvTransfer -> CircularProgressIndicator( Modifier.size(16.dp), color = Color.White, strokeWidth = 2.dp ) - CIFileStatus.RcvInvitation -> + is CIFileStatus.RcvInvitation -> Icon( Icons.Outlined.ArrowDownward, stringResource(R.string.icon_descr_asked_to_receive), @@ -187,7 +187,7 @@ fun CIImageView( generalGetString(R.string.waiting_for_image), generalGetString(R.string.image_will_be_received_when_contact_is_online) ) - CIFileStatus.RcvTransfer -> {} // ? + CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10) -> {} // ? CIFileStatus.RcvComplete -> {} // ? CIFileStatus.RcvCancelled -> {} // TODO else -> {} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt index a42acadea..c20df776a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt @@ -210,9 +210,9 @@ private fun VoiceMsgIndicator( PlayPauseButton(audioPlaying, sent, angle, strokeWidth, strokeColor, true, error, play, pause, longClick = longClick) } } else { - if (file?.fileStatus == CIFileStatus.RcvInvitation - || file?.fileStatus == CIFileStatus.RcvTransfer - || file?.fileStatus == CIFileStatus.RcvAccepted + if (file?.fileStatus is CIFileStatus.RcvInvitation + || file?.fileStatus is CIFileStatus.RcvTransfer + || file?.fileStatus is CIFileStatus.RcvAccepted ) { Box( Modifier diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt index 17a462f58..d5e2978d9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt @@ -234,12 +234,17 @@ const val MAX_VOICE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE const val MAX_VOICE_SIZE_FOR_SENDING: Long = 94680 // 6 chunks * 15780 bytes per chunk const val MAX_VOICE_MILLIS_FOR_SENDING: Int = 43_000 -const val MAX_FILE_SIZE: Long = 8000000 +//const val MAX_FILE_SIZE_SMP: Long = 8000000 // TODO distinguish between XFTP and SMP files +const val MAX_FILE_SIZE: Long = 1_073_741_824 fun getFilesDirectory(context: Context): String { return context.filesDir.toString() } +fun getTempFilesDirectory(context: Context): String { + return "${getFilesDirectory(context)}/temp_files" +} + fun getAppFilesDirectory(context: Context): String { return "${getFilesDirectory(context)}/app_files" } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ExperimentalFeaturesView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ExperimentalFeaturesView.kt index 3fb3ee9db..637bda9eb 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ExperimentalFeaturesView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ExperimentalFeaturesView.kt @@ -5,18 +5,18 @@ import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Videocam +import androidx.compose.material.icons.outlined.UploadFile import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import chat.simplex.app.R import chat.simplex.app.model.ChatModel +import chat.simplex.app.views.helpers.withApi @Composable -fun ExperimentalFeaturesView(chatModel: ChatModel, enableCalls: MutableState) { +fun ExperimentalFeaturesView(chatModel: ChatModel) { Column( Modifier.fillMaxWidth(), horizontalAlignment = Alignment.Start @@ -27,7 +27,11 @@ fun ExperimentalFeaturesView(chatModel: ChatModel, enableCalls: MutableStateMESSAGES CALLS Incognito mode + Send files via XFTP Your chat database