Merge branch 'master' into master-android

This commit is contained in:
Evgeny Poberezkin committed 2025-07-07 11:40:21 +01:00
commit 1523c8a9e8
102 files changed
+2000 -965

No files matched your search

@@ -58,8 +58,8 @@ typedef long* chat_ctrl;
extern char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl);
extern char *chat_close_store(chat_ctrl ctrl);
extern char *chat_send_cmd(chat_ctrl ctrl, const char *cmd);
extern char *chat_send_remote_cmd(chat_ctrl ctrl, const int rhId, const char *cmd);
extern char *chat_send_cmd_retry(chat_ctrl ctrl, const char *cmd, const int retryNum);
extern char *chat_send_remote_cmd_retry(chat_ctrl ctrl, const int rhId, const char *cmd, const int retryNum);
extern char *chat_recv_msg(chat_ctrl ctrl); // deprecated
extern char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait);
extern char *chat_parse_markdown(const char *str);
@@ -102,20 +102,20 @@ Java_chat_simplex_common_platform_CoreKt_chatCloseStore(JNIEnv *env, __unused jc
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatSendCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg) {
Java_chat_simplex_common_platform_CoreKt_chatSendCmdRetry(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg, jint retryNum) {
const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE);
//jint length = (jint) (*env)->GetStringUTFLength(env, msg);
//for (int i = 0; i < length; ++i)
// __android_log_print(ANDROID_LOG_ERROR, "simplex", "%d: %02x\n", i, _msg[i]);
jstring res = (*env)->NewStringUTF(env, chat_send_cmd((void*)controller, _msg));
jstring res = (*env)->NewStringUTF(env, chat_send_cmd_retry((void*)controller, _msg, retryNum));
(*env)->ReleaseStringUTFChars(env, msg, _msg);
return res;
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatSendRemoteCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jint rhId, jstring msg) {
Java_chat_simplex_common_platform_CoreKt_chatSendRemoteCmdRetry(JNIEnv *env, __unused jclass clazz, jlong controller, jint rhId, jstring msg, jint retryNum) {
const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE);
jstring res = (*env)->NewStringUTF(env, chat_send_remote_cmd((void*)controller, rhId, _msg));
jstring res = (*env)->NewStringUTF(env, chat_send_remote_cmd_retry((void*)controller, rhId, _msg, retryNum));
(*env)->ReleaseStringUTFChars(env, msg, _msg);
return res;
}
@@ -31,8 +31,8 @@ typedef long* chat_ctrl;
extern char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl);
extern char *chat_close_store(chat_ctrl ctrl);
extern char *chat_send_cmd(chat_ctrl ctrl, const char *cmd);
extern char *chat_send_remote_cmd(chat_ctrl ctrl, const int rhId, const char *cmd);
extern char *chat_send_cmd_retry(chat_ctrl ctrl, const char *cmd, const int retryNum);
extern char *chat_send_remote_cmd_retry(chat_ctrl ctrl, const int rhId, const char *cmd, const int retryNum);
extern char *chat_recv_msg(chat_ctrl ctrl); // deprecated
extern char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait);
extern char *chat_parse_markdown(const char *str);
@@ -115,17 +115,17 @@ Java_chat_simplex_common_platform_CoreKt_chatCloseStore(JNIEnv *env, jclass claz
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatSendCmd(JNIEnv *env, jclass clazz, jlong controller, jstring msg) {
Java_chat_simplex_common_platform_CoreKt_chatSendCmdRetry(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg, jint retryNum) {
const char *_msg = encode_to_utf8_chars(env, msg);
jstring res = decode_to_utf8_string(env, chat_send_cmd((void*)controller, _msg));
jstring res = decode_to_utf8_string(env, chat_send_cmd_retry((void*)controller, _msg, retryNum));
(*env)->ReleaseStringUTFChars(env, msg, _msg);
return res;
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatSendRemoteCmd(JNIEnv *env, jclass clazz, jlong controller, jint rhId, jstring msg) {
Java_chat_simplex_common_platform_CoreKt_chatSendRemoteCmdRetry(JNIEnv *env, __unused jclass clazz, jlong controller, jint rhId, jstring msg, jint retryNum) {
const char *_msg = encode_to_utf8_chars(env, msg);
jstring res = decode_to_utf8_string(env, chat_send_remote_cmd((void*)controller, rhId, _msg));
jstring res = decode_to_utf8_string(env, chat_send_remote_cmd_retry((void*)controller, rhId, _msg, retryNum));
(*env)->ReleaseStringUTFChars(env, msg, _msg);
return res;
}
@@ -1288,6 +1288,8 @@ interface SomeChat {
val ready: Boolean
val chatDeleted: Boolean
val nextConnect: Boolean
val nextConnectPrepared: Boolean
val profileChangeProhibited: Boolean
val incognito: Boolean
fun featureEnabled(feature: ChatFeature): Boolean
val timedMessagesTTL: Int?
@@ -1368,6 +1370,8 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val ready get() = contact.ready
override val chatDeleted get() = contact.chatDeleted
override val nextConnect get() = contact.nextConnect
override val nextConnectPrepared get() = contact.nextConnectPrepared
override val profileChangeProhibited get() = contact.profileChangeProhibited
override val incognito get() = contact.incognito
override fun featureEnabled(feature: ChatFeature) = contact.featureEnabled(feature)
override val timedMessagesTTL: Int? get() = contact.timedMessagesTTL
@@ -1393,6 +1397,8 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val ready get() = groupInfo.ready
override val chatDeleted get() = groupInfo.chatDeleted
override val nextConnect get() = groupInfo.nextConnect
override val nextConnectPrepared get() = groupInfo.nextConnectPrepared
override val profileChangeProhibited get() = groupInfo.profileChangeProhibited
override val incognito get() = groupInfo.incognito
override fun featureEnabled(feature: ChatFeature) = groupInfo.featureEnabled(feature)
override val timedMessagesTTL: Int? get() = groupInfo.timedMessagesTTL
@@ -1417,6 +1423,8 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val ready get() = noteFolder.ready
override val chatDeleted get() = noteFolder.chatDeleted
override val nextConnect get() = noteFolder.nextConnect
override val nextConnectPrepared get() = noteFolder.nextConnectPrepared
override val profileChangeProhibited get() = noteFolder.profileChangeProhibited
override val incognito get() = noteFolder.incognito
override fun featureEnabled(feature: ChatFeature) = noteFolder.featureEnabled(feature)
override val timedMessagesTTL: Int? get() = noteFolder.timedMessagesTTL
@@ -1441,6 +1449,8 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val ready get() = contactRequest.ready
override val chatDeleted get() = contactRequest.chatDeleted
override val nextConnect get() = contactRequest.nextConnect
override val nextConnectPrepared get() = contactRequest.nextConnectPrepared
override val profileChangeProhibited get() = contactRequest.profileChangeProhibited
override val incognito get() = contactRequest.incognito
override fun featureEnabled(feature: ChatFeature) = contactRequest.featureEnabled(feature)
override val timedMessagesTTL: Int? get() = contactRequest.timedMessagesTTL
@@ -1465,6 +1475,8 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val ready get() = contactConnection.ready
override val chatDeleted get() = contactConnection.chatDeleted
override val nextConnect get() = contactConnection.nextConnect
override val nextConnectPrepared get() = contactConnection.nextConnectPrepared
override val profileChangeProhibited get() = contactConnection.profileChangeProhibited
override val incognito get() = contactConnection.incognito
override fun featureEnabled(feature: ChatFeature) = contactConnection.featureEnabled(feature)
override val timedMessagesTTL: Int? get() = contactConnection.timedMessagesTTL
@@ -1494,6 +1506,8 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val ready get() = false
override val chatDeleted get() = false
override val nextConnect get() = false
override val nextConnectPrepared get() = false
override val profileChangeProhibited get() = false
override val incognito get() = false
override fun featureEnabled(feature: ChatFeature) = false
override val timedMessagesTTL: Int? get() = null
@@ -1688,7 +1702,8 @@ data class Contact(
val active get() = contactStatus == ContactStatus.Active
override val nextConnect get() = sendMsgToConnect
val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent
val nextConnectPrepared get() = preparedContact != null && (activeConn == null || activeConn.connStatus == ConnStatus.Prepared)
override val nextConnectPrepared get() = preparedContact != null && (activeConn == null || activeConn.connStatus == ConnStatus.Prepared)
override val profileChangeProhibited get() = activeConn != null
val nextAcceptContactRequest get() = contactRequestId != null && (activeConn == null || activeConn.connStatus == ConnStatus.New)
val sendMsgToConnect get() = nextSendGrpInv || nextConnectPrepared
override val incognito get() = contactConnIncognito
@@ -1942,7 +1957,8 @@ data class GroupInfo (
override val apiId get() = groupId
override val ready get() = membership.memberActive
override val nextConnect get() = nextConnectPrepared
val nextConnectPrepared = if (preparedGroup != null) !preparedGroup.connLinkStartedConnection else false
override val nextConnectPrepared = if (preparedGroup != null) !preparedGroup.connLinkStartedConnection else false
override val profileChangeProhibited get() = preparedGroup?.connLinkPreparedConnection ?: false
override val chatDeleted get() = false
override val incognito get() = membership.memberIncognito
override fun featureEnabled(feature: ChatFeature) = when (feature) {
@@ -2015,6 +2031,7 @@ data class GroupInfo (
@Serializable
data class PreparedGroup (
val connLinkToConnect: CreatedConnLink,
val connLinkPreparedConnection: Boolean,
val connLinkStartedConnection: Boolean
)
@@ -2396,6 +2413,8 @@ class NoteFolder(
override val chatDeleted get() = false
override val ready get() = true
override val nextConnect get() = false
override val nextConnectPrepared get() = false
override val profileChangeProhibited get() = false
override val incognito get() = false
override fun featureEnabled(feature: ChatFeature) = feature == ChatFeature.Voice
override val timedMessagesTTL: Int? get() = null
@@ -2432,6 +2451,8 @@ class UserContactRequest (
override val chatDeleted get() = false
override val ready get() = true
override val nextConnect get() = false
override val nextConnectPrepared get() = false
override val profileChangeProhibited get() = false
override val incognito get() = false
override fun featureEnabled(feature: ChatFeature) = false
override val timedMessagesTTL: Int? get() = null
@@ -2473,6 +2494,8 @@ class PendingContactConnection(
override val chatDeleted get() = false
override val ready get() = false
override val nextConnect get() = false
override val nextConnectPrepared get() = false
override val profileChangeProhibited get() = false
override val incognito get() = customUserProfileId != null
override fun featureEnabled(feature: ChatFeature) = false
override val timedMessagesTTL: Int? get() = null
@@ -149,8 +149,10 @@ class AppPreferences {
val networkHostMode: SharedPreference<HostMode> = mkSafeEnumPreference(SHARED_PREFS_NETWORK_HOST_MODE, HostMode.default)
val networkRequiredHostMode = mkBoolPreference(SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE, false)
val networkSMPWebPortServers: SharedPreference<SMPWebPortServers> = mkSafeEnumPreference(SHARED_PREFS_NETWORK_SMP_WEB_PORT_SERVERS, SMPWebPortServers.default)
val networkTCPConnectTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT, NetCfg.defaults.tcpConnectTimeout, NetCfg.proxyDefaults.tcpConnectTimeout)
val networkTCPTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT, NetCfg.defaults.tcpTimeout, NetCfg.proxyDefaults.tcpTimeout)
val networkTCPConnectTimeoutBackground = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT_BACKGROUND, NetCfg.defaults.tcpConnectTimeout.backgroundTimeout, NetCfg.proxyDefaults.tcpConnectTimeout.backgroundTimeout)
val networkTCPConnectTimeoutInteractive = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT_INTERACTIVE, NetCfg.defaults.tcpConnectTimeout.interactiveTimeout, NetCfg.proxyDefaults.tcpConnectTimeout.interactiveTimeout)
val networkTCPTimeoutBackground = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT_BACKGROUND, NetCfg.defaults.tcpTimeout.backgroundTimeout, NetCfg.proxyDefaults.tcpTimeout.backgroundTimeout)
val networkTCPTimeoutInteractive = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT_INTERACTIVE, NetCfg.defaults.tcpTimeout.interactiveTimeout, NetCfg.proxyDefaults.tcpTimeout.interactiveTimeout)
val networkTCPTimeoutPerKb = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT_PER_KB, NetCfg.defaults.tcpTimeoutPerKb, NetCfg.proxyDefaults.tcpTimeoutPerKb)
val networkRcvConcurrency = mkIntPreference(SHARED_PREFS_NETWORK_RCV_CONCURRENCY, NetCfg.defaults.rcvConcurrency)
val networkSMPPingInterval = mkLongPreference(SHARED_PREFS_NETWORK_SMP_PING_INTERVAL, NetCfg.defaults.smpPingInterval)
@@ -271,13 +273,14 @@ class AppPreferences {
set = fun(value) = settings.putFloat(prefName, value)
)
private fun mkTimeoutPreference(prefName: String, default: Long, proxyDefault: Long): SharedPreference<Long> {
val d = if (networkUseSocksProxy.get()) proxyDefault else default
return SharedPreference(
get = fun() = settings.getLong(prefName, d),
private fun mkTimeoutPreference(prefName: String, default: Long, proxyDefault: Long): SharedPreference<Long> =
SharedPreference(
get = {
val d = if (networkUseSocksProxy.get()) proxyDefault else default
settings.getLong(prefName, d)
},
set = fun(value) = settings.putLong(prefName, value)
)
}
private fun mkBoolPreference(prefName: String, default: Boolean) =
SharedPreference(
@@ -402,8 +405,12 @@ class AppPreferences {
private const val SHARED_PREFS_NETWORK_HOST_MODE = "NetworkHostMode"
private const val SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE = "NetworkRequiredHostMode"
private const val SHARED_PREFS_NETWORK_SMP_WEB_PORT_SERVERS = "NetworkSMPWebPortServers"
private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT = "NetworkTCPConnectTimeout"
private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT = "NetworkTCPTimeout"
// private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT = "NetworkTCPConnectTimeout"
// private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT = "NetworkTCPTimeout"
private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT_BACKGROUND = "NetworkTCPConnectTimeoutBackground"
private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT_INTERACTIVE = "NetworkTCPConnectTimeoutInteractive"
private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT_BACKGROUND = "NetworkTCPTimeoutBackground"
private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT_INTERACTIVE = "NetworkTCPTimeoutInteractive"
private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT_PER_KB = "networkTCPTimeoutPerKb"
private const val SHARED_PREFS_NETWORK_RCV_CONCURRENCY = "networkRcvConcurrency"
private const val SHARED_PREFS_NETWORK_SMP_PING_INTERVAL = "NetworkSMPPingInterval"
@@ -666,7 +673,87 @@ object ChatController {
}
}
suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null, log: Boolean = true): API {
private suspend fun sendCmdWithRetry(rhId: Long?, cmd: CC, retryNum: Int = 0): API? {
val r = sendCmd(rhId, cmd, retryNum = retryNum)
val alert = if (r is API.Error) retryableNetworkErrorAlert(r.err) else null
if (alert == null) return r
else return suspendCancellableCoroutine { cont ->
showRetryAlert(
alert,
onCancel = {
cont.resumeWith(Result.success(null))
},
onRetry = {
withLongRunningApi {
cont.resumeWith(
runCatching {
coroutineScope {
sendCmdWithRetry(rhId, cmd, retryNum = retryNum + 1)
}
}
)
}
}
)
cont.invokeOnCancellation {
cont.resumeWith(Result.success(null))
}
}
}
private fun showRetryAlert(alert: Pair<StringResource, String>, onCancel: () -> Unit, onRetry: () -> Unit) {
AlertManager.shared.showAlertDialog(
title = generalGetString(alert.first),
text = alert.second,
confirmText = generalGetString(MR.strings.retry_verb),
onConfirm = onRetry,
onDismiss = onCancel,
onDismissRequest = onCancel
)
}
private fun retryableNetworkErrorAlert(err: ChatError): Pair<StringResource, String>? {
if (err !is ChatError.ChatErrorAgent) return null
val e = err.agentError
when (e) {
is AgentErrorType.BROKER -> {
val message = { String.format(generalGetString(MR.strings.network_error_desc), serverHostname(e.brokerAddress)) }
return when (e.brokerErr) {
is BrokerErrorType.TIMEOUT -> MR.strings.connection_timeout to message()
is BrokerErrorType.NETWORK -> MR.strings.connection_error to message()
else -> null
}
}
is AgentErrorType.SMP ->
if (e.smpErr is SMPErrorType.PROXY && e.smpErr.proxyErr is ProxyError.BROKER) {
val message = { String.format(generalGetString(MR.strings.smp_proxy_error_connecting), serverHostname(e.serverAddress)) }
return when (e.smpErr.proxyErr.brokerErr) {
is BrokerErrorType.TIMEOUT -> MR.strings.private_routing_timeout to message()
is BrokerErrorType.NETWORK -> MR.strings.private_routing_error to message()
else -> null
}
}
is AgentErrorType.PROXY ->
if (e.proxyErr is ProxyClientError.ProxyProtocolError && e.proxyErr.protocolErr is SMPErrorType.PROXY) {
val message = { String.format(generalGetString(MR.strings.proxy_destination_error_failed_to_connect), serverHostname(e.proxyServer), serverHostname(e.relayServer)) }
return when (e.proxyErr.protocolErr.proxyErr) {
is ProxyError.BROKER ->
when (e.proxyErr.protocolErr.proxyErr.brokerErr) {
is BrokerErrorType.TIMEOUT -> MR.strings.private_routing_timeout to message()
is BrokerErrorType.NETWORK -> MR.strings.private_routing_error to message()
else -> null
}
is ProxyError.NO_SESSION -> MR.strings.private_routing_no_session to message()
else -> null
}
}
else -> return null
}
return null
}
suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null, retryNum: Int = 0, log: Boolean = true): API {
val ctrl = otherCtrl ?: ctrl ?: throw Exception("Controller is not initialized")
return withContext(Dispatchers.IO) {
@@ -675,7 +762,7 @@ object ChatController {
chatModel.addTerminalItem(TerminalItem.cmd(rhId, cmd.obfuscated))
Log.d(TAG, "sendCmd: ${cmd.cmdType}")
}
val rStr = if (rhId == null) chatSendCmd(ctrl, c) else chatSendRemoteCmd(ctrl, rhId.toInt(), c)
val rStr = if (rhId == null) chatSendCmdRetry(ctrl, c, retryNum) else chatSendRemoteCmdRetry(ctrl, rhId.toInt(), c, retryNum)
// coroutine was cancelled already, no need to process response (helps with apiListMembers - very heavy query in large groups)
interruptIfCancelled()
val r = json.decodeFromString<API>(rStr)
@@ -1208,16 +1295,16 @@ object ChatController {
}
suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair<RcvMsgInfo?, ServerQueueInfo>? {
val r = sendCmd(rh, CC.APIContactQueueInfo(contactId))
val r = sendCmdWithRetry(rh, CC.APIContactQueueInfo(contactId))
if (r is API.Result && r.res is CR.QueueInfoR) return r.res.rcvMsgInfo to r.res.queueInfo
apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r)
if (r != null) apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r)
return null
}
suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair<RcvMsgInfo?, ServerQueueInfo>? {
val r = sendCmd(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId))
val r = sendCmdWithRetry(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId))
if (r is API.Result && r.res is CR.QueueInfoR) return r.res.rcvMsgInfo to r.res.queueInfo
apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r)
if (r != null) apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r)
return null
}
@@ -1293,9 +1380,10 @@ object ChatController {
suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair<Pair<CreatedConnLink, PendingContactConnection>?, (() -> Unit)?> {
val userId = try { currentUserId("apiAddContact") } catch (e: Exception) { return null to null }
val r = sendCmd(rh, CC.APIAddContact(userId, incognito = incognito))
val r = sendCmdWithRetry(rh, CC.APIAddContact(userId, incognito = incognito))
return when {
r is API.Result && r.res is CR.Invitation -> (r.res.connLinkInvitation to r.res.connection) to null
r == null -> null to null
!(networkErrorAlert(r)) -> null to { apiErrorAlert("apiAddContact", generalGetString(MR.strings.connection_error), r) }
else -> null to null
}
@@ -1311,8 +1399,9 @@ object ChatController {
}
suspend fun apiChangeConnectionUser(rh: Long?, connId: Long, userId: Long): PendingContactConnection? {
val r = sendCmd(rh, CC.ApiChangeConnectionUser(connId, userId))
val r = sendCmdWithRetry(rh, CC.ApiChangeConnectionUser(connId, userId))
if (r is API.Result && r.res is CR.ConnectionUserChanged) return r.res.toConnection
if (r == null) return null
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiChangeConnectionUser", generalGetString(MR.strings.error_sending_message), r)
}
@@ -1321,15 +1410,15 @@ object ChatController {
suspend fun apiConnectPlan(rh: Long?, connLink: String): Pair<CreatedConnLink, ConnectionPlan>? {
val userId = kotlin.runCatching { currentUserId("apiConnectPlan") }.getOrElse { return null }
val r = sendCmd(rh, CC.APIConnectPlan(userId, connLink))
val r = sendCmdWithRetry(rh, CC.APIConnectPlan(userId, connLink))
if (r is API.Result && r.res is CR.CRConnectionPlan) return r.res.connLink to r.res.connectionPlan
apiConnectResponseAlert(r)
if (r != null) apiConnectResponseAlert(r)
return null
}
suspend fun apiConnect(rh: Long?, incognito: Boolean, connLink: CreatedConnLink): PendingContactConnection? {
val userId = try { currentUserId("apiConnect") } catch (e: Exception) { return null }
val r = sendCmd(rh, CC.APIConnect(userId, incognito, connLink))
val r = sendCmdWithRetry(rh, CC.APIConnect(userId, incognito, connLink))
when {
r is API.Result && r.res is CR.SentConfirmation -> return r.res.connection
r is API.Result && r.res is CR.SentInvitation -> return r.res.connection
@@ -1338,7 +1427,7 @@ object ChatController {
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), r.res.contact.displayName)
)
else -> apiConnectResponseAlert(r)
r != null -> apiConnectResponseAlert(r)
}
return null
}
@@ -1426,8 +1515,9 @@ object ChatController {
}
suspend fun apiConnectPreparedContact(rh: Long?, contactId: Long, incognito: Boolean, msg: MsgContent?): Contact? {
val r = sendCmd(rh, CC.APIConnectPreparedContact(contactId, incognito, msg))
val r = sendCmdWithRetry(rh, CC.APIConnectPreparedContact(contactId, incognito, msg))
if (r is API.Result && r.res is CR.StartedConnectionToContact) return r.res.contact
if (r == null) return null
Log.e(TAG, "apiConnectPreparedContact bad response: ${r.responseType} ${r.details}")
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiConnectPreparedContact", generalGetString(MR.strings.connection_error), r)
@@ -1436,8 +1526,9 @@ object ChatController {
}
suspend fun apiConnectPreparedGroup(rh: Long?, groupId: Long, incognito: Boolean, msg: MsgContent?): GroupInfo? {
val r = sendCmd(rh, CC.APIConnectPreparedGroup(groupId, incognito, msg))
val r = sendCmdWithRetry(rh, CC.APIConnectPreparedGroup(groupId, incognito, msg))
if (r is API.Result && r.res is CR.StartedConnectionToGroup) return r.res.groupInfo
if (r == null) return null
Log.e(TAG, "apiConnectPreparedGroup bad response: ${r.responseType} ${r.details}")
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiConnectPreparedGroup", generalGetString(MR.strings.connection_error), r)
@@ -1447,8 +1538,9 @@ object ChatController {
suspend fun apiConnectContactViaAddress(rh: Long?, incognito: Boolean, contactId: Long): Contact? {
val userId = try { currentUserId("apiConnectContactViaAddress") } catch (e: Exception) { return null }
val r = sendCmd(rh, CC.ApiConnectContactViaAddress(userId, incognito, contactId))
val r = sendCmdWithRetry(rh, CC.ApiConnectContactViaAddress(userId, incognito, contactId))
if (r is API.Result && r.res is CR.SentInvitationToContact) return r.res.contact
if (r == null) return null
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiConnectContactViaAddress", generalGetString(MR.strings.connection_error), r)
}
@@ -1592,8 +1684,9 @@ object ChatController {
suspend fun apiCreateUserAddress(rh: Long?): CreatedConnLink? {
val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null }
val r = sendCmd(rh, CC.ApiCreateMyAddress(userId))
val r = sendCmdWithRetry(rh, CC.ApiCreateMyAddress(userId))
if (r is API.Result && r.res is CR.UserContactLinkCreated) return r.res.connLinkContact
if (r == null) return null
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiCreateUserAddress", generalGetString(MR.strings.error_creating_address), r)
}
@@ -1602,8 +1695,9 @@ object ChatController {
suspend fun apiDeleteUserAddress(rh: Long?): User? {
val userId = try { currentUserId("apiDeleteUserAddress") } catch (e: Exception) { return null }
val r = sendCmd(rh, CC.ApiDeleteMyAddress(userId))
val r = sendCmdWithRetry(rh, CC.ApiDeleteMyAddress(userId))
if (r is API.Result && r.res is CR.UserContactLinkDeleted) return r.res.user.updateRemoteHostId(rh)
if (r == null) return null
Log.e(TAG, "apiDeleteUserAddress bad response: ${r.responseType} ${r.details}")
return null
}
@@ -1623,8 +1717,9 @@ object ChatController {
suspend fun apiAddMyAddressShortLink(rh: Long?): UserContactLinkRec? {
val userId = kotlin.runCatching { currentUserId("apiAddMyAddressShortLink") }.getOrElse { return null }
val r = sendCmd(rh, CC.ApiAddMyAddressShortLink(userId))
val r = sendCmdWithRetry(rh, CC.ApiAddMyAddressShortLink(userId))
if (r is API.Result && r.res is CR.UserContactLink) return r.res.contactLink
if (r == null) return null
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiAddMyAddressShortLink", generalGetString(MR.strings.error_creating_address), r)
}
@@ -1633,19 +1728,20 @@ object ChatController {
suspend fun apiSetUserAddressSettings(rh: Long?, settings: AddressSettings): UserContactLinkRec? {
val userId = kotlin.runCatching { currentUserId("apiSetUserAddressSettings") }.getOrElse { return null }
val r = sendCmd(rh, CC.ApiSetAddressSettings(userId, settings))
val r = sendCmdWithRetry(rh, CC.ApiSetAddressSettings(userId, settings))
if (r is API.Result && r.res is CR.UserContactLinkUpdated) return r.res.contactLink
if (r is API.Error && r.err is ChatError.ChatErrorStore
&& r.err.storeError is StoreError.UserContactLinkNotFound
) {
return null
}
if (r == null) return null
Log.e(TAG, "userAddressAutoAccept bad response: ${r.responseType} ${r.details}")
return null
}
suspend fun apiAcceptContactRequest(rh: Long?, incognito: Boolean, contactReqId: Long): Contact? {
val r = sendCmd(rh, CC.ApiAcceptContact(incognito, contactReqId))
val r = sendCmdWithRetry(rh, CC.ApiAcceptContact(incognito, contactReqId))
return when {
r is API.Result && r.res is CR.AcceptingContactRequest -> r.res.contact
r is API.Error && r.err is ChatError.ChatErrorAgent
@@ -1657,12 +1753,13 @@ object ChatController {
)
null
}
else -> {
r != null -> {
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiAcceptContactRequest", generalGetString(MR.strings.error_accepting_contact_request), r)
}
null
}
else -> null
}
}
@@ -1944,7 +2041,7 @@ object ChatController {
}
suspend fun apiJoinGroup(rh: Long?, groupId: Long) {
val r = sendCmd(rh, CC.ApiJoinGroup(groupId))
val r = sendCmdWithRetry(rh, CC.ApiJoinGroup(groupId))
when {
r is API.Result && r.res is CR.UserAcceptedGroupSent ->
withContext(Dispatchers.Main) {
@@ -1965,7 +2062,7 @@ object ChatController {
apiErrorAlert("apiJoinGroup", generalGetString(MR.strings.error_joining_group), r)
}
}
else -> apiErrorAlert("apiJoinGroup", generalGetString(MR.strings.error_joining_group), r)
r != null -> apiErrorAlert("apiJoinGroup", generalGetString(MR.strings.error_joining_group), r)
}
}
@@ -2046,8 +2143,9 @@ object ChatController {
}
suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): GroupLink? {
val r = sendCmd(rh, CC.APICreateGroupLink(groupId, memberRole))
val r = sendCmdWithRetry(rh, CC.APICreateGroupLink(groupId, memberRole))
if (r is API.Result && r.res is CR.GroupLinkCreated) return r.res.groupLink
if (r == null) return null
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiCreateGroupLink", generalGetString(MR.strings.error_creating_link_for_group), r)
}
@@ -2064,8 +2162,9 @@ object ChatController {
}
suspend fun apiDeleteGroupLink(rh: Long?, groupId: Long): Boolean {
val r = sendCmd(rh, CC.APIDeleteGroupLink(groupId))
val r = sendCmdWithRetry(rh, CC.APIDeleteGroupLink(groupId))
if (r is API.Result && r.res is CR.GroupLinkDeleted) return true
if (r == null) return false
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiDeleteGroupLink", generalGetString(MR.strings.error_deleting_link_for_group), r)
}
@@ -2080,8 +2179,9 @@ object ChatController {
}
suspend fun apiAddGroupShortLink(rh: Long?, groupId: Long): GroupLink? {
val r = sendCmd(rh, CC.ApiAddGroupShortLink(groupId))
val r = sendCmdWithRetry(rh, CC.ApiAddGroupShortLink(groupId))
if (r is API.Result && r.res is CR.CRGroupLink) return r.res.groupLink
if (r == null) return null
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiAddGroupShortLink", generalGetString(MR.strings.error_creating_link_for_group), r)
}
@@ -2510,6 +2610,12 @@ object ChatController {
chatModel.networkStatuses[s.agentConnId] = s.networkStatus
}
}
is CR.ChatInfoUpdated ->
if (active(r.user)) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateChatInfo(rhId, r.chatInfo)
}
}
is CR.NewChatItems -> withBGApi {
r.chatItems.forEach { chatItem ->
val cInfo = chatItem.chatInfo
@@ -3306,8 +3412,14 @@ object ChatController {
val smpProxyMode = appPrefs.networkSMPProxyMode.get()
val smpProxyFallback = appPrefs.networkSMPProxyFallback.get()
val smpWebPortServers = appPrefs.networkSMPWebPortServers.get()
val tcpConnectTimeout = appPrefs.networkTCPConnectTimeout.get()
val tcpTimeout = appPrefs.networkTCPTimeout.get()
val tcpConnectTimeout = NetworkTimeout(
backgroundTimeout = appPrefs.networkTCPConnectTimeoutBackground.get(),
interactiveTimeout = appPrefs.networkTCPConnectTimeoutInteractive.get()
)
val tcpTimeout = NetworkTimeout(
backgroundTimeout = appPrefs.networkTCPTimeoutBackground.get(),
interactiveTimeout = appPrefs.networkTCPTimeoutInteractive.get()
)
val tcpTimeoutPerKb = appPrefs.networkTCPTimeoutPerKb.get()
val rcvConcurrency = appPrefs.networkRcvConcurrency.get()
val smpPingInterval = appPrefs.networkSMPPingInterval.get()
@@ -3350,8 +3462,10 @@ object ChatController {
appPrefs.networkSMPProxyMode.set(cfg.smpProxyMode)
appPrefs.networkSMPProxyFallback.set(cfg.smpProxyFallback)
appPrefs.networkSMPWebPortServers.set(cfg.smpWebPortServers)
appPrefs.networkTCPConnectTimeout.set(cfg.tcpConnectTimeout)
appPrefs.networkTCPTimeout.set(cfg.tcpTimeout)
appPrefs.networkTCPConnectTimeoutBackground.set(cfg.tcpConnectTimeout.backgroundTimeout)
appPrefs.networkTCPConnectTimeoutInteractive.set(cfg.tcpConnectTimeout.interactiveTimeout)
appPrefs.networkTCPTimeoutBackground.set(cfg.tcpTimeout.backgroundTimeout)
appPrefs.networkTCPTimeoutInteractive.set(cfg.tcpTimeout.interactiveTimeout)
appPrefs.networkTCPTimeoutPerKb.set(cfg.tcpTimeoutPerKb)
appPrefs.networkRcvConcurrency.set(cfg.rcvConcurrency)
appPrefs.networkSMPPingInterval.set(cfg.smpPingInterval)
@@ -4502,8 +4616,8 @@ data class NetCfg(
val smpProxyMode: SMPProxyMode = SMPProxyMode.default,
val smpProxyFallback: SMPProxyFallback = SMPProxyFallback.default,
val smpWebPortServers: SMPWebPortServers = SMPWebPortServers.default,
val tcpConnectTimeout: Long, // microseconds
val tcpTimeout: Long, // microseconds
val tcpConnectTimeout: NetworkTimeout,
val tcpTimeout: NetworkTimeout,
val tcpTimeoutPerKb: Long, // microseconds
val rcvConcurrency: Int, // pool size
val tcpKeepAlive: KeepAliveOpts? = KeepAliveOpts.defaults,
@@ -4522,8 +4636,8 @@ data class NetCfg(
val defaults: NetCfg =
NetCfg(
socksProxy = null,
tcpConnectTimeout = 25_000_000,
tcpTimeout = 15_000_000,
tcpConnectTimeout = NetworkTimeout(backgroundTimeout = 45_000_000, interactiveTimeout = 15_000_000),
tcpTimeout = NetworkTimeout(backgroundTimeout = 30_000_000, interactiveTimeout = 10_000_000),
tcpTimeoutPerKb = 10_000,
rcvConcurrency = 12,
smpPingInterval = 1200_000_000
@@ -4532,8 +4646,8 @@ data class NetCfg(
val proxyDefaults: NetCfg =
NetCfg(
socksProxy = ":9050",
tcpConnectTimeout = 35_000_000,
tcpTimeout = 20_000_000,
tcpConnectTimeout = NetworkTimeout(backgroundTimeout = 60_000_000, interactiveTimeout = 30_000_000),
tcpTimeout = NetworkTimeout(backgroundTimeout = 40_000_000, interactiveTimeout = 20_000_000),
tcpTimeoutPerKb = 15_000,
rcvConcurrency = 8,
smpPingInterval = 1200_000_000
@@ -4557,6 +4671,12 @@ data class NetCfg(
}
}
@Serializable
data class NetworkTimeout(
val backgroundTimeout: Long, // microseconds
val interactiveTimeout: Long // microseconds
)
@Serializable
data class NetworkProxy(
val username: String = "",
@@ -5959,6 +6079,7 @@ sealed class CR {
// TODO remove above
@Serializable @SerialName("networkStatus") class NetworkStatusResp(val networkStatus: NetworkStatus, val connections: List<String>): CR()
@Serializable @SerialName("networkStatuses") class NetworkStatuses(val user_: UserRef?, val networkStatuses: List<ConnNetworkStatus>): CR()
@Serializable @SerialName("chatInfoUpdated") class ChatInfoUpdated(val user: UserRef, val chatInfo: ChatInfo): CR()
@Serializable @SerialName("newChatItems") class NewChatItems(val user: UserRef, val chatItems: List<AChatItem>): CR()
@Serializable @SerialName("chatItemsStatusesUpdated") class ChatItemsStatusesUpdated(val user: UserRef, val chatItems: List<AChatItem>): CR()
@Serializable @SerialName("chatItemUpdated") class ChatItemUpdated(val user: UserRef, val chatItem: AChatItem): CR()
@@ -6144,6 +6265,7 @@ sealed class CR {
is ContactSubSummary -> "contactSubSummary"
is NetworkStatusResp -> "networkStatus"
is NetworkStatuses -> "networkStatuses"
is ChatInfoUpdated -> "chatInfoUpdated"
is NewChatItems -> "newChatItems"
is ChatItemsStatusesUpdated -> "chatItemsStatusesUpdated"
is ChatItemUpdated -> "chatItemUpdated"
@@ -6321,6 +6443,7 @@ sealed class CR {
is ContactSubSummary -> withUser(user, json.encodeToString(contactSubscriptions))
is NetworkStatusResp -> "networkStatus $networkStatus\nconnections: $connections"
is NetworkStatuses -> withUser(user_, json.encodeToString(networkStatuses))
is ChatInfoUpdated -> withUser(user, json.encodeToString(chatInfo))
is NewChatItems -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) })
is ChatItemsStatusesUpdated -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) })
is ChatItemUpdated -> withUser(user, json.encodeToString(chatItem))
@@ -6467,17 +6590,6 @@ data class CreatedConnLink(val connFullLink: String, val connShortLink: String?)
fun simplexChatUri(short: Boolean): String =
if (short) connShortLink ?: simplexChatLink(connFullLink)
else simplexChatLink(connFullLink)
companion object {
val nullableStateSaver: Saver<CreatedConnLink?, Pair<String?, String?>> = Saver(
save = { link -> link?.connFullLink to link?.connShortLink },
restore = { saved ->
val connFullLink = saved.first
if (connFullLink == null) null
else CreatedConnLink(connFullLink = connFullLink, connShortLink = saved.second)
}
)
}
}
fun simplexChatLink(uri: String): String =
@@ -6645,7 +6757,45 @@ data class GroupLink(
val shortLinkDataSet: Boolean,
val groupLinkId: String,
val acceptMemberRole: GroupMemberRole
)
) {
companion object {
val nullableStateSaver: Saver<GroupLink?, List<Any?>> = Saver(
save = { groupLink ->
if (groupLink == null) return@Saver null
val conn = groupLink.connLinkContact
val connData = conn.connFullLink to conn.connShortLink
listOf(
groupLink.userContactLinkId,
connData,
groupLink.shortLinkDataSet,
groupLink.groupLinkId,
groupLink.acceptMemberRole.name
)
},
restore = { saved ->
val list = saved as? List<*> ?: return@Saver null
val userContactLinkId = list.getOrNull(0) as? Long ?: return@Saver null
val connPair = list.getOrNull(1) as? Pair<*, *> ?: return@Saver null
val connFullLink = connPair.first as? String ?: return@Saver null
val connShortLink = connPair.second as? String
val shortLinkDataSet = list.getOrNull(2) as? Boolean ?: return@Saver null
val groupLinkId = list.getOrNull(3) as? String ?: return@Saver null
val roleName = list.getOrNull(4) as? String ?: return@Saver null
GroupLink(
userContactLinkId = userContactLinkId,
connLinkContact = CreatedConnLink(connFullLink, connShortLink),
shortLinkDataSet = shortLinkDataSet,
groupLinkId = groupLinkId,
acceptMemberRole = GroupMemberRole.valueOf(roleName)
)
}
)
}
}
@Serializable
data class CoreVersionInfo(
@@ -22,8 +22,8 @@ external fun pipeStdOutToSocket(socketName: String) : Int
typealias ChatCtrl = Long
external fun chatMigrateInit(dbPath: String, dbKey: String, confirm: String): Array<Any>
external fun chatCloseStore(ctrl: ChatCtrl): String
external fun chatSendCmd(ctrl: ChatCtrl, msg: String): String
external fun chatSendRemoteCmd(ctrl: ChatCtrl, rhId: Int, msg: String): String
external fun chatSendCmdRetry(ctrl: ChatCtrl, msg: String, retryNum: Int): String
external fun chatSendRemoteCmdRetry(ctrl: ChatCtrl, rhId: Int, msg: String, retryNum: Int): String
external fun chatRecvMsg(ctrl: ChatCtrl): String
external fun chatRecvMsgWait(ctrl: ChatCtrl, timeout: Int): String
external fun chatParseMarkdown(str: String): String
@@ -729,7 +729,8 @@ private fun connectingText(chatInfo: ChatInfo): String? {
is ChatInfo.Group ->
when (chatInfo.groupInfo.membership.memberStatus) {
GroupMemberStatus.MemAccepted -> generalGetString(MR.strings.group_connection_pending) // TODO [short links] add member status to show transition from prepared group to started connection earlier?
GroupMemberStatus.MemUnknown -> if (chatInfo.groupInfo.preparedGroup?.connLinkStartedConnection == true) generalGetString(MR.strings.group_connection_pending) else null
GroupMemberStatus.MemAccepted -> generalGetString(MR.strings.group_connection_pending)
else -> null
}
@@ -65,7 +65,7 @@ fun ComposeContextProfilePickerView(
Modifier.size(20.dp),
tint = MaterialTheme.colors.secondary,
)
} else {
} else if (!chat.chatInfo.profileChangeProhibited) {
Icon(
painterResource(
MR.images.ic_chevron_up
@@ -103,14 +103,21 @@ fun ComposeContextProfilePickerView(
keepingChatId = chat.id
)
if (chatModel.currentUser.value?.userId != newUser.userId) {
AlertManager.shared.showAlertMsg(generalGetString(
MR.strings.switching_profile_error_title),
AlertManager.shared.showAlertMsg(
generalGetString(MR.strings.switching_profile_error_title),
String.format(generalGetString(MR.strings.switching_profile_error_message), newUser.chatViewName)
)
}
}
}
fun showCantChangeProfileAlert() {
AlertManager.shared.showAlertMsg(
generalGetString(MR.strings.context_user_picker_cant_change_profile_alert_title),
generalGetString(MR.strings.context_user_picker_cant_change_profile_alert_message)
)
}
@Composable
fun ProfilePickerUserOption(user: User) {
Row(
@@ -118,15 +125,19 @@ fun ComposeContextProfilePickerView(
.fillMaxWidth()
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp)
.clickable(onClick = {
if (selectedUser.value.userId == user.userId) {
if (!incognitoDefault) {
listExpanded.value = !listExpanded.value
if (!chat.chatInfo.profileChangeProhibited) {
if (selectedUser.value.userId == user.userId) {
if (!incognitoDefault) {
listExpanded.value = !listExpanded.value
} else {
chatModel.controller.appPrefs.incognito.set(false)
listExpanded.value = false
}
} else {
chatModel.controller.appPrefs.incognito.set(false)
listExpanded.value = false
changeProfile(user)
}
} else {
changeProfile(user)
showCantChangeProfileAlert()
}
})
.padding(horizontal = DEFAULT_PADDING_HALF, vertical = 4.dp),
@@ -156,11 +167,15 @@ fun ComposeContextProfilePickerView(
.fillMaxWidth()
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp)
.clickable(onClick = {
if (incognitoDefault) {
listExpanded.value = !listExpanded.value
if (!chat.chatInfo.profileChangeProhibited) {
if (incognitoDefault) {
listExpanded.value = !listExpanded.value
} else {
chatModel.controller.appPrefs.incognito.set(true)
listExpanded.value = false
}
} else {
chatModel.controller.appPrefs.incognito.set(true)
listExpanded.value = false
showCantChangeProfileAlert()
}
})
.padding(horizontal = DEFAULT_PADDING_HALF, vertical = 4.dp),
@@ -265,7 +280,13 @@ fun ComposeContextProfilePickerView(
color = MaterialTheme.colors.secondary
)
if (incognitoDefault) {
if (chat.chatInfo.profileChangeProhibited) {
if (chat.chatInfo.incognito) {
IncognitoOption()
} else {
ProfilePickerUserOption(selectedUser.value)
}
} else if (incognitoDefault) {
IncognitoOption()
} else {
ProfilePickerUserOption(selectedUser.value)
@@ -273,9 +294,9 @@ fun ComposeContextProfilePickerView(
}
}
if (listExpanded.value) {
ProfilePicker()
} else {
if (!listExpanded.value || chat.chatInfo.profileChangeProhibited) {
CurrentSelection()
} else {
ProfilePicker()
}
}
@@ -532,10 +532,11 @@ fun ComposeView(
suspend fun sendConnectPreparedContact() {
val mc = checkLinkPreview()
sending()
val incognito = if (chat.chatInfo.profileChangeProhibited) chat.chatInfo.incognito else chatModel.controller.appPrefs.incognito.get()
val contact = chatModel.controller.apiConnectPreparedContact(
rh = chat.remoteHostId,
contactId = chat.chatInfo.apiId,
incognito = chatModel.controller.appPrefs.incognito.get(),
incognito = incognito,
msg = mc
)
if (contact != null) {
@@ -573,10 +574,11 @@ fun ComposeView(
suspend fun connectPreparedGroup() {
val mc = checkLinkPreview()
sending()
val incognito = if (chat.chatInfo.profileChangeProhibited) chat.chatInfo.incognito else chatModel.controller.appPrefs.incognito.get()
val groupInfo = chatModel.controller.apiConnectPreparedGroup(
rh = chat.remoteHostId,
groupId = chat.chatInfo.apiId,
incognito = chatModel.controller.appPrefs.incognito.get(),
incognito = incognito,
msg = mc
)
if (groupInfo != null) {
@@ -1328,12 +1330,7 @@ fun ComposeView(
Column {
val currentUser = chatModel.currentUser.value
if ((
(chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.nextConnectPrepared)
|| (chat.chatInfo is ChatInfo.Group && chat.chatInfo.groupInfo.nextConnectPrepared)
)
&& currentUser != null
) {
if (chat.chatInfo.nextConnectPrepared && currentUser != null) {
ComposeContextProfilePickerView(
rhId = rhId,
chat = chat,
@@ -33,7 +33,7 @@ fun GroupLinkView(
creatingGroup: Boolean = false,
close: (() -> Unit)? = null
) {
var groupLinkVar by rememberSaveable { mutableStateOf(groupLink) }
var groupLinkVar by rememberSaveable(stateSaver = GroupLink.nullableStateSaver) { mutableStateOf(groupLink) }
val groupLinkMemberRole = rememberSaveable { mutableStateOf(groupLink?.acceptMemberRole) }
var creatingLink by rememberSaveable { mutableStateOf(false) }
fun createLink() {
@@ -138,19 +138,21 @@ fun ChatPreviewView(
val deleting by remember(disabled, chat.id) { mutableStateOf(chatModel.deletedChats.value.contains(chat.remoteHostId to chat.chatInfo.id)) }
when (cInfo) {
is ChatInfo.Direct -> {
if (cInfo.contact.verified) {
VerifiedIcon()
Row(verticalAlignment = Alignment.CenterVertically) {
if (cInfo.contact.verified) {
VerifiedIcon()
}
val color = if (deleting)
MaterialTheme.colors.secondary
else if (cInfo.contact.nextAcceptContactRequest || cInfo.contact.sendMsgToConnect) {
MaterialTheme.colors.primary
} else if (!cInfo.contact.sndReady) {
MaterialTheme.colors.secondary
} else {
Color.Unspecified
}
chatPreviewTitleText(color = color)
}
val color = if (deleting)
MaterialTheme.colors.secondary
else if (cInfo.contact.nextAcceptContactRequest || cInfo.contact.sendMsgToConnect) {
MaterialTheme.colors.primary
} else if (!cInfo.contact.sndReady) {
MaterialTheme.colors.secondary
} else {
Color.Unspecified
}
chatPreviewTitleText(color = color)
}
is ChatInfo.Group -> {
val color = if (deleting) {
@@ -48,8 +48,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
val smpProxyFallback = remember { mutableStateOf(currentCfgVal.smpProxyFallback) }
val smpWebPortServers = remember { mutableStateOf(currentCfgVal.smpWebPortServers) }
val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) }
val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) }
val networkTCPConnectTimeoutInteractive = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout.interactiveTimeout) }
val networkTCPConnectTimeoutBackground = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout.backgroundTimeout) }
val networkTCPTimeoutInteractive = remember { mutableStateOf(currentCfgVal.tcpTimeout.interactiveTimeout) }
val networkTCPTimeoutBackground = remember { mutableStateOf(currentCfgVal.tcpTimeout.backgroundTimeout) }
val networkTCPTimeoutPerKb = remember { mutableStateOf(currentCfgVal.tcpTimeoutPerKb) }
val networkRcvConcurrency = remember { mutableStateOf(currentCfgVal.rcvConcurrency) }
val networkSMPPingInterval = remember { mutableStateOf(currentCfgVal.smpPingInterval) }
@@ -86,8 +88,14 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
smpProxyMode = smpProxyMode.value,
smpProxyFallback = smpProxyFallback.value,
smpWebPortServers = smpWebPortServers.value,
tcpConnectTimeout = networkTCPConnectTimeout.value,
tcpTimeout = networkTCPTimeout.value,
tcpConnectTimeout = NetworkTimeout(
backgroundTimeout = networkTCPConnectTimeoutBackground.value,
interactiveTimeout = networkTCPConnectTimeoutInteractive.value
) ,
tcpTimeout = NetworkTimeout(
backgroundTimeout = networkTCPTimeoutBackground.value,
interactiveTimeout = networkTCPTimeoutInteractive.value
),
tcpTimeoutPerKb = networkTCPTimeoutPerKb.value,
rcvConcurrency = networkRcvConcurrency.value,
tcpKeepAlive = tcpKeepAlive,
@@ -101,8 +109,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
smpProxyMode.value = cfg.smpProxyMode
smpProxyFallback.value = cfg.smpProxyFallback
smpWebPortServers.value = cfg.smpWebPortServers
networkTCPConnectTimeout.value = cfg.tcpConnectTimeout
networkTCPTimeout.value = cfg.tcpTimeout
networkTCPConnectTimeoutInteractive.value = cfg.tcpConnectTimeout.interactiveTimeout
networkTCPConnectTimeoutBackground.value = cfg.tcpConnectTimeout.backgroundTimeout
networkTCPTimeoutInteractive.value = cfg.tcpTimeout.interactiveTimeout
networkTCPTimeoutBackground.value = cfg.tcpTimeout.backgroundTimeout
networkTCPTimeoutPerKb.value = cfg.tcpTimeoutPerKb
networkRcvConcurrency.value = cfg.rcvConcurrency
networkSMPPingInterval.value = cfg.smpPingInterval
@@ -156,8 +166,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
smpProxyMode = smpProxyMode,
smpProxyFallback = smpProxyFallback,
smpWebPortServers,
networkTCPConnectTimeout,
networkTCPTimeout,
networkTCPConnectTimeoutInteractive,
networkTCPConnectTimeoutBackground,
networkTCPTimeoutInteractive,
networkTCPTimeoutBackground,
networkTCPTimeoutPerKb,
networkRcvConcurrency,
networkSMPPingInterval,
@@ -189,8 +201,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
smpProxyMode: MutableState<SMPProxyMode>,
smpProxyFallback: MutableState<SMPProxyFallback>,
smpWebPortServers: MutableState<SMPWebPortServers>,
networkTCPConnectTimeout: MutableState<Long>,
networkTCPTimeout: MutableState<Long>,
networkTCPConnectTimeoutInteractive: MutableState<Long>,
networkTCPConnectTimeoutBackground: MutableState<Long>,
networkTCPTimeoutInteractive: MutableState<Long>,
networkTCPTimeoutBackground: MutableState<Long>,
networkTCPTimeoutPerKb: MutableState<Long>,
networkRcvConcurrency: MutableState<Int>,
networkSMPPingInterval: MutableState<Long>,
@@ -242,14 +256,26 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
SectionView(stringResource(MR.strings.network_option_tcp_connection).uppercase()) {
SectionItemView {
TimeoutSettingRow(
stringResource(MR.strings.network_option_tcp_connection_timeout), networkTCPConnectTimeout,
listOf(10_000000, 15_000000, 20_000000, 30_000000, 45_000000, 60_000000, 90_000000), secondsLabel
stringResource(MR.strings.network_option_tcp_connection_timeout), networkTCPConnectTimeoutInteractive,
listOf(10_000000, 15_000000, 20_000000, 30_000000), secondsLabel
)
}
SectionItemView {
TimeoutSettingRow(
stringResource(MR.strings.network_option_protocol_timeout), networkTCPTimeout,
listOf(5_000000, 7_000000, 10_000000, 15_000000, 20_000_000, 30_000_000), secondsLabel
stringResource(MR.strings.network_option_tcp_connection_timeout_background), networkTCPConnectTimeoutBackground,
listOf(30_000000, 45_000000, 60_000000, 90_000000), secondsLabel
)
}
SectionItemView {
TimeoutSettingRow(
stringResource(MR.strings.network_option_protocol_timeout), networkTCPTimeoutInteractive,
listOf(5_000000, 7_000000, 10_000000, 15_000000, 20_000_000), secondsLabel
)
}
SectionItemView {
TimeoutSettingRow(
stringResource(MR.strings.network_option_protocol_timeout_background), networkTCPTimeoutBackground,
listOf(15_000000, 20_000000, 30_000000, 45_000000, 60_000_000), secondsLabel
)
}
SectionItemView {
@@ -555,8 +581,10 @@ fun PreviewAdvancedNetworkSettingsLayout() {
smpProxyMode = remember { mutableStateOf(SMPProxyMode.Never) },
smpProxyFallback = remember { mutableStateOf(SMPProxyFallback.Allow) },
smpWebPortServers = remember { mutableStateOf(SMPWebPortServers.Preset) },
networkTCPConnectTimeout = remember { mutableStateOf(10_000000) },
networkTCPTimeout = remember { mutableStateOf(10_000000) },
networkTCPConnectTimeoutInteractive = remember { mutableStateOf(15_000000) },
networkTCPConnectTimeoutBackground = remember { mutableStateOf(45_000000) },
networkTCPTimeoutInteractive = remember { mutableStateOf(10_000000) },
networkTCPTimeoutBackground = remember { mutableStateOf(30_000000) },
networkTCPTimeoutPerKb = remember { mutableStateOf(10_000) },
networkRcvConcurrency = remember { mutableStateOf(8) },
networkSMPPingInterval = remember { mutableStateOf(10_000000) },
@@ -147,7 +147,9 @@
<string name="network_error_desc">Please check your network connection with %1$s and try again.</string>
<string name="network_error_broker_host_desc">Server address is incompatible with network settings: %1$s.</string>
<string name="network_error_broker_version_desc">Server version is incompatible with your app: %1$s.</string>
<string name="private_routing_timeout">Private routing timeout</string>
<string name="private_routing_error">Private routing error</string>
<string name="private_routing_no_session">No private routing session</string>
<string name="smp_proxy_error_connecting">Error connecting to forwarding server %1$s. Please try later.</string>
<string name="smp_proxy_error_broker_host">Forwarding server address is incompatible with network settings: %1$s.</string>
<string name="smp_proxy_error_broker_version">Forwarding server version is incompatible with network settings: %1$s.</string>
@@ -870,6 +872,8 @@
<!-- ComposeContextProfilePickerView.kt -->
<string name="context_user_picker_your_profile">Your profile</string>
<string name="context_user_picker_cant_change_profile_alert_title">Can\'t change profile</string>
<string name="context_user_picker_cant_change_profile_alert_message">To use another profile after connection attempt, delete the chat and use the link again.</string>
<!-- ScanCodeView.kt -->
<string name="scan_code">Scan code</string>
@@ -1974,7 +1978,9 @@
<string name="network_options_reset_to_defaults">Reset to defaults</string>
<string name="network_option_seconds_label">sec</string>
<string name="network_option_tcp_connection_timeout">TCP connection timeout</string>
<string name="network_option_tcp_connection_timeout_background">TCP connection bg timeout</string>
<string name="network_option_protocol_timeout">Protocol timeout</string>
<string name="network_option_protocol_timeout_background">Protocol background timeout</string>
<string name="network_option_protocol_timeout_per_kb">Protocol timeout per KB</string>
<string name="network_option_rcv_concurrency">Receiving concurrency</string>
<string name="network_option_ping_interval">PING interval</string>
+4 -4
View File
@@ -24,13 +24,13 @@ android.nonTransitiveRClass=true
kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.jvm.target=11
android.version_name=6.4-beta.3
android.version_code=297
android.version_name=6.4-beta.4
android.version_code=300
android.bundle=false
desktop.version_name=6.4-beta.3
desktop.version_code=107
desktop.version_name=6.4-beta.4
desktop.version_code=109
kotlin.version=1.9.23
gradle.plugin.version=8.2.0