mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 00:02:28 +00:00
core, ui: chat relay test (#6736)
This commit is contained in:
1 parent
42fe94752c
commit
a14a66db14
40 files changed
+1670
-148
No files matched your search
+10
-1
@@ -2277,11 +2277,16 @@ enum class RelayStatus {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class RelayProfile(
|
||||
val name: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UserChatRelay(
|
||||
val chatRelayId: Long?,
|
||||
val address: String,
|
||||
val name: String,
|
||||
val relayProfile: RelayProfile,
|
||||
val domains: List<String>,
|
||||
val preset: Boolean,
|
||||
val tested: Boolean? = null,
|
||||
@@ -2291,6 +2296,10 @@ data class UserChatRelay(
|
||||
@Transient
|
||||
private val createdAt: Date = Date()
|
||||
val id: String get() = "$address $createdAt"
|
||||
|
||||
val name: String get() = relayProfile.name
|
||||
|
||||
fun copyWithName(name: String): UserChatRelay = copy(relayProfile = RelayProfile(name = name))
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
||||
+54
@@ -1216,6 +1216,14 @@ object ChatController {
|
||||
throw Exception("testProtoServer bad response: ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
suspend fun testChatRelay(rh: Long?, address: String): Pair<RelayProfile?, RelayTestFailure?> {
|
||||
val userId = currentUserId("testChatRelay")
|
||||
val r = sendCmd(rh, CC.APITestChatRelay(userId, address))
|
||||
if (r is API.Result && r.res is CR.ChatRelayTestResult) return r.res.relayProfile to r.res.relayTestFailure
|
||||
Log.e(TAG, "testChatRelay bad response: ${r.responseType} ${r.details}")
|
||||
throw Exception("testChatRelay bad response: ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
suspend fun getServerOperators(rh: Long?): ServerOperatorConditionsDetail? {
|
||||
val r = sendCmd(rh, CC.ApiGetServerOperators())
|
||||
if (r is API.Result && r.res is CR.ServerOperatorConditions) return r.res.conditions
|
||||
@@ -3637,6 +3645,7 @@ sealed class CC {
|
||||
class APISendMemberContactInvitation(val contactId: Long, val mc: MsgContent): CC()
|
||||
class APIAcceptMemberContact(val contactId: Long): CC()
|
||||
class APITestProtoServer(val userId: Long, val server: String): CC()
|
||||
class APITestChatRelay(val userId: Long, val address: String): CC()
|
||||
class ApiGetServerOperators(): CC()
|
||||
class ApiSetServerOperators(val operators: List<ServerOperator>): CC()
|
||||
class ApiGetUserServers(val userId: Long): CC()
|
||||
@@ -3837,6 +3846,7 @@ sealed class CC {
|
||||
is APISendMemberContactInvitation -> "/_invite member contact @$contactId ${mc.cmdString}"
|
||||
is APIAcceptMemberContact -> "/_accept member contact @$contactId"
|
||||
is APITestProtoServer -> "/_server test $userId $server"
|
||||
is APITestChatRelay -> "/_relay test $userId $address"
|
||||
is ApiGetServerOperators -> "/_operators"
|
||||
is ApiSetServerOperators -> "/_operators ${json.encodeToString(operators)}"
|
||||
is ApiGetUserServers -> "/_servers $userId"
|
||||
@@ -4015,6 +4025,7 @@ sealed class CC {
|
||||
is APISendMemberContactInvitation -> "apiSendMemberContactInvitation"
|
||||
is APIAcceptMemberContact -> "apiAcceptMemberContact"
|
||||
is APITestProtoServer -> "testProtoServer"
|
||||
is APITestChatRelay -> "apiTestChatRelay"
|
||||
is ApiGetServerOperators -> "apiGetServerOperators"
|
||||
is ApiSetServerOperators -> "apiSetServerOperators"
|
||||
is ApiGetUserServers -> "apiGetUserServers"
|
||||
@@ -4679,6 +4690,44 @@ data class ProtocolTestFailure(
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class RelayTestStep {
|
||||
@SerialName("getLink") GetLink,
|
||||
@SerialName("decodeLink") DecodeLink,
|
||||
@SerialName("connect") Connect,
|
||||
@SerialName("waitResponse") WaitResponse,
|
||||
@SerialName("verify") Verify;
|
||||
|
||||
val text: String get() = when (this) {
|
||||
GetLink -> generalGetString(MR.strings.relay_test_step_get_link)
|
||||
DecodeLink -> generalGetString(MR.strings.relay_test_step_decode_link)
|
||||
Connect -> generalGetString(MR.strings.relay_test_step_connect)
|
||||
WaitResponse -> generalGetString(MR.strings.relay_test_step_wait_response)
|
||||
Verify -> generalGetString(MR.strings.relay_test_step_verify)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class RelayTestFailure(
|
||||
val rtfStep: RelayTestStep,
|
||||
val rtfError: ChatError
|
||||
) {
|
||||
val localizedDescription: String get() {
|
||||
val err = String.format(generalGetString(MR.strings.error_relay_test_failed_at_step), rtfStep.text)
|
||||
return when {
|
||||
rtfError is ChatError.ChatErrorAgent &&
|
||||
rtfError.agentError is AgentErrorType.SMP && rtfError.agentError.smpErr is SMPErrorType.AUTH ->
|
||||
err + " " + generalGetString(MR.strings.error_relay_test_server_auth)
|
||||
rtfError is ChatError.ChatErrorAgent &&
|
||||
rtfError.agentError is AgentErrorType.BROKER && rtfError.agentError.brokerErr is BrokerErrorType.NETWORK &&
|
||||
rtfError.agentError.brokerErr.networkError is NetworkError.UnknownCAError ->
|
||||
err + " " + generalGetString(MR.strings.error_smp_test_certificate)
|
||||
else ->
|
||||
err + " " + String.format(generalGetString(MR.strings.error_with_info), rtfError.string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ServerAddress(
|
||||
val serverProtocol: ServerProtocol,
|
||||
@@ -6206,6 +6255,7 @@ sealed class CR {
|
||||
@Serializable @SerialName("chatTags") class ChatTags(val user: UserRef, val userTags: List<ChatTag>): CR()
|
||||
@Serializable @SerialName("chatItemInfo") class ApiChatItemInfo(val user: UserRef, val chatItem: AChatItem, val chatItemInfo: ChatItemInfo): CR()
|
||||
@Serializable @SerialName("serverTestResult") class ServerTestResult(val user: UserRef, val testServer: String, val testFailure: ProtocolTestFailure? = null): CR()
|
||||
@Serializable @SerialName("chatRelayTestResult") class ChatRelayTestResult(val user: UserRef, val relayProfile: RelayProfile? = null, val relayTestFailure: RelayTestFailure? = null): CR()
|
||||
@Serializable @SerialName("serverOperatorConditions") class ServerOperatorConditions(val conditions: ServerOperatorConditionsDetail): CR()
|
||||
@Serializable @SerialName("userServers") class UserServers(val user: UserRef, val userServers: List<UserOperatorServers>): CR()
|
||||
@Serializable @SerialName("userServersValidation") class UserServersValidation(val user: UserRef, val serverErrors: List<UserServersError>, val serverWarnings: List<UserServersWarning> = emptyList()): CR()
|
||||
@@ -6393,6 +6443,7 @@ sealed class CR {
|
||||
is ChatTags -> "chatTags"
|
||||
is ApiChatItemInfo -> "chatItemInfo"
|
||||
is ServerTestResult -> "serverTestResult"
|
||||
is ChatRelayTestResult -> "chatRelayTestResult"
|
||||
is ServerOperatorConditions -> "serverOperatorConditions"
|
||||
is UserServers -> "userServers"
|
||||
is UserServersValidation -> "userServersValidation"
|
||||
@@ -6572,6 +6623,7 @@ sealed class CR {
|
||||
is ChatTags -> withUser(user, "userTags: ${json.encodeToString(userTags)}")
|
||||
is ApiChatItemInfo -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\n${json.encodeToString(chatItemInfo)}")
|
||||
is ServerTestResult -> withUser(user, "server: $testServer\nresult: ${json.encodeToString(testFailure)}")
|
||||
is ChatRelayTestResult -> withUser(user, "relayProfile: $relayProfile\ntestFailure: $relayTestFailure")
|
||||
is ServerOperatorConditions -> "conditions: ${json.encodeToString(conditions)}"
|
||||
is UserServers -> withUser(user, "userServers: ${json.encodeToString(userServers)}")
|
||||
is UserServersValidation -> withUser(user, "serverErrors: ${json.encodeToString(serverErrors)}")
|
||||
@@ -7179,6 +7231,7 @@ sealed class ChatErrorType {
|
||||
is ConnectionIncognitoChangeProhibited -> "connectionIncognitoChangeProhibited"
|
||||
is ConnectionUserChangeProhibited -> "connectionUserChangeProhibited"
|
||||
is PeerChatVRangeIncompatible -> "peerChatVRangeIncompatible"
|
||||
is RelayTestError -> "relayTestError $message"
|
||||
is InternalError -> "internalError"
|
||||
is CEException -> "exception $message"
|
||||
}
|
||||
@@ -7260,6 +7313,7 @@ sealed class ChatErrorType {
|
||||
@Serializable @SerialName("connectionIncognitoChangeProhibited") object ConnectionIncognitoChangeProhibited: ChatErrorType()
|
||||
@Serializable @SerialName("connectionUserChangeProhibited") object ConnectionUserChangeProhibited: ChatErrorType()
|
||||
@Serializable @SerialName("peerChatVRangeIncompatible") object PeerChatVRangeIncompatible: ChatErrorType()
|
||||
@Serializable @SerialName("relayTestError") class RelayTestError(val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("internalError") class InternalError(val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("exception") class CEException(val message: String): ChatErrorType()
|
||||
}
|
||||
|
||||
+4
-2
@@ -32,7 +32,8 @@ fun TextEditor(
|
||||
placeholder: String? = null,
|
||||
contentPadding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING),
|
||||
isValid: (String) -> Boolean = { true },
|
||||
focusRequester: FocusRequester? = null
|
||||
focusRequester: FocusRequester? = null,
|
||||
enabled: Boolean = true
|
||||
) {
|
||||
var valid by rememberSaveable { mutableStateOf(true) }
|
||||
var focused by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -64,6 +65,7 @@ fun TextEditor(
|
||||
value = value.value,
|
||||
onValueChange = { value.value = it },
|
||||
modifier = if (focusRequester == null) textFieldModifier else textFieldModifier.focusRequester(focusRequester),
|
||||
enabled = enabled,
|
||||
textStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground, lineHeight = 22.sp),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
@@ -83,7 +85,7 @@ fun TextEditor(
|
||||
leadingIcon = null,
|
||||
trailingIcon = null,
|
||||
singleLine = false,
|
||||
enabled = true,
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified)
|
||||
|
||||
+114
-42
@@ -4,6 +4,7 @@ import SectionBottomSpacer
|
||||
import SectionDividerSpaced
|
||||
import SectionItemView
|
||||
import SectionItemViewSpaceBetween
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
@@ -25,6 +26,7 @@ import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.usersettings.PreferenceToggle
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ShowRelayTestStatus(relay: UserChatRelay, modifier: Modifier = Modifier) =
|
||||
@@ -115,6 +117,18 @@ fun ChatRelayView(
|
||||
) {
|
||||
val relayToEdit = remember { mutableStateOf(relay) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
snapshotFlow { relayToEdit.value.address }
|
||||
.distinctUntilChanged()
|
||||
.collect {
|
||||
if (relayToEdit.value.address == relay.address) {
|
||||
relayToEdit.value = relayToEdit.value.copy(tested = relay.tested, relayProfile = relay.relayProfile)
|
||||
} else {
|
||||
relayToEdit.value = relayToEdit.value.copy(tested = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModalView(
|
||||
close = {
|
||||
val validName = validRelayName(relayToEdit.value.name)
|
||||
@@ -149,25 +163,25 @@ private fun ChatRelayLayout(
|
||||
relay: MutableState<UserChatRelay>,
|
||||
onDelete: (() -> Unit)?
|
||||
) {
|
||||
ColumnWithScrollBar {
|
||||
AppBarTitle(stringResource(MR.strings.chat_relay))
|
||||
if (relay.value.preset) {
|
||||
PresetRelay(relay)
|
||||
} else {
|
||||
CustomRelay(relay, onDelete)
|
||||
val testing = remember { mutableStateOf(false) }
|
||||
Box {
|
||||
ColumnWithScrollBar {
|
||||
AppBarTitle(stringResource(MR.strings.chat_relay))
|
||||
if (relay.value.preset) {
|
||||
PresetRelay(relay, testing)
|
||||
} else {
|
||||
CustomRelay(relay, onDelete, testing)
|
||||
}
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
if (testing.value) {
|
||||
DefaultProgressView(null)
|
||||
}
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PresetRelay(relay: MutableState<UserChatRelay>) {
|
||||
SectionView(stringResource(MR.strings.preset_relay_name).uppercase()) {
|
||||
SectionItemView {
|
||||
Text(relay.value.name)
|
||||
}
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
private fun PresetRelay(relay: MutableState<UserChatRelay>, testing: MutableState<Boolean>) {
|
||||
SectionView(stringResource(MR.strings.preset_relay_address).uppercase()) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
@@ -178,13 +192,20 @@ private fun PresetRelay(relay: MutableState<UserChatRelay>) {
|
||||
}
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
UseRelaySection(relay)
|
||||
SectionView(stringResource(MR.strings.preset_relay_name).uppercase()) {
|
||||
SectionItemView {
|
||||
Text(relay.value.name)
|
||||
}
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
UseRelaySection(relay, testing = testing)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CustomRelay(
|
||||
relay: MutableState<UserChatRelay>,
|
||||
onDelete: (() -> Unit)?
|
||||
onDelete: (() -> Unit)?,
|
||||
testing: MutableState<Boolean>
|
||||
) {
|
||||
val relayName = remember { mutableStateOf(relay.value.name) }
|
||||
val relayAddress = remember { mutableStateOf(relay.value.address) }
|
||||
@@ -194,7 +215,12 @@ private fun CustomRelay(
|
||||
LaunchedEffect(Unit) {
|
||||
snapshotFlow { relayName.value }
|
||||
.distinctUntilChanged()
|
||||
.collect { relay.value = relay.value.copy(name = it) }
|
||||
.collect { relay.value = relay.value.copyWithName(it) }
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
snapshotFlow { relay.value.name }
|
||||
.distinctUntilChanged()
|
||||
.collect { relayName.value = it }
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
snapshotFlow { relayAddress.value }
|
||||
@@ -202,6 +228,18 @@ private fun CustomRelay(
|
||||
.collect { relay.value = relay.value.copy(address = it) }
|
||||
}
|
||||
|
||||
SectionView(
|
||||
stringResource(MR.strings.your_relay_address).uppercase(),
|
||||
icon = painterResource(MR.images.ic_error),
|
||||
iconTint = if (!validAddress.value) MaterialTheme.colors.error else Color.Transparent,
|
||||
) {
|
||||
TextEditor(
|
||||
relayAddress,
|
||||
Modifier.height(144.dp)
|
||||
)
|
||||
}
|
||||
SectionDividerSpaced(maxTopPadding = true)
|
||||
|
||||
Column {
|
||||
val iconSize = with(LocalDensity.current) { 21.sp.toDp() }
|
||||
Row(Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
@@ -224,25 +262,17 @@ private fun CustomRelay(
|
||||
TextEditor(
|
||||
relayName,
|
||||
Modifier,
|
||||
placeholder = generalGetString(MR.strings.enter_relay_name)
|
||||
placeholder = generalGetString(MR.strings.enter_relay_name),
|
||||
enabled = relay.value.tested != true
|
||||
)
|
||||
}
|
||||
}
|
||||
SectionDividerSpaced(maxTopPadding = true)
|
||||
|
||||
SectionView(
|
||||
stringResource(MR.strings.your_relay_address).uppercase(),
|
||||
icon = painterResource(MR.images.ic_error),
|
||||
iconTint = if (!validAddress.value) MaterialTheme.colors.error else Color.Transparent,
|
||||
) {
|
||||
TextEditor(
|
||||
relayAddress,
|
||||
Modifier.height(144.dp)
|
||||
)
|
||||
if (relay.value.tested != true) {
|
||||
SectionTextFooter(annotatedStringResource(MR.strings.test_relay_to_retrieve_name))
|
||||
}
|
||||
SectionDividerSpaced(maxTopPadding = true)
|
||||
|
||||
UseRelaySection(relay, validAddress.value)
|
||||
UseRelaySection(relay, validAddress.value, testing)
|
||||
|
||||
if (onDelete != null) {
|
||||
SectionDividerSpaced()
|
||||
@@ -257,21 +287,31 @@ private fun CustomRelay(
|
||||
@Composable
|
||||
private fun UseRelaySection(
|
||||
relay: MutableState<UserChatRelay>,
|
||||
valid: Boolean = true
|
||||
valid: Boolean = true,
|
||||
testing: MutableState<Boolean>
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
SectionView(stringResource(MR.strings.use_relay).uppercase()) {
|
||||
SectionItemViewSpaceBetween(
|
||||
click = {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.not_implemented),
|
||||
text = generalGetString(MR.strings.relay_testing_not_available)
|
||||
)
|
||||
testing.value = true
|
||||
relay.value = relay.value.copy(tested = null)
|
||||
scope.launch {
|
||||
val f = testRelayConnection(relay)
|
||||
if (f != null) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.relay_test_failed_alert),
|
||||
text = f.localizedDescription
|
||||
)
|
||||
}
|
||||
testing.value = false
|
||||
}
|
||||
},
|
||||
disabled = !valid
|
||||
disabled = !valid || testing.value
|
||||
) {
|
||||
Text(
|
||||
stringResource(MR.strings.test_relay),
|
||||
color = if (valid) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary
|
||||
color = if (valid && !testing.value) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary
|
||||
)
|
||||
ShowRelayTestStatus(relay.value)
|
||||
}
|
||||
@@ -322,12 +362,20 @@ fun ModalData.NewChatRelayView(
|
||||
val relayToEdit = remember {
|
||||
mutableStateOf(
|
||||
UserChatRelay(
|
||||
chatRelayId = null, address = "", name = "", domains = emptyList(),
|
||||
chatRelayId = null, address = "", relayProfile = RelayProfile(name = ""), domains = emptyList(),
|
||||
preset = false, tested = null, enabled = true, deleted = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
snapshotFlow { relayToEdit.value.address }
|
||||
.distinctUntilChanged()
|
||||
.collect {
|
||||
relayToEdit.value = relayToEdit.value.copy(tested = null)
|
||||
}
|
||||
}
|
||||
|
||||
ModalView(close = {
|
||||
addChatRelay(relayToEdit.value, userServers, serverErrors, serverWarnings, rhId, close)
|
||||
}) {
|
||||
@@ -337,9 +385,33 @@ fun ModalData.NewChatRelayView(
|
||||
|
||||
@Composable
|
||||
private fun NewChatRelayLayout(relay: MutableState<UserChatRelay>) {
|
||||
ColumnWithScrollBar {
|
||||
AppBarTitle(stringResource(MR.strings.new_chat_relay))
|
||||
CustomRelay(relay, onDelete = null)
|
||||
SectionBottomSpacer()
|
||||
val testing = remember { mutableStateOf(false) }
|
||||
Box {
|
||||
ColumnWithScrollBar {
|
||||
AppBarTitle(stringResource(MR.strings.new_chat_relay))
|
||||
CustomRelay(relay, onDelete = null, testing = testing)
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
if (testing.value) {
|
||||
DefaultProgressView(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun testRelayConnection(relay: MutableState<UserChatRelay>): RelayTestFailure? =
|
||||
try {
|
||||
val (relayProfile, testFailure) = chatModel.controller.testChatRelay(chatModel.remoteHostId(), relay.value.address)
|
||||
if (testFailure != null) {
|
||||
relay.value = relay.value.copy(tested = false)
|
||||
testFailure
|
||||
} else {
|
||||
relay.value = relay.value.copy(tested = true).let {
|
||||
if (relayProfile != null) it.copyWithName(relayProfile.name) else it
|
||||
}
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "testRelayConnection ${e.stackTraceToString()}")
|
||||
relay.value = relay.value.copy(tested = false)
|
||||
null
|
||||
}
|
||||
+19
-10
@@ -444,21 +444,30 @@ fun OperatorViewLayout(
|
||||
testing = testing,
|
||||
smpServers = userServers.value[operatorIndex].smpServers,
|
||||
xftpServers = userServers.value[operatorIndex].xftpServers,
|
||||
) { p, l ->
|
||||
when (p) {
|
||||
ServerProtocol.XFTP -> userServers.value = userServers.value.toMutableList().apply {
|
||||
this[operatorIndex] = this[operatorIndex].copy(
|
||||
xftpServers = l
|
||||
)
|
||||
}
|
||||
chatRelays = userServers.value[operatorIndex].chatRelays,
|
||||
onUpdate = { p, l ->
|
||||
when (p) {
|
||||
ServerProtocol.XFTP -> userServers.value = userServers.value.toMutableList().apply {
|
||||
this[operatorIndex] = this[operatorIndex].copy(
|
||||
xftpServers = l
|
||||
)
|
||||
}
|
||||
|
||||
ServerProtocol.SMP -> userServers.value = userServers.value.toMutableList().apply {
|
||||
ServerProtocol.SMP -> userServers.value = userServers.value.toMutableList().apply {
|
||||
this[operatorIndex] = this[operatorIndex].copy(
|
||||
smpServers = l
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onUpdateRelays = { relays ->
|
||||
userServers.value = userServers.value.toMutableList().apply {
|
||||
this[operatorIndex] = this[operatorIndex].copy(
|
||||
smpServers = l
|
||||
chatRelays = relays
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
SectionBottomSpacer()
|
||||
|
||||
+69
-17
@@ -203,21 +203,30 @@ fun YourServersViewLayout(
|
||||
testing = testing,
|
||||
smpServers = userServers.value[operatorIndex].smpServers,
|
||||
xftpServers = userServers.value[operatorIndex].xftpServers,
|
||||
) { p, l ->
|
||||
when (p) {
|
||||
ServerProtocol.XFTP -> userServers.value = userServers.value.toMutableList().apply {
|
||||
this[operatorIndex] = this[operatorIndex].copy(
|
||||
xftpServers = l
|
||||
)
|
||||
}
|
||||
chatRelays = userServers.value[operatorIndex].chatRelays,
|
||||
onUpdate = { p, l ->
|
||||
when (p) {
|
||||
ServerProtocol.XFTP -> userServers.value = userServers.value.toMutableList().apply {
|
||||
this[operatorIndex] = this[operatorIndex].copy(
|
||||
xftpServers = l
|
||||
)
|
||||
}
|
||||
|
||||
ServerProtocol.SMP -> userServers.value = userServers.value.toMutableList().apply {
|
||||
ServerProtocol.SMP -> userServers.value = userServers.value.toMutableList().apply {
|
||||
this[operatorIndex] = this[operatorIndex].copy(
|
||||
smpServers = l
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onUpdateRelays = { relays ->
|
||||
userServers.value = userServers.value.toMutableList().apply {
|
||||
this[operatorIndex] = this[operatorIndex].copy(
|
||||
smpServers = l
|
||||
chatRelays = relays
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
HowToButton()
|
||||
}
|
||||
@@ -229,16 +238,20 @@ fun YourServersViewLayout(
|
||||
fun TestServersButton(
|
||||
smpServers: List<UserServer>,
|
||||
xftpServers: List<UserServer>,
|
||||
chatRelays: List<UserChatRelay> = emptyList(),
|
||||
testing: MutableState<Boolean>,
|
||||
onUpdate: (ServerProtocol, List<UserServer>) -> Unit
|
||||
onUpdate: (ServerProtocol, List<UserServer>) -> Unit,
|
||||
onUpdateRelays: ((List<UserChatRelay>) -> Unit)? = null
|
||||
) {
|
||||
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 } && chatRelays.filter { !it.deleted }.none { it.enabled }) || testing.value
|
||||
}
|
||||
|
||||
SectionItemView(
|
||||
{
|
||||
scope.launch {
|
||||
testServers(testing, smpServers, xftpServers, chatModel, onUpdate)
|
||||
testServers(testing, smpServers, xftpServers, chatRelays, chatModel, onUpdate, onUpdateRelays)
|
||||
}
|
||||
},
|
||||
disabled = disabled.value
|
||||
@@ -338,20 +351,28 @@ private suspend fun testServers(
|
||||
testing: MutableState<Boolean>,
|
||||
smpServers: List<UserServer>,
|
||||
xftpServers: List<UserServer>,
|
||||
chatRelays: List<UserChatRelay>,
|
||||
m: ChatModel,
|
||||
onUpdate: (ServerProtocol, List<UserServer>) -> Unit
|
||||
onUpdate: (ServerProtocol, List<UserServer>) -> Unit,
|
||||
onUpdateRelays: ((List<UserChatRelay>) -> Unit)?
|
||||
) {
|
||||
val relaysResetStatus = resetRelayTestStatus(chatRelays)
|
||||
onUpdateRelays?.invoke(relaysResetStatus)
|
||||
val smpResetStatus = resetTestStatus(smpServers)
|
||||
onUpdate(ServerProtocol.SMP, smpResetStatus)
|
||||
val xftpResetStatus = resetTestStatus(xftpServers)
|
||||
onUpdate(ServerProtocol.XFTP, xftpResetStatus)
|
||||
testing.value = true
|
||||
val relayFailures = runRelaysTest(relaysResetStatus) { onUpdateRelays?.invoke(it) }
|
||||
val smpFailures = runServersTest(smpResetStatus, m) { onUpdate(ServerProtocol.SMP, it) }
|
||||
val xftpFailures = runServersTest(xftpResetStatus, m) { onUpdate(ServerProtocol.XFTP, it) }
|
||||
testing.value = false
|
||||
val fs = smpFailures + xftpFailures
|
||||
if (fs.isNotEmpty()) {
|
||||
val msg = fs.map { it.key + ": " + it.value.localizedDescription }.joinToString("\n")
|
||||
val failures = mutableListOf<String>()
|
||||
failures += relayFailures.map { (name, f) -> "$name: ${f.localizedDescription}" }
|
||||
failures += smpFailures.map { (srv, f) -> "$srv: ${f.localizedDescription}" }
|
||||
failures += xftpFailures.map { (srv, f) -> "$srv: ${f.localizedDescription}" }
|
||||
if (failures.isNotEmpty()) {
|
||||
val msg = failures.joinToString("\n")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.smp_servers_test_failed),
|
||||
text = generalGetString(MR.strings.smp_servers_test_some_failed) + "\n" + msg
|
||||
@@ -389,6 +410,37 @@ private suspend fun runServersTest(servers: List<UserServer>, m: ChatModel, onUp
|
||||
return fs
|
||||
}
|
||||
|
||||
private fun resetRelayTestStatus(relays: List<UserChatRelay>): List<UserChatRelay> {
|
||||
val copy = ArrayList(relays)
|
||||
for ((index, relay) in relays.withIndex()) {
|
||||
if (relay.enabled && !relay.deleted) {
|
||||
copy.removeAt(index)
|
||||
copy.add(index, relay.copy(tested = null))
|
||||
}
|
||||
}
|
||||
return copy
|
||||
}
|
||||
|
||||
private suspend fun runRelaysTest(relays: List<UserChatRelay>, onUpdated: (List<UserChatRelay>) -> Unit): Map<String, RelayTestFailure> {
|
||||
val fs: MutableMap<String, RelayTestFailure> = mutableMapOf()
|
||||
val updatedRelays = ArrayList<UserChatRelay>(relays)
|
||||
for ((index, relay) in relays.withIndex()) {
|
||||
if (relay.enabled && !relay.deleted) {
|
||||
interruptIfCancelled()
|
||||
val relayState = mutableStateOf(relay)
|
||||
val f = testRelayConnection(relayState)
|
||||
updatedRelays.removeAt(index)
|
||||
updatedRelays.add(index, relayState.value)
|
||||
onUpdated(updatedRelays.toList())
|
||||
if (f != null) {
|
||||
val name = relayState.value.name.ifEmpty { relayState.value.domains.firstOrNull() ?: relayState.value.address }
|
||||
fs[name] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
fun deleteXFTPServer(
|
||||
userServers: MutableState<List<UserOperatorServers>>,
|
||||
operatorServersIndex: Int,
|
||||
|
||||
@@ -2840,8 +2840,15 @@
|
||||
<string name="test_relay">Test relay</string>
|
||||
<string name="use_for_new_channels">Use for new channels</string>
|
||||
<string name="delete_relay">Delete relay</string>
|
||||
<string name="not_implemented">Not implemented</string>
|
||||
<string name="relay_testing_not_available">Relay testing is not yet available.</string>
|
||||
<string name="test_relay_to_retrieve_name"><![CDATA[<b>Test relay</b> to retrieve its name.]]></string>
|
||||
<string name="relay_test_failed_alert">Relay test failed!</string>
|
||||
<string name="relay_test_step_get_link">Get link</string>
|
||||
<string name="relay_test_step_decode_link">Decode link</string>
|
||||
<string name="relay_test_step_connect">Connect</string>
|
||||
<string name="relay_test_step_wait_response">Wait response</string>
|
||||
<string name="relay_test_step_verify">Verify</string>
|
||||
<string name="error_relay_test_failed_at_step">Test failed at step %s.</string>
|
||||
<string name="error_relay_test_server_auth">Server requires authorization to connect to relay, check password.</string>
|
||||
<string name="invalid_relay_name">Invalid relay name!</string>
|
||||
<string name="check_relay_name">Check relay name and try again.</string>
|
||||
<string name="invalid_relay_address">Invalid relay address!</string>
|
||||
|
||||
Reference in new issue
Block a user