mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Merge branch 'master' into master-ios
This commit is contained in:
@@ -54,10 +54,8 @@ class ChatModel(val controller: ChatController) {
|
||||
|
||||
val terminalItems = mutableStateListOf<TerminalItem>()
|
||||
val userAddress = mutableStateOf<UserContactLinkRec?>(null)
|
||||
val userSMPServers = mutableStateOf<(List<ServerCfg>)?>(null)
|
||||
// Allows to temporary save servers that are being edited on multiple screens
|
||||
val userSMPServersUnsaved = mutableStateOf<(List<ServerCfg>)?>(null)
|
||||
val presetSMPServers = mutableStateOf<(List<String>)?>(null)
|
||||
val chatItemTTL = mutableStateOf<ChatItemTTL>(ChatItemTTL.None)
|
||||
|
||||
// set when app opened from external intent
|
||||
|
||||
@@ -337,9 +337,6 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
|
||||
suspend fun getUserChatData() {
|
||||
chatModel.userAddress.value = apiGetUserAddress()
|
||||
val smpServers = getUserSMPServers()
|
||||
chatModel.userSMPServers.value = smpServers?.first
|
||||
chatModel.presetSMPServers.value = smpServers?.second
|
||||
chatModel.chatItemTTL.value = getChatItemTTL()
|
||||
val chats = apiGetChats()
|
||||
chatModel.updateChats(chats)
|
||||
@@ -579,38 +576,44 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun getUserSMPServers(): Pair<List<ServerCfg>, List<String>>? {
|
||||
val userId = kotlin.runCatching { currentUserId("getUserSMPServers") }.getOrElse { return null }
|
||||
val r = sendCmd(CC.APIGetUserSMPServers(userId))
|
||||
if (r is CR.UserSMPServers) return r.smpServers to r.presetSMPServers
|
||||
Log.e(TAG, "getUserSMPServers bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
suspend fun getUserProtoServers(serverProtocol: ServerProtocol): UserProtocolServers? {
|
||||
val userId = kotlin.runCatching { currentUserId("getUserProtoServers") }.getOrElse { return null }
|
||||
val r = sendCmd(CC.APIGetUserProtoServers(userId, serverProtocol))
|
||||
return if (r is CR.UserProtoServers) r.servers
|
||||
else {
|
||||
Log.e(TAG, "getUserProtoServers bad response: ${r.responseType} ${r.details}")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(if (serverProtocol == ServerProtocol.SMP) R.string.error_loading_smp_servers else R.string.error_loading_xftp_servers),
|
||||
"${r.responseType}: ${r.details}"
|
||||
)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setUserSMPServers(smpServers: List<ServerCfg>): Boolean {
|
||||
val userId = kotlin.runCatching { currentUserId("setUserSMPServers") }.getOrElse { return false }
|
||||
val r = sendCmd(CC.APISetUserSMPServers(userId, smpServers))
|
||||
suspend fun setUserProtoServers(serverProtocol: ServerProtocol, servers: List<ServerCfg>): Boolean {
|
||||
val userId = kotlin.runCatching { currentUserId("setUserProtoServers") }.getOrElse { return false }
|
||||
val r = sendCmd(CC.APISetUserProtoServers(userId, serverProtocol, servers))
|
||||
return when (r) {
|
||||
is CR.CmdOk -> true
|
||||
else -> {
|
||||
Log.e(TAG, "setUserSMPServers bad response: ${r.responseType} ${r.details}")
|
||||
Log.e(TAG, "setUserProtoServers bad response: ${r.responseType} ${r.details}")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(R.string.error_saving_smp_servers),
|
||||
generalGetString(R.string.ensure_smp_server_address_are_correct_format_and_unique)
|
||||
generalGetString(if (serverProtocol == ServerProtocol.SMP) R.string.error_saving_smp_servers else R.string.error_saving_xftp_servers),
|
||||
generalGetString(if (serverProtocol == ServerProtocol.SMP) R.string.ensure_smp_server_address_are_correct_format_and_unique else R.string.ensure_xftp_server_address_are_correct_format_and_unique)
|
||||
)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun testSMPServer(smpServer: String): SMPTestFailure? {
|
||||
val userId = currentUserId("testSMPServer")
|
||||
val r = sendCmd(CC.APITestSMPServer(userId, smpServer))
|
||||
suspend fun testProtoServer(server: String): ProtocolTestFailure? {
|
||||
val userId = currentUserId("testProtoServer")
|
||||
val r = sendCmd(CC.APITestProtoServer(userId, server))
|
||||
return when (r) {
|
||||
is CR.SmpTestResult -> r.smpTestFailure
|
||||
is CR.ServerTestResult -> r.testFailure
|
||||
else -> {
|
||||
Log.e(TAG, "testSMPServer bad response: ${r.responseType} ${r.details}")
|
||||
throw Exception("testSMPServer bad response: ${r.responseType} ${r.details}")
|
||||
Log.e(TAG, "testProtoServer bad response: ${r.responseType} ${r.details}")
|
||||
throw Exception("testProtoServer bad response: ${r.responseType} ${r.details}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1002,6 +1005,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
val chatItem = apiCancelFile(fileId)
|
||||
if (chatItem != null) {
|
||||
chatItemSimpleUpdate(user, chatItem)
|
||||
cleanupFile(chatItem)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1417,40 +1421,27 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
chatItemSimpleUpdate(r.user, r.chatItem)
|
||||
is CR.RcvFileComplete ->
|
||||
chatItemSimpleUpdate(r.user, r.chatItem)
|
||||
is CR.RcvFileSndCancelled ->
|
||||
is CR.RcvFileSndCancelled -> {
|
||||
chatItemSimpleUpdate(r.user, r.chatItem)
|
||||
cleanupFile(r.chatItem)
|
||||
}
|
||||
is CR.RcvFileProgressXFTP ->
|
||||
chatItemSimpleUpdate(r.user, r.chatItem)
|
||||
is CR.SndFileStart ->
|
||||
chatItemSimpleUpdate(r.user, r.chatItem)
|
||||
is CR.SndFileComplete -> {
|
||||
chatItemSimpleUpdate(r.user, r.chatItem)
|
||||
val cItem = r.chatItem.chatItem
|
||||
val mc = cItem.content.msgContent
|
||||
val fileName = cItem.file?.fileName
|
||||
if (
|
||||
r.chatItem.chatInfo.chatType == ChatType.Direct
|
||||
&& mc is MsgContent.MCFile
|
||||
&& fileName != null
|
||||
) {
|
||||
removeFile(appContext, fileName)
|
||||
}
|
||||
cleanupDirectFile(r.chatItem)
|
||||
}
|
||||
is CR.SndFileRcvCancelled ->
|
||||
is CR.SndFileRcvCancelled -> {
|
||||
chatItemSimpleUpdate(r.user, r.chatItem)
|
||||
cleanupDirectFile(r.chatItem)
|
||||
}
|
||||
is CR.SndFileProgressXFTP ->
|
||||
chatItemSimpleUpdate(r.user, r.chatItem)
|
||||
is CR.SndFileCompleteXFTP -> {
|
||||
chatItemSimpleUpdate(r.user, r.chatItem)
|
||||
val cItem = r.chatItem.chatItem
|
||||
val mc = cItem.content.msgContent
|
||||
val fileName = cItem.file?.fileName
|
||||
if (
|
||||
mc is MsgContent.MCFile
|
||||
&& fileName != null
|
||||
) {
|
||||
removeFile(appContext, fileName)
|
||||
}
|
||||
cleanupFile(r.chatItem)
|
||||
}
|
||||
is CR.CallInvitation -> {
|
||||
chatModel.callManager.reportNewIncomingCall(r.callInvitation)
|
||||
@@ -1502,6 +1493,24 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanupDirectFile(aChatItem: AChatItem) {
|
||||
if (aChatItem.chatInfo.chatType == ChatType.Direct) {
|
||||
cleanupFile(aChatItem)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanupFile(aChatItem: AChatItem) {
|
||||
val cItem = aChatItem.chatItem
|
||||
val mc = cItem.content.msgContent
|
||||
val fileName = cItem.file?.fileName
|
||||
if (
|
||||
mc is MsgContent.MCFile
|
||||
&& fileName != null
|
||||
) {
|
||||
removeFile(appContext, fileName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun active(user: User): Boolean = user.userId == chatModel.currentUser.value?.userId
|
||||
|
||||
private fun withCall(r: CR, contact: Contact, perform: (Call) -> Unit) {
|
||||
@@ -1859,9 +1868,9 @@ sealed class CC {
|
||||
class APIGroupLinkMemberRole(val groupId: Long, val memberRole: GroupMemberRole): CC()
|
||||
class APIDeleteGroupLink(val groupId: Long): CC()
|
||||
class APIGetGroupLink(val groupId: Long): CC()
|
||||
class APIGetUserSMPServers(val userId: Long): CC()
|
||||
class APISetUserSMPServers(val userId: Long, val smpServers: List<ServerCfg>): CC()
|
||||
class APITestSMPServer(val userId: Long, val smpServer: String): CC()
|
||||
class APIGetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol): CC()
|
||||
class APISetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol, val servers: List<ServerCfg>): CC()
|
||||
class APITestProtoServer(val userId: Long, val server: String): CC()
|
||||
class APISetChatItemTTL(val userId: Long, val seconds: Long?): CC()
|
||||
class APIGetChatItemTTL(val userId: Long): CC()
|
||||
class APISetNetworkConfig(val networkConfig: NetCfg): CC()
|
||||
@@ -1943,9 +1952,9 @@ sealed class CC {
|
||||
is APIGroupLinkMemberRole -> "/_set link role #$groupId ${memberRole.name.lowercase()}"
|
||||
is APIDeleteGroupLink -> "/_delete link #$groupId"
|
||||
is APIGetGroupLink -> "/_get link #$groupId"
|
||||
is APIGetUserSMPServers -> "/_smp $userId"
|
||||
is APISetUserSMPServers -> "/_smp $userId ${smpServersStr(smpServers)}"
|
||||
is APITestSMPServer -> "/_smp test $userId $smpServer"
|
||||
is APIGetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()}"
|
||||
is APISetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()} ${protoServersStr(servers)}"
|
||||
is APITestProtoServer -> "/_server test $userId $server"
|
||||
is APISetChatItemTTL -> "/_ttl $userId ${chatItemTTLStr(seconds)}"
|
||||
is APIGetChatItemTTL -> "/_ttl $userId"
|
||||
is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}"
|
||||
@@ -2028,9 +2037,9 @@ sealed class CC {
|
||||
is APIGroupLinkMemberRole -> "apiGroupLinkMemberRole"
|
||||
is APIDeleteGroupLink -> "apiDeleteGroupLink"
|
||||
is APIGetGroupLink -> "apiGetGroupLink"
|
||||
is APIGetUserSMPServers -> "apiGetUserSMPServers"
|
||||
is APISetUserSMPServers -> "apiSetUserSMPServers"
|
||||
is APITestSMPServer -> "testSMPServer"
|
||||
is APIGetUserProtoServers -> "apiGetUserProtoServers"
|
||||
is APISetUserProtoServers -> "apiSetUserProtoServers"
|
||||
is APITestProtoServer -> "testProtoServer"
|
||||
is APISetChatItemTTL -> "apiSetChatItemTTL"
|
||||
is APIGetChatItemTTL -> "apiGetChatItemTTL"
|
||||
is APISetNetworkConfig -> "/apiSetNetworkConfig"
|
||||
@@ -2107,7 +2116,7 @@ sealed class CC {
|
||||
companion object {
|
||||
fun chatRef(chatType: ChatType, id: Long) = "${chatType.type}${id}"
|
||||
|
||||
fun smpServersStr(smpServers: List<ServerCfg>) = if (smpServers.isEmpty()) "default" else json.encodeToString(SMPServersConfig(smpServers))
|
||||
fun protoServersStr(servers: List<ServerCfg>) = json.encodeToString(ProtoServersConfig(servers))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2142,8 +2151,21 @@ class ArchiveConfig(val archivePath: String, val disableCompression: Boolean? =
|
||||
class DBEncryptionConfig(val currentKey: String, val newKey: String)
|
||||
|
||||
@Serializable
|
||||
data class SMPServersConfig(
|
||||
val smpServers: List<ServerCfg>
|
||||
enum class ServerProtocol {
|
||||
@SerialName("smp") SMP,
|
||||
@SerialName("xftp") XFTP;
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ProtoServersConfig(
|
||||
val servers: List<ServerCfg>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UserProtocolServers(
|
||||
val serverProtocol: ServerProtocol,
|
||||
val protoServers: List<ServerCfg>,
|
||||
val presetServers: List<String>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@@ -2197,29 +2219,39 @@ data class ServerCfg(
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class SMPTestStep {
|
||||
enum class ProtocolTestStep {
|
||||
@SerialName("connect") Connect,
|
||||
@SerialName("disconnect") Disconnect,
|
||||
@SerialName("createQueue") CreateQueue,
|
||||
@SerialName("secureQueue") SecureQueue,
|
||||
@SerialName("deleteQueue") DeleteQueue,
|
||||
@SerialName("disconnect") Disconnect;
|
||||
@SerialName("createFile") CreateFile,
|
||||
@SerialName("uploadFile") UploadFile,
|
||||
@SerialName("downloadFile") DownloadFile,
|
||||
@SerialName("compareFile") CompareFile,
|
||||
@SerialName("deleteFile") DeleteFile;
|
||||
|
||||
val text: String get() = when (this) {
|
||||
Connect -> generalGetString(R.string.smp_server_test_connect)
|
||||
Disconnect -> generalGetString(R.string.smp_server_test_disconnect)
|
||||
CreateQueue -> generalGetString(R.string.smp_server_test_create_queue)
|
||||
SecureQueue -> generalGetString(R.string.smp_server_test_secure_queue)
|
||||
DeleteQueue -> generalGetString(R.string.smp_server_test_delete_queue)
|
||||
Disconnect -> generalGetString(R.string.smp_server_test_disconnect)
|
||||
CreateFile -> generalGetString(R.string.smp_server_test_create_file)
|
||||
UploadFile -> generalGetString(R.string.smp_server_test_upload_file)
|
||||
DownloadFile -> generalGetString(R.string.smp_server_test_download_file)
|
||||
CompareFile -> generalGetString(R.string.smp_server_test_compare_file)
|
||||
DeleteFile -> generalGetString(R.string.smp_server_test_delete_file)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SMPTestFailure(
|
||||
val testStep: SMPTestStep,
|
||||
data class ProtocolTestFailure(
|
||||
val testStep: ProtocolTestStep,
|
||||
val testError: AgentErrorType
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is SMPTestFailure) return false
|
||||
if (other !is ProtocolTestFailure) return false
|
||||
return other.testStep == this.testStep
|
||||
}
|
||||
|
||||
@@ -2232,6 +2264,8 @@ data class SMPTestFailure(
|
||||
return when {
|
||||
testError is AgentErrorType.SMP && testError.smpErr is SMPErrorType.AUTH ->
|
||||
err + " " + generalGetString(R.string.error_smp_test_server_auth)
|
||||
testError is AgentErrorType.XFTP && testError.xftpErr is XFTPErrorType.AUTH ->
|
||||
err + " " + generalGetString(R.string.error_xftp_test_server_auth)
|
||||
testError is AgentErrorType.BROKER && testError.brokerErr is BrokerErrorType.NETWORK ->
|
||||
err + " " + generalGetString(R.string.error_smp_test_certificate)
|
||||
else -> err
|
||||
@@ -2241,6 +2275,7 @@ data class SMPTestFailure(
|
||||
|
||||
@Serializable
|
||||
data class ServerAddress(
|
||||
val serverProtocol: ServerProtocol,
|
||||
val hostnames: List<String>,
|
||||
val port: String,
|
||||
val keyHash: String,
|
||||
@@ -2248,19 +2283,21 @@ data class ServerAddress(
|
||||
) {
|
||||
val uri: String
|
||||
get() =
|
||||
"smp://${keyHash}${if (basicAuth.isEmpty()) "" else ":$basicAuth"}@${hostnames.joinToString(",")}"
|
||||
"${serverProtocol}://${keyHash}${if (basicAuth.isEmpty()) "" else ":$basicAuth"}@${hostnames.joinToString(",")}"
|
||||
|
||||
val valid: Boolean
|
||||
get() = hostnames.isNotEmpty() && hostnames.toSet().size == hostnames.size
|
||||
|
||||
companion object {
|
||||
val empty = ServerAddress(
|
||||
fun empty(serverProtocol: ServerProtocol) = ServerAddress(
|
||||
serverProtocol = serverProtocol,
|
||||
hostnames = emptyList(),
|
||||
port = "",
|
||||
keyHash = "",
|
||||
basicAuth = ""
|
||||
)
|
||||
val sampleData = ServerAddress(
|
||||
serverProtocol = ServerProtocol.SMP,
|
||||
hostnames = listOf("smp.simplex.im", "1234.onion"),
|
||||
port = "",
|
||||
keyHash = "LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=",
|
||||
@@ -3005,8 +3042,8 @@ sealed class CR {
|
||||
@Serializable @SerialName("chatStopped") class ChatStopped: CR()
|
||||
@Serializable @SerialName("apiChats") class ApiChats(val user: User, val chats: List<Chat>): CR()
|
||||
@Serializable @SerialName("apiChat") class ApiChat(val user: User, val chat: Chat): CR()
|
||||
@Serializable @SerialName("userSMPServers") class UserSMPServers(val user: User, val smpServers: List<ServerCfg>, val presetSMPServers: List<String>): CR()
|
||||
@Serializable @SerialName("smpTestResult") class SmpTestResult(val user: User, val smpTestFailure: SMPTestFailure? = null): CR()
|
||||
@Serializable @SerialName("userProtoServers") class UserProtoServers(val user: User, val servers: UserProtocolServers): CR()
|
||||
@Serializable @SerialName("serverTestResult") class ServerTestResult(val user: User, val testServer: String, val testFailure: ProtocolTestFailure? = null): CR()
|
||||
@Serializable @SerialName("chatItemTTL") class ChatItemTTL(val user: User, val chatItemTTL: Long? = null): CR()
|
||||
@Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR()
|
||||
@Serializable @SerialName("contactInfo") class ContactInfo(val user: User, val contact: Contact, val connectionStats: ConnectionStats, val customUserProfile: Profile? = null): CR()
|
||||
@@ -3113,8 +3150,8 @@ sealed class CR {
|
||||
is ChatStopped -> "chatStopped"
|
||||
is ApiChats -> "apiChats"
|
||||
is ApiChat -> "apiChat"
|
||||
is UserSMPServers -> "userSMPServers"
|
||||
is SmpTestResult -> "smpTestResult"
|
||||
is UserProtoServers -> "userProtoServers"
|
||||
is ServerTestResult -> "serverTestResult"
|
||||
is ChatItemTTL -> "chatItemTTL"
|
||||
is NetworkConfig -> "networkConfig"
|
||||
is ContactInfo -> "contactInfo"
|
||||
@@ -3219,8 +3256,8 @@ sealed class CR {
|
||||
is ChatStopped -> noDetails()
|
||||
is ApiChats -> withUser(user, json.encodeToString(chats))
|
||||
is ApiChat -> withUser(user, json.encodeToString(chat))
|
||||
is UserSMPServers -> withUser(user, "$smpServers: ${json.encodeToString(smpServers)}\n$presetSMPServers: ${json.encodeToString(presetSMPServers)}")
|
||||
is SmpTestResult -> withUser(user, json.encodeToString(smpTestFailure))
|
||||
is UserProtoServers -> withUser(user, "servers: ${json.encodeToString(servers)}")
|
||||
is ServerTestResult -> withUser(user, "server: $testServer\nresult: ${json.encodeToString(testFailure)}")
|
||||
is ChatItemTTL -> withUser(user, json.encodeToString(chatItemTTL))
|
||||
is NetworkConfig -> json.encodeToString(networkConfig)
|
||||
is ContactInfo -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats)}")
|
||||
@@ -3453,6 +3490,7 @@ sealed class AgentErrorType {
|
||||
is CMD -> "CMD ${cmdErr.string}"
|
||||
is CONN -> "CONN ${connErr.string}"
|
||||
is SMP -> "SMP ${smpErr.string}"
|
||||
is XFTP -> "XFTP ${xftpErr.string}"
|
||||
is BROKER -> "BROKER ${brokerErr.string}"
|
||||
is AGENT -> "AGENT ${agentErr.string}"
|
||||
is INTERNAL -> "INTERNAL $internalErr"
|
||||
@@ -3460,6 +3498,7 @@ sealed class AgentErrorType {
|
||||
@Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("SMP") class SMP(val smpErr: SMPErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("XFTP") class XFTP(val xftpErr: XFTPErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("BROKER") class BROKER(val brokerAddress: String, val brokerErr: BrokerErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("AGENT") class AGENT(val agentErr: SMPAgentError): AgentErrorType()
|
||||
@Serializable @SerialName("INTERNAL") class INTERNAL(val internalErr: String): AgentErrorType()
|
||||
@@ -3527,7 +3566,7 @@ sealed class SMPErrorType {
|
||||
}
|
||||
@Serializable @SerialName("BLOCK") class BLOCK: SMPErrorType()
|
||||
@Serializable @SerialName("SESSION") class SESSION: SMPErrorType()
|
||||
@Serializable @SerialName("CMD") class CMD(val cmdErr: SMPCommandError): SMPErrorType()
|
||||
@Serializable @SerialName("CMD") class CMD(val cmdErr: ProtocolCommandError): SMPErrorType()
|
||||
@Serializable @SerialName("AUTH") class AUTH: SMPErrorType()
|
||||
@Serializable @SerialName("QUOTA") class QUOTA: SMPErrorType()
|
||||
@Serializable @SerialName("NO_MSG") class NO_MSG: SMPErrorType()
|
||||
@@ -3536,7 +3575,7 @@ sealed class SMPErrorType {
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class SMPCommandError {
|
||||
sealed class ProtocolCommandError {
|
||||
val string: String get() = when (this) {
|
||||
is UNKNOWN -> "UNKNOWN"
|
||||
is SYNTAX -> "SYNTAX"
|
||||
@@ -3544,11 +3583,11 @@ sealed class SMPCommandError {
|
||||
is HAS_AUTH -> "HAS_AUTH"
|
||||
is NO_QUEUE -> "NO_QUEUE"
|
||||
}
|
||||
@Serializable @SerialName("UNKNOWN") class UNKNOWN: SMPCommandError()
|
||||
@Serializable @SerialName("SYNTAX") class SYNTAX: SMPCommandError()
|
||||
@Serializable @SerialName("NO_AUTH") class NO_AUTH: SMPCommandError()
|
||||
@Serializable @SerialName("HAS_AUTH") class HAS_AUTH: SMPCommandError()
|
||||
@Serializable @SerialName("NO_QUEUE") class NO_QUEUE: SMPCommandError()
|
||||
@Serializable @SerialName("UNKNOWN") class UNKNOWN: ProtocolCommandError()
|
||||
@Serializable @SerialName("SYNTAX") class SYNTAX: ProtocolCommandError()
|
||||
@Serializable @SerialName("NO_AUTH") class NO_AUTH: ProtocolCommandError()
|
||||
@Serializable @SerialName("HAS_AUTH") class HAS_AUTH: ProtocolCommandError()
|
||||
@Serializable @SerialName("NO_QUEUE") class NO_QUEUE: ProtocolCommandError()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -3590,3 +3629,33 @@ sealed class SMPAgentError {
|
||||
@Serializable @SerialName("A_VERSION") class A_VERSION: SMPAgentError()
|
||||
@Serializable @SerialName("A_ENCRYPTION") class A_ENCRYPTION: SMPAgentError()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class XFTPErrorType {
|
||||
val string: String get() = when (this) {
|
||||
is BLOCK -> "BLOCK"
|
||||
is SESSION -> "SESSION"
|
||||
is CMD -> "CMD ${cmdErr.string}"
|
||||
is AUTH -> "AUTH"
|
||||
is SIZE -> "SIZE"
|
||||
is QUOTA -> "QUOTA"
|
||||
is DIGEST -> "DIGEST"
|
||||
is CRYPTO -> "CRYPTO"
|
||||
is NO_FILE -> "NO_FILE"
|
||||
is HAS_FILE -> "HAS_FILE"
|
||||
is FILE_IO -> "FILE_IO"
|
||||
is INTERNAL -> "INTERNAL"
|
||||
}
|
||||
@Serializable @SerialName("BLOCK") object BLOCK: XFTPErrorType()
|
||||
@Serializable @SerialName("SESSION") object SESSION: XFTPErrorType()
|
||||
@Serializable @SerialName("CMD") class CMD(val cmdErr: ProtocolCommandError): XFTPErrorType()
|
||||
@Serializable @SerialName("AUTH") object AUTH: XFTPErrorType()
|
||||
@Serializable @SerialName("SIZE") object SIZE: XFTPErrorType()
|
||||
@Serializable @SerialName("QUOTA") object QUOTA: XFTPErrorType()
|
||||
@Serializable @SerialName("DIGEST") object DIGEST: XFTPErrorType()
|
||||
@Serializable @SerialName("CRYPTO") object CRYPTO: XFTPErrorType()
|
||||
@Serializable @SerialName("NO_FILE") object NO_FILE: XFTPErrorType()
|
||||
@Serializable @SerialName("HAS_FILE") object HAS_FILE: XFTPErrorType()
|
||||
@Serializable @SerialName("FILE_IO") object FILE_IO: XFTPErrorType()
|
||||
@Serializable @SerialName("INTERNAL") object INTERNAL: XFTPErrorType()
|
||||
}
|
||||
+10
-1
@@ -40,6 +40,7 @@ fun NetworkAndServersView(
|
||||
|
||||
NetworkAndServersLayout(
|
||||
developerTools = developerTools,
|
||||
xftpSendEnabled = remember { chatModel.controller.appPrefs.xftpSendEnabled.state },
|
||||
networkUseSocksProxy = networkUseSocksProxy,
|
||||
onionHosts = onionHosts,
|
||||
sessionMode = sessionMode,
|
||||
@@ -135,6 +136,7 @@ fun NetworkAndServersView(
|
||||
|
||||
@Composable fun NetworkAndServersLayout(
|
||||
developerTools: Boolean,
|
||||
xftpSendEnabled: State<Boolean>,
|
||||
networkUseSocksProxy: MutableState<Boolean>,
|
||||
onionHosts: MutableState<OnionHosts>,
|
||||
sessionMode: MutableState<TransportSessionMode>,
|
||||
@@ -152,8 +154,14 @@ fun NetworkAndServersView(
|
||||
) {
|
||||
AppBarTitle(stringResource(R.string.network_and_servers))
|
||||
SectionView(generalGetString(R.string.settings_section_title_messages)) {
|
||||
SettingsActionItem(Icons.Outlined.Dns, stringResource(R.string.smp_servers), showCustomModal { m, close -> SMPServersView(m, close) })
|
||||
SettingsActionItem(Icons.Outlined.Dns, stringResource(R.string.smp_servers), showCustomModal { m, close -> ProtocolServersView(m, ServerProtocol.SMP, close) })
|
||||
SectionDivider()
|
||||
|
||||
if (xftpSendEnabled.value) {
|
||||
SettingsActionItem(Icons.Outlined.Dns, stringResource(R.string.xftp_servers), showCustomModal { m, close -> ProtocolServersView(m, ServerProtocol.XFTP, close) })
|
||||
SectionDivider()
|
||||
}
|
||||
|
||||
SectionItemView {
|
||||
UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy)
|
||||
}
|
||||
@@ -297,6 +305,7 @@ fun PreviewNetworkAndServersLayout() {
|
||||
SimpleXTheme {
|
||||
NetworkAndServersLayout(
|
||||
developerTools = true,
|
||||
xftpSendEnabled = remember { mutableStateOf(true) },
|
||||
networkUseSocksProxy = remember { mutableStateOf(true) },
|
||||
showModal = { {} },
|
||||
showSettingsModal = { {} },
|
||||
|
||||
+16
-7
@@ -32,12 +32,13 @@ import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun SMPServerView(m: ChatModel, server: ServerCfg, onUpdate: (ServerCfg) -> Unit, onDelete: () -> Unit) {
|
||||
fun ProtocolServerView(m: ChatModel, server: ServerCfg, serverProtocol: ServerProtocol, onUpdate: (ServerCfg) -> Unit, onDelete: () -> Unit) {
|
||||
var testing by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
SMPServerLayout(
|
||||
ProtocolServerLayout(
|
||||
testing,
|
||||
server,
|
||||
serverProtocol,
|
||||
testServer = {
|
||||
testing = true
|
||||
scope.launch {
|
||||
@@ -68,9 +69,10 @@ fun SMPServerView(m: ChatModel, server: ServerCfg, onUpdate: (ServerCfg) -> Unit
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SMPServerLayout(
|
||||
private fun ProtocolServerLayout(
|
||||
testing: Boolean,
|
||||
server: ServerCfg,
|
||||
serverProtocol: ServerProtocol,
|
||||
testServer: () -> Unit,
|
||||
onUpdate: (ServerCfg) -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
@@ -86,7 +88,7 @@ private fun SMPServerLayout(
|
||||
if (server.preset) {
|
||||
PresetServer(testing, server, testServer, onUpdate, onDelete)
|
||||
} else {
|
||||
CustomServer(testing, server, testServer, onUpdate, onDelete)
|
||||
CustomServer(testing, server, serverProtocol, testServer, onUpdate, onDelete)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,12 +121,19 @@ private fun PresetServer(
|
||||
private fun CustomServer(
|
||||
testing: Boolean,
|
||||
server: ServerCfg,
|
||||
serverProtocol: ServerProtocol,
|
||||
testServer: () -> Unit,
|
||||
onUpdate: (ServerCfg) -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
) {
|
||||
val serverAddress = remember { mutableStateOf(server.server) }
|
||||
val valid = remember { derivedStateOf { parseServerAddress(serverAddress.value)?.valid == true } }
|
||||
val valid = remember {
|
||||
derivedStateOf {
|
||||
with(parseServerAddress(serverAddress.value)) {
|
||||
this?.valid == true && this.serverProtocol == serverProtocol
|
||||
}
|
||||
}
|
||||
}
|
||||
SectionView(
|
||||
stringResource(R.string.smp_servers_your_server_address).uppercase(),
|
||||
icon = Icons.Outlined.ErrorOutline,
|
||||
@@ -187,9 +196,9 @@ fun ShowTestStatus(server: ServerCfg, modifier: Modifier = Modifier) =
|
||||
else -> Icon(Icons.Outlined.Check, null, modifier, tint = Color.Transparent)
|
||||
}
|
||||
|
||||
suspend fun testServerConnection(server: ServerCfg, m: ChatModel): Pair<ServerCfg, SMPTestFailure?> =
|
||||
suspend fun testServerConnection(server: ServerCfg, m: ChatModel): Pair<ServerCfg, ProtocolTestFailure?> =
|
||||
try {
|
||||
val r = m.controller.testSMPServer(server.server)
|
||||
val r = m.controller.testProtoServer(server.server)
|
||||
server.copy(tested = r == null) to r
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "testServerConnection ${e.stackTraceToString()}")
|
||||
+43
-27
@@ -27,17 +27,19 @@ import chat.simplex.app.views.helpers.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun SMPServersView(m: ChatModel, close: () -> Unit) {
|
||||
fun ProtocolServersView(m: ChatModel, serverProtocol: ServerProtocol, close: () -> Unit) {
|
||||
var presetServers by remember { mutableStateOf(emptyList<String>()) }
|
||||
var servers by remember {
|
||||
mutableStateOf(m.userSMPServersUnsaved.value ?: m.userSMPServers.value ?: emptyList())
|
||||
mutableStateOf(m.userSMPServersUnsaved.value ?: emptyList())
|
||||
}
|
||||
val currServers = remember { mutableStateOf(servers) }
|
||||
val testing = rememberSaveable { mutableStateOf(false) }
|
||||
val serversUnchanged = remember { derivedStateOf { servers == m.userSMPServers.value || testing.value } }
|
||||
val serversUnchanged = remember { derivedStateOf { servers == currServers.value || testing.value } }
|
||||
val allServersDisabled = remember { derivedStateOf { servers.all { !it.enabled } } }
|
||||
val saveDisabled = remember {
|
||||
derivedStateOf {
|
||||
servers.isEmpty() ||
|
||||
servers == m.userSMPServers.value ||
|
||||
servers == currServers.value ||
|
||||
testing.value ||
|
||||
!servers.all { srv ->
|
||||
val address = parseServerAddress(srv.server)
|
||||
@@ -47,13 +49,25 @@ fun SMPServersView(m: ChatModel, close: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
val res = m.controller.getUserProtoServers(serverProtocol)
|
||||
if (res != null) {
|
||||
currServers.value = res.protoServers
|
||||
presetServers = res.presetServers
|
||||
if (servers.isEmpty()) {
|
||||
servers = currServers.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showServer(server: ServerCfg) {
|
||||
ModalManager.shared.showModalCloseable(true) { close ->
|
||||
var old by remember { mutableStateOf(server) }
|
||||
val index = servers.indexOf(old)
|
||||
SMPServerView(
|
||||
ProtocolServerView(
|
||||
m,
|
||||
old,
|
||||
serverProtocol,
|
||||
onUpdate = { updated ->
|
||||
val newServers = ArrayList(servers)
|
||||
newServers.removeAt(index)
|
||||
@@ -75,11 +89,12 @@ fun SMPServersView(m: ChatModel, close: () -> Unit) {
|
||||
ModalView(
|
||||
close = {
|
||||
if (saveDisabled.value) close()
|
||||
else showUnsavedChangesAlert({ saveSMPServers(servers, m, close) }, close)
|
||||
else showUnsavedChangesAlert({ saveServers(serverProtocol, currServers, servers, m, close) }, close)
|
||||
},
|
||||
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
|
||||
) {
|
||||
SMPServersLayout(
|
||||
ProtocolServersLayout(
|
||||
serverProtocol,
|
||||
testing = testing.value,
|
||||
servers = servers,
|
||||
serversUnchanged = serversUnchanged.value,
|
||||
@@ -102,7 +117,7 @@ fun SMPServersView(m: ChatModel, close: () -> Unit) {
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
ModalManager.shared.showModalCloseable { close ->
|
||||
ScanSMPServer {
|
||||
ScanProtocolServer {
|
||||
close()
|
||||
servers = servers + it
|
||||
m.userSMPServersUnsaved.value = servers
|
||||
@@ -112,11 +127,11 @@ fun SMPServersView(m: ChatModel, close: () -> Unit) {
|
||||
) {
|
||||
Text(stringResource(R.string.smp_servers_scan_qr))
|
||||
}
|
||||
val hasAllPresets = hasAllPresets(servers, m)
|
||||
val hasAllPresets = hasAllPresets(presetServers, servers, m)
|
||||
if (!hasAllPresets) {
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
servers = (servers + addAllPresets(servers, m)).sortedByDescending { it.preset }
|
||||
servers = (servers + addAllPresets(presetServers, servers, m)).sortedByDescending { it.preset }
|
||||
}) {
|
||||
Text(stringResource(R.string.smp_servers_preset_add), color = MaterialTheme.colors.onBackground)
|
||||
}
|
||||
@@ -134,11 +149,11 @@ fun SMPServersView(m: ChatModel, close: () -> Unit) {
|
||||
}
|
||||
},
|
||||
resetServers = {
|
||||
servers = m.userSMPServers.value ?: emptyList()
|
||||
servers = currServers.value ?: emptyList()
|
||||
m.userSMPServersUnsaved.value = null
|
||||
},
|
||||
saveSMPServers = {
|
||||
saveSMPServers(servers, m)
|
||||
saveServers(serverProtocol, currServers, servers, m)
|
||||
},
|
||||
showServer = ::showServer,
|
||||
)
|
||||
@@ -161,7 +176,8 @@ fun SMPServersView(m: ChatModel, close: () -> Unit) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SMPServersLayout(
|
||||
private fun ProtocolServersLayout(
|
||||
serverProtocol: ServerProtocol,
|
||||
testing: Boolean,
|
||||
servers: List<ServerCfg>,
|
||||
serversUnchanged: Boolean,
|
||||
@@ -180,12 +196,12 @@ private fun SMPServersLayout(
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(bottom = DEFAULT_PADDING),
|
||||
) {
|
||||
AppBarTitle(stringResource(R.string.your_SMP_servers))
|
||||
AppBarTitle(stringResource(if (serverProtocol == ServerProtocol.SMP) R.string.your_SMP_servers else R.string.your_XFTP_servers))
|
||||
|
||||
SectionView(stringResource(R.string.smp_servers).uppercase()) {
|
||||
SectionView(stringResource(if (serverProtocol == ServerProtocol.SMP) R.string.smp_servers else R.string.xftp_servers).uppercase()) {
|
||||
for (srv in servers) {
|
||||
SectionItemView({ showServer(srv) }, disabled = testing) {
|
||||
SmpServerView(srv, servers, testing)
|
||||
ProtocolServerView(serverProtocol, srv, servers, testing)
|
||||
}
|
||||
SectionDivider()
|
||||
}
|
||||
@@ -232,10 +248,10 @@ private fun SMPServersLayout(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SmpServerView(srv: ServerCfg, servers: List<ServerCfg>, disabled: Boolean) {
|
||||
private fun ProtocolServerView(serverProtocol: ServerProtocol, srv: ServerCfg, servers: List<ServerCfg>, disabled: Boolean) {
|
||||
val address = parseServerAddress(srv.server)
|
||||
when {
|
||||
address == null || !address.valid || !uniqueAddress(srv, address, servers) -> InvalidServer()
|
||||
address == null || !address.valid || address.serverProtocol != serverProtocol || !uniqueAddress(srv, address, servers) -> InvalidServer()
|
||||
!srv.enabled -> Icon(Icons.Outlined.DoNotDisturb, null, tint = HighOrLowlight)
|
||||
else -> ShowTestStatus(srv)
|
||||
}
|
||||
@@ -271,12 +287,12 @@ private fun uniqueAddress(s: ServerCfg, address: ServerAddress, servers: List<Se
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasAllPresets(servers: List<ServerCfg>, m: ChatModel): Boolean =
|
||||
m.presetSMPServers.value?.all { hasPreset(it, servers) } ?: true
|
||||
private fun hasAllPresets(presetServers: List<String>, servers: List<ServerCfg>, m: ChatModel): Boolean =
|
||||
presetServers.all { hasPreset(it, servers) } ?: true
|
||||
|
||||
private fun addAllPresets(servers: List<ServerCfg>, m: ChatModel): List<ServerCfg> {
|
||||
private fun addAllPresets(presetServers: List<String>, servers: List<ServerCfg>, m: ChatModel): List<ServerCfg> {
|
||||
val toAdd = ArrayList<ServerCfg>()
|
||||
for (srv in m.presetSMPServers.value ?: emptyList()) {
|
||||
for (srv in presetServers) {
|
||||
if (!hasPreset(srv, servers)) {
|
||||
toAdd.add(ServerCfg(srv, preset = true, tested = null, enabled = true))
|
||||
}
|
||||
@@ -313,8 +329,8 @@ private fun resetTestStatus(servers: List<ServerCfg>): List<ServerCfg> {
|
||||
return copy
|
||||
}
|
||||
|
||||
private suspend fun runServersTest(servers: List<ServerCfg>, m: ChatModel, onUpdated: (List<ServerCfg>) -> Unit): Map<String, SMPTestFailure> {
|
||||
val fs: MutableMap<String, SMPTestFailure> = mutableMapOf()
|
||||
private suspend fun runServersTest(servers: List<ServerCfg>, m: ChatModel, onUpdated: (List<ServerCfg>) -> Unit): Map<String, ProtocolTestFailure> {
|
||||
val fs: MutableMap<String, ProtocolTestFailure> = mutableMapOf()
|
||||
val updatedServers = ArrayList<ServerCfg>(servers)
|
||||
for ((index, server) in servers.withIndex()) {
|
||||
if (server.enabled) {
|
||||
@@ -331,10 +347,10 @@ private suspend fun runServersTest(servers: List<ServerCfg>, m: ChatModel, onUpd
|
||||
return fs
|
||||
}
|
||||
|
||||
private fun saveSMPServers(servers: List<ServerCfg>, m: ChatModel, afterSave: () -> Unit = {}) {
|
||||
private fun saveServers(protocol: ServerProtocol, currServers: MutableState<List<ServerCfg>>, servers: List<ServerCfg>, m: ChatModel, afterSave: () -> Unit = {}) {
|
||||
withApi {
|
||||
if (m.controller.setUserSMPServers(servers)) {
|
||||
m.userSMPServers.value = servers
|
||||
if (m.controller.setUserProtoServers(protocol, servers)) {
|
||||
currServers.value = servers
|
||||
m.userSMPServersUnsaved.value = null
|
||||
}
|
||||
afterSave()
|
||||
+3
-4
@@ -2,7 +2,6 @@ package chat.simplex.app.views.usersettings
|
||||
|
||||
import android.Manifest
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -17,16 +16,16 @@ import chat.simplex.app.views.newchat.QRCodeScanner
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
|
||||
@Composable
|
||||
fun ScanSMPServer(onNext: (ServerCfg) -> Unit) {
|
||||
fun ScanProtocolServer(onNext: (ServerCfg) -> Unit) {
|
||||
val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
|
||||
LaunchedEffect(Unit) {
|
||||
cameraPermissionState.launchPermissionRequest()
|
||||
}
|
||||
ScanSMPServerLayout(onNext)
|
||||
ScanProtocolServerLayout(onNext)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScanSMPServerLayout(onNext: (ServerCfg) -> Unit) {
|
||||
private fun ScanProtocolServerLayout(onNext: (ServerCfg) -> Unit) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
@@ -60,7 +60,11 @@
|
||||
|
||||
<!-- SimpleXAPI.kt -->
|
||||
<string name="error_saving_smp_servers">Error saving SMP servers</string>
|
||||
<string name="error_saving_xftp_servers">Error saving XFTP servers</string>
|
||||
<string name="ensure_smp_server_address_are_correct_format_and_unique">Make sure SMP server addresses are in correct format, line separated and are not duplicated.</string>
|
||||
<string name="ensure_xftp_server_address_are_correct_format_and_unique">Make sure XFTP server addresses are in correct format, line separated and are not duplicated.</string>
|
||||
<string name="error_loading_smp_servers">Error loading SMP servers</string>
|
||||
<string name="error_loading_xftp_servers">Error loading XFTP servers</string>
|
||||
<string name="error_setting_network_config">Error updating network configuration</string>
|
||||
<string name="failed_to_parse_chat_title">Failed to load chat</string>
|
||||
<string name="failed_to_parse_chats_title">Failed to load chats</string>
|
||||
@@ -96,12 +100,18 @@
|
||||
<string name="error_changing_address">Error changing address</string>
|
||||
<string name="error_smp_test_failed_at_step">Test failed at step %s.</string>
|
||||
<string name="error_smp_test_server_auth">Server requires authorization to create queues, check password</string>
|
||||
<string name="error_xftp_test_server_auth">Server requires authorization to upload, check password</string>
|
||||
<string name="error_smp_test_certificate">Possibly, certificate fingerprint in server address is incorrect</string>
|
||||
<string name="smp_server_test_connect">Connect</string>
|
||||
<string name="smp_server_test_disconnect">Disconnect</string>
|
||||
<string name="smp_server_test_create_queue">Create queue</string>
|
||||
<string name="smp_server_test_secure_queue">Secure queue</string>
|
||||
<string name="smp_server_test_delete_queue">Delete queue</string>
|
||||
<string name="smp_server_test_disconnect">Disconnect</string>
|
||||
<string name="smp_server_test_create_file">Create file</string>
|
||||
<string name="smp_server_test_upload_file">Upload file</string>
|
||||
<string name="smp_server_test_download_file">Download file</string>
|
||||
<string name="smp_server_test_compare_file">Compare file</string>
|
||||
<string name="smp_server_test_delete_file">Delete file</string>
|
||||
<string name="error_deleting_user">Error deleting user profile</string>
|
||||
<string name="error_updating_user_privacy">Error updating user privacy</string>
|
||||
|
||||
@@ -476,12 +486,14 @@
|
||||
<string name="smp_servers_delete_server">Delete server</string>
|
||||
<string name="smp_servers_per_user">The servers for new connections of your current chat profile</string>
|
||||
<string name="smp_save_servers_question">Save servers?</string>
|
||||
<string name="xftp_servers">XFTP servers</string>
|
||||
<string name="install_simplex_chat_for_terminal">Install <xliff:g id="appNameFull">SimpleX Chat</xliff:g> for terminal</string>
|
||||
<string name="star_on_github">Star on GitHub</string>
|
||||
<string name="contribute">Contribute</string>
|
||||
<string name="rate_the_app">Rate the app</string>
|
||||
<string name="use_simplex_chat_servers__question">Use <xliff:g id="appNameFull">SimpleX Chat</xliff:g> servers?</string>
|
||||
<string name="your_SMP_servers">Your SMP servers</string>
|
||||
<string name="your_XFTP_servers">Your XFTP servers</string>
|
||||
<string name="using_simplex_chat_servers">Using <xliff:g id="appNameFull">SimpleX Chat</xliff:g> servers.</string>
|
||||
<string name="how_to">How to</string>
|
||||
<string name="how_to_use_your_servers">How to use your servers</string>
|
||||
@@ -732,7 +744,7 @@
|
||||
<string name="settings_section_title_language" translatable="false">LANGUAGE</string>
|
||||
<string name="settings_section_title_icon">APP ICON</string>
|
||||
<string name="settings_section_title_themes">THEMES</string>
|
||||
<string name="settings_section_title_messages">MESSAGES</string>
|
||||
<string name="settings_section_title_messages">MESSAGES AND FILES</string>
|
||||
<string name="settings_section_title_calls">CALLS</string>
|
||||
<string name="settings_section_title_incognito">Incognito mode</string>
|
||||
<string name="settings_section_title_experimenta">EXPERIMENTAL</string>
|
||||
|
||||
@@ -745,6 +745,7 @@ func apiReceiveFile(fileId: Int64, inline: Bool? = nil) async -> AChatItem? {
|
||||
func cancelFile(user: User, fileId: Int64) async {
|
||||
if let chatItem = await apiCancelFile(fileId: fileId) {
|
||||
DispatchQueue.main.async { chatItemSimpleUpdate(user, chatItem) }
|
||||
cleanupFile(chatItem)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1329,31 +1330,22 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
case let .rcvFileSndCancelled(user, aChatItem, _):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
cleanupFile(aChatItem)
|
||||
case let .rcvFileProgressXFTP(user, aChatItem, _, _):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
case let .sndFileStart(user, aChatItem, _):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
case let .sndFileComplete(user, aChatItem, _):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
let cItem = aChatItem.chatItem
|
||||
let mc = cItem.content.msgContent
|
||||
if aChatItem.chatInfo.chatType == .direct,
|
||||
case .file = mc,
|
||||
let fileName = cItem.file?.filePath {
|
||||
removeFile(fileName)
|
||||
}
|
||||
cleanupDirectFile(aChatItem)
|
||||
case let .sndFileRcvCancelled(user, aChatItem, _):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
cleanupDirectFile(aChatItem)
|
||||
case let .sndFileProgressXFTP(user, aChatItem, _, _, _):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
case let .sndFileCompleteXFTP(user, aChatItem, _):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
let cItem = aChatItem.chatItem
|
||||
let mc = cItem.content.msgContent
|
||||
if case .file = mc,
|
||||
let fileName = cItem.file?.filePath {
|
||||
removeFile(fileName)
|
||||
}
|
||||
cleanupFile(aChatItem)
|
||||
case let .callInvitation(invitation):
|
||||
m.callInvitations[invitation.contact.id] = invitation
|
||||
activateCall(invitation)
|
||||
|
||||
@@ -177,7 +177,7 @@ struct ProtocolServersView: View {
|
||||
HStack {
|
||||
Group {
|
||||
if let address = address {
|
||||
if !address.valid {
|
||||
if !address.valid || address.serverProtocol != serverProtocol {
|
||||
invalidServer()
|
||||
} else if !uniqueAddress(srv, address) {
|
||||
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
|
||||
|
||||
@@ -292,6 +292,18 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
|
||||
}
|
||||
}
|
||||
return cItem.showMutableNotification ? (aChatItem.chatId, .nse(notification: createMessageReceivedNtf(user, cInfo, cItem))) : nil
|
||||
case let .rcvFileSndCancelled(_, aChatItem, _):
|
||||
cleanupFile(aChatItem)
|
||||
return nil
|
||||
case let .sndFileComplete(_, aChatItem, _):
|
||||
cleanupDirectFile(aChatItem)
|
||||
return nil
|
||||
case let .sndFileRcvCancelled(_, aChatItem, _):
|
||||
cleanupDirectFile(aChatItem)
|
||||
return nil
|
||||
case let .sndFileCompleteXFTP(_, aChatItem, _):
|
||||
cleanupFile(aChatItem)
|
||||
return nil
|
||||
case let .callInvitation(invitation):
|
||||
// Do not post it without CallKit support, iOS will stop launching the app without showing CallKit
|
||||
return (
|
||||
|
||||
@@ -99,11 +99,11 @@
|
||||
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E42868AA7F001FD2EF /* SuspendChat.swift */; };
|
||||
5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E62868D76D001FD2EF /* NotificationsView.swift */; };
|
||||
5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */; };
|
||||
5CB6348E29DE261C0066AD6B /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB6348929DE261C0066AD6B /* libgmpxx.a */; };
|
||||
5CB6348F29DE261C0066AD6B /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB6348A29DE261C0066AD6B /* libffi.a */; };
|
||||
5CB6349029DE261C0066AD6B /* libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB6348B29DE261C0066AD6B /* libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i.a */; };
|
||||
5CB6349129DE261C0066AD6B /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB6348C29DE261C0066AD6B /* libgmp.a */; };
|
||||
5CB6349229DE261C0066AD6B /* libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB6348D29DE261C0066AD6B /* libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i-ghc8.10.7.a */; };
|
||||
5CB6349829DF7CF00066AD6B /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB6349329DF7CF00066AD6B /* libgmpxx.a */; };
|
||||
5CB6349929DF7CF00066AD6B /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB6349429DF7CF00066AD6B /* libffi.a */; };
|
||||
5CB6349A29DF7CF00066AD6B /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB6349529DF7CF00066AD6B /* libgmp.a */; };
|
||||
5CB6349B29DF7CF00066AD6B /* libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB6349629DF7CF00066AD6B /* libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD-ghc8.10.7.a */; };
|
||||
5CB6349C29DF7CF00066AD6B /* libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB6349729DF7CF00066AD6B /* libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD.a */; };
|
||||
5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D627A8563F00ACCCDD /* SettingsView.swift */; };
|
||||
5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E027A867BA00ACCCDD /* UserProfile.swift */; };
|
||||
5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E327A8683A00ACCCDD /* UserAddress.swift */; };
|
||||
@@ -355,11 +355,11 @@
|
||||
5CB346E42868AA7F001FD2EF /* SuspendChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuspendChat.swift; sourceTree = "<group>"; };
|
||||
5CB346E62868D76D001FD2EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
|
||||
5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushEnvironment.swift; sourceTree = "<group>"; };
|
||||
5CB6348929DE261C0066AD6B /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CB6348A29DE261C0066AD6B /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CB6348B29DE261C0066AD6B /* libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i.a"; sourceTree = "<group>"; };
|
||||
5CB6348C29DE261C0066AD6B /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CB6348D29DE261C0066AD6B /* libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5CB6349329DF7CF00066AD6B /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CB6349429DF7CF00066AD6B /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CB6349529DF7CF00066AD6B /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CB6349629DF7CF00066AD6B /* libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5CB6349729DF7CF00066AD6B /* libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD.a"; sourceTree = "<group>"; };
|
||||
5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||
5CB924E027A867BA00ACCCDD /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = "<group>"; };
|
||||
5CB924E327A8683A00ACCCDD /* UserAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddress.swift; sourceTree = "<group>"; };
|
||||
@@ -471,13 +471,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CB6348E29DE261C0066AD6B /* libgmpxx.a in Frameworks */,
|
||||
5CB6349C29DF7CF00066AD6B /* libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD.a in Frameworks */,
|
||||
5CB6349B29DF7CF00066AD6B /* libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD-ghc8.10.7.a in Frameworks */,
|
||||
5CB6349829DF7CF00066AD6B /* libgmpxx.a in Frameworks */,
|
||||
5CB6349A29DF7CF00066AD6B /* libgmp.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5CB6349029DE261C0066AD6B /* libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5CB6349129DE261C0066AD6B /* libgmp.a in Frameworks */,
|
||||
5CB6349229DE261C0066AD6B /* libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i-ghc8.10.7.a in Frameworks */,
|
||||
5CB6348F29DE261C0066AD6B /* libffi.a in Frameworks */,
|
||||
5CB6349929DF7CF00066AD6B /* libffi.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -536,11 +536,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CB6348A29DE261C0066AD6B /* libffi.a */,
|
||||
5CB6348C29DE261C0066AD6B /* libgmp.a */,
|
||||
5CB6348929DE261C0066AD6B /* libgmpxx.a */,
|
||||
5CB6348D29DE261C0066AD6B /* libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i-ghc8.10.7.a */,
|
||||
5CB6348B29DE261C0066AD6B /* libHSsimplex-chat-4.6.1.1-44qOXuEu6Xh4AokU7t538i.a */,
|
||||
5CB6349429DF7CF00066AD6B /* libffi.a */,
|
||||
5CB6349529DF7CF00066AD6B /* libgmp.a */,
|
||||
5CB6349329DF7CF00066AD6B /* libgmpxx.a */,
|
||||
5CB6349629DF7CF00066AD6B /* libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD-ghc8.10.7.a */,
|
||||
5CB6349729DF7CF00066AD6B /* libHSsimplex-chat-4.6.1.1-KdibHfABD5wCx2UMHpStjD.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
||||
@@ -602,7 +602,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .apiChats(u, chats): return withUser(u, String(describing: chats))
|
||||
case let .apiChat(u, chat): return withUser(u, String(describing: chat))
|
||||
case let .userProtoServers(u, servers): return withUser(u, "servers: \(String(describing: servers))")
|
||||
case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\result: \(String(describing: testFailure))")
|
||||
case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))")
|
||||
case let .chatItemTTL(u, chatItemTTL): return withUser(u, String(describing: chatItemTTL))
|
||||
case let .networkConfig(networkConfig): return String(describing: networkConfig)
|
||||
case let .contactInfo(u, contact, connectionStats, customUserProfile): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))\ncustomUserProfile: \(String(describing: customUserProfile))")
|
||||
@@ -891,13 +891,13 @@ public struct ProtocolTestFailure: Decodable, Error, Equatable {
|
||||
}
|
||||
|
||||
public struct ServerAddress: Decodable {
|
||||
public var serverProtocol: ServerProtocol?
|
||||
public var serverProtocol: ServerProtocol
|
||||
public var hostnames: [String]
|
||||
public var port: String
|
||||
public var keyHash: String
|
||||
public var basicAuth: String
|
||||
|
||||
public init(serverProtocol: ServerProtocol?, hostnames: [String], port: String, keyHash: String, basicAuth: String = "") {
|
||||
public init(serverProtocol: ServerProtocol, hostnames: [String], port: String, keyHash: String, basicAuth: String = "") {
|
||||
self.serverProtocol = serverProtocol
|
||||
self.hostnames = hostnames
|
||||
self.port = port
|
||||
@@ -906,7 +906,7 @@ public struct ServerAddress: Decodable {
|
||||
}
|
||||
|
||||
public var uri: String {
|
||||
"\(serverProtocol?.rawValue ?? "smp")://\(keyHash)\(basicAuth == "" ? "" : ":" + basicAuth)@\(hostnames.joined(separator: ","))"
|
||||
"\(serverProtocol)://\(keyHash)\(basicAuth == "" ? "" : ":" + basicAuth)@\(hostnames.joined(separator: ","))"
|
||||
}
|
||||
|
||||
public var valid: Bool {
|
||||
|
||||
@@ -200,6 +200,21 @@ public func removeFile(_ fileName: String) {
|
||||
}
|
||||
}
|
||||
|
||||
public func cleanupDirectFile(_ aChatItem: AChatItem) {
|
||||
if aChatItem.chatInfo.chatType == .direct {
|
||||
cleanupFile(aChatItem)
|
||||
}
|
||||
}
|
||||
|
||||
public func cleanupFile(_ aChatItem: AChatItem) {
|
||||
let cItem = aChatItem.chatItem
|
||||
let mc = cItem.content.msgContent
|
||||
if case .file = mc,
|
||||
let fileName = cItem.file?.filePath {
|
||||
removeFile(fileName)
|
||||
}
|
||||
}
|
||||
|
||||
public func getMaxFileSize(_ fileProtocol: FileProtocol) -> Int64 {
|
||||
switch fileProtocol {
|
||||
case .xftp: return MAX_FILE_SIZE_XFTP
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 4.6.1.1
|
||||
version: 4.6.1.2
|
||||
#synopsis:
|
||||
#description:
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 4.6.1.1
|
||||
version: 4.6.1.2
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
|
||||
Reference in New Issue
Block a user