Allow users to set their NTF servers in server view

This commit is contained in:
sim
2025-07-31 16:21:25 +02:00
parent c3618c1b0d
commit 7ebf707540
6 changed files with 95 additions and 6 deletions
@@ -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<String>,
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<UserServer> = listOf(),
val smpServers: List<UserServer>,
val xftpServers: List<UserServer>
) {
@@ -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)
}
@@ -932,6 +932,15 @@ fun globalXFTPServersError(serverErrors: List<UserServersError>): String? {
return null
}
fun globalNTFServersError(serverErrors: List<UserServersError>): String? {
for (err in serverErrors) {
if (err.globalNTFError != null) {
return err.globalNTFError
}
}
return null
}
fun findDuplicateHosts(serverErrors: List<UserServersError>): Set<String> {
val duplicateHostsList = serverErrors.mapNotNull { err ->
if (err is UserServersError.DuplicateServer) {
@@ -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)
@@ -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
@@ -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<UserServer>,
xftpServers: List<UserServer>,
ntfServers: List<UserServer>,
testing: MutableState<Boolean>,
onUpdate: (ServerProtocol, List<UserServer>) -> 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<Boolean>,
smpServers: List<UserServer>,
xftpServers: List<UserServer>,
ntfServers: List<UserServer>,
m: ChatModel,
onUpdate: (ServerProtocol, List<UserServer>) -> 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(
@@ -131,6 +131,8 @@
<string name="no_media_servers_configured">No media &amp; file servers.</string>
<string name="no_media_servers_configured_for_sending">No servers to send files.</string>
<string name="no_media_servers_configured_for_private_routing">No servers to receive files.</string>
<string name="no_notif_servers_configured">No notification servers.</string>
<string name="no_notif_servers_configured_for_receiving">No servers to notify.</string>
<string name="for_chat_profile">For chat profile %s:</string>
<string name="errors_in_servers_configuration">Errors in servers configuration.</string>
<string name="error_accepting_operator_conditions">Error accepting conditions</string>
@@ -898,6 +900,7 @@
<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="media_and_file_servers">Media &amp; file servers</string>
<string name="notif_servers">Notifications servers</string>
<string name="xftp_servers">XFTP servers</string>
<string name="xftp_servers_configured">Configured XFTP servers</string>
<string name="xftp_servers_other">Other XFTP servers</string>
@@ -1921,6 +1924,7 @@
<string name="operator_use_for_files">Use for files</string>
<string name="operator_use_for_sending">To send</string>
<string name="xftp_servers_per_user">The servers for new files of your current chat profile</string>
<string name="ntf_servers_per_user">The servers for your notifications.</string>
<string name="operator_added_xftp_servers">Added media &amp; file servers</string>
<string name="operator_open_conditions">Open conditions</string>
<string name="operator_open_changes">Open changes</string>