Merge branch 'master' into master-ios

This commit is contained in:
Evgeny Poberezkin
2023-04-07 23:23:26 +01:00
15 changed files with 289 additions and 158 deletions
@@ -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()
}
@@ -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 = { {} },
@@ -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()}")
@@ -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()
@@ -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>
+5 -13
View File
@@ -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 (
+20 -20
View File
@@ -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>";
+4 -4
View File
@@ -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 {
+15
View File
@@ -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
View File
@@ -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
View File
@@ -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