core, ui: replace map of network statuses with subscription status of current chat (#6353)

* core: subscription status wip

* update

* update

* update

* remove statuses core

* cleanup ios

* comment

* plans

* remove NetworkStatus

* ios wip

* contact sub status

* Revert "contact sub status"

This reverts commit 50cf94beed.

* sub status

* set on connected

* kotlin

* rename

* layout

* member status

* kotlin

* fix chat subscription status

* string

* core: update simplexmq

* client notices

* update simplexmq

* update alert

* update simplexmq

* android/desktop

* formatting

* fix tests

* update plans and API docs

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
spaced4ndy
2025-10-18 21:53:47 +00:00
committed by GitHub
parent 0dc33708a3
commit a65151ba6d
74 changed files with 529 additions and 527 deletions
@@ -96,11 +96,12 @@ object ChatModel {
val dbMigrationInProgress = mutableStateOf(false)
val incompleteInitializedDbRemoved = mutableStateOf(false)
// map of connections network statuses, key is agent connection id
val networkStatuses = mutableStateMapOf<String, NetworkStatus>()
val switchingUsersAndHosts = mutableStateOf(false)
// current chat
val chatId = mutableStateOf<String?>(null)
val chatAgentConnId = mutableStateOf<String?>(null)
val chatSubStatus = mutableStateOf<SubscriptionStatus?>(null)
val openAroundItemId: MutableState<Long?> = mutableStateOf(null)
val chatsContext = ChatsContext(null)
val secondaryChatsContext = mutableStateOf<ChatsContext?>(null)
@@ -1147,21 +1148,6 @@ object ChatModel {
showingInvitation.value = showingInvitation.value?.copy(connChatUsed = true)
}
fun setContactNetworkStatus(contact: Contact, status: NetworkStatus) {
val conn = contact.activeConn
if (conn != null) {
networkStatuses[conn.agentConnId] = status
}
}
fun contactNetworkStatus(contact: Contact): NetworkStatus {
val conn = contact.activeConn
return if (conn != null)
networkStatuses[conn.agentConnId] ?: NetworkStatus.Unknown()
else
NetworkStatus.Unknown()
}
fun addTerminalItem(item: TerminalItem) {
val maxItems = if (appPreferences.developerTools.get()) 500 else 200
if (terminalsVisible.isNotEmpty()) {
@@ -1721,30 +1707,6 @@ sealed class ChatInfo: SomeChat, NamedChat {
}
}
@Serializable
sealed class NetworkStatus {
val statusString: String get() =
when (this) {
is Connected -> generalGetString(MR.strings.server_connected)
is Error -> generalGetString(MR.strings.server_error)
else -> generalGetString(MR.strings.server_connecting)
}
val statusExplanation: String get() =
when (this) {
is Connected -> generalGetString(MR.strings.connected_to_server_to_receive_messages_from_contact)
is Error -> String.format(generalGetString(MR.strings.trying_to_connect_to_server_to_receive_messages_with_error), connectionError)
else -> generalGetString(MR.strings.trying_to_connect_to_server_to_receive_messages)
}
@Serializable @SerialName("unknown") class Unknown: NetworkStatus()
@Serializable @SerialName("connected") class Connected: NetworkStatus()
@Serializable @SerialName("disconnected") class Disconnected: NetworkStatus()
@Serializable @SerialName("error") class Error(val connectionError: String): NetworkStatus()
}
@Serializable
data class ConnNetworkStatus(val agentConnId: String, val networkStatus: NetworkStatus)
@Serializable
data class Contact(
val contactId: Long,
@@ -13,6 +13,7 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -27,12 +28,14 @@ import dev.icerock.moko.resources.compose.painterResource
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.call.*
import chat.simplex.common.views.chat.item.contentModerationPostLink
import chat.simplex.common.views.chat.item.showContentBlockedAlert
import chat.simplex.common.views.chat.item.showQuotedItemDoesNotExistAlert
import chat.simplex.common.views.chatlist.openGroupChat
import chat.simplex.common.views.migration.MigrationFileLinkData
import chat.simplex.common.views.onboarding.OnboardingStage
import chat.simplex.common.views.usersettings.*
import chat.simplex.common.views.usersettings.networkAndServers.defaultConditionsLink
import chat.simplex.common.views.usersettings.networkAndServers.serverHostname
import com.charleskorn.kaml.Yaml
import com.charleskorn.kaml.YamlConfiguration
@@ -46,12 +49,17 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toJavaLocalDateTime
import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Date
typealias ChatCtrl = Long
@@ -1709,6 +1717,11 @@ object ChatController {
val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null }
val r = sendCmdWithRetry(rh, CC.ApiCreateMyAddress(userId))
if (r is API.Result && r.res is CR.UserContactLinkCreated) return r.res.connLinkContact
if (r is API.Error && r.err is ChatError.ChatErrorAgent && r.err.agentError is AgentErrorType.NOTICE) {
val e = r.err.agentError
showClientNoticeAlert(e.server, e.preset, e.expiresAt)
return null
}
if (r == null) return null
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiCreateUserAddress", generalGetString(MR.strings.error_creating_address), r)
@@ -1842,13 +1855,6 @@ object ChatController {
return r.result is CR.CmdOk
}
suspend fun apiGetNetworkStatuses(rh: Long?): List<ConnNetworkStatus>? {
val r = sendCmd(rh, CC.ApiGetNetworkStatuses())
if (r is API.Result && r.res is CR.NetworkStatuses) return r.res.networkStatuses
Log.e(TAG, "apiGetNetworkStatuses bad response: ${r.responseType} ${r.details}")
return null
}
suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long): Boolean {
val r = sendCmd(rh, CC.ApiChatRead(type, id, scope = null))
if (r.result is CR.CmdOk) return true
@@ -2175,6 +2181,11 @@ object ChatController {
suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): GroupLink? {
val r = sendCmdWithRetry(rh, CC.APICreateGroupLink(groupId, memberRole))
if (r is API.Result && r.res is CR.GroupLinkCreated) return r.res.groupLink
if (r is API.Error && r.err is ChatError.ChatErrorAgent && r.err.agentError is AgentErrorType.NOTICE) {
val e = r.err.agentError
showClientNoticeAlert(e.server, e.preset, e.expiresAt)
return null
}
if (r == null) return null
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiCreateGroupLink", generalGetString(MR.strings.error_creating_link_for_group), r)
@@ -2546,12 +2557,15 @@ object ChatController {
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
chatModel.chatsContext.removeChat(rhId, conn.id)
}
if (r.contact.id == chatModel.chatId.value && conn != null) {
chatModel.chatAgentConnId.value = conn.agentConnId
chatModel.chatSubStatus.value = SubscriptionStatus.Active
}
}
}
if (r.contact.directOrUsed) {
ntfManager.notifyContactConnected(r.user, r.contact)
}
chatModel.setContactNetworkStatus(r.contact, NetworkStatus.Connected())
}
is CR.ContactConnecting -> {
if (active(r.user) && r.contact.directOrUsed) {
@@ -2576,7 +2590,6 @@ object ChatController {
}
}
}
chatModel.setContactNetworkStatus(r.contact, NetworkStatus.Connected())
}
is CR.ReceivedContactRequest -> {
val contactRequest = r.contactRequest
@@ -2618,18 +2631,12 @@ object ChatController {
}
}
}
// ContactsSubscribed, ContactsDisconnected are only used in CLI,
// They have to be used here for remote desktop to process these status updates.
is CR.ContactsSubscribed -> updateContactsStatus(r.contactRefs, NetworkStatus.Connected())
is CR.ContactsDisconnected -> updateContactsStatus(r.contactRefs, NetworkStatus.Disconnected())
is CR.NetworkStatusResp -> {
for (cId in r.connections) {
chatModel.networkStatuses[cId] = r.networkStatus
}
}
is CR.NetworkStatuses -> {
for (s in r.networkStatuses) {
chatModel.networkStatuses[s.agentConnId] = s.networkStatus
is CR.SubscriptionStatusEvt -> {
val chatAgentConnId = chatModel.chatAgentConnId.value
if (chatAgentConnId != null && r.connections.contains(chatAgentConnId)) {
withContext(Dispatchers.Main) {
chatModel.chatSubStatus.value = r.subscriptionStatus
}
}
}
is CR.ChatInfoUpdated ->
@@ -2916,9 +2923,6 @@ object ChatController {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
}
if (r.memberContact != null) {
chatModel.setContactNetworkStatus(r.memberContact, NetworkStatus.Connected())
}
}
is CR.GroupUpdated ->
if (active(r.user)) {
@@ -3231,12 +3235,6 @@ object ChatController {
m.users.clear()
m.users.addAll(users)
getUserChatData(null)
val statuses = apiGetNetworkStatuses(null)
if (statuses != null) {
chatModel.networkStatuses.clear()
val ss = statuses.associate { it.agentConnId to it.networkStatus }.toMap()
chatModel.networkStatuses.putAll(ss)
}
}
private fun activeUser(rhId: Long?, user: UserLike): Boolean =
@@ -3349,12 +3347,6 @@ object ChatController {
}
}
private fun updateContactsStatus(contactRefs: List<ContactRef>, status: NetworkStatus) {
for (c in contactRefs) {
chatModel.networkStatuses[c.agentConnId] = status
}
}
suspend fun switchUIRemoteHost(rhId: Long?) = showProgressIfNeeded {
// TODO lock the switch so that two switches can't run concurrently?
chatModel.chatId.value = null
@@ -3381,12 +3373,6 @@ object ChatController {
chatModel.secondaryChatsContext.value?.popChatCollector?.clear()
}
}
val statuses = apiGetNetworkStatuses(rhId)
if (statuses != null) {
chatModel.networkStatuses.clear()
val ss = statuses.associate { it.agentConnId to it.networkStatus }.toMap()
chatModel.networkStatuses.putAll(ss)
}
getUserChatData(rhId)
}
@@ -3645,7 +3631,6 @@ sealed class CC {
class ApiSendCallExtraInfo(val contact: Contact, val extraInfo: WebRTCExtraInfo): CC()
class ApiEndCall(val contact: Contact): CC()
class ApiCallStatus(val contact: Contact, val callStatus: WebRTCCallStatus): CC()
class ApiGetNetworkStatuses(): CC()
class ApiAcceptContact(val incognito: Boolean, val contactReqId: Long): CC()
class ApiRejectContact(val contactReqId: Long): CC()
class ApiChatRead(val type: ChatType, val id: Long, val scope: GroupChatScope?): CC()
@@ -3844,7 +3829,6 @@ sealed class CC {
is ApiSendCallExtraInfo -> "/_call extra @${contact.apiId} ${json.encodeToString(extraInfo)}"
is ApiEndCall -> "/_call end @${contact.apiId}"
is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}"
is ApiGetNetworkStatuses -> "/_network_statuses"
is ApiChatRead -> "/_read chat ${chatRef(type, id, scope)}"
is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id, scope)} ${itemIds.joinToString(",")}"
is ApiChatUnread -> "/_unread chat ${chatRef(type, id, scope = null)} ${onOff(unreadChat)}"
@@ -4019,7 +4003,6 @@ sealed class CC {
is ApiSendCallExtraInfo -> "apiSendCallExtraInfo"
is ApiEndCall -> "apiEndCall"
is ApiCallStatus -> "apiCallStatus"
is ApiGetNetworkStatuses -> "apiGetNetworkStatuses"
is ApiChatRead -> "apiChatRead"
is ApiChatItemsRead -> "apiChatItemsRead"
is ApiChatUnread -> "apiChatUnread"
@@ -6159,12 +6142,7 @@ sealed class CR {
@Serializable @SerialName("contactRequestRejected") class ContactRequestRejected(val user: UserRef, val contactRequest: UserContactRequest, val contact_: Contact?): CR()
@Serializable @SerialName("contactUpdated") class ContactUpdated(val user: UserRef, val toContact: Contact): CR()
@Serializable @SerialName("groupMemberUpdated") class GroupMemberUpdated(val user: UserRef, val groupInfo: GroupInfo, val fromMember: GroupMember, val toMember: GroupMember): CR()
// TODO remove below
@Serializable @SerialName("contactsSubscribed") class ContactsSubscribed(val server: String, val contactRefs: List<ContactRef>): CR()
@Serializable @SerialName("contactsDisconnected") class ContactsDisconnected(val server: String, val contactRefs: List<ContactRef>): 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("subscriptionStatus") class SubscriptionStatusEvt(val subscriptionStatus: SubscriptionStatus, val connections: List<String>): 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()
@@ -6345,10 +6323,7 @@ sealed class CR {
is ContactRequestRejected -> "contactRequestRejected"
is ContactUpdated -> "contactUpdated"
is GroupMemberUpdated -> "groupMemberUpdated"
is ContactsSubscribed -> "contactsSubscribed"
is ContactsDisconnected -> "contactsDisconnected"
is NetworkStatusResp -> "networkStatus"
is NetworkStatuses -> "networkStatuses"
is SubscriptionStatusEvt -> "subscriptionStatus"
is ChatInfoUpdated -> "chatInfoUpdated"
is NewChatItems -> "newChatItems"
is ChatItemsStatusesUpdated -> "chatItemsStatusesUpdated"
@@ -6521,10 +6496,7 @@ sealed class CR {
is ContactRequestRejected -> withUser(user, "contactRequest: ${json.encodeToString(contactRequest)}\ncontact_: ${json.encodeToString(contact_)}")
is ContactUpdated -> withUser(user, json.encodeToString(toContact))
is GroupMemberUpdated -> withUser(user, "groupInfo: $groupInfo\nfromMember: $fromMember\ntoMember: $toMember")
is ContactsSubscribed -> "server: $server\ncontacts:\n${json.encodeToString(contactRefs)}"
is ContactsDisconnected -> "server: $server\ncontacts:\n${json.encodeToString(contactRefs)}"
is NetworkStatusResp -> "networkStatus $networkStatus\nconnections: $connections"
is NetworkStatuses -> withUser(user_, json.encodeToString(networkStatuses))
is SubscriptionStatusEvt -> "subscriptionStatus $subscriptionStatus\nconnections: $connections"
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) })
@@ -6747,7 +6719,8 @@ class ConnectionStats(
val rcvQueuesInfo: List<RcvQueueInfo>,
val sndQueuesInfo: List<SndQueueInfo>,
val ratchetSyncState: RatchetSyncState,
val ratchetSyncSupported: Boolean
val ratchetSyncSupported: Boolean,
var subStatus: SubscriptionStatus?
) {
val ratchetSyncAllowed: Boolean get() =
ratchetSyncSupported && listOf(RatchetSyncState.Allowed, RatchetSyncState.Required).contains(ratchetSyncState)
@@ -6762,8 +6735,10 @@ class ConnectionStats(
@Serializable
class RcvQueueInfo(
val rcvServer: String,
var status: QueueStatus,
val rcvSwitchStatus: RcvSwitchStatus?,
var canAbortSwitch: Boolean
var canAbortSwitch: Boolean,
var subStatus: SubscriptionStatus
)
@Serializable
@@ -6777,6 +6752,7 @@ enum class RcvSwitchStatus {
@Serializable
class SndQueueInfo(
val sndServer: String,
var status: QueueStatus,
val sndSwitchStatus: SndSwitchStatus?
)
@@ -6814,6 +6790,39 @@ enum class RatchetSyncState {
@SerialName("agreed") Agreed
}
@Serializable
enum class QueueStatus {
@SerialName("new") New,
@SerialName("confirmed") Confirmed,
@SerialName("secured") Secured,
@SerialName("active") Active,
@SerialName("disabled") Disabled
}
@Serializable
sealed class SubscriptionStatus {
@Serializable @SerialName("active") object Active: SubscriptionStatus()
@Serializable @SerialName("pending") object Pending: SubscriptionStatus()
@Serializable @SerialName("removed") class Removed(val subError: String): SubscriptionStatus()
@Serializable @SerialName("noSub") object NoSub: SubscriptionStatus()
val statusString: String get() =
when (this) {
is Active -> generalGetString(MR.strings.server_connected)
is Pending -> generalGetString(MR.strings.server_connecting)
is Removed -> generalGetString(MR.strings.server_error)
is NoSub -> generalGetString(MR.strings.server_no_sub)
}
val statusExplanation: String get() =
when (this) {
is Active -> generalGetString(MR.strings.connected_to_server_to_receive_messages_from_contact)
is Pending -> generalGetString(MR.strings.trying_to_connect_to_server_to_receive_messages)
is Removed -> String.format(generalGetString(MR.strings.error_connecting_to_server_to_receive_messages), subError)
is NoSub -> generalGetString(MR.strings.not_connected_to_server_to_receive_messages_no_sub)
}
}
interface SimplexAddress {
val connLinkContact: CreatedConnLink
val shortLinkDataSet: Boolean
@@ -7306,6 +7315,7 @@ sealed class AgentErrorType {
is RCP -> "RCP ${rcpErr.string}"
is BROKER -> "BROKER ${brokerErr.string}"
is AGENT -> "AGENT ${agentErr.string}"
is NOTICE -> "NOTICE $server $expiresAt"
is INTERNAL -> "INTERNAL $internalErr"
is CRITICAL -> "CRITICAL $offerRestart $criticalErr"
is INACTIVE -> "INACTIVE"
@@ -7319,6 +7329,7 @@ sealed class AgentErrorType {
@Serializable @SerialName("RCP") class RCP(val rcpErr: RCErrorType): AgentErrorType()
@Serializable @SerialName("BROKER") class BROKER(val brokerAddress: String, val brokerErr: BrokerErrorType): AgentErrorType()
@Serializable @SerialName("AGENT") class AGENT(val agentErr: SMPAgentError): AgentErrorType()
@Serializable @SerialName("NOTICE") class NOTICE(val server: String, val preset: Boolean, val expiresAt: Instant?): AgentErrorType()
@Serializable @SerialName("INTERNAL") class INTERNAL(val internalErr: String): AgentErrorType()
@Serializable @SerialName("CRITICAL") data class CRITICAL(val offerRestart: Boolean, val criticalErr: String): AgentErrorType()
@Serializable @SerialName("INACTIVE") object INACTIVE: AgentErrorType()
@@ -8046,3 +8057,34 @@ enum class MsgType {
@SerialName("quota")
QUOTA
}
fun showClientNoticeAlert(server: String, preset: Boolean, expiresAt: Instant?) {
var message = "Server: $server.\nConditions of use violation notice received from ${if (preset) "preset" else "this"} server.\nNo ID shared, see How it works."
if (expiresAt != null) {
val tz = TimeZone.currentSystemDefault()
val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
message += "\n\nNew addresses can be created after ${expiresAt.toLocalDateTime(tz).toJavaLocalDateTime().format(formatter)}."
}
AlertManager.shared.showAlertDialogButtonsColumn(title = "Not allowed", text = AnnotatedString(message)) {
val uriHandler = LocalUriHandler.current
Column {
SectionItemView({ AlertManager.shared.hideAlert() }) {
Text(generalGetString(MR.strings.ok), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
if (preset) {
SectionItemView({
AlertManager.shared.hideAlert()
uriHandler.openUriCatching(defaultConditionsLink)
}) {
Text(generalGetString(MR.strings.operator_conditions_of_use), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
}
SectionItemView({
AlertManager.shared.hideAlert()
uriHandler.openUriCatching(contentModerationPostLink)
}) {
Text(generalGetString(MR.strings.how_it_works), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
}
}
}
@@ -72,9 +72,6 @@ fun ChatInfoView(
val connStats = remember(contact.id, connectionStats) { mutableStateOf(connectionStats) }
val developerTools = chatModel.controller.appPrefs.developerTools.get()
if (chat != null && currentUser != null) {
val contactNetworkStatus = remember(chatModel.networkStatuses.toMap(), contact) {
mutableStateOf(chatModel.contactNetworkStatus(contact))
}
val chatRh = chat.remoteHostId
val sendReceipts = remember(contact.id) { mutableStateOf(SendReceipts.fromBool(contact.chatSettings.sendRcpts, currentUser.sendRcptsContacts)) }
val chatItemTTL = remember(contact.id) { mutableStateOf(if (contact.chatItemTTL != null) ChatItemTTL.fromSeconds(contact.chatItemTTL) else null) }
@@ -101,7 +98,6 @@ fun ChatInfoView(
setChatTTLAlert(chatsCtx, chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, deletingItems)
},
connStats = connStats,
contactNetworkStatus.value,
customUserProfile,
localAlias,
connectionCode,
@@ -524,7 +520,6 @@ fun ChatInfoLayout(
chatItemTTL: MutableState<ChatItemTTL?>,
setChatItemTTL: (ChatItemTTL?) -> Unit,
connStats: MutableState<ConnectionStats?>,
contactNetworkStatus: NetworkStatus,
customUserProfile: Profile?,
localAlias: String,
connectionCode: String?,
@@ -643,13 +638,16 @@ fun ChatInfoLayout(
if (contact.ready && contact.active) {
SectionView(title = stringResource(MR.strings.conn_stats_section_title_servers)) {
SectionItemView({
AlertManager.shared.showAlertMsg(
generalGetString(MR.strings.network_status),
contactNetworkStatus.statusExplanation
)
}) {
NetworkStatusRow(contactNetworkStatus)
val chatSubStatus = chatModel.chatSubStatus.value
if (chatSubStatus != null) {
SectionItemView({
AlertManager.shared.showAlertMsg(
generalGetString(MR.strings.network_status),
chatSubStatus.statusExplanation
)
}) {
SubStatusRow(chatSubStatus)
}
}
if (cStats != null) {
SwitchAddressButton(
@@ -1063,7 +1061,7 @@ fun InfoViewActionButton(
}
@Composable
private fun NetworkStatusRow(networkStatus: NetworkStatus) {
fun SubStatusRow(subStatus: SubscriptionStatus) {
Row(
Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.SpaceBetween,
@@ -1086,25 +1084,24 @@ private fun NetworkStatusRow(networkStatus: NetworkStatus) {
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
networkStatus.statusString,
subStatus.statusString,
color = MaterialTheme.colors.secondary
)
ServerImage(networkStatus)
ServerImage(subStatus)
}
}
}
@Composable
private fun ServerImage(networkStatus: NetworkStatus) {
private fun ServerImage(subStatus: SubscriptionStatus) {
Box(Modifier.size(18.dp)) {
when (networkStatus) {
is NetworkStatus.Connected ->
when (subStatus) {
is SubscriptionStatus.Active ->
Icon(painterResource(MR.images.ic_circle_filled), stringResource(MR.strings.icon_descr_server_status_connected), tint = Color.Green)
is NetworkStatus.Disconnected ->
is SubscriptionStatus.Pending ->
Icon(painterResource(MR.images.ic_pending_filled), stringResource(MR.strings.icon_descr_server_status_disconnected), tint = MaterialTheme.colors.secondary)
is NetworkStatus.Error ->
is SubscriptionStatus.Removed, SubscriptionStatus.NoSub ->
Icon(painterResource(MR.images.ic_error_filled), stringResource(MR.strings.icon_descr_server_status_error), tint = MaterialTheme.colors.secondary)
else -> Icon(painterResource(MR.images.ic_circle), stringResource(MR.strings.icon_descr_server_status_pending), tint = MaterialTheme.colors.secondary)
}
}
}
@@ -1455,7 +1452,6 @@ fun PreviewChatInfoLayout() {
connectionCode = "123",
developerTools = false,
connStats = remember { mutableStateOf(null) },
contactNetworkStatus = NetworkStatus.Connected(),
onLocalAliasChanged = {},
customUserProfile = null,
openPreferences = {},
@@ -102,6 +102,7 @@ fun ChatView(
val chat = chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }
// They have their own iterator inside for a reason to prevent crash "Reading a state that was created after the snapshot..."
val remoteHostId = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.remoteHostId } }
val chatRh = remoteHostId.value
val activeChat = remember { derivedStateOf {
var chat = chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }
val chatInfo = chat?.chatInfo
@@ -120,6 +121,8 @@ fun ChatView(
if (chat == null || chatInfo == null || user == null) {
LaunchedEffect(Unit) {
chatModel.chatId.value = null
chatModel.chatAgentConnId.value = null
chatModel.chatSubStatus.value = null
ModalManager.end.closeModals()
}
} else {
@@ -168,6 +171,25 @@ fun ChatView(
showSearch.value = false
searchText.value = ""
selectedChatItems.value = null
if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.activeConn != null) {
withBGApi {
val r = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId)
if (r != null) {
val contactStats = r.first
if (contactStats != null)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnectionStats(chatRh, chat.chatInfo.contact, contactStats)
chatModel.chatAgentConnId.value = chat.chatInfo.contact.activeConn.agentConnId
chatModel.chatSubStatus.value = contactStats.subStatus
}
}
}
} else {
withContext(Dispatchers.Main) {
chatModel.chatAgentConnId.value = null
chatModel.chatSubStatus.value = null
}
}
}
}
}
@@ -184,7 +206,6 @@ fun ChatView(
}
}
val view = LocalMultiplatformView()
val chatRh = remoteHostId.value
// We need to have real unreadCount value for displaying it inside top right button
// Having activeChat reloaded on every change in it is inefficient (UI lags)
val unreadCount = remember {
@@ -356,7 +377,7 @@ fun ChatView(
if (chatInfo is ChatInfo.Direct) {
var contactInfo: Pair<ConnectionStats?, Profile?>? by remember { mutableStateOf(preloadedContactInfo) }
var code: String? by remember { mutableStateOf(preloadedCode) }
KeyChangeEffect(chatInfo.id, ChatModel.networkStatuses.toMap()) {
KeyChangeEffect(chatInfo.id) {
contactInfo = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId)
preloadedContactInfo = contactInfo
code = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second
@@ -1281,9 +1302,51 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo
)
}
}
val chatSubStatus = chatModel.chatSubStatus.value
if (
cInfo is ChatInfo.Direct &&
cInfo.contact.ready &&
cInfo.contact.active &&
chatSubStatus != null &&
chatSubStatus != SubscriptionStatus.Active
) {
Box(
Modifier.padding(start = 10.dp)
) {
SubStatusView(chatSubStatus)
}
}
}
}
@Composable
fun SubStatusView(status: SubscriptionStatus) {
when (status) {
SubscriptionStatus.Active ->
Box {}
SubscriptionStatus.Pending ->
SubProgressView()
is SubscriptionStatus.Removed, SubscriptionStatus.NoSub ->
Icon(
painterResource(MR.images.ic_error),
contentDescription = null,
tint = MaterialTheme.colors.secondary,
modifier = Modifier
.size(19.sp.toDp())
)
}
}
@Composable
private fun SubProgressView() {
CircularProgressIndicator(
Modifier
.size(15.sp.toDp()),
color = MaterialTheme.colors.secondary,
strokeWidth = 1.5.dp
)
}
@Composable
private fun SupportChatsCountToolbar(
chatInfo: ChatInfo,
@@ -162,7 +162,6 @@ fun acceptMemberContact(
chatModel.chatsContext.updateContact(rhId, contact)
inProgress?.value = false
}
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
val chat = Chat(remoteHostId = rhId, ChatInfo.Direct(contact), listOf())
close?.invoke(chat)
} else {
@@ -533,7 +533,6 @@ fun ComposeView(
withContext(Dispatchers.Main) {
chatsCtx.updateContact(chat.remoteHostId, contact)
clearState()
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
}
} else {
composeState.value = composeState.value.copy(inProgress = false)
@@ -554,7 +553,6 @@ fun ComposeView(
withContext(Dispatchers.Main) {
chatsCtx.updateContact(chat.remoteHostId, contact)
clearState()
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
}
} else {
composeState.value = composeState.value.copy(inProgress = false)
@@ -109,7 +109,6 @@ fun GroupMemberInfoView(
}
openDirectChat(rhId, memberContact.contactId)
closeAll()
chatModel.setContactNetworkStatus(memberContact, NetworkStatus.Connected())
}
progressIndicator = false
}
@@ -495,6 +494,17 @@ fun GroupMemberInfoLayout(
if (cStats != null) {
SectionDividerSpaced()
SectionView(title = stringResource(MR.strings.conn_stats_section_title_servers)) {
val subStatus = cStats.subStatus
if (subStatus != null) {
SectionItemView({
AlertManager.shared.showAlertMsg(
generalGetString(MR.strings.network_status),
subStatus.statusExplanation
)
}) {
SubStatusRow(subStatus)
}
}
SwitchAddressButton(
disabled = cStats.rcvQueuesInfo.any { it.rcvSwitchStatus != null } || !member.sendMsgEnabled,
switchAddress = switchMemberAddress
@@ -62,12 +62,11 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>) {
when (chat.chatInfo) {
is ChatInfo.Direct -> {
val contactNetworkStatus = chatModel.contactNetworkStatus(chat.chatInfo.contact)
val defaultClickAction = { if (chatModel.chatId.value != chat.id) scope.launch { directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) } }
ChatListNavLinkLayout(
chatLinkPreview = {
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, contactNetworkStatus, disabled, linkMode, inProgress = false, progressByTimeout = false, defaultClickAction)
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, disabled, linkMode, inProgress = false, progressByTimeout = false, defaultClickAction)
}
},
click = defaultClickAction,
@@ -87,7 +86,7 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>) {
ChatListNavLinkLayout(
chatLinkPreview = {
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, disabled, linkMode, inProgress.value, progressByTimeout, defaultClickAction)
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, disabled, linkMode, inProgress.value, progressByTimeout, defaultClickAction)
}
},
click = defaultClickAction,
@@ -107,7 +106,7 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>) {
ChatListNavLinkLayout(
chatLinkPreview = {
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, disabled, linkMode, inProgress = false, progressByTimeout = false, defaultClickAction)
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, disabled, linkMode, inProgress = false, progressByTimeout = false, defaultClickAction)
}
},
click = defaultClickAction,
@@ -744,7 +743,6 @@ fun acceptContactRequest(
}
inProgress?.value = false
}
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
close?.invoke(chat)
} else {
inProgress?.value = false
@@ -1057,7 +1055,6 @@ fun PreviewChatListNavLinkDirect() {
null,
null,
null,
null,
disabled = false,
linkMode = SimplexLinkMode.DESCRIPTION,
inProgress = false,
@@ -1103,7 +1100,6 @@ fun PreviewChatListNavLinkGroup() {
null,
null,
null,
null,
disabled = false,
linkMode = SimplexLinkMode.DESCRIPTION,
inProgress = false,
@@ -42,7 +42,6 @@ fun ChatPreviewView(
chatModelDraft: ComposeState?,
chatModelDraftChatId: ChatId?,
currentUserProfileDisplayName: String?,
contactNetworkStatus: NetworkStatus?,
disabled: Boolean,
linkMode: SimplexLinkMode,
inProgress: Boolean,
@@ -349,33 +348,7 @@ fun ChatPreviewView(
@Composable
fun chatStatusImage() {
if (cInfo is ChatInfo.Direct) {
if (
cInfo.contact.active &&
(cInfo.contact.activeConn?.connStatus == ConnStatus.Ready || cInfo.contact.activeConn?.connStatus == ConnStatus.SndReady)
) {
val descr = contactNetworkStatus?.statusString
when (contactNetworkStatus) {
is NetworkStatus.Connected ->
IncognitoIcon(chat.chatInfo.incognito)
is NetworkStatus.Error ->
Icon(
painterResource(MR.images.ic_error),
contentDescription = descr,
tint = MaterialTheme.colors.secondary,
modifier = Modifier
.size(19.sp.toDp())
.offset(x = 2.sp.toDp())
)
else ->
progressView()
}
} else {
IncognitoIcon(chat.chatInfo.incognito)
}
} else if (cInfo is ChatInfo.Group) {
if (cInfo is ChatInfo.Group) {
if (progressByTimeout) {
progressView()
} else if (chat.chatStats.reportsCount > 0) {
@@ -636,6 +609,6 @@ private data class ActiveVoicePreview(
@Composable
fun PreviewChatPreviewView() {
SimpleXTheme {
ChatPreviewView(Chat.sampleData, true, null, null, "", contactNetworkStatus = NetworkStatus.Connected(), disabled = false, linkMode = SimplexLinkMode.DESCRIPTION, inProgress = false, progressByTimeout = false, {})
ChatPreviewView(Chat.sampleData, true, null, null, "", disabled = false, linkMode = SimplexLinkMode.DESCRIPTION, inProgress = false, progressByTimeout = false, {})
}
}
@@ -64,7 +64,7 @@ enum class SubscriptionColorType {
ACTIVE, ACTIVE_SOCKS_PROXY, DISCONNECTED, ACTIVE_DISCONNECTED
}
data class SubscriptionStatus(
data class AppSubscriptionStatus(
val color: SubscriptionColorType,
val variableValue: Float,
val statusPercent: Float
@@ -75,7 +75,7 @@ fun subscriptionStatusColorAndPercentage(
socksProxy: String?,
subs: SMPServerSubs,
hasSess: Boolean
): SubscriptionStatus {
): AppSubscriptionStatus {
fun roundedToQuarter(n: Float): Float = when {
n >= 1 -> 1f
n <= 0 -> 0f
@@ -83,25 +83,25 @@ fun subscriptionStatusColorAndPercentage(
}
val activeColor: SubscriptionColorType = if (socksProxy != null) SubscriptionColorType.ACTIVE_SOCKS_PROXY else SubscriptionColorType.ACTIVE
val noConnColorAndPercent = SubscriptionStatus(SubscriptionColorType.DISCONNECTED, 1f, 0f)
val noConnColorAndPercent = AppSubscriptionStatus(SubscriptionColorType.DISCONNECTED, 1f, 0f)
val activeSubsRounded = roundedToQuarter(subs.shareOfActive)
return if (!online)
noConnColorAndPercent
else if (subs.total == 0 && !hasSess)
// On freshly installed app (without chats) and on app start
SubscriptionStatus(activeColor, 0f, 0f)
AppSubscriptionStatus(activeColor, 0f, 0f)
else if (subs.ssActive == 0) {
if (hasSess)
SubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive)
AppSubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive)
else
noConnColorAndPercent
} else { // ssActive > 0
if (hasSess)
SubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive)
AppSubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive)
else
// This would mean implementation error
SubscriptionStatus(SubscriptionColorType.ACTIVE_DISCONNECTED, activeSubsRounded, subs.shareOfActive)
AppSubscriptionStatus(SubscriptionColorType.ACTIVE_DISCONNECTED, activeSubsRounded, subs.shareOfActive)
}
}
@@ -612,13 +612,14 @@ private fun SingleOperatorUsageConditionsView(
}
}
val defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md"
@Composable
fun ConditionsTextView(
rhId: Long?
) {
val conditionsData = remember { mutableStateOf<Triple<UsageConditionsDetail, String?, UsageConditionsDetail?>?>(null) }
val failedToLoad = remember { mutableStateOf(false) }
val defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md"
val scope = rememberCoroutineScope()
// can show conditions when animation between modals finishes to prevent glitches
val canShowConditionsAt = remember { System.currentTimeMillis() + 300 }
@@ -1131,7 +1131,7 @@
<string name="trying_to_connect_to_server_to_receive_messages">محاولة الاتصال بالخادم المستخدم لاستلام الرسائل من جهة الاتصال هذه.</string>
<string name="choose_file_title">اختيار ملف</string>
<string name="icon_descr_sent_msg_status_unauthorized_send">إرسال غير مصرح به</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">محاولة الاتصال بالخادم المستخدم لاستلام الرسائل من جهة الاتصال هذه (خطأ: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">محاولة الاتصال بالخادم المستخدم لاستلام الرسائل من جهة الاتصال هذه (خطأ: %1$s).</string>
<string name="la_notice_turn_on">تشغيل</string>
<string name="webrtc_ice_servers">خوادم WebRTC ICE</string>
<string name="alert_title_cant_invite_contacts_descr">أنت تستخدم ملف تعريف متخفي لهذه المجموعة - لمنع مشاركة ملفك التعريفي الرئيسي الذي يدعو جهات الاتصال غير مسموح به</string>
@@ -32,9 +32,11 @@
<string name="server_connected">connected</string>
<string name="server_error">error</string>
<string name="server_connecting">connecting</string>
<string name="connected_to_server_to_receive_messages_from_contact">You are connected to the server used to receive messages from this contact.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Trying to connect to the server used to receive messages from this contact (error: %1$s).</string>
<string name="trying_to_connect_to_server_to_receive_messages">Trying to connect to the server used to receive messages from this contact.</string>
<string name="server_no_sub">no subscription</string>
<string name="connected_to_server_to_receive_messages_from_contact">You are connected to the server used to receive messages from this connection.</string>
<string name="trying_to_connect_to_server_to_receive_messages">Trying to connect to the server used to receive messages from this connection.</string>
<string name="error_connecting_to_server_to_receive_messages">Error connecting to the server used to receive messages from this connection: %1$s.</string>
<string name="not_connected_to_server_to_receive_messages_no_sub">You are not connected to the server used to receive messages from this connection (no subscription).</string>
<!-- Item Content - ChatModel.kt -->
<string name="deleted_description">deleted</string>
@@ -1182,7 +1182,7 @@
<string name="messages_section_description">Тази настройка се прилага за съобщения в текущия ви профил</string>
<string name="to_protect_privacy_simplex_has_ids_for_queues">За да се защити поверителността, SimpleX използва идентификатори за опашки от съобщения, отделни за всеки от вашите контакти.</string>
<string name="trying_to_connect_to_server_to_receive_messages">Опит за свързване със сървъра, използван за получаване на съобщения от този контакт.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Опит за свързване със сървъра, използван за получаване на съобщения от този контакт (грешка: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Опит за свързване със сървъра, използван за получаване на съобщения от този контакт (грешка: %1$s).</string>
<string name="error_smp_test_failed_at_step">Тестът е неуспешен на стъпка %s.</string>
<string name="database_initialization_error_desc">Базата данни не работи правилно. Докоснете, за да научите повече</string>
<string name="image_decoding_exception_desc">Изображението не може да бъде декодирано. Моля, опитайте с друго изображение или се свържете с разработчиците.</string>
@@ -1556,7 +1556,7 @@
<string name="error_parsing_uri_desc">Comproveu que l\'enllaç SimpleX sigui correcte.</string>
<string name="sending_files_not_yet_supported">l\'enviament de fitxers encara no està suportat</string>
<string name="trying_to_connect_to_server_to_receive_messages">Intentant connectar-se al servidor utilitzat per rebre missatges d\'aquest contacte.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">S\'està provant de connectar-se al servidor utilitzat per rebre missatges d\'aquest contacte (error: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">S\'està provant de connectar-se al servidor utilitzat per rebre missatges d\'aquest contacte (error: %1$s).</string>
<string name="connect_use_current_profile">Usar perfil actual</string>
<string name="connect_use_new_incognito_profile">Usar nou perfil incògnit</string>
<string name="app_was_crashed">Error aplicació</string>
@@ -493,7 +493,7 @@
<string name="you_will_join_group">Připojtíte se ke všem členům skupiny.</string>
<string name="connect_via_link_verb">Připojení</string>
<string name="connected_to_server_to_receive_messages_from_contact">Jste připojeni k serveru, který se používá k přijímání zpráv od tohoto kontaktu.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Pokoušíte se připojit k serveru používaném pro příjem zpráv od tohoto kontaktu (chyba: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Pokoušíte se připojit k serveru používaném pro příjem zpráv od tohoto kontaktu (chyba: %1$s).</string>
<string name="marked_deleted_description">označeno jako smazáno</string>
<string name="sending_files_not_yet_supported">Odesílání souborů zatím není podporováno</string>
<string name="receiving_files_not_yet_supported">přijímání souborů zatím není podporováno</string>
@@ -178,7 +178,7 @@
<string name="server_error">fejl</string>
<string name="server_connecting">forbinder</string>
<string name="connected_to_server_to_receive_messages_from_contact">Du har forbindelse til den server, der bruges til at modtage beskeder fra denne kontakt.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Forsøger at oprette forbindelse til den server, der bruges til at modtage beskeder fra denne kontakt (fejl: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Forsøger at oprette forbindelse til den server, der bruges til at modtage beskeder fra denne kontakt (fejl: %1$s).</string>
<string name="trying_to_connect_to_server_to_receive_messages">Forsøger at oprette forbindelse til den server, der bruges til at modtage beskeder fra denne kontakt.</string>
<string name="deleted_description">slettet</string>
<string name="marked_deleted_description">markeret som slettet</string>
@@ -14,7 +14,7 @@
<string name="server_error">Fehler</string>
<string name="server_connecting">Verbinde</string>
<string name="connected_to_server_to_receive_messages_from_contact">Sie sind mit dem Server verbunden, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Beim Versuch, die Verbindung mit dem Server aufzunehmen, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird, ist ein Fehler aufgetreten (Fehler: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Beim Versuch, die Verbindung mit dem Server aufzunehmen, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird, ist ein Fehler aufgetreten (Fehler: %1$s).</string>
<string name="trying_to_connect_to_server_to_receive_messages">Versuche die Verbindung mit dem Server aufzunehmen, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird.</string>
<!-- Item Content - ChatModel.kt -->
<string name="deleted_description">Gelöscht</string>
@@ -806,7 +806,7 @@
<string name="update_network_settings_question">¿Actualizar la configuración de red\?</string>
<string name="trying_to_connect_to_server_to_receive_messages">Intentando conectar con el servidor para recibir mensajes de este contacto.</string>
<string name="unknown_message_format">formato de mensaje desconocido</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Intentando conectar con el servidor para recibir mensajes de este contacto (error: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Intentando conectar con el servidor para recibir mensajes de este contacto (error: %1$s).</string>
<string name="error_smp_test_failed_at_step">Prueba no superada en el paso %s.</string>
<string name="tap_to_start_new_chat">Pulsa para iniciar chat nuevo</string>
<string name="share_message">Compartir mensaje…</string>
@@ -35,7 +35,7 @@
<string name="connect_via_link_verb">متصل شدن</string>
<string name="non_content_uri_alert_title">مسیر نامعتبر فایل</string>
<string name="app_was_crashed">برنامه از کار افتاد</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">در حال تلاش برای اتصال به سرور مورد استفاده برای دریافت پیام‌ها از این مخاطب (خطا: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">در حال تلاش برای اتصال به سرور مورد استفاده برای دریافت پیام‌ها از این مخاطب (خطا: %1$s).</string>
<string name="deleted_description">حذف شد</string>
<string name="marked_deleted_description">علامت گذاشته شده به عنوان حذف شده</string>
<string name="moderated_item_description">توسط %s حذف شد</string>
@@ -1036,7 +1036,7 @@
<string name="profile_will_be_sent_to_contact_sending_link">Profiilisi lähetetään kontaktille, jolta sait tämän linkin.</string>
<string name="you_will_join_group">Liityt ryhmään, johon tämä linkki viittaa, ja muodostat yhteyden sen ryhmän jäseniin.</string>
<string name="connected_to_server_to_receive_messages_from_contact">Olet yhteydessä palvelimeen, jota käytetään vastaanottamaan viestejä tältä kontaktilta.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Yritetään muodostaa yhteys palvelimeen, jota käytetään viestien vastaanottamiseen tältä kontaktilta (virhe: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Yritetään muodostaa yhteys palvelimeen, jota käytetään viestien vastaanottamiseen tältä kontaktilta (virhe: %1$s).</string>
<string name="sender_you_pronoun">sinä</string>
<string name="description_via_one_time_link">kertalinkillä</string>
<string name="simplex_link_connection">%1$s:n kautta</string>
@@ -52,7 +52,7 @@
<string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Veuillez vérifier que vous avez utilisé le bon lien ou demandez à votre contact de vous en envoyer un autre.</string>
<string name="connection_error">Erreur de connexion</string>
<string name="error_adding_members">Erreur lors de l\'ajout de membre·s</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Tentative de connexion au serveur utilisé pour recevoir les messages de ce contact (erreur : %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Tentative de connexion au serveur utilisé pour recevoir les messages de ce contact (erreur : %1$s).</string>
<string name="invalid_message_format">format de message invalide</string>
<string name="simplex_link_mode_full">Lien entier</string>
<string name="error_saving_smp_servers">Erreur lors de la sauvegarde des serveurs SMP</string>
@@ -1317,7 +1317,7 @@
<string name="image_decoding_exception_desc">A kép nem dekódolható. Próbálja meg egy másik képpel, vagy lépjen kapcsolatba a fejlesztőkkel.</string>
<string name="non_content_uri_alert_text">Érvénytelen fájlelérési útvonalat osztott meg. Jelentse a problémát az alkalmazás fejlesztőinek.</string>
<string name="failed_to_create_user_duplicate_desc">Már van egy csevegési profil ugyanezzel a megjelenítendő névvel. Válasszon egy másik nevet.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (hiba: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (hiba: %1$s).</string>
<string name="stop_rcv_file__message">A fájl fogadása le fog állni.</string>
<string name="la_please_remember_to_store_password">Ne felejtse el, vagy tárolja biztonságosan az elveszett jelszót nem lehet visszaállítani!</string>
<string name="video_will_be_received_when_contact_completes_uploading">A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését.</string>
@@ -104,7 +104,7 @@
<string name="error_parsing_uri_title">Tautan tidak valid</string>
<string name="error_parsing_uri_desc">Periksa apakah tautan SimpleX sudah benar.</string>
<string name="connected_to_server_to_receive_messages_from_contact">Anda terhubung ke server yang digunakan untuk menerima pesan dari kontak ini.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Mencoba menyambung ke server yang digunakan untuk menerima pesan dari kontak ini (error: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Mencoba menyambung ke server yang digunakan untuk menerima pesan dari kontak ini (error: %1$s).</string>
<string name="database_migration_in_progress">Migrasi basis data sedang berlangsung,
\nmemerlukan waktu beberapa menit.</string>
<string name="server_connecting">menghubungkan</string>
@@ -201,7 +201,7 @@
<string name="reset_verb">Ripristina</string>
<string name="ok">OK</string>
<string name="connect_via_contact_link">Connettere via indirizzo del contatto?</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Tentativo di connessione al server usato per ricevere messaggi da questo contatto (errore: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Tentativo di connessione al server usato per ricevere messaggi da questo contatto (errore: %1$s).</string>
<string name="you_will_join_group">Ti connetterai a tutti i membri del gruppo.</string>
<string name="connection_local_display_name">connessione %1$d</string>
<string name="simplex_link_mode_description">Descrizione</string>
@@ -1063,7 +1063,7 @@
<string name="to_verify_compare">כדי לאמת הצפנה מקצה־לקצה עם איש הקשר שלכם, יש להשוות (או לסרוק) את הקוד במכשירים שלכם.</string>
<string name="your_chat_profiles">פרופילי צ׳אט</string>
<string name="update_network_session_mode_question">לעדכן מצב בידוד תעבורה\?</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">מנסה להתחבר לשרת המשמש לקבלת הודעות מאיש קשר זה (שגיאה: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">מנסה להתחבר לשרת המשמש לקבלת הודעות מאיש קשר זה (שגיאה: %1$s).</string>
<string name="unknown_message_format">פורמט הודעה לא ידוע</string>
<string name="simplex_link_mode_browser">דרך הדפדפן</string>
<string name="unmute_chat">בטל השתקה</string>
@@ -887,7 +887,7 @@
<string name="to_protect_privacy_simplex_has_ids_for_queues">あなたのプライバシーを守るために、他のアプリと違って、ユーザーIDの変わりに SimpleX メッセージ束毎にIDを配布し、各連絡先が別々と扱います。</string>
<string name="group_main_profile_sent">あなたのチャットプロフィールが他のグループメンバーに公開されます。</string>
<string name="to_verify_compare">エンドツーエンド暗号化を確認するには、ご自分の端末と連絡先の端末のコードを比べます (スキャンします)。</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">このコンタクトから受信するメッセージのサーバに接続しようとしてます。(エラー: %1$s)。</string>
<string name="error_connecting_to_server_to_receive_messages">このコンタクトから受信するメッセージのサーバに接続しようとしてます。(エラー: %1$s)。</string>
<string name="connection_error_auth_desc">使用済みリンク、または連絡先による接続の削除ではなければ、バッグの可能性があります。開発者にお伝えください。
\n繋がるには、連絡先に新しくリンクを発行してもらって、電波が安定かどうかご確認ください。</string>
<string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">接続を完了するには、連絡相手がオンラインになる必要があります。
@@ -1404,7 +1404,7 @@
<string name="profile_will_be_sent_to_contact_sending_link">Jūsų profilis bus išsiųstas kontaktui iš kurio gavote šią nuorodą.</string>
<string name="you_will_join_group">Prisijungsite prie visų grupės narių.</string>
<string name="connected_to_server_to_receive_messages_from_contact">Esate prisijungę prie serverio skirto gauti žinutes iš šio kontakto.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Bandoma prisijungti prie serverio skirto žinučių gavimui iš šio kontakto (klaida: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Bandoma prisijungti prie serverio skirto žinučių gavimui iš šio kontakto (klaida: %1$s).</string>
<string name="trying_to_connect_to_server_to_receive_messages">Bandoma prisijungti prie serverio skirto žinučių gavimui iš šio kontakto.</string>
<string name="no_details">nėra detalių</string>
<string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery"><![CDATA[Kad išlaikyti jūsų privatumą, vietoj tiesioginių pranešimų, programėlė turi <b>SimpleX fono tarnybą</b> - ji naudoja kelis procentus akumuliatoriaus per dieną.]]></string>
@@ -913,7 +913,7 @@
<string name="simplex_link_mode">SimpleX links</string>
<string name="simplex_link_invitation">Eenmalige SimpleX uitnodiging</string>
<string name="trying_to_connect_to_server_to_receive_messages">Proberen verbinding te maken met de server die wordt gebruikt om berichten van dit contact te ontvangen.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Er wordt geprobeerd verbinding te maken met de server die wordt gebruikt om berichten van dit contact te ontvangen (fout: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Er wordt geprobeerd verbinding te maken met de server die wordt gebruikt om berichten van dit contact te ontvangen (fout: %1$s).</string>
<string name="unknown_message_format">onbekend berichtformaat</string>
<string name="simplex_link_mode_browser">Via browser</string>
<string name="description_via_contact_address_link">via contact adres link</string>
@@ -26,7 +26,7 @@
<string name="sending_files_not_yet_supported">wysyłanie plików nie jest jeszcze obsługiwane</string>
<string name="receiving_files_not_yet_supported">odbieranie plików nie jest jeszcze obsługiwane</string>
<string name="trying_to_connect_to_server_to_receive_messages">Próbowanie połączenia z serwerem używanym do odbierania wiadomości od tego kontaktu.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Próbowanie połączenia z serwerem używanym do odbierania wiadomości od tego kontaktu (błąd: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Próbowanie połączenia z serwerem używanym do odbierania wiadomości od tego kontaktu (błąd: %1$s).</string>
<string name="unknown_message_format">nieznany format wiadomości</string>
<string name="app_name">SimpleX</string>
<string name="sender_you_pronoun">Ty</string>
@@ -971,7 +971,7 @@
<string name="tap_to_activate_profile">Toque para ativar o perfil.</string>
<string name="unhide_chat_profile">Mostrar perfil de chat</string>
<string name="unhide_profile">Mostrar perfil</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Tentando se conectar ao servidor utilizado para receber mensagens deste contato (erro:%1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Tentando se conectar ao servidor utilizado para receber mensagens deste contato (erro:%1$s).</string>
<string name="trying_to_connect_to_server_to_receive_messages">Tentando se conectar ao servidor utilizado para receber mensagens deste contato.</string>
<string name="connected_to_server_to_receive_messages_from_contact">Você está conectado ao servidor usado para receber mensagens desse contato.</string>
<string name="smp_servers_your_server">Seu servidor</string>
@@ -2077,7 +2077,7 @@
<string name="image_decoding_exception_desc">Imaginea nu poate fi decodificată. Vă rugăm să încercați o altă imagine sau să contactați dezvoltatorii.</string>
<string name="xftp_servers_per_user">Serverele pentru fișierele noi ale profilului tău de conversații actual</string>
<string name="alert_message_no_group">Acest grup nu mai există.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Se încearcă conectarea la serverul folosit pentru a primi mesaje de la acest contact (eroare: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Se încearcă conectarea la serverul folosit pentru a primi mesaje de la acest contact (eroare: %1$s).</string>
<string name="updating_settings_will_reconnect_client_to_all_servers">Actualizarea setărilor va reconecta clientul la toate serverele.</string>
<string name="this_string_is_not_a_connection_link">Acest șir de caractere nu este un link!</string>
<string name="remote_ctrl_error_timeout">S-a atins timpul de expirare la conectarea la desktop</string>
@@ -14,7 +14,7 @@
<string name="server_error">ошибка</string>
<string name="server_connecting">соединяется</string>
<string name="connected_to_server_to_receive_messages_from_contact">Установлено соединение с сервером, через который Вы получаете сообщения от этого контакта.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Устанавливается соединение с сервером, через который Вы получаете сообщения от этого контакта (ошибка: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Устанавливается соединение с сервером, через который Вы получаете сообщения от этого контакта (ошибка: %1$s).</string>
<string name="trying_to_connect_to_server_to_receive_messages">Устанавливается соединение с сервером, через который Вы получаете сообщения от этого контакта.</string>
<!-- Item Content - ChatModel.kt -->
<string name="deleted_description">удалено</string>
@@ -1043,7 +1043,7 @@
<string name="you_are_invited_to_group">คุณได้รับเชิญให้เข้าร่วมกลุ่ม</string>
<string name="incognito_random_profile">โปรไฟล์แบบสุ่มของคุณ</string>
<string name="theme">ธีม</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">กำลังพยายามเชื่อมต่อกับเซิร์ฟเวอร์ที่ใช้รับข้อความจากผู้ติดต่อนี้ (ข้อผิดพลาด: %1$s)</string>
<string name="error_connecting_to_server_to_receive_messages">กำลังพยายามเชื่อมต่อกับเซิร์ฟเวอร์ที่ใช้รับข้อความจากผู้ติดต่อนี้ (ข้อผิดพลาด: %1$s)</string>
<string name="app_name">SimpleX</string>
<string name="connected_to_server_to_receive_messages_from_contact">คุณเชื่อมต่อกับเซิร์ฟเวอร์ที่ใช้รับข้อความจากผู้ติดต่อนี้</string>
<string name="profile_will_be_sent_to_contact_sending_link">โปรไฟล์ของคุณจะถูกส่งไปยังผู้ติดต่อที่ส่งลิงก์นี้มาให้คุณ</string>
@@ -1023,7 +1023,7 @@
<string name="v4_6_hidden_chat_profiles_descr">Sohbet profillerini parola ile koru!</string>
<string name="v4_5_reduced_battery_usage">Daha az pil kullanımı</string>
<string name="to_protect_privacy_simplex_has_ids_for_queues">Gizliliği korumak için, SimpleX her bir konuşma için farklı bir ID kullanır.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Bu kişiden mesaj almak için kullanılan sunucuya bağlanılmaya çalışılıyor (hata: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Bu kişiden mesaj almak için kullanılan sunucuya bağlanılmaya çalışılıyor (hata: %1$s).</string>
<string name="v4_4_live_messages_desc">Alıcılar güncellemeleri siz yazdıkça görürler.</string>
<string name="auth_log_in_using_credential">Bilgilerinizi kullanarak giriş yapın</string>
<string name="markdown_in_messages">Mesajlarda Markdown</string>
@@ -124,7 +124,7 @@
<string name="server_error">помилка</string>
<string name="server_connecting">підключення</string>
<string name="connected_to_server_to_receive_messages_from_contact">Ви підключені до сервера для отримання повідомлень від цього контакту.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Спроба підключитися до сервера для отримання повідомлень від цього контакту (помилка: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Спроба підключитися до сервера для отримання повідомлень від цього контакту (помилка: %1$s).</string>
<string name="deleted_description">видалено</string>
<string name="trying_to_connect_to_server_to_receive_messages">Спроба підключитися до сервера для отримання повідомлень від цього контакту.</string>
<string name="marked_deleted_description">відзначено як видалено</string>
@@ -2034,7 +2034,7 @@
<string name="la_notice_turn_on">Bật</string>
<string name="servers_info_subscriptions_total">Tổng</string>
<string name="group_member_status_unknown_short">không xác định</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Đang cố gắng kết nối tới máy chủ dùng để nhận tin nhắn từ liên hệ này (lỗi: %1$s).</string>
<string name="error_connecting_to_server_to_receive_messages">Đang cố gắng kết nối tới máy chủ dùng để nhận tin nhắn từ liên hệ này (lỗi: %1$s).</string>
<string name="trying_to_connect_to_server_to_receive_messages">Đang cố gắng kết nối tới máy chủ dùng để nhận tin nhắn từ liên hệ này.</string>
<string name="to_verify_compare">Để xác minh mã hóa đầu cuối với liên hệ của bạn, so sánh (hoặc quét) mã trên các thiết bị của các bạn.</string>
<string name="network_session_mode_transport_isolation">Cách ly truyền tải</string>
@@ -589,7 +589,7 @@
<string name="rcv_group_event_member_deleted">已删除 %1$s</string>
<string name="snd_group_event_member_deleted">你删除了 %1$s</string>
<string name="profile_will_be_sent_to_contact_sending_link">你的个人资料将发送给你收到此链接的联系人。</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">正在尝试连接到用于从该联系人接收消息的服务器(错误:%1$s)。</string>
<string name="error_connecting_to_server_to_receive_messages">正在尝试连接到用于从该联系人接收消息的服务器(错误:%1$s)。</string>
<string name="connected_to_server_to_receive_messages_from_contact">你已连接到用于接收该联系人消息的服务器。</string>
<string name="description_you_shared_one_time_link">你分享了一次性链接</string>
<string name="message_delivery_error_desc">很可能此联系人已经删除了与你的联系。</string>
@@ -238,7 +238,7 @@
<string name="server_error">錯誤</string>
<string name="server_connecting">連接中</string>
<string name="connected_to_server_to_receive_messages_from_contact">你已連接到此聯絡人使用的伺服器以接收訊息。</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">嘗試連接至用於接收此聯絡人訊息的伺服器 (錯誤:%1$s)。</string>
<string name="error_connecting_to_server_to_receive_messages">嘗試連接至用於接收此聯絡人訊息的伺服器 (錯誤:%1$s)。</string>
<string name="trying_to_connect_to_server_to_receive_messages">正在嘗試連接到用於接收此聯絡人訊息伺服器。</string>
<string name="deleted_description">已刪除</string>
<string name="marked_deleted_description">已標記為已刪除</string>