android: servers UI/API (#2155)
* android: servers UI/API * non-optional server protocol in parsed address * make another enum for ServerProtocol --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
parent
85537a99e8
commit
991332a809
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1865,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()
|
||||
@ -1949,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)}"
|
||||
@ -2034,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"
|
||||
@ -2113,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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -2148,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
|
||||
@ -2203,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
|
||||
}
|
||||
|
||||
@ -2238,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
|
||||
@ -2247,6 +2275,7 @@ data class SMPTestFailure(
|
||||
|
||||
@Serializable
|
||||
data class ServerAddress(
|
||||
val serverProtocol: ServerProtocol,
|
||||
val hostnames: List<String>,
|
||||
val port: String,
|
||||
val keyHash: String,
|
||||
@ -2254,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=",
|
||||
@ -3011,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()
|
||||
@ -3119,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"
|
||||
@ -3225,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)}")
|
||||
@ -3459,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"
|
||||
@ -3466,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()
|
||||
@ -3533,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()
|
||||
@ -3542,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"
|
||||
@ -3550,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
|
||||
@ -3596,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>
|
||||
|
@ -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))")
|
||||
|
Loading…
Reference in New Issue
Block a user