From 7ebf707540c1b1eacfb9df39ed46361fbe0e1b54 Mon Sep 17 00:00:00 2001 From: sim Date: Thu, 31 Jul 2025 16:21:25 +0200 Subject: [PATCH] Allow users to set their NTF servers in server view --- .../chat/simplex/common/model/SimpleXAPI.kt | 33 ++++++++++++++ .../networkAndServers/NetworkAndServers.kt | 9 ++++ .../networkAndServers/NewServerView.kt | 5 ++- .../networkAndServers/OperatorView.kt | 7 ++- .../networkAndServers/ProtocolServersView.kt | 43 +++++++++++++++++-- .../commonMain/resources/MR/base/strings.xml | 4 ++ 6 files changed, 95 insertions(+), 6 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 7cb2d9fe5e..70d302cf10 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -3906,6 +3906,7 @@ class DBEncryptionConfig(val currentKey: String, val newKey: String) @Serializable enum class ServerProtocol { + @SerialName("ntf") NTF, @SerialName("smp") SMP, @SerialName("xftp") XFTP; } @@ -4023,6 +4024,12 @@ data class ServerOperator( val serverDomains: List, val conditionsAcceptance: ConditionsAcceptance, val enabled: Boolean, + /** + * Roles for ntf servers + * + * default to (true, true), for transition from old ServerOperator + */ + val ntfRoles: ServerRoles = ServerRoles(true, true), val smpRoles: ServerRoles, val xftpRoles: ServerRoles, ) { @@ -4044,6 +4051,7 @@ data class ServerOperator( serverDomains = listOf("simplex.im"), conditionsAcceptance = ConditionsAcceptance.Accepted(acceptedAt = null, autoAccepted = false), enabled = true, + ntfRoles = ServerRoles(storage = true, proxy = true), smpRoles = ServerRoles(storage = true, proxy = true), xftpRoles = ServerRoles(storage = true, proxy = true) ) @@ -4061,6 +4069,7 @@ data class ServerOperator( other.serverDomains == this.serverDomains && other.conditionsAcceptance == this.conditionsAcceptance && other.enabled == this.enabled && + other.ntfRoles == this.ntfRoles && other.smpRoles == this.smpRoles && other.xftpRoles == this.xftpRoles } @@ -4073,6 +4082,7 @@ data class ServerOperator( result = 31 * result + serverDomains.hashCode() result = 31 * result + conditionsAcceptance.hashCode() result = 31 * result + enabled.hashCode() + result = 31 * result + ntfRoles.hashCode() result = 31 * result + smpRoles.hashCode() result = 31 * result + xftpRoles.hashCode() return result @@ -4111,6 +4121,7 @@ data class ServerRoles( @Serializable data class UserOperatorServers( val operator: ServerOperator?, + val ntfServers: List = listOf(), val smpServers: List, val xftpServers: List ) { @@ -4126,6 +4137,7 @@ data class UserOperatorServers( serverDomains = emptyList(), conditionsAcceptance = ConditionsAcceptance.Accepted(null, autoAccepted = false), enabled = false, + ntfRoles = ServerRoles(storage = true, proxy = true), smpRoles = ServerRoles(storage = true, proxy = true), xftpRoles = ServerRoles(storage = true, proxy = true) ) @@ -4133,12 +4145,14 @@ data class UserOperatorServers( companion object { val sampleData1 = UserOperatorServers( operator = ServerOperator.sampleData1, + ntfServers = listOf(), smpServers = listOf(UserServer.sampleData.preset), xftpServers = listOf(UserServer.sampleData.xftpPreset) ) val sampleDataNilOperator = UserOperatorServers( operator = null, + ntfServers = listOf(), smpServers = listOf(UserServer.sampleData.preset), xftpServers = listOf(UserServer.sampleData.xftpPreset) ) @@ -4154,6 +4168,7 @@ sealed class UserServersError { val globalError: String? get() = when (this.protocol_) { + ServerProtocol.NTF -> globalNTFError ServerProtocol.SMP -> globalSMPError ServerProtocol.XFTP -> globalXFTPError } @@ -4202,6 +4217,24 @@ sealed class UserServersError { null } + val globalNTFError: String? + get() = if (this.protocol_ == ServerProtocol.SMP) { + when (this) { + is NoServers -> this.user?.let { "${userStr(it)} ${generalGetString(MR.strings.no_notif_servers_configured)}" } + ?: generalGetString(MR.strings.no_notif_servers_configured) + + is StorageMissing -> this.user?.let { "${userStr(it)} ${generalGetString(MR.strings.no_notif_servers_configured_for_receiving)}" } + ?: generalGetString(MR.strings.no_notif_servers_configured_for_receiving) + + is ProxyMissing -> this.user?.let { "${userStr(it)} ${generalGetString(MR.strings.no_notif_servers_configured_for_receiving)}" } + ?: generalGetString(MR.strings.no_notif_servers_configured_for_receiving) + + else -> null + } + } else { + null + } + private fun userStr(user: UserRef): String { return String.format(generalGetString(MR.strings.for_chat_profile), user.localDisplayName) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt index 98f671ddc4..b658734ef7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt @@ -932,6 +932,15 @@ fun globalXFTPServersError(serverErrors: List): String? { return null } +fun globalNTFServersError(serverErrors: List): String? { + for (err in serverErrors) { + if (err.globalNTFError != null) { + return err.globalNTFError + } + } + return null +} + fun findDuplicateHosts(serverErrors: List): Set { val duplicateHostsList = serverErrors.mapNotNull { err -> if (err is UserServersError.DuplicateServer) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt index 1ec2534ab1..c2f659ba0f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt @@ -107,12 +107,15 @@ fun addServer( val operatorServers = updatedUserServers[operatorIndex] // Create a mutable copy of the smpServers or xftpServers and add the server when (serverProtocol) { + ServerProtocol.NTF -> { + // We use a single ntf server + updatedUserServers[operatorIndex] = operatorServers.copy(ntfServers = listOf(server)) + } ServerProtocol.SMP -> { val updatedSMPServers = operatorServers.smpServers.toMutableList() updatedSMPServers.add(server) updatedUserServers[operatorIndex] = operatorServers.copy(smpServers = updatedSMPServers) } - ServerProtocol.XFTP -> { val updatedXFTPServers = operatorServers.xftpServers.toMutableList() updatedXFTPServers.add(server) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index cc72387875..baa667bb8d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -387,14 +387,19 @@ fun OperatorViewLayout( testing = testing, smpServers = userServers.value[operatorIndex].smpServers, xftpServers = userServers.value[operatorIndex].xftpServers, + ntfServers = userServers.value[operatorIndex].ntfServers, ) { p, l -> when (p) { + ServerProtocol.NTF -> userServers.value = userServers.value.toMutableList().apply { + this[operatorIndex] = this[operatorIndex].copy( + ntfServers = l + ) + } ServerProtocol.XFTP -> userServers.value = userServers.value.toMutableList().apply { this[operatorIndex] = this[operatorIndex].copy( xftpServers = l ) } - ServerProtocol.SMP -> userServers.value = userServers.value.toMutableList().apply { this[operatorIndex] = this[operatorIndex].copy( smpServers = l diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt index 63bf8b1dc4..4276df28a6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt @@ -148,7 +148,32 @@ fun YourServersViewLayout( } } + if (userServers.value[operatorIndex].ntfServers.any { !it.deleted }) { + SectionDividerSpaced() + SectionView(generalGetString(MR.strings.notif_servers).uppercase()) { + userServers.value[operatorIndex].ntfServers.forEachIndexed { i, server -> + if (server.deleted) return@forEachIndexed + SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.NTF) }) { + ProtocolServerViewLink( + srv = server, + serverProtocol = ServerProtocol.NTF, + duplicateHosts = duplicateHosts + ) + } + } + } + val ntfErr = globalNTFServersError(serverErrors.value) + if (ntfErr != null) { + SectionCustomFooter { + ServersErrorFooter(ntfErr) + } + } else { + SectionTextFooter(generalGetString(MR.strings.ntf_servers_per_user)) + } + } + if ( + userServers.value[operatorIndex].ntfServers.any { !it.deleted } || userServers.value[operatorIndex].smpServers.any { !it.deleted } || userServers.value[operatorIndex].xftpServers.any { !it.deleted } ) { @@ -178,14 +203,19 @@ fun YourServersViewLayout( testing = testing, smpServers = userServers.value[operatorIndex].smpServers, xftpServers = userServers.value[operatorIndex].xftpServers, + ntfServers = userServers.value[operatorIndex].ntfServers, ) { p, l -> when (p) { + ServerProtocol.NTF -> userServers.value = userServers.value.toMutableList().apply { + this[operatorIndex] = this[operatorIndex].copy( + ntfServers = l + ) + } ServerProtocol.XFTP -> userServers.value = userServers.value.toMutableList().apply { this[operatorIndex] = this[operatorIndex].copy( xftpServers = l ) } - ServerProtocol.SMP -> userServers.value = userServers.value.toMutableList().apply { this[operatorIndex] = this[operatorIndex].copy( smpServers = l @@ -204,16 +234,17 @@ fun YourServersViewLayout( fun TestServersButton( smpServers: List, xftpServers: List, + ntfServers: List, testing: MutableState, onUpdate: (ServerProtocol, List) -> Unit ) { val scope = rememberCoroutineScope() - val disabled = derivedStateOf { (smpServers.none { it.enabled } && xftpServers.none { it.enabled }) || testing.value } + val disabled = derivedStateOf { (smpServers.none { it.enabled } && xftpServers.none { it.enabled } && ntfServers.none { it.enabled }) || testing.value } SectionItemView( { scope.launch { - testServers(testing, smpServers, xftpServers, chatModel, onUpdate) + testServers(testing, smpServers, xftpServers, ntfServers, chatModel, onUpdate) } }, disabled = disabled.value @@ -303,6 +334,7 @@ private suspend fun testServers( testing: MutableState, smpServers: List, xftpServers: List, + ntfServers: List, m: ChatModel, onUpdate: (ServerProtocol, List) -> Unit ) { @@ -310,11 +342,14 @@ private suspend fun testServers( onUpdate(ServerProtocol.SMP, smpResetStatus) val xftpResetStatus = resetTestStatus(xftpServers) onUpdate(ServerProtocol.XFTP, xftpResetStatus) + val ntfResetStatus = resetTestStatus(ntfServers) + onUpdate(ServerProtocol.NTF, ntfResetStatus) testing.value = true val smpFailures = runServersTest(smpResetStatus, m) { onUpdate(ServerProtocol.SMP, it) } val xftpFailures = runServersTest(xftpResetStatus, m) { onUpdate(ServerProtocol.XFTP, it) } + val ntfFailures = runServersTest(ntfResetStatus, m) { onUpdate(ServerProtocol.NTF, it) } testing.value = false - val fs = smpFailures + xftpFailures + val fs = smpFailures + xftpFailures + ntfFailures if (fs.isNotEmpty()) { val msg = fs.map { it.key + ": " + it.value.localizedDescription }.joinToString("\n") AlertManager.shared.showAlertMsg( diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index b5bf2efaff..e9922a0448 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -131,6 +131,8 @@ No media & file servers. No servers to send files. No servers to receive files. + No notification servers. + No servers to notify. For chat profile %s: Errors in servers configuration. Error accepting conditions @@ -898,6 +900,7 @@ The servers for new connections of your current chat profile Save servers? Media & file servers + Notifications servers XFTP servers Configured XFTP servers Other XFTP servers @@ -1921,6 +1924,7 @@ Use for files To send The servers for new files of your current chat profile + The servers for your notifications. Added media & file servers Open conditions Open changes