diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt index af5a27be11..b51be6e694 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt @@ -3,11 +3,11 @@ package chat.simplex.common.views.usersettings import android.Manifest import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import chat.simplex.common.model.ServerCfg +import chat.simplex.common.model.UserServer import com.google.accompanist.permissions.rememberPermissionState @Composable -actual fun ScanProtocolServer(rhId: Long?, onNext: (ServerCfg) -> Unit) { +actual fun ScanProtocolServer(rhId: Long?, onNext: (UserServer) -> Unit) { val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) LaunchedEffect(Unit) { cameraPermissionState.launchPermissionRequest() 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 fab85fa679..47c76162c5 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 @@ -955,36 +955,6 @@ object ChatController { return null } - suspend fun getUserProtoServers(rh: Long?, serverProtocol: ServerProtocol): UserProtocolServers? { - val userId = kotlin.runCatching { currentUserId("getUserProtoServers") }.getOrElse { return null } - val r = sendCmd(rh, CC.APIGetUserProtoServers(userId, serverProtocol)) - return if (r is CR.UserProtoServers) { if (rh == null) r.servers else r.servers.copy(protoServers = r.servers.protoServers.map { it.copy(remoteHostId = rh) }) } - else { - Log.e(TAG, "getUserProtoServers bad response: ${r.responseType} ${r.details}") - AlertManager.shared.showAlertMsg( - generalGetString(if (serverProtocol == ServerProtocol.SMP) MR.strings.error_loading_smp_servers else MR.strings.error_loading_xftp_servers), - "${r.responseType}: ${r.details}" - ) - null - } - } - - suspend fun setUserProtoServers(rh: Long?, serverProtocol: ServerProtocol, servers: List): Boolean { - val userId = kotlin.runCatching { currentUserId("setUserProtoServers") }.getOrElse { return false } - val r = sendCmd(rh, CC.APISetUserProtoServers(userId, serverProtocol, servers)) - return when (r) { - is CR.CmdOk -> true - else -> { - Log.e(TAG, "setUserProtoServers bad response: ${r.responseType} ${r.details}") - AlertManager.shared.showAlertMsg( - generalGetString(if (serverProtocol == ServerProtocol.SMP) MR.strings.error_saving_smp_servers else MR.strings.error_saving_xftp_servers), - generalGetString(if (serverProtocol == ServerProtocol.SMP) MR.strings.ensure_smp_server_address_are_correct_format_and_unique else MR.strings.ensure_xftp_server_address_are_correct_format_and_unique) - ) - false - } - } - } - suspend fun testProtoServer(rh: Long?, server: String): ProtocolTestFailure? { val userId = currentUserId("testProtoServer") val r = sendCmd(rh, CC.APITestProtoServer(userId, server)) @@ -997,6 +967,97 @@ object ChatController { } } + suspend fun getServerOperators(rh: Long?): ServerOperatorConditionsDetail? { + val r = sendCmd(rh, CC.ApiGetServerOperators()) + + return when (r) { + is CR.ServerOperatorConditions -> r.conditions + else -> { + Log.e(TAG, "getServerOperators bad response: ${r.responseType} ${r.details}") + null + } + } + } + + suspend fun setServerOperators(rh: Long?, operators: List): ServerOperatorConditionsDetail? { + val r = sendCmd(rh, CC.ApiSetServerOperators(operators)) + return when (r) { + is CR.ServerOperatorConditions -> r.conditions + else -> { + Log.e(TAG, "setServerOperators bad response: ${r.responseType} ${r.details}") + null + } + } + } + + suspend fun getUserServers(rh: Long?): List? { + val userId = currentUserId("getUserServers") + val r = sendCmd(rh, CC.ApiGetUserServers(userId)) + return when (r) { + is CR.UserServers -> r.userServers + else -> { + Log.e(TAG, "getUserServers bad response: ${r.responseType} ${r.details}") + null + } + } + } + + suspend fun setUserServers(rh: Long?, userServers: List): Boolean { + val userId = currentUserId("setUserServers") + val r = sendCmd(rh, CC.ApiSetUserServers(userId, userServers)) + return when (r) { + is CR.CmdOk -> true + else -> { + Log.e(TAG, "setUserServers bad response: ${r.responseType} ${r.details}") + false + } + } + } + + suspend fun validateServers(rh: Long?, userServers: List): List? { + val r = sendCmd(rh, CC.ApiValidateServers(userServers)) + return when (r) { + is CR.UserServersValidation -> r.serverErrors + else -> { + Log.e(TAG, "validateServers bad response: ${r.responseType} ${r.details}") + null + } + } + } + + suspend fun getUsageConditions(rh: Long?): Triple? { + val r = sendCmd(rh, CC.ApiGetUsageConditions()) + return when (r) { + is CR.UsageConditions -> Triple(r.usageConditions, r.conditionsText, r.acceptedConditions) + else -> { + Log.e(TAG, "getUsageConditions bad response: ${r.responseType} ${r.details}") + null + } + } + } + + suspend fun setConditionsNotified(rh: Long?, conditionsId: Long): Boolean { + val r = sendCmd(rh, CC.ApiSetConditionsNotified(conditionsId)) + return when (r) { + is CR.CmdOk -> true + else -> { + Log.e(TAG, "setConditionsNotified bad response: ${r.responseType} ${r.details}") + false + } + } + } + + suspend fun acceptConditions(rh: Long?, conditionsId: Long, operatorIds: List): CR.ServerOperatorConditions? { + val r = sendCmd(rh, CC.ApiAcceptConditions(conditionsId, operatorIds)) + return when (r) { + is CR.ServerOperatorConditions -> r + else -> { + Log.e(TAG, "acceptConditions bad response: ${r.responseType} ${r.details}") + null + } + } + } + suspend fun getChatItemTTL(rh: Long?): ChatItemTTL { val userId = currentUserId("getChatItemTTL") val r = sendCmd(rh, CC.APIGetChatItemTTL(userId)) @@ -3029,9 +3090,15 @@ sealed class CC { class APIGetGroupLink(val groupId: Long): CC() class APICreateMemberContact(val groupId: Long, val groupMemberId: Long): CC() class APISendMemberContactInvitation(val contactId: Long, val mc: MsgContent): CC() - class APIGetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol): CC() - class APISetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol, val servers: List): CC() class APITestProtoServer(val userId: Long, val server: String): CC() + class ApiGetServerOperators(): CC() + class ApiSetServerOperators(val operators: List): CC() + class ApiGetUserServers(val userId: Long): CC() + class ApiSetUserServers(val userId: Long, val userServers: List): CC() + class ApiValidateServers(val userServers: List): CC() + class ApiGetUsageConditions(): CC() + class ApiSetConditionsNotified(val conditionsId: Long): CC() + class ApiAcceptConditions(val conditionsId: Long, val operatorIds: List): CC() class APISetChatItemTTL(val userId: Long, val seconds: Long?): CC() class APIGetChatItemTTL(val userId: Long): CC() class APISetNetworkConfig(val networkConfig: NetCfg): CC() @@ -3189,9 +3256,15 @@ sealed class CC { is APIGetGroupLink -> "/_get link #$groupId" is APICreateMemberContact -> "/_create member contact #$groupId $groupMemberId" is APISendMemberContactInvitation -> "/_invite member contact @$contactId ${mc.cmdString}" - is APIGetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()}" - is APISetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()} ${protoServersStr(servers)}" is APITestProtoServer -> "/_server test $userId $server" + is ApiGetServerOperators -> "/_operators" + is ApiSetServerOperators -> "/_operators ${json.encodeToString(operators)}}" + is ApiGetUserServers -> "/_servers $userId" + is ApiSetUserServers -> "/_servers $userId ${json.encodeToString(userServers)}" + is ApiValidateServers -> "/_validate_servers ${json.encodeToString(userServers)}" + is ApiGetUsageConditions -> "/_conditions" + is ApiSetConditionsNotified -> "/_conditions_notified ${conditionsId}" + is ApiAcceptConditions -> "/_accept_conditions ${conditionsId} ${operatorIds.joinToString(",")}" is APISetChatItemTTL -> "/_ttl $userId ${chatItemTTLStr(seconds)}" is APIGetChatItemTTL -> "/_ttl $userId" is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}" @@ -3334,9 +3407,15 @@ sealed class CC { is APIGetGroupLink -> "apiGetGroupLink" is APICreateMemberContact -> "apiCreateMemberContact" is APISendMemberContactInvitation -> "apiSendMemberContactInvitation" - is APIGetUserProtoServers -> "apiGetUserProtoServers" - is APISetUserProtoServers -> "apiSetUserProtoServers" is APITestProtoServer -> "testProtoServer" + is ApiGetServerOperators -> "apiGetServerOperators" + is ApiSetServerOperators -> "apiSetServerOperators" + is ApiGetUserServers -> "apiGetUserServers" + is ApiSetUserServers -> "apiSetUserServers" + is ApiValidateServers -> "apiValidateServers" + is ApiGetUsageConditions -> "apiGetUsageConditions" + is ApiSetConditionsNotified -> "apiSetConditionsNotified" + is ApiAcceptConditions -> "apiAcceptConditions" is APISetChatItemTTL -> "apiSetChatItemTTL" is APIGetChatItemTTL -> "apiGetChatItemTTL" is APISetNetworkConfig -> "apiSetNetworkConfig" @@ -3451,8 +3530,6 @@ sealed class CC { companion object { fun chatRef(chatType: ChatType, id: Long) = "${chatType.type}${id}" - - fun protoServersStr(servers: List) = json.encodeToString(ProtoServersConfig(servers)) } } @@ -3498,24 +3575,291 @@ enum class ServerProtocol { } @Serializable -data class ProtoServersConfig( - val servers: List +enum class OperatorTag { + @SerialName("simplex") SimpleX, + @SerialName("flux") Flux, + @SerialName("xyz") XYZ, + @SerialName("demo") Demo +} + +@Serializable +data class ServerOperatorInfo( + val description: String, + val website: String, + val logo: String, + val largeLogo: String, + val logoDarkMode: String, + val largeLogoDarkMode: String +) + +val operatorsInfo: Map = mapOf( + OperatorTag.SimpleX to ServerOperatorInfo( + description = "SimpleX Chat preset servers", + website = "https://simplex.chat", + logo = "decentralized", + largeLogo = "logo", + logoDarkMode = "decentralized-light", + largeLogoDarkMode = "logo-light" + ), + OperatorTag.Flux to ServerOperatorInfo( + description = "Flux is the largest decentralized cloud infrastructure, leveraging a global network of user-operated computational nodes. Flux offers a powerful, scalable, and affordable platform designed to support individuals, businesses, and cutting-edge technologies like AI. With high uptime and worldwide distribution, Flux ensures reliable, accessible cloud computing for all.", + website = "https://runonflux.com", + logo = "flux_logo_symbol", + largeLogo = "flux_logo", + logoDarkMode = "flux_logo_symbol", + largeLogoDarkMode = "flux_logo-light" + ), + OperatorTag.XYZ to ServerOperatorInfo( + description = "XYZ servers", + website = "XYZ website", + logo = "shield", + largeLogo = "logo", + logoDarkMode = "shield", + largeLogoDarkMode = "logo-light" + ), + OperatorTag.Demo to ServerOperatorInfo( + description = "Demo operator", + website = "Demo website", + logo = "decentralized", + largeLogo = "logo", + logoDarkMode = "decentralized-light", + largeLogoDarkMode = "logo-light" + ) ) @Serializable -data class UserProtocolServers( - val serverProtocol: ServerProtocol, - val protoServers: List, - val presetServers: List, +data class UsageConditionsDetail( + val conditionsId: Long, + val conditionsCommit: String, + val notifiedAt: Instant?, + val createdAt: Instant +) { + companion object { + val sampleData = UsageConditionsDetail( + conditionsId = 1, + conditionsCommit = "11a44dc1fd461a93079f897048b46998db55da5c", + notifiedAt = null, + createdAt = Clock.System.now() + ) + } +} + +@Serializable +sealed class UsageConditionsAction { + data class Review(val operators: List, val deadline: Date?, private val showNoticeFlag: Boolean) : UsageConditionsAction() { + val showNotice: Boolean + get() = showNoticeFlag + } + data class Accepted(val operators: List) : UsageConditionsAction() { + val showNotice: Boolean + get() = false + } +} + +@Serializable +data class ServerOperatorConditionsDetail( + val serverOperators: List, + val currentConditions: UsageConditionsDetail, + val conditionsAction: UsageConditionsAction? +) { + companion object { + val empty = ServerOperatorConditionsDetail( + serverOperators = emptyList(), + currentConditions = UsageConditionsDetail(conditionsId = 0, conditionsCommit = "empty", notifiedAt = null, createdAt = Clock.System.now()), + conditionsAction = null + ) + } +} + +@Serializable() +sealed class ConditionsAcceptance { + data class Accepted(val acceptedAt: Date?) : ConditionsAcceptance() + data class Required(val deadline: Date?) : ConditionsAcceptance() + + val conditionsAccepted: Boolean + get() = when (this) { + is Accepted -> true + is Required -> false + } + + val usageAllowed: Boolean + get() = when (this) { + is Accepted -> true + is Required -> this.deadline != null + } +} + +@Serializable +data class ServerOperator( + val operatorId: Long, + val operatorTag: OperatorTag?, + val tradeName: String, + val legalName: String?, + val serverDomains: List, + val conditionsAcceptance: ConditionsAcceptance, + val enabled: Boolean, + val smpRoles: ServerRoles, + val xftpRoles: ServerRoles, +) { + companion object { + val dummyOperatorInfo = ServerOperatorInfo( + description = "Default", + website = "Default", + logo = "decentralized", + largeLogo = "logo", + logoDarkMode = "decentralized-light", + largeLogoDarkMode = "logo-light" + ) + + val sampleData1 = ServerOperator( + operatorId = 1, + operatorTag = OperatorTag.SimpleX, + tradeName = "SimpleX Chat", + legalName = "SimpleX Chat Ltd", + serverDomains = listOf("simplex.im"), + conditionsAcceptance = ConditionsAcceptance.Accepted(acceptedAt = null), + enabled = true, + smpRoles = ServerRoles(storage = true, proxy = true), + xftpRoles = ServerRoles(storage = true, proxy = true) + ) + + val sampleData2 = ServerOperator( + operatorId = 2, + operatorTag = OperatorTag.XYZ, + tradeName = "XYZ", + legalName = null, + serverDomains = listOf("xyz.com"), + conditionsAcceptance = ConditionsAcceptance.Required(deadline = null), + enabled = false, + smpRoles = ServerRoles(storage = false, proxy = true), + xftpRoles = ServerRoles(storage = false, proxy = true) + ) + + val sampleData3 = ServerOperator( + operatorId = 3, + operatorTag = OperatorTag.Demo, + tradeName = "Demo", + legalName = null, + serverDomains = listOf("demo.com"), + conditionsAcceptance = ConditionsAcceptance.Required(deadline = null), + enabled = false, + smpRoles = ServerRoles(storage = true, proxy = false), + xftpRoles = ServerRoles(storage = true, proxy = false) + ) + } + + val id: Long + get() = operatorId + + override fun equals(other: Any?): Boolean { + if (other !is ServerOperator) return false + return other.operatorId == this.operatorId && + other.operatorTag == this.operatorTag && + other.tradeName == this.tradeName && + other.legalName == this.legalName && + other.serverDomains == this.serverDomains && + other.conditionsAcceptance == this.conditionsAcceptance && + other.enabled == this.enabled && + other.smpRoles == this.smpRoles && + other.xftpRoles == this.xftpRoles + } + + override fun hashCode(): Int { + var result = operatorId.hashCode() + result = 31 * result + (operatorTag?.hashCode() ?: 0) + result = 31 * result + tradeName.hashCode() + result = 31 * result + (legalName?.hashCode() ?: 0) + result = 31 * result + serverDomains.hashCode() + result = 31 * result + conditionsAcceptance.hashCode() + result = 31 * result + enabled.hashCode() + result = 31 * result + smpRoles.hashCode() + result = 31 * result + xftpRoles.hashCode() + return result + } + + val legalName_: String + get() = legalName ?: tradeName + + val info: ServerOperatorInfo get() { + return if (this.operatorTag != null) { + operatorsInfo[this.operatorTag] ?: dummyOperatorInfo + } else { + dummyOperatorInfo + } + } + + val logo: String get() { + return if (CurrentColors.value.base == DefaultTheme.LIGHT) info.logo else info.logoDarkMode + } + + val largeLogo: String get() { + return if (CurrentColors.value.base == DefaultTheme.LIGHT) info.largeLogo else info.largeLogoDarkMode + } +} + +@Serializable +data class ServerRoles( + val storage: Boolean, + val proxy: Boolean ) @Serializable -data class ServerCfg( +data class UserOperatorServers( + var operator: ServerOperator?, + val smpServers: List, + val xftpServers: List +) { + val id: String + get() = operator?.operatorId?.toString() ?: "nil operator" + + var operator_: ServerOperator + get() = operator ?: ServerOperator( + operatorId = 0, + operatorTag = null, + tradeName = "", + legalName = null, + serverDomains = emptyList(), + conditionsAcceptance = ConditionsAcceptance.Accepted(null), + enabled = false, + smpRoles = ServerRoles(storage = true, proxy = true), + xftpRoles = ServerRoles(storage = true, proxy = true) + ) + set(value) { + operator = value + } + + companion object { + val sampleData1 = UserOperatorServers( + operator = ServerOperator.sampleData1, + smpServers = listOf(UserServer.sampleData.preset), + xftpServers = listOf(UserServer.sampleData.xftpPreset) + ) + + val sampleDataNilOperator = UserOperatorServers( + operator = null, + smpServers = listOf(UserServer.sampleData.preset), + xftpServers = listOf(UserServer.sampleData.xftpPreset) + ) + } +} + +@Serializable +sealed class UserServersError { + class StorageMissing: UserServersError() + class ProxyMissing: UserServersError() + data class DuplicateSMP(val server: String) : UserServersError() + data class DuplicateXFTP(val server: String) : UserServersError() +} + +@Serializable +data class UserServer( val remoteHostId: Long?, + val serverId: Long?, val server: String, val preset: Boolean, val tested: Boolean? = null, - val enabled: Boolean + val enabled: Boolean, + val deleted: Boolean ) { @Transient private val createdAt: Date = Date() @@ -3529,35 +3873,51 @@ data class ServerCfg( get() = server.isBlank() companion object { - val empty = ServerCfg(remoteHostId = null, server = "", preset = false, tested = null, enabled = false) + val empty = UserServer(remoteHostId = null, serverId = null, server = "", preset = false, tested = null, enabled = false, deleted = false) class SampleData( - val preset: ServerCfg, - val custom: ServerCfg, - val untested: ServerCfg + val preset: UserServer, + val custom: UserServer, + val untested: UserServer, + val xftpPreset: UserServer ) val sampleData = SampleData( - preset = ServerCfg( + preset = UserServer( remoteHostId = null, + serverId = 1, server = "smp://abcd@smp8.simplex.im", preset = true, tested = true, - enabled = true + enabled = true, + deleted = false ), - custom = ServerCfg( + custom = UserServer( remoteHostId = null, + serverId = 2, server = "smp://abcd@smp9.simplex.im", preset = false, tested = false, - enabled = false + enabled = false, + deleted = false ), - untested = ServerCfg( + untested = UserServer( remoteHostId = null, + serverId = 3, server = "smp://abcd@smp10.simplex.im", preset = false, tested = null, - enabled = true + enabled = true, + deleted = false + ), + xftpPreset = UserServer( + remoteHostId = null, + serverId = 4, + server = "xftp://abcd@xftp8.simplex.im", + preset = true, + tested = true, + enabled = true, + deleted = false ) ) } @@ -4916,8 +5276,11 @@ sealed class CR { @Serializable @SerialName("apiChats") class ApiChats(val user: UserRef, val chats: List): CR() @Serializable @SerialName("apiChat") class ApiChat(val user: UserRef, val chat: Chat): CR() @Serializable @SerialName("chatItemInfo") class ApiChatItemInfo(val user: UserRef, val chatItem: AChatItem, val chatItemInfo: ChatItemInfo): CR() - @Serializable @SerialName("userProtoServers") class UserProtoServers(val user: UserRef, val servers: UserProtocolServers): CR() @Serializable @SerialName("serverTestResult") class ServerTestResult(val user: UserRef, val testServer: String, val testFailure: ProtocolTestFailure? = null): CR() + @Serializable @SerialName("serverOperatorConditions") class ServerOperatorConditions(val conditions: ServerOperatorConditionsDetail): CR() + @Serializable @SerialName("userServers") class UserServers(val user: UserRef, val userServers: List): CR() + @Serializable @SerialName("userServersValidation") class UserServersValidation(val serverErrors: List): CR() + @Serializable @SerialName("usageConditions") class UsageConditions(val usageConditions: UsageConditionsDetail, val conditionsText: String, val acceptedConditions: UsageConditions?): CR() @Serializable @SerialName("chatItemTTL") class ChatItemTTL(val user: UserRef, val chatItemTTL: Long? = null): CR() @Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR() @Serializable @SerialName("contactInfo") class ContactInfo(val user: UserRef, val contact: Contact, val connectionStats_: ConnectionStats? = null, val customUserProfile: Profile? = null): CR() @@ -5096,8 +5459,11 @@ sealed class CR { is ApiChats -> "apiChats" is ApiChat -> "apiChat" is ApiChatItemInfo -> "chatItemInfo" - is UserProtoServers -> "userProtoServers" is ServerTestResult -> "serverTestResult" + is ServerOperatorConditions -> "serverOperatorConditions" + is UserServers -> "userServers" + is UserServersValidation -> "userServersValidation" + is UsageConditions -> "usageConditions" is ChatItemTTL -> "chatItemTTL" is NetworkConfig -> "networkConfig" is ContactInfo -> "contactInfo" @@ -5266,8 +5632,11 @@ sealed class CR { is ApiChats -> withUser(user, json.encodeToString(chats)) is ApiChat -> withUser(user, json.encodeToString(chat)) is ApiChatItemInfo -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\n${json.encodeToString(chatItemInfo)}") - is UserProtoServers -> withUser(user, "servers: ${json.encodeToString(servers)}") is ServerTestResult -> withUser(user, "server: $testServer\nresult: ${json.encodeToString(testFailure)}") + is ServerOperatorConditions -> "conditions: ${json.encodeToString(conditions)}" + is UserServers -> withUser(user, "userServers: ${json.encodeToString(userServers)}") + is UserServersValidation -> "serverErrors: ${json.encodeToString(serverErrors)}" + is UsageConditions -> "usageConditions: ${json.encodeToString(usageConditions)}\nnacceptedConditions: ${json.encodeToString(acceptedConditions)}" is ChatItemTTL -> withUser(user, json.encodeToString(chatItemTTL)) is NetworkConfig -> json.encodeToString(networkConfig) is ContactInfo -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats_)}") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt index be566e6c5a..53d0278c43 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt @@ -31,7 +31,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.distinctUntilChanged @Composable -fun ProtocolServerView(m: ChatModel, server: ServerCfg, serverProtocol: ServerProtocol, onUpdate: (ServerCfg) -> Unit, onDelete: () -> Unit) { +fun ProtocolServerView(m: ChatModel, server: UserServer, serverProtocol: ServerProtocol, onUpdate: (UserServer) -> Unit, onDelete: () -> Unit) { var testing by remember { mutableStateOf(false) } ProtocolServerLayout( testing, @@ -69,10 +69,10 @@ fun ProtocolServerView(m: ChatModel, server: ServerCfg, serverProtocol: ServerPr @Composable private fun ProtocolServerLayout( testing: Boolean, - server: ServerCfg, + server: UserServer, serverProtocol: ServerProtocol, testServer: () -> Unit, - onUpdate: (ServerCfg) -> Unit, + onUpdate: (UserServer) -> Unit, onDelete: () -> Unit, ) { ColumnWithScrollBar { @@ -90,9 +90,9 @@ private fun ProtocolServerLayout( @Composable private fun PresetServer( testing: Boolean, - server: ServerCfg, + server: UserServer, testServer: () -> Unit, - onUpdate: (ServerCfg) -> Unit, + onUpdate: (UserServer) -> Unit, onDelete: () -> Unit, ) { SectionView(stringResource(MR.strings.smp_servers_preset_address).uppercase()) { @@ -114,10 +114,10 @@ private fun PresetServer( @Composable private fun CustomServer( testing: Boolean, - server: ServerCfg, + server: UserServer, serverProtocol: ServerProtocol, testServer: () -> Unit, - onUpdate: (ServerCfg) -> Unit, + onUpdate: (UserServer) -> Unit, onDelete: () -> Unit, ) { val serverAddress = remember { mutableStateOf(server.server) } @@ -162,9 +162,9 @@ private fun CustomServer( private fun UseServerSection( valid: Boolean, testing: Boolean, - server: ServerCfg, + server: UserServer, testServer: () -> Unit, - onUpdate: (ServerCfg) -> Unit, + onUpdate: (UserServer) -> Unit, onDelete: () -> Unit, ) { SectionView(stringResource(MR.strings.smp_servers_use_server).uppercase()) { @@ -189,14 +189,14 @@ private fun UseServerSection( } @Composable -fun ShowTestStatus(server: ServerCfg, modifier: Modifier = Modifier) = +fun ShowTestStatus(server: UserServer, modifier: Modifier = Modifier) = when (server.tested) { true -> Icon(painterResource(MR.images.ic_check), null, modifier, tint = SimplexGreen) false -> Icon(painterResource(MR.images.ic_close), null, modifier, tint = MaterialTheme.colors.error) else -> Icon(painterResource(MR.images.ic_check), null, modifier, tint = Color.Transparent) } -suspend fun testServerConnection(server: ServerCfg, m: ChatModel): Pair = +suspend fun testServerConnection(server: UserServer, m: ChatModel): Pair = try { val r = m.controller.testProtoServer(server.remoteHostId, server.server) server.copy(tested = r == null) to r diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt index f5e3cda2c7..8ae02874ac 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt @@ -28,8 +28,8 @@ import chat.simplex.res.MR @Composable fun ModalData.ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtocol, close: () -> Unit) { - var presetServers by remember(rhId) { mutableStateOf(emptyList()) } - var servers by remember { stateGetOrPut("servers") { emptyList() } } + var presetServers by remember(rhId) { mutableStateOf(emptyList()) } + var servers by remember { stateGetOrPut("servers") { emptyList() } } var serversAlreadyLoaded by remember { stateGetOrPut("serversAlreadyLoaded") { false } } val currServers = remember(rhId) { mutableStateOf(servers) } val testing = rememberSaveable(rhId) { mutableStateOf(false) } @@ -55,19 +55,19 @@ fun ModalData.ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: Ser LaunchedEffect(rhId) { withApi { - val res = m.controller.getUserProtoServers(rhId, serverProtocol) - if (res != null) { - currServers.value = res.protoServers - presetServers = res.presetServers - if (servers.isEmpty() && !serversAlreadyLoaded) { - servers = currServers.value - serversAlreadyLoaded = true - } - } +// val res = m.controller.getUserServers(rhId) +// if (res != null) { +// currServers.value = res.protoServers +// presetServers = res.presetServers +// if (servers.isEmpty() && !serversAlreadyLoaded) { +// servers = currServers.value +// serversAlreadyLoaded = true +// } +// } } } val testServersJob = CancellableOnGoneJob() - fun showServer(server: ServerCfg) { + fun showServer(server: UserServer) { ModalManager.start.showModalCloseable(true) { close -> var old by remember { mutableStateOf(server) } val index = servers.indexOf(old) @@ -111,7 +111,7 @@ fun ModalData.ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: Ser Column { SectionItemView({ AlertManager.shared.hideAlert() - servers = servers + ServerCfg.empty + servers = servers + UserServer.empty // No saving until something will be changed on the next screen to prevent blank servers on the list showServer(servers.last()) }) { @@ -181,7 +181,7 @@ fun ModalData.ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: Ser private fun ProtocolServersLayout( serverProtocol: ServerProtocol, testing: Boolean, - servers: List, + servers: List, serversUnchanged: Boolean, saveDisabled: Boolean, allServersDisabled: Boolean, @@ -190,7 +190,7 @@ private fun ProtocolServersLayout( testServers: () -> Unit, resetServers: () -> Unit, saveSMPServers: () -> Unit, - showServer: (ServerCfg) -> Unit, + showServer: (UserServer) -> Unit, ) { ColumnWithScrollBar { AppBarTitle(stringResource(if (serverProtocol == ServerProtocol.SMP) MR.strings.your_SMP_servers else MR.strings.your_XFTP_servers)) @@ -263,7 +263,7 @@ private fun ProtocolServersLayout( } @Composable -private fun ProtocolServerView(serverProtocol: ServerProtocol, srv: ServerCfg, servers: List, disabled: Boolean) { +private fun ProtocolServerView(serverProtocol: ServerProtocol, srv: UserServer, servers: List, disabled: Boolean) { val address = parseServerAddress(srv.server) when { address == null || !address.valid || address.serverProtocol != serverProtocol || !uniqueAddress(srv, address, servers) -> InvalidServer() @@ -296,17 +296,17 @@ fun InvalidServer() { Icon(painterResource(MR.images.ic_error), null, tint = MaterialTheme.colors.error) } -private fun uniqueAddress(s: ServerCfg, address: ServerAddress, servers: List): Boolean = servers.all { srv -> +private fun uniqueAddress(s: UserServer, address: ServerAddress, servers: List): Boolean = servers.all { srv -> address.hostnames.all { host -> srv.id == s.id || !srv.server.contains(host) } } -private fun hasAllPresets(presetServers: List, servers: List, m: ChatModel): Boolean = +private fun hasAllPresets(presetServers: List, servers: List, m: ChatModel): Boolean = presetServers.all { hasPreset(it, servers) } ?: true -private fun addAllPresets(rhId: Long?, presetServers: List, servers: List, m: ChatModel): List { - val toAdd = ArrayList() +private fun addAllPresets(rhId: Long?, presetServers: List, servers: List, m: ChatModel): List { + val toAdd = ArrayList() for (srv in presetServers) { if (!hasPreset(srv, servers)) { toAdd.add(srv) @@ -315,10 +315,10 @@ private fun addAllPresets(rhId: Long?, presetServers: List, servers: return toAdd } -private fun hasPreset(srv: ServerCfg, servers: List): Boolean = +private fun hasPreset(srv: UserServer, servers: List): Boolean = servers.any { it.server == srv.server } -private suspend fun testServers(testing: MutableState, servers: List, m: ChatModel, onUpdated: (List) -> Unit) { +private suspend fun testServers(testing: MutableState, servers: List, m: ChatModel, onUpdated: (List) -> Unit) { val resetStatus = resetTestStatus(servers) onUpdated(resetStatus) testing.value = true @@ -333,7 +333,7 @@ private suspend fun testServers(testing: MutableState, servers: List): List { +private fun resetTestStatus(servers: List): List { val copy = ArrayList(servers) for ((index, server) in servers.withIndex()) { if (server.enabled) { @@ -344,9 +344,9 @@ private fun resetTestStatus(servers: List): List { return copy } -private suspend fun runServersTest(servers: List, m: ChatModel, onUpdated: (List) -> Unit): Map { +private suspend fun runServersTest(servers: List, m: ChatModel, onUpdated: (List) -> Unit): Map { val fs: MutableMap = mutableMapOf() - val updatedServers = ArrayList(servers) + val updatedServers = ArrayList(servers) for ((index, server) in servers.withIndex()) { if (server.enabled) { interruptIfCancelled() @@ -363,11 +363,11 @@ private suspend fun runServersTest(servers: List, m: ChatModel, onUpd return fs } -private fun saveServers(rhId: Long?, protocol: ServerProtocol, currServers: MutableState>, servers: List, m: ChatModel, afterSave: () -> Unit = {}) { +private fun saveServers(rhId: Long?, protocol: ServerProtocol, currServers: MutableState>, servers: List, m: ChatModel, afterSave: () -> Unit = {}) { withBGApi { - if (m.controller.setUserProtoServers(rhId, protocol, servers)) { - currServers.value = servers - } +// if (m.controller.setUserServers(rhId, protocol, servers)) { +// currServers.value = servers +// } afterSave() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt index 966f44cac7..6d3641e8c3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt @@ -6,7 +6,7 @@ import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress -import chat.simplex.common.model.ServerCfg +import chat.simplex.common.model.UserServer import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.* @@ -14,16 +14,16 @@ import chat.simplex.common.views.newchat.QRCodeScanner import chat.simplex.res.MR @Composable -expect fun ScanProtocolServer(rhId: Long?, onNext: (ServerCfg) -> Unit) +expect fun ScanProtocolServer(rhId: Long?, onNext: (UserServer) -> Unit) @Composable -fun ScanProtocolServerLayout(rhId: Long?, onNext: (ServerCfg) -> Unit) { +fun ScanProtocolServerLayout(rhId: Long?, onNext: (UserServer) -> Unit) { ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.smp_servers_scan_qr)) QRCodeScanner { text -> val res = parseServerAddress(text) if (res != null) { - onNext(ServerCfg(remoteHostId = rhId, text, false, null, false)) + onNext(UserServer(remoteHostId = rhId, null, text, false, null, false, false)) } else { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.smp_servers_invalid_address), diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt index 2d436dbbf0..682840440c 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt @@ -1,9 +1,9 @@ package chat.simplex.common.views.usersettings import androidx.compose.runtime.Composable -import chat.simplex.common.model.ServerCfg +import chat.simplex.common.model.UserServer @Composable -actual fun ScanProtocolServer(rhId: Long?, onNext: (ServerCfg) -> Unit) { +actual fun ScanProtocolServer(rhId: Long?, onNext: (UserServer) -> Unit) { ScanProtocolServerLayout(rhId, onNext) }