diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1fc1c18fe1..7f72d114b9 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -343,7 +343,7 @@ jobs:
cli_asset_name: simplex-chat-macos-aarch64
desktop_asset_name: simplex-desktop-macos-aarch64.dmg
openssl_dir: "/opt/homebrew/opt"
- - os: macos-13
+ - os: macos-15-intel
ghc: ${{ needs.variables.outputs.GHC_VER }}
cli_asset_name: simplex-chat-macos-x86-64
desktop_asset_name: simplex-desktop-macos-x86_64.dmg
diff --git a/.github/workflows/reproduce-schedule.yml b/.github/workflows/reproduce-schedule.yml
index 7d28d6f70c..7364976467 100644
--- a/.github/workflows/reproduce-schedule.yml
+++ b/.github/workflows/reproduce-schedule.yml
@@ -23,6 +23,10 @@ jobs:
grep -i "tag_name" | \
awk -F \" '{print "TAG="$4}' >> $GITHUB_ENV
+ # Otherwise we run out of disk space with Docker build
+ - name: Free disk space
+ shell: bash
+ run: ./scripts/ci/linux_util_free_space.sh
- name: Execute reproduce script
run: |
${GITHUB_WORKSPACE}/scripts/simplex-chat-reproduce-builds.sh "$TAG" || :
diff --git a/.gitignore b/.gitignore
index 4560272980..bf565453a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,6 +54,7 @@ website/translations.json
website/src/img/images/
website/src/images/
website/src/js/lottie.min.js
+website/src/js/ethers*
website/src/privacy.md
# Generated files
website/package/generated*
diff --git a/Dockerfile.build b/Dockerfile.build
index 3c841cfb25..fddc96b6c2 100644
--- a/Dockerfile.build
+++ b/Dockerfile.build
@@ -6,7 +6,9 @@ FROM ubuntu:${TAG} AS build
ARG GHC=9.6.3
ARG CABAL=3.10.2.0
-ARG JAVA=17
+ARG JAVA_VER=17.0.17.10.1
+ARG JAVA_HASH_AMD64=e3e11daa5c22a45153bbeff1a0c21bf08631791e4e8d8ed14deba31c7cf9af1a
+ARG JAVA_HASH_ARM64=2b460859b681757b33a7591b6238ecaf51569d05d2684984e5f0a89c6514acbc
ENV TZ=Etc/UTC \
DEBIAN_FRONTEND=noninteractive
@@ -44,10 +46,26 @@ RUN apt-get update && \
# depends on libjpeg.so.8 and liblcms2.so.2 which are NOT copied into final
# /usr/lib/runtime/lib directory and I do not have time to figure out gradle.kotlin
# to fix this :(
-RUN curl --proto '=https' --tlsv1.2 -sSf 'https://apt.corretto.aws/corretto.key' | gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg &&\
- echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" > /etc/apt/sources.list.d/corretto.list &&\
- apt update &&\
- apt install -y java-${JAVA}-amazon-corretto-jdk
+RUN export JAVA_FILENAME='java-corretto.deb' \
+ JAVA_VER_MAJOR=$(printf "${JAVA_VER}" | awk -F. '{print $1}') \
+ JAVA_VER_DEB=$(printf "${JAVA_VER}" | sed 's/\.1$/-1/') && \
+ case "$(uname -m)" in \
+ x86_64) export JAVA_ARCH='amd64' JAVA_HASH="$JAVA_HASH_AMD64" ;; \
+ aarch64) export JAVA_ARCH='arm64' JAVA_HASH="$JAVA_HASH_ARM64" ;; \
+ *) echo "unknown arch $(uname -m)" && exit 1 ;; \
+ esac && \
+ curl --proto '=https' --tlsv1.2 -sSf \
+ "https://corretto.aws/downloads/resources/${JAVA_VER}/java-${JAVA_VER_MAJOR}-amazon-corretto-jdk_${JAVA_VER_DEB}_${JAVA_ARCH}.deb" \
+ -o "${JAVA_FILENAME}" && \
+ if echo "${JAVA_HASH} ${JAVA_FILENAME}" | sha256sum -c -; then \
+ if apt install -y ./"${JAVA_FILENAME}"; then \
+ rm ./"${JAVA_FILENAME}"; \
+ else \
+ echo "Failed to install Java Corretto" && exit 1; \
+ fi \
+ else \
+ echo "Checksum mismatch" && exit 1; \
+ fi
# Specify bootstrap Haskell versions
ENV BOOTSTRAP_HASKELL_GHC_VERSION=${GHC}
diff --git a/README.md b/README.md
index dc4b9658f7..b1d2556072 100644
--- a/README.md
+++ b/README.md
@@ -24,15 +24,15 @@
## Install the app
-[
](https://apps.apple.com/us/app/simplex-chat/id1605771084)
+[
](https://apps.apple.com/us/app/simplex-chat/id1605771084)
-[](https://play.google.com/store/apps/details?id=chat.simplex.app)
+[](https://play.google.com/store/apps/details?id=chat.simplex.app)
-[
](https://app.simplex.chat)
+[
](https://app.simplex.chat)
-[
](https://testflight.apple.com/join/DWuT2LQu)
+[
](https://testflight.apple.com/join/DWuT2LQu)
-[
](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)
+[
](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)
- 🖲 Protects your messages and metadata - who you talk to and when.
- 🔐 Double ratchet end-to-end encryption, with additional encryption layer.
@@ -54,24 +54,10 @@ If you are interested in helping us to integrate open-source language models, an
## Join user groups
-You can join the groups created by other users via the new [directory service](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). We are not responsible for the content shared in these groups.
+You can find the groups created by users in [SimpleX Directory](https://simplex.chat/directory/). It is also available as [SimpleX bot](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) that allows to add your own groups and communities to the directory. We are not responsible for the content shared in these groups.
**Please note**: The groups below are created for the users to be able to ask questions, make suggestions and ask questions about SimpleX Chat only.
-You also can:
-- criticize the app, and make comparisons with other messengers.
-- share new messengers you think could be interesting for privacy, as long as you don't spam.
-- share some privacy related publications, infrequently.
-- having preliminary approved with the admin in direct message, share the link to a group you created, but only once. Once the group has more than 10 members it can be submitted to [SimpleX Directory Service](./docs/DIRECTORY.md) where the new users will be able to discover it.
-
-You must:
-- be polite to other users
-- avoid spam (too frequent messages, even if they are relevant)
-- avoid any personal attacks or hostility.
-- avoid sharing any content that is not relevant to the above (that includes, but is not limited to, discussing politics or any aspects of society other than privacy, security, technology and communications, sharing any content that may be found offensive by other users, etc.).
-
-Messages not following these rules will be deleted, the right to send messages may be revoked, and the access to the new members to the group may be temporarily restricted, to prevent re-joining under a different name - our imperfect group moderation does not have a better solution at the moment.
-
You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://smp4.simplex.im/g#hr4lvFeBmndWMKTwqiodPz3VBo_6UmdGWocXd1SupsM)
There is also a group [#simplex-devs](https://smp6.simplex.im/g#Drx3efC-n418AuSpzTspw9SER0iJwrQTmKBafQHwkKM) for developers who build on SimpleX platform:
@@ -81,11 +67,7 @@ There is also a group [#simplex-devs](https://smp6.simplex.im/g#Drx3efC-n418AuSp
- social apps and services
- etc.
-There are groups in other languages, that we have the apps interface translated into. These groups are for testing, and asking questions to other SimpleX Chat users:
-
-[\#SimpleX-DE](https://smp6.simplex.im/g#V6tQ-lJqsdgJJdJiLPtP326oQFKHvwinIbgruZ9K2oU) (German-speaking), [\#SimpleX-ES](https://smp5.simplex.im/g#xJ5kwDLq2305O5FmpUzvgRIXXAcAJ9S5BItCd2Wmloc) (Spanish-speaking), [\#SimpleX-FR](https://smp6.simplex.im/g#cVOpB0CKd6hEf2aWQ6sJ22E2DVgQLtdHoiSdKxXeKqk) (French-speaking), [\#SimpleX-RU](https://smp5.simplex.im/g#vwXRdfG5SgtaG6aVcITiUGd--Ux0rY1IuH4QXYxlq3U) (Russian-speaking), [\#SimpleX-IT](https://smp5.simplex.im/g#BtRcjsl29ULFNBSE2OPhp1UwZfW7PW9gUYFQTKHdjqU) (Italian-speaking).
-
-You can join either by opening these links in the app or by opening them in a desktop browser and scanning the QR code.
+You can join these and other groups by opening these links in the app or by opening them in a desktop browser and scanning the QR code.
## Follow our updates
@@ -102,7 +84,7 @@ You need to share a link with your friend or scan a QR code from their phone, in
The channel through which you share the link does not have to be secure - it is enough that you can confirm who sent you the message and that your SimpleX connection is established.
-
+
After you connect, you can [verify connection security code](./blog/20230103-simplex-chat-v4.4-disappearing-messages.md#connection-security-verification).
@@ -165,8 +147,7 @@ It is possible to donate via:
- BTC: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u
- XMR: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt
- BCH: bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg
-- ETH: 0xD9ee7Db0AD0dc1Dfa7eD53290199ED06beA04692
-- USDT (Ethereum): 0xD9ee7Db0AD0dc1Dfa7eD53290199ED06beA04692
+- ETH/USDT (Ethereum, Arbitrum One): 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a
- ZEC: t1fwjQW5gpFhDqXNhxqDWyF9j9WeKvVS5Jg
- ZEC shielded: u16rnvkflumf5uw9frngc2lymvmzgdr2mmc9unyu0l44unwfmdcpfm0axujd2w34ct3ye709azxsqge45705lpvvqu264ltzvfay55ygyq
- DOGE: D99pV4n9TrPxBPCkQGx4w4SMSa6QjRBxPf
@@ -442,14 +423,16 @@ Please do NOT report security vulnerabilities via GitHub issues.
## License
-[AGPL v3](./LICENSE)
+This software is licensed under the GNU Affero General Public License version 3 (AGPLv3). See the [LICENSE](./LICENSE) file for details. The SimpleX and SimpleX Chat name, logo, and associated branding materials are not covered by this license and are subject to the terms outlined in the [TRADEMARK](./docs/TRADEMARK.md) file.
-[
](https://apps.apple.com/us/app/simplex-chat/id1605771084)
+Graphic designs, artworks and layouts are not licensed for re-use. If you want to use them in your publications, please ask for permission. Texts can be used as direct quotes, referencing the source.
+
+[
](https://apps.apple.com/us/app/simplex-chat/id1605771084)
-[](https://play.google.com/store/apps/details?id=chat.simplex.app)
+[](https://play.google.com/store/apps/details?id=chat.simplex.app)
-[
](https://app.simplex.chat)
+[
](https://app.simplex.chat)
-[
](https://testflight.apple.com/join/DWuT2LQu)
+[
](https://testflight.apple.com/join/DWuT2LQu)
-[
](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)
+[
](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)
diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift
index 00cc25c1d2..7adf7a0435 100644
--- a/apps/ios/Shared/ContentView.swift
+++ b/apps/ios/Shared/ContentView.swift
@@ -430,19 +430,27 @@ struct ContentView: View {
let m = ChatModel.shared
if let url = m.appOpenUrl {
m.appOpenUrl = nil
- dismissAllSheets() {
- var path = url.path
- if (path == "/contact" || path == "/invitation" || path == "/a" || path == "/c" || path == "/g" || path == "/i") {
- path.removeFirst()
- let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
- planAndConnect(
- link,
- theme: theme,
- dismiss: false
- )
- } else {
- AlertManager.shared.showAlert(Alert(title: Text("Error: URL is invalid")))
- }
+ connectViaUrl_(url)
+ } else if let url = m.appOpenUrlLater, AppChatState.shared.value == .active, scenePhase == .active {
+ // correcting branch in case .onChange(of: scenePhase) in SimpleXApp doesn't trigger and transfer appOpenUrlLater into appOpenUrl
+ m.appOpenUrlLater = nil
+ connectViaUrl_(url)
+ }
+ }
+
+ func connectViaUrl_(_ url: URL) {
+ dismissAllSheets() {
+ var path = url.path
+ if (path == "/contact" || path == "/invitation" || path == "/a" || path == "/c" || path == "/g" || path == "/i") {
+ path.removeFirst()
+ let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
+ planAndConnect(
+ link,
+ theme: theme,
+ dismiss: false
+ )
+ } else {
+ AlertManager.shared.showAlert(Alert(title: Text("Error: URL is invalid")))
}
}
}
diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift
index 2055a0ab99..193b675a57 100644
--- a/apps/ios/Shared/Model/AppAPITypes.swift
+++ b/apps/ios/Shared/Model/AppAPITypes.swift
@@ -158,7 +158,6 @@ enum ChatCommand: ChatCmdProtocol {
case apiGetCallInvitations
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
// WebRTC calls /
- case apiGetNetworkStatuses
case apiChatRead(type: ChatType, id: Int64, scope: GroupChatScope?)
case apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64])
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
@@ -359,7 +358,6 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiEndCall(contact): return "/_call end @\(contact.apiId)"
case .apiGetCallInvitations: return "/_call get"
case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)"
- case .apiGetNetworkStatuses: return "/_network_statuses"
case let .apiChatRead(type, id, scope): return "/_read chat \(ref(type, id, scope: scope))"
case let .apiChatItemsRead(type, id, scope, itemIds): return "/_read chat items \(ref(type, id, scope: scope)) \(joinedIds(itemIds))"
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id, scope: nil)) \(onOff(unreadChat))"
@@ -534,7 +532,6 @@ enum ChatCommand: ChatCmdProtocol {
case .apiEndCall: return "apiEndCall"
case .apiGetCallInvitations: return "apiGetCallInvitations"
case .apiCallStatus: return "apiCallStatus"
- case .apiGetNetworkStatuses: return "apiGetNetworkStatuses"
case .apiChatRead: return "apiChatRead"
case .apiChatItemsRead: return "apiChatItemsRead"
case .apiChatUnread: return "apiChatUnread"
@@ -574,6 +571,8 @@ enum ChatCommand: ChatCmdProtocol {
} else {
"(_support)"
}
+ case .reports:
+ "(reports, prohibited)" // can't use surrogate Reports scope
}
}
@@ -791,7 +790,6 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case userContactLinkDeleted(user: User)
case acceptingContactRequest(user: UserRef, contact: Contact)
case contactRequestRejected(user: UserRef, contactRequest: UserContactRequest, contact_: Contact?)
- case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus])
case newChatItems(user: UserRef, chatItems: [AChatItem])
case groupChatItemsDeleted(user: UserRef, groupInfo: GroupInfo, chatItemIDs: Set, byUser: Bool, member_: GroupMember?)
case forwardPlan(user: UserRef, chatItemIds: [Int64], forwardConfirmation: ForwardConfirmation?)
@@ -835,7 +833,6 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case .userContactLinkDeleted: "userContactLinkDeleted"
case .acceptingContactRequest: "acceptingContactRequest"
case .contactRequestRejected: "contactRequestRejected"
- case .networkStatuses: "networkStatuses"
case .newChatItems: "newChatItems"
case .groupChatItemsDeleted: "groupChatItemsDeleted"
case .forwardPlan: "forwardPlan"
@@ -868,7 +865,6 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case .userContactLinkDeleted: return noDetails
case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact))
case let .contactRequestRejected(u, contactRequest, contact_): return withUser(u, "contactRequest: \(String(describing: contactRequest))\ncontact_: \(String(describing: contact_))")
- case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses))
case let .newChatItems(u, chatItems):
let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n")
return withUser(u, itemsString)
@@ -910,6 +906,7 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case leftMemberUser(user: UserRef, groupInfo: GroupInfo)
case groupMembers(user: UserRef, group: SimpleXChat.Group)
case memberAccepted(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
+ case memberSupportChatRead(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
case memberSupportChatDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole)
case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool)
@@ -959,6 +956,7 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case .leftMemberUser: "leftMemberUser"
case .groupMembers: "groupMembers"
case .memberAccepted: "memberAccepted"
+ case .memberSupportChatRead: "memberSupportChatRead"
case .memberSupportChatDeleted: "memberSupportChatDeleted"
case .membersRoleUser: "membersRoleUser"
case .membersBlockedForAllUser: "membersBlockedForAllUser"
@@ -1004,6 +1002,7 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo))
case let .groupMembers(u, group): return withUser(u, String(describing: group))
case let .memberAccepted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
+ case let .memberSupportChatRead(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
case let .memberSupportChatDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)")
case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)")
@@ -1054,9 +1053,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest, chat_: ChatData?)
case contactUpdated(user: UserRef, toContact: Contact)
case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember)
- case contactsMerged(user: UserRef, intoContact: Contact, mergedContact: Contact)
- case networkStatus(networkStatus: NetworkStatus, connections: [String])
- case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus])
+ case subscriptionStatus(subscriptionStatus: SubscriptionStatus, connections: [String])
case chatInfoUpdated(user: UserRef, chatInfo: ChatInfo)
case newChatItems(user: UserRef, chatItems: [AChatItem])
case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem])
@@ -1133,9 +1130,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
case .receivedContactRequest: "receivedContactRequest"
case .contactUpdated: "contactUpdated"
case .groupMemberUpdated: "groupMemberUpdated"
- case .contactsMerged: "contactsMerged"
- case .networkStatus: "networkStatus"
- case .networkStatuses: "networkStatuses"
+ case .subscriptionStatus: "subscriptionStatus"
case .chatInfoUpdated: "chatInfoUpdated"
case .newChatItems: "newChatItems"
case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated"
@@ -1207,9 +1202,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
case let .receivedContactRequest(u, contactRequest, chat_): return withUser(u, "contactRequest: \(String(describing: contactRequest))\nchat_: \(String(describing: chat_))")
case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact))
case let .groupMemberUpdated(u, groupInfo, fromMember, toMember): return withUser(u, "groupInfo: \(groupInfo)\nfromMember: \(fromMember)\ntoMember: \(toMember)")
- case let .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)")
- case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))"
- case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses))
+ case let .subscriptionStatus(status, conns): return "subscriptionStatus: \(String(describing: status))\nconnections: \(String(describing: conns))"
case let .chatInfoUpdated(u, chatInfo): return withUser(u, String(describing: chatInfo))
case let .newChatItems(u, chatItems):
let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n")
@@ -1369,38 +1362,6 @@ enum ChatDeleteMode: Codable {
}
}
-enum NetworkStatus: Decodable, Equatable {
- case unknown
- case connected
- case disconnected
- case error(connectionError: String)
-
- var statusString: LocalizedStringKey {
- switch self {
- case .connected: "connected"
- case .error: "error"
- default: "connecting"
- }
- }
-
- var statusExplanation: LocalizedStringKey {
- switch self {
- case .connected: "You are connected to the server used to receive messages from this contact."
- case let .error(err): "Trying to connect to the server used to receive messages from this contact (error: \(err))."
- default: "Trying to connect to the server used to receive messages from this contact."
- }
- }
-
- var imageName: String {
- switch self {
- case .unknown: "circle.dotted"
- case .connected: "circle.fill"
- case .disconnected: "ellipsis.circle.fill"
- case .error: "exclamationmark.circle.fill"
- }
- }
-}
-
enum ForwardConfirmation: Decodable, Hashable {
case filesNotAccepted(fileIds: [Int64])
case filesInProgress(filesCount: Int)
@@ -1408,11 +1369,6 @@ enum ForwardConfirmation: Decodable, Hashable {
case filesFailed(filesCount: Int)
}
-struct ConnNetworkStatus: Decodable {
- var agentConnId: String
- var networkStatus: NetworkStatus
-}
-
struct UserMsgReceiptSettings: Codable {
var enable: Bool
var clearOverrides: Bool
@@ -1985,13 +1941,13 @@ struct ProtocolTestFailure: Decodable, Error, Equatable {
let err = String.localizedStringWithFormat(NSLocalizedString("Test failed at step %@.", comment: "server test failure"), testStep.text)
switch testError {
case .SMP(_, .AUTH):
- return err + " " + NSLocalizedString("Server requires authorization to create queues, check password", comment: "server test error")
+ return err + " " + NSLocalizedString("Server requires authorization to create queues, check password.", comment: "server test error")
case .XFTP(.AUTH):
- return err + " " + NSLocalizedString("Server requires authorization to upload, check password", comment: "server test error")
- case .BROKER(_, .NETWORK):
- return err + " " + NSLocalizedString("Possibly, certificate fingerprint in server address is incorrect", comment: "server test error")
+ return err + " " + NSLocalizedString("Server requires authorization to upload, check password.", comment: "server test error")
+ case .BROKER(_, .NETWORK(.unknownCAError)):
+ return err + " " + NSLocalizedString("Fingerprint in server address does not match certificate.", comment: "server test error")
default:
- return err
+ return err + " " + String.localizedStringWithFormat(NSLocalizedString("Error: %@.", comment: "server test error"), String(describing: testError))
}
}
}
diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift
index 33b3fd8009..f1f4e686bd 100644
--- a/apps/ios/Shared/Model/ChatModel.swift
+++ b/apps/ios/Shared/Model/ChatModel.swift
@@ -283,29 +283,6 @@ class ChatTagsModel: ObservableObject {
}
}
-class NetworkModel: ObservableObject {
- // map of connections network statuses, key is agent connection id
- @Published var networkStatuses: Dictionary = [:]
-
- static let shared = NetworkModel()
-
- private init() { }
-
- func setContactNetworkStatus(_ contact: Contact, _ status: NetworkStatus) {
- if let conn = contact.activeConn {
- networkStatuses[conn.agentConnId] = status
- }
- }
-
- func contactNetworkStatus(_ contact: Contact) -> NetworkStatus {
- if let conn = contact.activeConn {
- networkStatuses[conn.agentConnId] ?? .unknown
- } else {
- .unknown
- }
- }
-}
-
/// ChatItemWithMenu can depend on previous or next item for it's appearance
/// This dummy model is used to force an update of all chat items,
/// when they might have changed appearance.
@@ -374,6 +351,8 @@ final class ChatModel: ObservableObject {
@Published var deletedChats: Set = []
// current chat
@Published var chatId: String?
+ @Published var chatAgentConnId: String?
+ @Published var chatSubStatus: SubscriptionStatus?
@Published var openAroundItemId: ChatItem.ID? = nil
@Published var chatToTop: String?
@Published var groupMembers: [GMember] = []
@@ -385,6 +364,7 @@ final class ChatModel: ObservableObject {
@Published var userAddress: UserContactLink?
@Published var chatItemTTL: ChatItemTTL = .none
@Published var appOpenUrl: URL?
+ @Published var appOpenUrlLater: URL?
@Published var deviceToken: DeviceToken?
@Published var savedToken: DeviceToken?
@Published var tokenRegistered = false
@@ -667,20 +647,24 @@ final class ChatModel: ObservableObject {
func getCIItemsModel(_ cInfo: ChatInfo, _ ci: ChatItem) -> ItemsModel? {
let cInfoScope = cInfo.groupChatScope()
- if let cInfoScope = cInfoScope {
- switch cInfoScope {
- case .memberSupport:
- switch secondaryIM?.secondaryIMFilter {
- case .none:
- return nil
- case let .groupChatScopeContext(groupScopeInfo):
- return (cInfo.id == chatId && sameChatScope(cInfoScope, groupScopeInfo.toChatScope())) ? secondaryIM : nil
- case let .msgContentTagContext(contentTag):
- return (cInfo.id == chatId && ci.isReport && contentTag == .report) ? secondaryIM : nil
- }
+ return if let cInfoScope = cInfoScope {
+ switch (cInfoScope, secondaryIM?.secondaryIMFilter) {
+ case let (.memberSupport, .some(.groupChatScopeContext(groupScopeInfo))):
+ // Chat with member or Chat with admins opened (secondaryIM has .groupChatScopeContext filter), cInfo has matching scope
+ (cInfo.id == chatId && sameChatScope(cInfoScope, groupScopeInfo.toChatScope())) ? secondaryIM : nil
+
+ case let (.memberSupport, .some(.msgContentTagContext(contentTag))):
+ // Reports view opened (secondaryIM has .msgContentTagContext(.report) filter), we process event (cInfo has proper .memberSupport scope)
+ (cInfo.id == chatId && ci.isReport && contentTag == .report) ? secondaryIM : nil
+
+ case let (.reports, .some(.msgContentTagContext(contentTag))):
+ // Reports view opened (secondaryIM has .msgContentTagContext(.report) filter), we process user action (cInfo has surrogate .reports scope)
+ (cInfo.id == chatId && ci.isReport && contentTag == .report) ? secondaryIM : nil
+ default:
+ nil
}
} else {
- return cInfo.id == chatId ? im : nil
+ cInfo.id == chatId ? im : nil
}
}
@@ -782,11 +766,6 @@ final class ChatModel: ObservableObject {
}
func removeMemberItems(_ removedMember: GroupMember, byMember: GroupMember, _ groupInfo: GroupInfo) {
- // this should not happen, only another member can "remove" user, user can only "leave" (another event).
- if byMember.groupMemberId == groupInfo.membership.groupMemberId {
- logger.debug("exiting removeMemberItems")
- return
- }
if chatId == groupInfo.id {
for i in 0.. (title: String, message: Stri
title: NSLocalizedString("Connection timeout", comment: "alert title"),
message: serverErrorAlertMessage(addr)
)
+ case let .errorAgent(.BROKER(addr, .NETWORK(.unknownCAError))): nil
case let .errorAgent(.BROKER(addr, .NETWORK)): (
title: NSLocalizedString("Connection error", comment: "alert title"),
message: serverErrorAlertMessage(addr)
@@ -177,6 +176,7 @@ func retryableNetworkErrorAlert(_ e: ChatError) -> (title: String, message: Stri
title: NSLocalizedString("Private routing timeout", comment: "alert title"),
message: proxyErrorAlertMessage(serverAddress)
)
+ case let .errorAgent(.SMP(serverAddress, .PROXY(.BROKER(.NETWORK(.unknownCAError))))): nil
case let .errorAgent(.SMP(serverAddress, .PROXY(.BROKER(.NETWORK)))): (
title: NSLocalizedString("Private routing error", comment: "alert title"),
message: proxyErrorAlertMessage(serverAddress)
@@ -185,6 +185,7 @@ func retryableNetworkErrorAlert(_ e: ChatError) -> (title: String, message: Stri
title: NSLocalizedString("Private routing timeout", comment: "alert title"),
message: proxyDestinationErrorAlertMessage(proxyServer: proxyServer, destServer: destServer)
)
+ case let .errorAgent(.PROXY(proxyServer, destServer, .protocolError(.PROXY(.BROKER(.NETWORK(.unknownCAError)))))): nil
case let .errorAgent(.PROXY(proxyServer, destServer, .protocolError(.PROXY(.BROKER(.NETWORK))))): (
title: NSLocalizedString("Private routing error", comment: "alert title"),
message: proxyDestinationErrorAlertMessage(proxyServer: proxyServer, destServer: destServer)
@@ -1316,6 +1317,10 @@ func apiCreateUserAddress() async throws -> CreatedConnLink? {
let userId = try currentUserId("apiCreateUserAddress")
let r: APIResult? = await chatApiSendCmdWithRetry(.apiCreateMyAddress(userId: userId))
if case let .result(.userContactLinkCreated(_, connLink)) = r { return connLink }
+ if case let .error(.errorAgent(.NOTICE(server, preset, expires))) = r {
+ showClientNotice(server, preset, expires)
+ return nil
+ }
if let r { throw r.unexpected } else { return nil }
}
@@ -1391,8 +1396,14 @@ func apiRejectContactRequest(contactReqId: Int64) async throws -> Contact? {
throw r.unexpected
}
-func apiChatRead(type: ChatType, id: Int64, scope: GroupChatScope?) async throws {
- try await sendCommandOkResp(.apiChatRead(type: type, id: id, scope: scope))
+func apiChatRead(type: ChatType, id: Int64) async throws {
+ try await sendCommandOkResp(.apiChatRead(type: type, id: id, scope: nil))
+}
+
+func apiSupportChatRead(type: ChatType, id: Int64, scope: GroupChatScope) async throws -> (GroupInfo, GroupMember) {
+ let r: ChatResponse2 = try await chatSendCmd(.apiChatRead(type: type, id: id, scope: scope))
+ if case let .memberSupportChatRead(_, groupInfo, member) = r { return (groupInfo, member) }
+ throw r.unexpected
}
func apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64]) async throws -> ChatInfo {
@@ -1631,7 +1642,6 @@ func acceptContactRequest(incognito: Bool, contactRequestId: Int64, inProgress:
} else {
ChatModel.shared.replaceChat(contactRequestChatId(contactRequestId), chat)
}
- NetworkModel.shared.setContactNetworkStatus(contact, .connected)
inProgress?.wrappedValue = false
}
if contact.sndReady {
@@ -1719,17 +1729,11 @@ func apiCallStatus(_ contact: Contact, _ status: String) async throws {
}
}
-func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] {
- let r: ChatResponse1 = try chatSendCmdSync(.apiGetNetworkStatuses)
- if case let .networkStatuses(_, statuses) = r { return statuses }
- throw r.unexpected
-}
-
func markChatRead(_ im: ItemsModel, _ chat: Chat) async {
do {
if chat.chatStats.unreadCount > 0 {
let cInfo = chat.chatInfo
- try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope())
+ try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId)
await MainActor.run {
withAnimation { ChatModel.shared.markAllChatItemsRead(im, cInfo) }
}
@@ -1754,6 +1758,20 @@ func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async {
}
}
+func markSupportChatRead(_ groupInfo: GroupInfo, _ member: GroupMember) async {
+ do {
+ if member.supportChatNotRead {
+ let (updatedGroupInfo, updatedMember) = try await apiSupportChatRead(type: .group, id: groupInfo.apiId, scope: .memberSupport(groupMemberId_: member.groupMemberId))
+ await MainActor.run {
+ _ = ChatModel.shared.upsertGroupMember(updatedGroupInfo, updatedMember)
+ ChatModel.shared.updateGroup(updatedGroupInfo)
+ }
+ }
+ } catch {
+ logger.error("markSupportChatRead apiChatRead error: \(responseError(error))")
+ }
+}
+
func apiMarkChatItemsRead(_ im: ItemsModel, _ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], mentionsRead: Int) async {
do {
let updatedChatInfo = try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope(), itemIds: itemIds)
@@ -1819,7 +1837,7 @@ func apiDeleteMemberSupportChat(_ groupId: Int64, _ groupMemberId: Int64) async
throw r.unexpected
}
-func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool = false) async throws -> (GroupInfo, [GroupMember]) {
+func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool) async throws -> (GroupInfo, [GroupMember]) {
let r: ChatResponse2 = try await chatSendCmd(.apiRemoveMembers(groupId: groupId, memberIds: memberIds, withMessages: withMessages), bgTask: false)
if case let .userDeletedMembers(_, updatedGroupInfo, members, _withMessages) = r { return (updatedGroupInfo, members) }
throw r.unexpected
@@ -1876,6 +1894,10 @@ func apiUpdateGroup(_ groupId: Int64, _ groupProfile: GroupProfile) async throws
func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> GroupLink? {
let r: APIResult? = await chatApiSendCmdWithRetry(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole))
if case let .result(.groupLinkCreated(_, _, groupLink)) = r { return groupLink }
+ if case let .error(.errorAgent(.NOTICE(server, preset, expires))) = r {
+ showClientNotice(server, preset, expires)
+ return nil
+ }
if let r { throw r.unexpected } else { return nil }
}
@@ -1932,7 +1954,6 @@ func acceptMemberContact(contactId: Int64, inProgress: Binding? = nil) asy
if let contact = await apiAcceptMemberContact(contactId: contactId) {
await MainActor.run {
ChatModel.shared.updateContact(contact)
- NetworkModel.shared.setContactNetworkStatus(contact, .connected)
inProgress?.wrappedValue = false
}
if contact.sndReady {
@@ -2218,7 +2239,6 @@ class ChatReceiver {
func processReceivedMsg(_ res: ChatEvent) async {
let m = ChatModel.shared
- let n = NetworkModel.shared
logger.debug("processReceivedMsg: \(res.responseType)")
switch res {
case let .contactDeletedByContact(user, contact):
@@ -2235,14 +2255,15 @@ func processReceivedMsg(_ res: ChatEvent) async {
m.dismissConnReqView(conn.id)
m.removeChat(conn.id)
}
+ if contact.id == m.chatId, let conn = contact.activeConn {
+ m.chatAgentConnId = conn.agentConnId
+ m.chatSubStatus = .active
+ }
}
}
if contact.directOrUsed {
NtfManager.shared.notifyContactConnected(user, contact)
}
- await MainActor.run {
- n.setContactNetworkStatus(contact, .connected)
- }
case let .contactConnecting(user, contact):
if active(user) && contact.directOrUsed {
await MainActor.run {
@@ -2263,9 +2284,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
}
}
}
- await MainActor.run {
- n.setContactNetworkStatus(contact, .connected)
- }
case let .receivedContactRequest(user, contactRequest, chat_):
if active(user) {
await MainActor.run {
@@ -2304,39 +2322,10 @@ func processReceivedMsg(_ res: ChatEvent) async {
_ = m.upsertGroupMember(groupInfo, toMember)
}
}
- case let .contactsMerged(user, intoContact, mergedContact):
- if active(user) && m.hasChat(mergedContact.id) {
+ case let .subscriptionStatus(status, connections):
+ if let chatAgentConnId = m.chatAgentConnId, connections.contains(chatAgentConnId) {
await MainActor.run {
- if m.chatId == mergedContact.id {
- ItemsModel.shared.loadOpenChat(mergedContact.id)
- }
- m.removeChat(mergedContact.id)
- }
- }
- case let .networkStatus(status, connections):
- // dispatch queue to synchronize access
- networkStatusesLock.sync {
- var ns = n.networkStatuses
- // slow loop is on the background thread
- for cId in connections {
- ns[cId] = status
- }
- // fast model update is on the main thread
- DispatchQueue.main.sync {
- n.networkStatuses = ns
- }
- }
- case let .networkStatuses(_, statuses): ()
- // dispatch queue to synchronize access
- networkStatusesLock.sync {
- var ns = n.networkStatuses
- // slow loop is on the background thread
- for s in statuses {
- ns[s.agentConnId] = s.networkStatus
- }
- // fast model update is on the main thread
- DispatchQueue.main.sync {
- n.networkStatuses = ns
+ m.chatSubStatus = status
}
}
case let .chatInfoUpdated(user, chatInfo):
@@ -2540,11 +2529,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
_ = m.upsertGroupMember(groupInfo, member)
}
}
- if let contact = memberContact {
- await MainActor.run {
- n.setContactNetworkStatus(contact, .connected)
- }
- }
case let .groupUpdated(user, toGroup):
if active(user) {
await MainActor.run {
@@ -2770,13 +2754,10 @@ func processReceivedMsg(_ res: ChatEvent) async {
func switchToLocalSession() {
let m = ChatModel.shared
- let n = NetworkModel.shared
m.remoteCtrlSession = nil
do {
m.users = try listUsers()
try getUserChatData()
- let statuses = (try apiGetNetworkStatuses()).map { s in (s.agentConnId, s.networkStatus) }
- n.networkStatuses = Dictionary(uniqueKeysWithValues: statuses)
} catch let error {
logger.debug("error updating chat data: \(responseError(error))")
}
@@ -2884,3 +2865,26 @@ private struct UserResponse: Decodable {
var user: User?
var error: String?
}
+
+private func showClientNotice(_ server: String, _ preset: Bool, _ expiresAt: Date?) {
+ DispatchQueue.main.async {
+ var message = "Server: \(server).\nConditions of use violation notice received from \(preset ? "preset" : "this") server.\nNo IDs shared, see How it works."
+ if let expiresAt {
+ message += "\n\nNew addresses can be created after \(expiresAt.formatted(date: .abbreviated, time: .shortened))."
+ }
+ showAlert("Not allowed", message: message) {
+ let howItWorks = UIAlertAction(title: NSLocalizedString("How it works", comment: "alert button"), style: .default, handler: { _ in
+ UIApplication.shared.open(contentModerationPostLink)
+ })
+ return preset
+ ? [
+ okAlertAction,
+ UIAlertAction(title: NSLocalizedString("Conditions of use", comment: "alert button"), style: .default, handler: { _ in
+ UIApplication.shared.open(conditionsURL)
+ }),
+ howItWorks
+ ]
+ : [okAlertAction, howItWorks]
+ }
+ }
+}
diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift
index 5fbdafe19f..e1a6bb61e8 100644
--- a/apps/ios/Shared/SimpleXApp.swift
+++ b/apps/ios/Shared/SimpleXApp.swift
@@ -19,7 +19,6 @@ struct SimpleXApp: App {
@Environment(\.scenePhase) var scenePhase
@State private var enteredBackgroundAuthenticated: TimeInterval? = nil
- @State private var appOpenUrlLater: URL?
init() {
DispatchQueue.global(qos: .background).sync {
@@ -46,7 +45,7 @@ struct SimpleXApp: App {
if AppChatState.shared.value == .active {
chatModel.appOpenUrl = url
} else {
- appOpenUrlLater = url
+ chatModel.appOpenUrlLater = url
}
}
.onAppear() {
@@ -98,15 +97,15 @@ struct SimpleXApp: App {
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
await updateCallInvitations()
}
- if let url = appOpenUrlLater {
+ if let url = chatModel.appOpenUrlLater {
await MainActor.run {
- appOpenUrlLater = nil
+ chatModel.appOpenUrlLater = nil
chatModel.appOpenUrl = url
}
}
}
- } else if let url = appOpenUrlLater {
- appOpenUrlLater = nil
+ } else if let url = chatModel.appOpenUrlLater {
+ chatModel.appOpenUrlLater = nil
chatModel.appOpenUrl = url
}
}
diff --git a/apps/ios/Shared/Theme/Theme.swift b/apps/ios/Shared/Theme/Theme.swift
index de67390026..3bd8f00c25 100644
--- a/apps/ios/Shared/Theme/Theme.swift
+++ b/apps/ios/Shared/Theme/Theme.swift
@@ -42,12 +42,14 @@ class AppTheme: ObservableObject, Equatable {
}
func updateFromCurrentColors() {
- objectWillChange.send()
- name = CurrentColors.name
- base = CurrentColors.base
- colors.updateColorsFrom(CurrentColors.colors)
- appColors.updateColorsFrom(CurrentColors.appColors)
- wallpaper.updateWallpaperFrom(CurrentColors.wallpaper)
+ DispatchQueue.main.async {
+ self.objectWillChange.send()
+ self.name = CurrentColors.name
+ self.base = CurrentColors.base
+ self.colors.updateColorsFrom(CurrentColors.colors)
+ self.appColors.updateColorsFrom(CurrentColors.appColors)
+ self.wallpaper.updateWallpaperFrom(CurrentColors.wallpaper)
+ }
}
}
diff --git a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift
index b60842a4a0..37f3b982a1 100644
--- a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift
+++ b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift
@@ -12,6 +12,7 @@ import SimpleXChat
struct ChatInfoToolbar: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
+ @EnvironmentObject var m: ChatModel
@ObservedObject var chat: Chat
var imageSize: CGFloat = 32
@@ -56,6 +57,13 @@ struct ChatInfoToolbar: View {
.padding(.top, -2)
}
}
+ if let contact = chat.chatInfo.contact,
+ contact.ready && contact.active,
+ let chatSubStatus = m.chatSubStatus,
+ chatSubStatus != .active {
+ SubStatusView(status: chatSubStatus)
+ .padding(.leading, 4)
+ }
}
.foregroundColor(theme.colors.onBackground)
.frame(width: 220)
@@ -68,6 +76,30 @@ struct ChatInfoToolbar: View {
.baselineOffset(1)
.kerning(-2)
}
+
+ struct SubStatusView: View {
+ @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
+ @EnvironmentObject var theme: AppTheme
+ var status: SubscriptionStatus
+
+ var body: some View {
+ switch status {
+ case .active: EmptyView()
+ case .pending: ProgressView()
+ case .removed: subStatusError()
+ case .noSub: subStatusError()
+ }
+ }
+
+ @ViewBuilder private func subStatusError() -> some View {
+ let dynamicChatInfoSize = dynamicSize(userFont).chatInfoSize
+ Image(systemName: "exclamationmark.circle")
+ .resizable()
+ .scaledToFit()
+ .frame(width: dynamicChatInfoSize, height: dynamicChatInfoSize)
+ .foregroundColor(theme.colors.secondary)
+ }
+ }
}
struct ChatInfoToolbar_Previews: PreviewProvider {
diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
index 77c1db341a..ad82af05e2 100644
--- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
@@ -92,7 +92,6 @@ struct ChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@Environment(\.dismiss) var dismiss: DismissAction
- @ObservedObject var networkModel = NetworkModel.shared
@ObservedObject var chat: Chat
@State var contact: Contact
@State var localAlias: String
@@ -115,7 +114,7 @@ struct ChatInfoView: View {
enum ChatInfoViewAlert: Identifiable {
case clearChatAlert
- case networkStatusAlert
+ case subStatusAlert(status: SubscriptionStatus)
case switchAddressAlert
case abortSwitchAddressAlert
case syncConnectionForceAlert
@@ -126,7 +125,7 @@ struct ChatInfoView: View {
var id: String {
switch self {
case .clearChatAlert: return "clearChatAlert"
- case .networkStatusAlert: return "networkStatusAlert"
+ case let .subStatusAlert(status): return "subStatusAlert \(status)"
case .switchAddressAlert: return "switchAddressAlert"
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
@@ -236,10 +235,12 @@ struct ChatInfoView: View {
if contact.ready && contact.active {
Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) {
- networkStatusRow()
- .onTapGesture {
- alert = .networkStatusAlert
- }
+ if let chatSubStatus = chatModel.chatSubStatus {
+ SubStatusRow(status: chatSubStatus)
+ .onTapGesture {
+ alert = .subStatusAlert(status: chatSubStatus)
+ }
+ }
if let connStats = connectionStats {
Button("Change receiving address") {
alert = .switchAddressAlert
@@ -325,7 +326,7 @@ struct ChatInfoView: View {
.alert(item: $alert) { alertItem in
switch(alertItem) {
case .clearChatAlert: return clearChatAlert()
- case .networkStatusAlert: return networkStatusAlert()
+ case let .subStatusAlert(status): return subStatusAlert(status)
case .switchAddressAlert: return switchAddressAlert(switchContactAddress)
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress)
case .syncConnectionForceAlert:
@@ -546,26 +547,6 @@ struct ChatInfoView: View {
}
}
- private func networkStatusRow() -> some View {
- HStack {
- Text("Network status")
- Image(systemName: "info.circle")
- .foregroundColor(theme.colors.primary)
- .font(.system(size: 14))
- Spacer()
- Text(networkModel.contactNetworkStatus(contact).statusString)
- .foregroundColor(theme.colors.secondary)
- serverImage()
- }
- }
-
- private func serverImage() -> some View {
- let status = networkModel.contactNetworkStatus(contact)
- return Image(systemName: status.imageName)
- .foregroundColor(status == .connected ? .green : theme.colors.secondary)
- .font(.system(size: 12))
- }
-
private func deleteContactButton() -> some View {
Button(role: .destructive) {
deleteContactDialog(
@@ -605,10 +586,10 @@ struct ChatInfoView: View {
)
}
- private func networkStatusAlert() -> Alert {
+ private func subStatusAlert(_ status: SubscriptionStatus) -> Alert {
Alert(
title: Text("Network status"),
- message: Text(networkModel.contactNetworkStatus(contact).statusExplanation)
+ message: Text(status.statusExplanation)
)
}
@@ -667,6 +648,30 @@ struct ChatInfoView: View {
}
}
+struct SubStatusRow: View {
+ @EnvironmentObject var theme: AppTheme
+ var status: SubscriptionStatus
+
+ var body: some View {
+ HStack {
+ Text("Network status")
+ Image(systemName: "info.circle")
+ .foregroundColor(theme.colors.primary)
+ .font(.system(size: 14))
+ Spacer()
+ Text(status.statusString)
+ .foregroundColor(theme.colors.secondary)
+ serverImage(status)
+ }
+ }
+
+ private func serverImage(_ status: SubscriptionStatus) -> some View {
+ return Image(systemName: status.imageName)
+ .foregroundColor(status == .active ? .green : theme.colors.secondary)
+ .font(.system(size: 12))
+ }
+}
+
struct ChatTTLOption: View {
@ObservedObject var chat: Chat
@Binding var progressIndicator: Bool
diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift
index 2f8d6f2acd..709758655f 100644
--- a/apps/ios/Shared/Views/Chat/ChatView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatView.swift
@@ -60,6 +60,7 @@ struct ChatView: View {
@State private var animatedScrollingInProgress: Bool = false
@State private var showUserSupportChatSheet = false
@State private var showCommandsMenu = false
+ @State private var supportChatMemberInfoLinkActive = false
@State private var scrollView: EndlessScrollView = EndlessScrollView(frame: .zero)
@@ -178,6 +179,28 @@ struct ChatView: View {
if im.showLoadingProgress == chat.id {
ProgressView().scaleEffect(2)
}
+ if case let .group(groupInfo, _) = chat.chatInfo,
+ case let .groupChatScopeContext(groupScopeInfo) = im.secondaryIMFilter,
+ case let .memberSupport(groupMember_) = groupScopeInfo,
+ let groupMember = groupMember_ {
+ NavigationLink(isActive: $supportChatMemberInfoLinkActive) {
+ GroupMemberInfoView(
+ groupInfo: groupInfo,
+ chat: chat,
+ groupMember: GMember(groupMember),
+ scrollToItemId: $scrollToItemId,
+ openedFromSupportChat: true
+ )
+ .navigationBarHidden(false)
+ .modifier(BackButton(disabled: Binding.constant(false)) {
+ supportChatMemberInfoLinkActive = false
+ })
+ } label: {
+ EmptyView()
+ }
+ .frame(width: 1, height: 1)
+ .hidden()
+ }
}
.safeAreaInset(edge: .top) {
VStack(spacing: .zero) {
@@ -211,18 +234,20 @@ struct ChatView: View {
.confirmationDialog(selectedChatItems?.count == 1 ? "Archive report?" : "Archive \((selectedChatItems?.count ?? 0)) reports?", isPresented: $showArchiveSelectedReports, titleVisibility: .visible) {
Button("For me", role: .destructive) {
if let selected = selectedChatItems {
- archiveReports(chat.chatInfo, selected.sorted(), false, deletedSelectedMessages)
+ archiveReports(chat, selected.sorted(), false, deletedSelectedMessages)
}
}
if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, groupInfo.membership.memberActive {
Button("For all moderators", role: .destructive) {
if let selected = selectedChatItems {
- archiveReports(chat.chatInfo, selected.sorted(), true, deletedSelectedMessages)
+ archiveReports(chat, selected.sorted(), true, deletedSelectedMessages)
}
}
}
}
- .appSheet(item: $selectedMember) { member in
+ .appSheet(item: $selectedMember, onDismiss: {
+ chatModel.secondaryIM = nil
+ }) { member in
if case let .group(groupInfo, _) = chat.chatInfo {
GroupMemberInfoView(
groupInfo: groupInfo,
@@ -335,7 +360,10 @@ struct ChatView: View {
}
.onChange(of: chatModel.secondaryPendingInviteeChatOpened) { opened in
if im.secondaryIMFilter != nil && !opened {
- dismiss()
+ Task {
+ try? await Task.sleep(nanoseconds: 650_000000)
+ dismiss()
+ }
}
}
.onChange(of: chatModel.openAroundItemId) { openAround in
@@ -365,6 +393,8 @@ struct ChatView: View {
if chatModel.chatId == cInfo.id && !presentationMode.wrappedValue.isPresented {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
if chatModel.chatId == nil {
+ chatModel.chatAgentConnId = nil
+ chatModel.chatSubStatus = nil
im.reversedChatItems = []
im.chatState.clear()
chatModel.groupMembers = []
@@ -459,7 +489,10 @@ struct ChatView: View {
ChatInfoToolbar(chat: chat)
.tint(theme.colors.primary)
}
- .appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) {
+ .appSheet(isPresented: $showChatInfoSheet, onDismiss: {
+ chatModel.secondaryIM = nil
+ theme = buildTheme()
+ }) {
GroupChatInfoView(
chat: chat,
groupInfo: Binding(
@@ -562,10 +595,16 @@ struct ChatView: View {
switch groupScopeInfo {
case let .memberSupport(groupMember_):
if let groupMember = groupMember_ {
- MemberSupportChatToolbar(groupMember: groupMember)
+ Button {
+ supportChatMemberInfoLinkActive = true
+ } label: {
+ MemberSupportChatToolbar(groupMember: groupMember)
+ }
} else {
textChatToolbar("Chat with admins")
}
+ case .reports:
+ textChatToolbar("Member reports")
}
case let .msgContentTagContext(contentTag):
switch contentTag {
@@ -620,18 +659,30 @@ struct ChatView: View {
private func initChatView() {
let cInfo = chat.chatInfo
// This check prevents the call to apiContactInfo after the app is suspended, and the database is closed.
- if case .active = scenePhase,
- case let .direct(contact) = cInfo {
- Task {
- do {
- let (stats, _) = try await apiContactInfo(chat.chatInfo.apiId)
- await MainActor.run {
- if let s = stats {
- chatModel.updateContactConnectionStats(contact, s)
+ if case .active = scenePhase {
+ if case let .direct(contact) = cInfo {
+ Task {
+ do {
+ let (stats, _) = try await apiContactInfo(chat.chatInfo.apiId)
+ await MainActor.run {
+ if let s = stats {
+ chatModel.updateContactConnectionStats(contact, s)
+ if let conn = contact.activeConn {
+ chatModel.chatAgentConnId = conn.agentConnId
+ chatModel.chatSubStatus = s.subStatus
+ }
+ }
}
+ } catch let error {
+ logger.error("apiContactInfo error: \(responseError(error))")
+ }
+ }
+ } else {
+ Task {
+ await MainActor.run {
+ chatModel.chatAgentConnId = nil
+ chatModel.chatSubStatus = nil
}
- } catch let error {
- logger.error("apiContactInfo error: \(responseError(error))")
}
}
}
@@ -1881,14 +1932,14 @@ struct ChatView: View {
.confirmationDialog(archivingReports?.count == 1 ? "Archive report?" : "Archive \(archivingReports?.count ?? 0) reports?", isPresented: $showArchivingReports, titleVisibility: .visible) {
Button("For me", role: .destructive) {
if let reports = self.archivingReports {
- archiveReports(chat.chatInfo, reports.sorted(), false)
+ archiveReports(chat, reports.sorted(), false)
self.archivingReports = []
}
}
if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, groupInfo.membership.memberActive {
Button("For all moderators", role: .destructive) {
if let reports = self.archivingReports {
- archiveReports(chat.chatInfo, reports.sorted(), true)
+ archiveReports(chat, reports.sorted(), true)
self.archivingReports = []
}
}
@@ -2680,13 +2731,13 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe
await MainActor.run {
for di in deletedItems {
if let toItem = di.toChatItem {
- _ = ChatModel.shared.upsertChatItem(chat.chatInfo, toItem.chatItem)
+ _ = ChatModel.shared.upsertChatItem(chatInfo, toItem.chatItem)
} else {
ChatModel.shared.removeChatItem(chatInfo, di.deletedChatItem.chatItem)
}
let deletedItem = di.deletedChatItem.chatItem
if deletedItem.isActiveReport {
- ChatModel.shared.decreaseGroupReportsCounter(chat.chatInfo.id)
+ ChatModel.shared.decreaseGroupReportsCounter(chatInfo.id)
}
}
if let updatedChatInfo = deletedItems.last?.deletedChatItem.chatInfo {
@@ -2701,8 +2752,9 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe
}
}
-func archiveReports(_ chatInfo: ChatInfo, _ itemIds: [Int64], _ forAll: Bool, _ onSuccess: @escaping () async -> Void = {}) {
+func archiveReports(_ chat: Chat, _ itemIds: [Int64], _ forAll: Bool, _ onSuccess: @escaping () async -> Void = {}) {
if itemIds.count > 0 {
+ let chatInfo = chat.chatInfo
Task {
do {
let deleted = try await apiDeleteReceivedReports(
diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
index 683dea0f56..3745d0f0b8 100644
--- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
+++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
@@ -788,7 +788,6 @@ struct ComposeView: View {
await MainActor.run {
self.chatModel.updateContact(contact)
clearState()
- NetworkModel.shared.setContactNetworkStatus(contact, .connected)
}
} else {
AlertManager.shared.showAlertMsg(title: "Empty message!")
@@ -827,7 +826,6 @@ struct ComposeView: View {
if let contact = await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognito, msg: mc) {
await MainActor.run {
self.chatModel.updateContact(contact)
- NetworkModel.shared.setContactNetworkStatus(contact, .connected)
clearState()
}
} else {
diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift
index 143bf42ea4..e9913053ea 100644
--- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift
+++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift
@@ -48,7 +48,7 @@ func showRejectMemberAlert(_ groupInfo: GroupInfo, _ member: GroupMember, dismis
showAlert(
title: NSLocalizedString("Reject member?", comment: "alert title"),
buttonTitle: "Reject",
- buttonAction: { removeMember(groupInfo, member, dismiss: dismiss) },
+ buttonAction: { removeMember(groupInfo, member, withMessages: false, dismiss: dismiss) },
cancelButton: true
)
}
diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift
index 31d4ceecc6..c5fd8e39d0 100644
--- a/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift
+++ b/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift
@@ -71,13 +71,17 @@ struct NativeTextEditor: UIViewRepresentable {
if field.selectedRange != selectedRange {
field.selectedRange = selectedRange
}
- if focused && !field.isFocused {
- DispatchQueue.main.async {
- if !field.isFocused {
- field.becomeFirstResponder()
- }
- }
- }
+// This block causes delays in closing keyboard when navigating from chat view to chat list.
+// It is also a candidate for iOS 26.1 freeze.
+// This was added in commit below to open keyboard programmatically via a passed binding but this approach is not reliable.
+// https://github.com/simplex-chat/simplex-chat/pull/6003/commits/cb666de51375623451a5e80dcf59449adc7d2a5f
+// if focused && !field.isFocused {
+// DispatchQueue.main.async {
+// if !field.isFocused {
+// field.becomeFirstResponder()
+// }
+// }
+// }
}
}
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
index 872e65c7a3..96b5e2898a 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
@@ -46,7 +46,6 @@ struct GroupChatInfoView: View {
case unblockMemberAlert(mem: GroupMember)
case blockForAllAlert(mem: GroupMember)
case unblockForAllAlert(mem: GroupMember)
- case removeMemberAlert(mem: GroupMember)
case error(title: LocalizedStringKey, error: LocalizedStringKey?)
var id: String {
@@ -60,7 +59,6 @@ struct GroupChatInfoView: View {
case let .unblockMemberAlert(mem): return "unblockMemberAlert \(mem.groupMemberId)"
case let .blockForAllAlert(mem): return "blockForAllAlert \(mem.groupMemberId)"
case let .unblockForAllAlert(mem): return "unblockForAllAlert \(mem.groupMemberId)"
- case let .removeMemberAlert(mem): return "removeMemberAlert \(mem.groupMemberId)"
case let .error(title, _): return "error \(title)"
}
}
@@ -98,7 +96,7 @@ struct GroupChatInfoView: View {
memberSupportButton()
}
if groupInfo.canModerate {
- GroupReportsChatNavLink(chat: chat, scrollToItemId: $scrollToItemId)
+ GroupReportsChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
}
if groupInfo.membership.memberActive
&& (groupInfo.membership.memberRole < .moderator || groupInfo.membership.supportChat != nil) {
@@ -212,7 +210,6 @@ struct GroupChatInfoView: View {
case let .unblockMemberAlert(mem): return unblockMemberAlert(groupInfo, mem)
case let .blockForAllAlert(mem): return blockForAllAlert(groupInfo, mem)
case let .unblockForAllAlert(mem): return unblockForAllAlert(groupInfo, mem)
- case let .removeMemberAlert(mem): return removeMemberAlert(mem)
case let .error(title, error): return mkAlert(title: title, message: error)
}
}
@@ -517,7 +514,7 @@ struct GroupChatInfoView: View {
private func removeSwipe(_ member: GroupMember, _ v: V) -> some View {
v.swipeActions(edge: .trailing) {
Button(role: .destructive) {
- alert = .removeMemberAlert(mem: member)
+ showRemoveMemberAlert(groupInfo, member)
} label: {
Label("Remove member", systemImage: "trash")
.foregroundColor(Color.red)
@@ -612,15 +609,19 @@ struct GroupChatInfoView: View {
}
struct GroupReportsChatNavLink: View {
- @EnvironmentObject var chatModel: ChatModel
- @EnvironmentObject var theme: AppTheme
- @State private var navLinkActive = false
@ObservedObject var chat: Chat
+ @EnvironmentObject var theme: AppTheme
+ var groupInfo: GroupInfo
+ @EnvironmentObject var chatModel: ChatModel
@Binding var scrollToItemId: ChatItem.ID?
+ @State private var navLinkActive = false
var body: some View {
NavigationLink(isActive: $navLinkActive) {
- SecondaryChatView(chat: chat, scrollToItemId: $scrollToItemId)
+ SecondaryChatView(
+ chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: .reports), chatItems: [], chatStats: ChatStats()),
+ scrollToItemId: $scrollToItemId
+ )
} label: {
HStack {
Label {
@@ -787,32 +788,38 @@ struct GroupChatInfoView: View {
alert = .largeGroupReceiptsDisabled
}
}
-
- private func removeMemberAlert(_ mem: GroupMember) -> Alert {
- let messageLabel: LocalizedStringKey = (
- groupInfo.businessChat == nil
- ? "Member will be removed from group - this cannot be undone!"
- : "Member will be removed from chat - this cannot be undone!"
- )
- return Alert(
- title: Text("Remove member?"),
- message: Text(messageLabel),
- primaryButton: .destructive(Text("Remove")) {
- removeMember(groupInfo, mem)
- },
- secondaryButton: .cancel()
- )
- }
}
-func removeMember(_ groupInfo: GroupInfo, _ mem: GroupMember, dismiss: DismissAction? = nil) {
+func showRemoveMemberAlert(_ groupInfo: GroupInfo, _ mem: GroupMember, dismiss: DismissAction? = nil) {
+ showAlert(
+ NSLocalizedString("Remove member?", comment: "alert title"),
+ message:
+ groupInfo.businessChat == nil
+ ? NSLocalizedString("Member will be removed from group - this cannot be undone!", comment: "alert message")
+ : NSLocalizedString("Member will be removed from chat - this cannot be undone!", comment: "alert message"),
+ actions: {[
+ UIAlertAction(title: NSLocalizedString("Remove", comment: "alert action"), style: .destructive) { _ in
+ removeMember(groupInfo, mem, withMessages: false, dismiss: dismiss)
+ },
+ UIAlertAction(title: NSLocalizedString("Remove and delete messages", comment: "alert action"), style: .destructive) { _ in
+ removeMember(groupInfo, mem, withMessages: true, dismiss: dismiss)
+ },
+ cancelAlertAction
+ ]}
+ )
+}
+
+func removeMember(_ groupInfo: GroupInfo, _ mem: GroupMember, withMessages: Bool, dismiss: DismissAction?) {
Task {
do {
- let (updatedGroupInfo, updatedMembers) = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId])
+ let (updatedGroupInfo, updatedMembers) = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId], withMessages)
await MainActor.run {
ChatModel.shared.updateGroup(updatedGroupInfo)
updatedMembers.forEach { updatedMember in
_ = ChatModel.shared.upsertGroupMember(updatedGroupInfo, updatedMember)
+ if withMessages {
+ ChatModel.shared.removeMemberItems(updatedMember, byMember: groupInfo.membership, groupInfo)
+ }
}
dismiss?()
}
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
index 2057b9b43c..207c2170a3 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
@@ -18,6 +18,7 @@ struct GroupMemberInfoView: View {
@ObservedObject var groupMember: GMember
@Binding var scrollToItemId: ChatItem.ID?
var navigation: Bool = false
+ var openedFromSupportChat: Bool = false
@State private var connectionStats: ConnectionStats? = nil
@State private var connectionCode: String? = nil
@State private var connectionLoaded: Bool = false
@@ -35,7 +36,6 @@ struct GroupMemberInfoView: View {
case unblockMemberAlert(mem: GroupMember)
case blockForAllAlert(mem: GroupMember)
case unblockForAllAlert(mem: GroupMember)
- case removeMemberAlert(mem: GroupMember)
case changeMemberRoleAlert(mem: GroupMember, role: GroupMemberRole)
case switchAddressAlert
case abortSwitchAddressAlert
@@ -50,7 +50,6 @@ struct GroupMemberInfoView: View {
case let .unblockMemberAlert(mem): return "unblockMemberAlert \(mem.groupMemberId)"
case let .blockForAllAlert(mem): return "blockForAllAlert \(mem.groupMemberId)"
case let .unblockForAllAlert(mem): return "unblockForAllAlert \(mem.groupMemberId)"
- case let .removeMemberAlert(mem): return "removeMemberAlert \(mem.groupMemberId)"
case let .changeMemberRoleAlert(mem, role): return "changeMemberRoleAlert \(mem.groupMemberId) \(role.rawValue)"
case .switchAddressAlert: return "switchAddressAlert"
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
@@ -101,7 +100,8 @@ struct GroupMemberInfoView: View {
if member.memberActive {
Section {
- if groupInfo.membership.memberRole >= .moderator
+ if !openedFromSupportChat
+ && groupInfo.membership.memberRole >= .moderator
&& (member.memberRole < .moderator || member.supportChat != nil) {
MemberInfoSupportChatNavLink(groupInfo: groupInfo, member: groupMember, scrollToItemId: $scrollToItemId)
}
@@ -158,7 +158,15 @@ struct GroupMemberInfoView: View {
if let connStats = connectionStats {
Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) {
- // TODO network connection status
+ if let subStatus = connStats.subStatus {
+ SubStatusRow(status: subStatus)
+ .onTapGesture {
+ showAlert(
+ NSLocalizedString("Network status", comment: "alert title"),
+ message: subStatus.statusExplanation
+ )
+ }
+ }
Button("Change receiving address") {
alert = .switchAddressAlert
}
@@ -263,7 +271,6 @@ struct GroupMemberInfoView: View {
case let .unblockMemberAlert(mem): return unblockMemberAlert(groupInfo, mem)
case let .blockForAllAlert(mem): return blockForAllAlert(groupInfo, mem)
case let .unblockForAllAlert(mem): return unblockForAllAlert(groupInfo, mem)
- case let .removeMemberAlert(mem): return removeMemberAlert(mem)
case let .changeMemberRoleAlert(mem, _): return changeMemberRoleAlert(mem)
case .switchAddressAlert: return switchAddressAlert(switchMemberAddress)
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchMemberAddress)
@@ -394,7 +401,6 @@ struct GroupMemberInfoView: View {
ItemsModel.shared.loadOpenChat(memberContact.id) {
dismissAllSheets(animated: true)
}
- NetworkModel.shared.setContactNetworkStatus(memberContact, .connected)
}
} catch let error {
logger.error("createMemberContactButton apiCreateMemberContact error: \(responseError(error))")
@@ -570,7 +576,11 @@ struct GroupMemberInfoView: View {
}
}
if canRemove {
- removeMemberButton(mem)
+ if mem.memberStatus == .memRemoved || mem.memberStatus == .memLeft {
+ deleteMemberMessagesButton(mem)
+ } else {
+ removeMemberButton(mem)
+ }
}
}
}
@@ -625,41 +635,32 @@ struct GroupMemberInfoView: View {
private func removeMemberButton(_ mem: GroupMember) -> some View {
Button(role: .destructive) {
- alert = .removeMemberAlert(mem: mem)
+ showRemoveMemberAlert(groupInfo, mem, dismiss: dismiss)
} label: {
Label("Remove member", systemImage: "trash")
.foregroundColor(.red)
}
}
- private func removeMemberAlert(_ mem: GroupMember) -> Alert {
- let label: LocalizedStringKey = (
- groupInfo.businessChat == nil
- ? "Member will be removed from group - this cannot be undone!"
- : "Member will be removed from chat - this cannot be undone!"
- )
- return Alert(
- title: Text("Remove member?"),
- message: Text(label),
- primaryButton: .destructive(Text("Remove")) {
- Task {
- do {
- let (updatedGroupInfo, updatedMembers) = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId])
- await MainActor.run {
- chatModel.updateGroup(updatedGroupInfo)
- updatedMembers.forEach { updatedMember in
- _ = chatModel.upsertGroupMember(updatedGroupInfo, updatedMember)
- }
- dismiss()
- }
- } catch let error {
- logger.error("apiRemoveMembers error: \(responseError(error))")
- let a = getErrorAlert(error, "Error removing member")
- alert = .error(title: a.title, error: a.message)
- }
- }
- },
- secondaryButton: .cancel()
+ private func deleteMemberMessagesButton(_ mem: GroupMember) -> some View {
+ Button(role: .destructive) {
+ showDeleteMemberMessagesAlert(mem)
+ } label: {
+ Label("Delete member messages", systemImage: "trash")
+ .foregroundColor(.red)
+ }
+ }
+
+ func showDeleteMemberMessagesAlert(_ mem: GroupMember) {
+ showAlert(
+ NSLocalizedString("Delete member messages?", comment: "alert title"),
+ message: NSLocalizedString("Member messages will be deleted - this cannot be undone!", comment: "alert message"),
+ actions: {[
+ UIAlertAction(title: NSLocalizedString("Delete messages", comment: "alert action"), style: .destructive) { _ in
+ removeMember(groupInfo, mem, withMessages: true, dismiss: dismiss)
+ },
+ cancelAlertAction
+ ]}
)
}
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift
index 440ed5227d..cdbed7fe30 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift
@@ -107,6 +107,8 @@ struct GroupMentionsView: View {
} else {
return member.memberRole >= .moderator
}
+ case .reports:
+ return false
}
case .msgContentTagContext:
return false
diff --git a/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift b/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift
index 7f3672ea17..75a6840c4e 100644
--- a/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift
@@ -92,6 +92,16 @@ struct MemberSupportView: View {
.frame(width: 1, height: 1)
.hidden()
}
+ .if(!memberWithChat.wrapped.memberPending && memberWithChat.wrapped.supportChatNotRead) { v in
+ v.swipeActions(edge: .leading, allowsFullSwipe: true) {
+ Button {
+ Task { await markSupportChatRead(groupInfo, memberWithChat.wrapped) }
+ } label: {
+ Label("Read", systemImage: "checkmark")
+ }
+ .tint(theme.colors.primary)
+ }
+ }
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
if memberWithChat.wrapped.memberPending {
Button {
@@ -271,7 +281,7 @@ func deleteMemberSupportChat(_ groupInfo: GroupInfo, _ member: GroupMember) {
logger.error("apiDeleteMemberSupportChat error: \(responseError(error))")
await MainActor.run {
showAlert(
- NSLocalizedString("Error deleting chat with member", comment: "alert title"),
+ NSLocalizedString("Error deleting chat", comment: "alert title"),
message: responseError(error)
)
}
diff --git a/apps/ios/Shared/Views/Chat/Group/SecondaryChatView.swift b/apps/ios/Shared/Views/Chat/Group/SecondaryChatView.swift
index 47c5df264f..e2092f7a24 100644
--- a/apps/ios/Shared/Views/Chat/Group/SecondaryChatView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/SecondaryChatView.swift
@@ -10,6 +10,7 @@ import SwiftUI
import SimpleXChat
struct SecondaryChatView: View {
+ @Environment(\.dismiss) var dismiss
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var chat: Chat
@Binding var scrollToItemId: ChatItem.ID?
@@ -23,9 +24,10 @@ struct SecondaryChatView: View {
floatingButtonModel: FloatingButtonModel(im: im),
scrollToItemId: $scrollToItemId
)
- .onDisappear {
+ .modifier(BackButton(disabled: Binding.constant(false)) {
chatModel.secondaryIM = nil
- }
+ dismiss()
+ })
}
}
}
diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
index c56d947a5a..be2c456802 100644
--- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
@@ -460,12 +460,6 @@ struct ChatPreviewView: View {
@ViewBuilder private func chatStatusImage() -> some View {
let size = dynamicSize(userFont).incognitoSize
switch chat.chatInfo {
- case let .direct(contact):
- if contact.active, let status = contact.activeConn?.connStatus, status == .ready || status == .sndReady {
- NetworkStatusView(contact: contact, size: size)
- } else {
- incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
- }
case .group:
if progressByTimeout {
ProgressView()
@@ -482,30 +476,6 @@ struct ChatPreviewView: View {
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
}
}
-
- struct NetworkStatusView: View {
- @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
- @EnvironmentObject var theme: AppTheme
- @ObservedObject var networkModel = NetworkModel.shared
-
- let contact: Contact
- let size: CGFloat
-
- var body: some View {
- let dynamicChatInfoSize = dynamicSize(userFont).chatInfoSize
- switch (networkModel.contactNetworkStatus(contact)) {
- case .connected: incognitoIcon(contact.contactConnIncognito, theme.colors.secondary, size: size)
- case .error:
- Image(systemName: "exclamationmark.circle")
- .resizable()
- .scaledToFit()
- .frame(width: dynamicChatInfoSize, height: dynamicChatInfoSize)
- .foregroundColor(theme.colors.secondary)
- default:
- ProgressView()
- }
- }
- }
}
@ViewBuilder func incognitoIcon(_ incognito: Bool, _ secondaryColor: Color, size: CGFloat) -> some View {
diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift
index 656cef4a04..33ffa04a50 100644
--- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift
+++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift
@@ -63,8 +63,6 @@ struct OnboardingConditionsView: View {
@State private var notificationsModeNavLinkActive = false
@State private var justOpened = true
- var selectedOperators: [ServerOperator] { serverOperators.filter { selectedOperatorIds.contains($0.operatorId) } }
-
var body: some View {
GeometryReader { g in
let v = ScrollView {
@@ -169,9 +167,7 @@ struct OnboardingConditionsView: View {
Task {
do {
let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId
- let acceptForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted }
- let operatorIds = acceptForOperators.map { $0.operatorId }
- let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: operatorIds)
+ let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: Array(selectedOperatorIds))
await MainActor.run {
ChatModel.shared.conditions = r
}
diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift
index 1e5b4bff16..d40ec116f4 100644
--- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift
+++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift
@@ -196,12 +196,13 @@ struct UserAddressView: View {
progressIndicator = true
Task {
do {
- if let connLinkContact = try await apiCreateUserAddress() {
- DispatchQueue.main.async {
+ let connLinkContact = try await apiCreateUserAddress()
+ DispatchQueue.main.async {
+ if let connLinkContact {
chatModel.userAddress = UserContactLink(connLinkContact)
alert = .shareOnCreate
- progressIndicator = false
}
+ progressIndicator = false
}
} catch let error {
logger.error("UserAddressView apiCreateUserAddress: \(responseError(error))")
diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
index 2331fc9a32..427430b833 100644
--- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
+++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
@@ -2231,8 +2231,8 @@ We will be adding server redundancy to prevent lost messages.
Please store passphrase securely, you will NOT be able to change it if you lose it.
No comment provided by engineer.
-
- Possibly, certificate fingerprint in server address is incorrect
+
+ Fingerprint in server address does not match certificate.
server test error
@@ -2559,8 +2559,8 @@ We will be adding server redundancy to prevent lost messages.
Sent messages will be deleted after set time.
No comment provided by engineer.
-
- Server requires authorization to create queues, check password
+
+ Server requires authorization to create queues, check password.
يتطلب الخادم إذنًا لإنشاء قوائم انتظار، تحقق من كلمة المرور
server test error
@@ -4249,8 +4249,8 @@ SimpleX servers cannot see your profile.
Server type
نوع الخادم
-
- Server requires authorization to upload, check password
+
+ Server requires authorization to upload, check password.
يتطلب الخادم إذنًا للرفع، تحقق من كلمة المرور
diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
index 32d32aeb67..a59179ddfa 100644
--- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
+++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
@@ -362,6 +362,7 @@
**Scan / Paste link**: to connect via a link you received.
+ **Сканирай / Постави линк**: за свързване чрез получения линк.
No comment provided by engineer.
@@ -475,6 +476,7 @@ time interval
1-time link can be used *with one contact only* - share in person or via any messenger.
+ Еднократният линк може да се използва само веднъж *с един контакт* - споделете го лично или чрез някой месинджър.
No comment provided by engineer.
@@ -602,6 +604,7 @@ swipe action
Accept member
+ Приеми член
alert title
@@ -621,6 +624,7 @@ swipe action
Active
+ Активен
token status text
@@ -640,10 +644,12 @@ swipe action
Add list
+ Добави списък
No comment provided by engineer.
Add message
+ Добави съобщение
placeholder for sending contact request
@@ -673,6 +679,7 @@ swipe action
Add to list
+ Добави към списъка
No comment provided by engineer.
@@ -752,6 +759,7 @@ swipe action
All
+ Всички
No comment provided by engineer.
@@ -766,6 +774,7 @@ swipe action
All chats will be removed from the list %@, and the list deleted.
+ Всички чатове ще бъдат премахнати от списъка %@, а списъкът ще бъде изтрит.
alert message
@@ -785,6 +794,7 @@ swipe action
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
+ Всички съобщения и файлове се изпращат с **криптиране от край до край**, с постквантова сигурност в директните съобщения.
No comment provided by engineer.
@@ -809,10 +819,12 @@ swipe action
All reports will be archived for you.
+ Всички доклади за нарушения ще бъдат архивирани за вас.
No comment provided by engineer.
All servers
+ Всички сървъри
No comment provided by engineer.
@@ -857,6 +869,7 @@ swipe action
Allow files and media only if your contact allows them.
+ Разреши файлове и медия само ако вашият контакт ги разрешава.
No comment provided by engineer.
@@ -896,6 +909,7 @@ swipe action
Allow to report messsages to moderators.
+ Позволи докладването на съобщения на модераторите.
No comment provided by engineer.
@@ -945,6 +959,7 @@ swipe action
Allow your contacts to send files and media.
+ Позволи на вашите контактите да изпращат файлове и медия.
No comment provided by engineer.
@@ -984,6 +999,7 @@ swipe action
Another reason
+ Друга причина
report reason
@@ -1013,6 +1029,7 @@ swipe action
App group:
+ Група приложения:
No comment provided by engineer.
@@ -1062,14 +1079,17 @@ swipe action
Archive
+ Архивирай
No comment provided by engineer.
Archive %lld reports?
+ Архивирай %lld доклад(а)?
No comment provided by engineer.
Archive all reports?
+ Архивиране на всички доклади за нарушения?
No comment provided by engineer.
@@ -1084,14 +1104,17 @@ swipe action
Archive report
+ Архивирай доклад за нарушения
No comment provided by engineer.
Archive report?
+ Архивирай доклад за нарушения?
No comment provided by engineer.
Archive reports
+ Архивирай докладите за нарушения
swipe action
@@ -1201,6 +1224,7 @@ swipe action
Better groups performance
+ По-добра производителност на групите
No comment provided by engineer.
@@ -1225,6 +1249,7 @@ swipe action
Better privacy and security
+ По-добра поверителност и сигурност
No comment provided by engineer.
@@ -1239,10 +1264,12 @@ swipe action
Bio
+ Био
No comment provided by engineer.
Bio too large
+ Биографията е твърде дълга
alert title
@@ -1297,6 +1324,7 @@ swipe action
Bot
+ Бот
No comment provided by engineer.
@@ -1321,6 +1349,7 @@ swipe action
Both you and your contact can send files and media.
+ И вие, и вашият контакт можете да изпращате файлове и медия.
No comment provided by engineer.
@@ -1345,10 +1374,12 @@ swipe action
Business connection
+ Бизнес връзка
No comment provided by engineer.
Businesses
+ Бизнеси
No comment provided by engineer.
@@ -1360,6 +1391,9 @@ swipe action
By using SimpleX Chat you agree to:
- send only legal content in public groups.
- respect other users – no spam.
+ С използването на SimpleX Chat вие се съгласявате със:
+- изпращане само на легално съдържание в публични групи.
+- уважение към другите потребители – без спам.
No comment provided by engineer.
@@ -1394,6 +1428,7 @@ swipe action
Can't change profile
+ Промяната на профила е невъзможна
alert title
@@ -1408,6 +1443,7 @@ swipe action
Can't message member
+ Изпращането на съобщения на груповия член не е налично
No comment provided by engineer.
@@ -1429,6 +1465,7 @@ new chat action
Cannot forward message
+ Съобщение не може да бъде препратено
No comment provided by engineer.
@@ -1453,6 +1490,7 @@ new chat action
Change automatic message deletion?
+ Промяна на автоматичното изтриване на съобщения?
alert title
@@ -1508,18 +1546,22 @@ set passcode view
Chat
+ Чат
No comment provided by engineer.
Chat already exists
+ Чатът вече съществува
No comment provided by engineer.
Chat already exists!
+ Чатът вече съществува!
new chat sheet title
Chat colors
+ Цветове на чата
No comment provided by engineer.
@@ -1539,6 +1581,7 @@ set passcode view
Chat database exported
+ Базата данни е експортирана
No comment provided by engineer.
@@ -1563,6 +1606,7 @@ set passcode view
Chat list
+ Списък с чатове
No comment provided by engineer.
@@ -1577,6 +1621,7 @@ set passcode view
Chat preferences were changed.
+ Настройките на чата бяха променени.
alert message
@@ -1586,26 +1631,32 @@ set passcode view
Chat theme
+ Тема на чата
No comment provided by engineer.
Chat will be deleted for all members - this cannot be undone!
+ Чатът ще бъде изтрит за всички членове - това не може да бъде отменено!
No comment provided by engineer.
Chat will be deleted for you - this cannot be undone!
+ Чатът ще бъде изтрит за вас - това не може да бъде отменено!
No comment provided by engineer.
Chat with admins
+ Чат с администраторите
chat toolbar
Chat with member
+ Чат с член
No comment provided by engineer.
Chat with members before they join.
+ Разговаряйте с членовете, преди да се присъединят.
No comment provided by engineer.
@@ -1615,14 +1666,17 @@ set passcode view
Chats with members
+ Чатове с членовете
No comment provided by engineer.
Check messages every 20 min.
+ Проверявай за съобщенията на всеки 20 минути.
No comment provided by engineer.
Check messages when allowed.
+ Проверявай за съобщенията, когато е разрешено.
No comment provided by engineer.
@@ -1652,14 +1706,17 @@ set passcode view
Chunks deleted
+ Изтрити парчета
No comment provided by engineer.
Chunks downloaded
+ Изтеглени парчета
No comment provided by engineer.
Chunks uploaded
+ Качени парчета
No comment provided by engineer.
@@ -1679,10 +1736,12 @@ set passcode view
Clear group?
+ Изчисти група?
No comment provided by engineer.
Clear or delete group?
+ Изчисти или изтрий група?
No comment provided by engineer.
@@ -1697,14 +1756,17 @@ set passcode view
Color chats with the new themes.
+ Цветни чатове с нови теми.
No comment provided by engineer.
Color mode
+ Цветен режим
No comment provided by engineer.
@@ -1719,34 +1781,42 @@ set passcode view
Completed
+ Завършен
No comment provided by engineer.
Conditions accepted on: %@.
+ Условия, приети на: %@.
No comment provided by engineer.
Conditions are accepted for the operator(s): **%@**.
+ Условията са приети за оператора(ите): **%@**.
No comment provided by engineer.
Conditions are already accepted for these operator(s): **%@**.
+ Условията вече са приети за тези оператори: **%@**.
No comment provided by engineer.
Conditions of use
- No comment provided by engineer.
+ Условия за ползване
+ alert button
Conditions will be accepted for the operator(s): **%@**.
+ Условията ще бъдат приети за операторите: **%@**.
No comment provided by engineer.
Conditions will be accepted on: %@.
+ Условията ще бъдат приети на: %@.
No comment provided by engineer.
Conditions will be automatically accepted for enabled operators on: %@.
+ Условията ще бъдат автоматично приети за активираните оператори на: %@.
No comment provided by engineer.
@@ -1756,6 +1826,7 @@ set passcode view
Configure server operators
+ Конфигуриране на сървърни оператори
No comment provided by engineer.
@@ -1770,6 +1841,7 @@ set passcode view
Confirm contact deletion?
+ Потвърди изтриването на контакта?
No comment provided by engineer.
@@ -1779,6 +1851,7 @@ set passcode view
Confirm files from unknown servers.
+ Потвърди файлове от неизвестни сървъри.
No comment provided by engineer.
@@ -1808,6 +1881,7 @@ set passcode view
Confirmed
+ Потвърдено
token status text
@@ -1822,6 +1896,7 @@ set passcode view
Connect faster! 🚀
+ Свържете се по-бързо! 🚀
No comment provided by engineer.
@@ -1831,6 +1906,7 @@ set passcode view
Connect to your friends faster.
+ Свържете се с приятелите си по-бързо.
No comment provided by engineer.
@@ -1869,6 +1945,7 @@ This is your own one-time link!
Connected
+ Свързан
No comment provided by engineer.
@@ -1878,6 +1955,7 @@ This is your own one-time link!
Connected servers
+ Свързани сървъри
No comment provided by engineer.
@@ -1887,6 +1965,7 @@ This is your own one-time link!
Connecting
+ Свързване
No comment provided by engineer.
@@ -1901,6 +1980,7 @@ This is your own one-time link!
Connecting to contact, please wait or check later!
+ Тече свързване с контакт, моля изчакайте или проверете по-късно!
No comment provided by engineer.
@@ -1915,10 +1995,12 @@ This is your own one-time link!
Connection and servers status.
+ Състояние на връзката и сървърите.
No comment provided by engineer.
Connection blocked
+ Връзката е блокирана
No comment provided by engineer.
@@ -1934,14 +2016,18 @@ This is your own one-time link!
Connection is blocked by server operator:
%@
+ Връзката е блокирана от оператора на сървъра:
+%@
No comment provided by engineer.
Connection not ready.
+ Връзката не е готова.
No comment provided by engineer.
Connection notifications
+ Известия за връзката
No comment provided by engineer.
@@ -1951,6 +2037,7 @@ This is your own one-time link!
Connection requires encryption renegotiation.
+ Връзката изисква предоговаряне на криптирането.
No comment provided by engineer.
@@ -3152,6 +3239,10 @@ chat item action
Error connecting to forwarding server %@. Please try later.
alert message
+
+ Error connecting to the server used to receive messages from this connection: %@
+ subscription status explanation
+
Error creating address
Грешка при създаване на адрес
@@ -3195,15 +3286,15 @@ chat item action
Грешка при декриптирането на файла
No comment provided by engineer.
+
+ Error deleting chat
+ alert title
+
Error deleting chat database
Грешка при изтриване на базата данни
alert title
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
Грешка при изтриването на чата!
@@ -3465,6 +3556,10 @@ chat item action
file error text
snd error text
+
+ Error: %@.
+ server test error
+
Error: URL is invalid
Грешка: URL адресът е невалиден
@@ -3671,6 +3766,23 @@ snd error text
Намирайте чатове по-бързо
No comment provided by engineer.
+
+ Fingerprint in destination server address does not match certificate: %@.
+ No comment provided by engineer.
+
+
+ Fingerprint in forwarding server address does not match certificate: %@.
+ No comment provided by engineer.
+
+
+ Fingerprint in server address does not match certificate.
+ Въжможно е пръстовият отпечатък на сертификата в адреса на сървъра да е неправилен
+ server test error
+
+
+ Fingerprint in server address does not match certificate: %@.
+ No comment provided by engineer.
+
Fix
Поправи
@@ -4645,6 +4757,10 @@ This is your link for group %@!
Член
No comment provided by engineer.
+
+ Member %@
+ past/unknown group member
+
Member admission
No comment provided by engineer.
@@ -5022,7 +5138,7 @@ This is your link for group %@!
Network status
Състояние на мрежата
- No comment provided by engineer.
+ alert title
New
@@ -5613,11 +5729,6 @@ Requires compatible VPN.
Парола за показване
No comment provided by engineer.
-
- Past member %@
- Бивш член %@
- past/unknown group member
-
Paste desktop address
Постави адрес на настолно устройство
@@ -5767,11 +5878,6 @@ Error: %@
Port
No comment provided by engineer.
-
- Possibly, certificate fingerprint in server address is incorrect
- Въжможно е пръстовият отпечатък на сертификата в адреса на сървъра да е неправилен
- server test error
-
Preserve the last message draft, with attachments.
Запазете последната чернова на съобщението с прикачени файлове.
@@ -6052,11 +6158,6 @@ Enable in *Network & servers* settings.
Получено в: %@
copied message info
-
- Received file event
- Събитие за получен файл
- notification
-
Received message
Получено съобщение
@@ -6827,11 +6928,6 @@ chat item action
Sent directly
No comment provided by engineer.
-
- Sent file event
- Събитие за изпратен файл
- notification
-
Sent message
Изпратено съобщение
@@ -6890,13 +6986,13 @@ chat item action
Server protocol changed.
alert title
-
- Server requires authorization to create queues, check password
+
+ Server requires authorization to create queues, check password.
Сървърът изисква оторизация за създаване на опашки, проверете паролата
server test error
-
- Server requires authorization to upload, check password
+
+ Server requires authorization to upload, check password.
Сървърът изисква оторизация за качване, проверете паролата
server test error
@@ -7923,15 +8019,9 @@ You will be prompted to complete authentication before this feature is enabled.<
Transport sessions
No comment provided by engineer.
-
- Trying to connect to the server used to receive messages from this contact (error: %@).
- Опит за свързване със сървъра, използван за получаване на съобщения от този контакт (грешка: %@).
- No comment provided by engineer.
-
-
- Trying to connect to the server used to receive messages from this contact.
- Опит за свързване със сървъра, използван за получаване на съобщения от този контакт.
- No comment provided by engineer.
+
+ Trying to connect to the server used to receive messages from this connection.
+ subscription status explanation
Turkish interface
@@ -8602,16 +8692,19 @@ Repeat join request?
Изпрати отново заявката за присъединяване?
new chat sheet title
-
- You are connected to the server used to receive messages from this contact.
- Вие сте свързани към сървъра, използван за получаване на съобщения от този контакт.
- No comment provided by engineer.
+
+ You are connected to the server used to receive messages from this connection.
+ subscription status explanation
You are invited to group
Поканени сте в групата
No comment provided by engineer.
+
+ You are not connected to the server used to receive messages from this connection (no subscription).
+ subscription status explanation
+
You are not connected to these servers. Private routing is used to deliver messages to them.
No comment provided by engineer.
@@ -9627,6 +9720,10 @@ pref value
липсва e2e криптиране
No comment provided by engineer.
+
+ no subscription
+ No comment provided by engineer.
+
no text
няма текст
diff --git a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff
index c6420fb34d..fbda1abd29 100644
--- a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff
+++ b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff
@@ -2597,8 +2597,8 @@
Polish interface
No comment provided by engineer.
-
- Possibly, certificate fingerprint in server address is incorrect
+
+ Fingerprint in server address does not match certificate.
server test error
@@ -3073,12 +3073,12 @@
Sent messages will be deleted after set time.
No comment provided by engineer.
-
- Server requires authorization to create queues, check password
+
+ Server requires authorization to create queues, check password.
server test error
-
- Server requires authorization to upload, check password
+
+ Server requires authorization to upload, check password.
server test error
diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
index 4bfe80e388..ace3079550 100644
--- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
+++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
@@ -410,6 +410,9 @@
- optionally notify deleted contacts.
- profile names with spaces.
- and more!
+ - volitelně informuje smazané kontakty.
+- profilová jména s mezerami.
+- a více!
No comment provided by engineer.
@@ -423,6 +426,7 @@
0 sec
+ 0 sek
time to disappear
@@ -460,6 +464,7 @@ time interval
1 year
+ 1 rok
delete after time
@@ -468,6 +473,7 @@ time interval
1-time link can be used *with one contact only* - share in person or via any messenger.
+ Jednorázový odkaz lze použít *pouze s jedním kontaktem* - sdílejte osobně nebo prostřednictvím libovolného komunikátoru.
No comment provided by engineer.
@@ -541,6 +547,7 @@ time interval
About operators
+ O operátorech
No comment provided by engineer.
@@ -557,14 +564,17 @@ swipe action
Accept as member
+ Přijmout za člena
alert action
Accept as observer
+ Přijmout jako pozorovatele
alert action
Accept conditions
+ Přijmout podmínky
No comment provided by engineer.
@@ -574,6 +584,7 @@ swipe action
Accept contact request
+ Přijmout žádost o kontakt
alert title
@@ -589,10 +600,12 @@ swipe action
Accept member
+ Přijmout člena
alert title
Accepted conditions
+ Přijaté podmínky
No comment provided by engineer.
@@ -605,10 +618,12 @@ swipe action
Active
+ Aktivní
token status text
Active connections
+ Aktivní spojení
No comment provided by engineer.
@@ -618,6 +633,7 @@ swipe action
Add friends
+ Přidat přátele
No comment provided by engineer.
@@ -645,6 +661,7 @@ swipe action
Add team members
+ Přidat členy týmu
No comment provided by engineer.
@@ -654,6 +671,7 @@ swipe action
Add to list
+ Přidat do seznamu
No comment provided by engineer.
@@ -663,6 +681,7 @@ swipe action
Add your team members to the conversations.
+ Přidat členy týmu do konverzace.
No comment provided by engineer.
@@ -671,14 +690,17 @@ swipe action
Added message servers
+ Přidané servery zpráv
No comment provided by engineer.
Additional accent
+ Další zbarvení
No comment provided by engineer.
Additional accent 2
+ Další zbarvení 2
No comment provided by engineer.
@@ -697,14 +719,17 @@ swipe action
Address or 1-time link?
+ Adresa nebo jednorázový odkaz?
No comment provided by engineer.
Address settings
+ Nastavení adresy
No comment provided by engineer.
Admins can block a member for all.
+ Správci mohou blokovat člena pro všechny.
No comment provided by engineer.
@@ -719,10 +744,12 @@ swipe action
Advanced settings
+ Pokročilá nastavení
No comment provided by engineer.
All
+ Vše
No comment provided by engineer.
@@ -737,6 +764,7 @@ swipe action
All chats will be removed from the list %@, and the list deleted.
+ Všechny chaty budou odstraněny ze seznamu %@ a seznam bude odstraněn.
alert message
@@ -746,6 +774,7 @@ swipe action
All data is kept private on your device.
+ Všechna data jsou uchována ve vašem zařízení.
No comment provided by engineer.
@@ -768,10 +797,12 @@ swipe action
All new messages from %@ will be hidden!
+ Všechny nové zprávy od %@ budou skryté!
No comment provided by engineer.
All profiles
+ Všechny profily
profile dropdown
@@ -780,6 +811,7 @@ swipe action
All servers
+ Všechny servery
No comment provided by engineer.
@@ -808,6 +840,7 @@ swipe action
Allow calls?
+ Povolit volání?
No comment provided by engineer.
@@ -850,6 +883,7 @@ swipe action
Allow sharing
+ Povolit sdílení
No comment provided by engineer.
@@ -859,10 +893,12 @@ swipe action
Allow to report messsages to moderators.
+ Povolit nahlášení zpráv moderátorům.
No comment provided by engineer.
Allow to send SimpleX links.
+ Povolit odesílání SimpleX odkazů.
No comment provided by engineer.
@@ -907,6 +943,7 @@ swipe action
Allow your contacts to send files and media.
+ Povolit vašim kontaktům odesílání souborů a médii.
No comment provided by engineer.
@@ -921,14 +958,17 @@ swipe action
Already connecting!
+ Již připojováno!
new chat sheet title
Already joining the group!
+ Již se ke skupině připojujete!
new chat sheet title
Always use private routing.
+ Vždy používat soukromé směrování.
No comment provided by engineer.
@@ -943,6 +983,7 @@ swipe action
Another reason
+ Jiný důvod
report reason
@@ -962,6 +1003,7 @@ swipe action
App data migration
+ Přenos dat aplikace
No comment provided by engineer.
@@ -1009,6 +1051,7 @@ swipe action
Apply
+ Použít
No comment provided by engineer.
@@ -1017,6 +1060,7 @@ swipe action
Archive
+ Archiv
No comment provided by engineer.
@@ -1025,10 +1069,12 @@ swipe action
Archive all reports?
+ Archivovat všechny hlášení?
No comment provided by engineer.
Archive and upload
+ Archivovat a nahrát
No comment provided by engineer.
@@ -1049,6 +1095,7 @@ swipe action
Archived contacts
+ Archivované kontakty
No comment provided by engineer.
@@ -1122,6 +1169,7 @@ swipe action
Background
+ Pozadí
No comment provided by engineer.
@@ -1140,18 +1188,22 @@ swipe action
Better calls
+ Lepší volání
No comment provided by engineer.
Better groups
+ Lepší skupiny
No comment provided by engineer.
Better groups performance
+ Lepší výkon skupin
No comment provided by engineer.
Better message dates.
+ Lepší datumy zpráv.
No comment provided by engineer.
@@ -1161,18 +1213,22 @@ swipe action
Better networking
+ Lepší sítě
No comment provided by engineer.
Better notifications
+ Lepší upozornění
No comment provided by engineer.
Better privacy and security
+ Lepší soukromí a zabezpečení
No comment provided by engineer.
Better security ✅
+ Lepší zabezpečení ✅
No comment provided by engineer.
@@ -1205,6 +1261,7 @@ swipe action
Block member
+ Blokovat člena
No comment provided by engineer.
@@ -1213,10 +1270,12 @@ swipe action
Block member?
+ Blokovat člena?
No comment provided by engineer.
Blocked by admin
+ Blokován správcem
No comment provided by engineer.
@@ -1225,6 +1284,7 @@ swipe action
Blur media
+ Rozmazat média
No comment provided by engineer.
@@ -1253,6 +1313,7 @@ swipe action
Both you and your contact can send files and media.
+ Vy i vaše kontakty můžete posílat soubory a média.
No comment provided by engineer.
@@ -1267,6 +1328,7 @@ swipe action
Business address
+ Obchodní adresa
No comment provided by engineer.
@@ -1304,22 +1366,27 @@ swipe action
Calls prohibited!
+ Volání zakázáno!
No comment provided by engineer.
Camera not available
+ Kamera není k dispozici
No comment provided by engineer.
Can't call contact
+ Kontaktu nelze volat
No comment provided by engineer.
Can't call member
+ Členu nelze volat
No comment provided by engineer.
Can't change profile
+ Nelze změnit profil
alert title
@@ -1334,6 +1401,7 @@ swipe action
Can't message member
+ Členu nelze poslat zprávu
No comment provided by engineer.
@@ -1345,6 +1413,7 @@ new chat action
Cancel migration
+ Zrušit přesun
No comment provided by engineer.
@@ -1354,6 +1423,7 @@ new chat action
Cannot forward message
+ Zprávu nelze přeposlat
No comment provided by engineer.
@@ -1363,10 +1433,12 @@ new chat action
Capacity exceeded - recipient did not receive previously sent messages.
+ Kapacita překročena - příjemce neobdrží dříve poslané zprávy.
snd error text
Cellular
+ Mobilní
No comment provided by engineer.
@@ -1376,10 +1448,12 @@ new chat action
Change automatic message deletion?
+ Změnit automatické mazání zpráv?
alert title
Change chat profiles
+ Změnit chat profily
authentication reason
@@ -1442,6 +1516,7 @@ set passcode view
Chat colors
+ Barvy chatu
No comment provided by engineer.
@@ -1461,6 +1536,7 @@ set passcode view
Chat database exported
+ Chat databáze exportována
No comment provided by engineer.
@@ -1488,6 +1564,7 @@ set passcode view
Chat migrated!
+ Chat přesunut!
No comment provided by engineer.
@@ -1653,7 +1730,7 @@ set passcode view
Conditions of use
- No comment provided by engineer.
+ alert button
Conditions will be accepted for the operator(s): **%@**.
@@ -1719,6 +1796,7 @@ set passcode view
Confirm upload
+ Potvrdit nahrání
No comment provided by engineer.
@@ -1732,6 +1810,7 @@ set passcode view
Connect automatically
+ Připojit automaticky
No comment provided by engineer.
@@ -1754,6 +1833,8 @@ This is your own SimpleX address!
Connect to yourself?
This is your own one-time link!
+ Připojit se k sobě?
+Toto je váš vlastní jednorázový odkaz!
new chat sheet title
@@ -3024,6 +3105,10 @@ chat item action
Error connecting to forwarding server %@. Please try later.
alert message
+
+ Error connecting to the server used to receive messages from this connection: %@
+ subscription status explanation
+
Error creating address
Chyba při vytváření adresy
@@ -3066,15 +3151,15 @@ chat item action
Chyba dešifrování souboru
No comment provided by engineer.
+
+ Error deleting chat
+ alert title
+
Error deleting chat database
Chyba při mazání databáze chatu
alert title
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
Chyba při mazání chatu!
@@ -3330,6 +3415,10 @@ chat item action
file error text
snd error text
+
+ Error: %@.
+ server test error
+
Error: URL is invalid
Chyba: Adresa URL je neplatná
@@ -3530,6 +3619,23 @@ snd error text
Najděte chaty rychleji
No comment provided by engineer.
+
+ Fingerprint in destination server address does not match certificate: %@.
+ No comment provided by engineer.
+
+
+ Fingerprint in forwarding server address does not match certificate: %@.
+ No comment provided by engineer.
+
+
+ Fingerprint in server address does not match certificate.
+ Je možné, že otisk certifikátu v adrese serveru je nesprávný
+ server test error
+
+
+ Fingerprint in server address does not match certificate: %@.
+ No comment provided by engineer.
+
Fix
Opravit
@@ -4472,6 +4578,10 @@ This is your link for group %@!
Člen
No comment provided by engineer.
+
+ Member %@
+ past/unknown group member
+
Member admission
No comment provided by engineer.
@@ -4833,7 +4943,7 @@ This is your link for group %@!
Network status
Stav sítě
- No comment provided by engineer.
+ alert title
New
@@ -5412,10 +5522,6 @@ Vyžaduje povolení sítě VPN.
Heslo k zobrazení
No comment provided by engineer.
-
- Past member %@
- past/unknown group member
-
Paste desktop address
No comment provided by engineer.
@@ -5558,11 +5664,6 @@ Error: %@
Port
No comment provided by engineer.
-
- Possibly, certificate fingerprint in server address is incorrect
- Je možné, že otisk certifikátu v adrese serveru je nesprávný
- server test error
-
Preserve the last message draft, with attachments.
Zachování posledního návrhu zprávy s přílohami.
@@ -5837,11 +5938,6 @@ Enable in *Network & servers* settings.
Přijato v: %@
copied message info
-
- Received file event
- Událost přijatého souboru
- notification
-
Received message
Přijatá zpráva
@@ -6598,11 +6694,6 @@ chat item action
Sent directly
No comment provided by engineer.
-
- Sent file event
- Odeslaná událost souboru
- notification
-
Sent message
Poslaná zpráva
@@ -6661,13 +6752,13 @@ chat item action
Server protocol changed.
alert title
-
- Server requires authorization to create queues, check password
+
+ Server requires authorization to create queues, check password.
Server vyžaduje autorizaci pro vytváření front, zkontrolujte heslo
server test error
-
- Server requires authorization to upload, check password
+
+ Server requires authorization to upload, check password.
Server vyžaduje autorizaci pro nahrávání, zkontrolujte heslo
server test error
@@ -7672,15 +7763,9 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření.
Transport sessions
No comment provided by engineer.
-
- Trying to connect to the server used to receive messages from this contact (error: %@).
- Pokus o připojení k serveru používanému k přijímání zpráv od tohoto kontaktu (chyba: %@).
- No comment provided by engineer.
-
-
- Trying to connect to the server used to receive messages from this contact.
- Pokus o připojení k serveru používanému pro příjem zpráv od tohoto kontaktu.
- No comment provided by engineer.
+
+ Trying to connect to the server used to receive messages from this connection.
+ subscription status explanation
Turkish interface
@@ -8312,16 +8397,19 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
Repeat join request?
new chat sheet title
-
- You are connected to the server used to receive messages from this contact.
- Jste připojeni k serveru, který se používá k přijímání zpráv od tohoto kontaktu.
- No comment provided by engineer.
+
+ You are connected to the server used to receive messages from this connection.
+ subscription status explanation
You are invited to group
Jste pozváni do skupiny
No comment provided by engineer.
+
+ You are not connected to the server used to receive messages from this connection (no subscription).
+ subscription status explanation
+
You are not connected to these servers. Private routing is used to deliver messages to them.
No comment provided by engineer.
@@ -9319,6 +9407,10 @@ pref value
bez šifrování e2e
No comment provided by engineer.
+
+ no subscription
+ No comment provided by engineer.
+
no text
žádný text
diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
index 4db5d3a826..34bbab2a6a 100644
--- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
+++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
@@ -1802,7 +1802,7 @@ set passcode view
Conditions of use
Nutzungsbedingungen
- No comment provided by engineer.
+ alert button
Conditions will be accepted for the operator(s): **%@**.
@@ -2016,7 +2016,7 @@ Das ist Ihr eigener Einmal-Link!
Connection is blocked by server operator:
%@
- Die Verbindung wurde vom Server-Betreiber blockiert:
+ Die Verbindung wurde vom Serverbetreiber blockiert:
%@
No comment provided by engineer.
@@ -3310,6 +3310,11 @@ chat item action
Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es später erneut.
alert message
+
+ Error connecting to the server used to receive messages from this connection: %@
+ Fehler beim Herstellen der Verbindung zum Server, der für den Empfang von Nachrichten dieser Verbindung genutzt wird: %@
+ subscription status explanation
+
Error creating address
Fehler beim Erstellen der Adresse
@@ -3355,16 +3360,16 @@ chat item action
Fehler beim Entschlüsseln der Datei
No comment provided by engineer.
+
+ Error deleting chat
+ Fehler beim Löschen des Chats mit dem Mitglied
+ alert title
+
Error deleting chat database
Fehler beim Löschen der Chat-Datenbank
alert title
-
- Error deleting chat with member
- Fehler beim Löschen des Chats mit dem Mitglied
- alert title
-
Error deleting chat!
Fehler beim Löschen des Chats!
@@ -3642,6 +3647,11 @@ chat item action
file error text
snd error text
+
+ Error: %@.
+ Fehler: %@.
+ server test error
+
Error: URL is invalid
Fehler: URL ist ungültig
@@ -3762,7 +3772,7 @@ snd error text
File is blocked by server operator:
%@.
- Datei wurde vom Server-Betreiber blockiert:
+ Die Datei wurde vom Serverbetreiber blockiert:
%@.
file error text
@@ -3866,6 +3876,26 @@ snd error text
Chats schneller finden
No comment provided by engineer.
+
+ Fingerprint in destination server address does not match certificate: %@.
+ Fingerabdruck in der Zielserveradresse stimmt nicht mit dem Zertifikat überein: %@.
+ No comment provided by engineer.
+
+
+ Fingerprint in forwarding server address does not match certificate: %@.
+ Fingerabdruck in der Weiterleitungsserveradresse stimmt nicht mit dem Zertifikat überein: %@.
+ No comment provided by engineer.
+
+
+ Fingerprint in server address does not match certificate.
+ Fingerabdruck in der Serveradresse stimmt nicht mit dem Zertifikat überein.
+ server test error
+
+
+ Fingerprint in server address does not match certificate: %@.
+ Fingerabdruck in der Serveradresse stimmt nicht mit dem Zertifikat überein: %@.
+ No comment provided by engineer.
+
Fix
Reparieren
@@ -4892,6 +4922,11 @@ Das ist Ihr Link für die Gruppe %@!
Mitglied
No comment provided by engineer.
+
+ Member %@
+ Mitglied %@
+ past/unknown group member
+
Member admission
Aufnahme von Mitgliedern
@@ -5300,7 +5335,7 @@ Das ist Ihr Link für die Gruppe %@!
Network status
Netzwerkstatus
- No comment provided by engineer.
+ alert title
New
@@ -5949,11 +5984,6 @@ Dies erfordert die Aktivierung eines VPNs.
Passwort anzeigen
No comment provided by engineer.
-
- Past member %@
- Ehemaliges Mitglied %@
- past/unknown group member
-
Paste desktop address
Desktop-Adresse einfügen
@@ -6113,11 +6143,6 @@ Fehler: %@
Port
No comment provided by engineer.
-
- Possibly, certificate fingerprint in server address is incorrect
- Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig
- server test error
-
Preserve the last message draft, with attachments.
Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren.
@@ -6420,11 +6445,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.
Empfangen um: %@
copied message info
-
- Received file event
- Datei-Ereignis empfangen
- notification
-
Received message
Empfangene Nachricht
@@ -7040,7 +7060,7 @@ chat item action
Security assessment
- Sicherheits-Gutachten
+ Security-Gutachten
No comment provided by engineer.
@@ -7258,11 +7278,6 @@ chat item action
Direkt gesendet
No comment provided by engineer.
-
- Sent file event
- Datei-Ereignis wurde gesendet
- notification
-
Sent message
Gesendete Nachricht
@@ -7333,14 +7348,14 @@ chat item action
Das Server-Protokoll wurde geändert.
alert title
-
- Server requires authorization to create queues, check password
- Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort
+
+ Server requires authorization to create queues, check password.
+ Der Server erfordert zum Erstellen von Warteschlangen eine Autorisierung. Bitte überprüfen Sie das Passwort.
server test error
-
- Server requires authorization to upload, check password
- Bitte das Passwort überprüfen - für den Upload benötigt der Server eine Berechtigung
+
+ Server requires authorization to upload, check password.
+ Der Server erfordert zum Hochladen eine Autorisierung. Bitte überprüfen Sie das Passwort.
server test error
@@ -8461,15 +8476,10 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt
Transport-Sitzungen
No comment provided by engineer.
-
- Trying to connect to the server used to receive messages from this contact (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: %@).
- No comment provided by engineer.
-
-
- Trying to connect to the server used to receive messages from this contact.
- Versuche die Verbindung mit dem Server aufzunehmen, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird.
- No comment provided by engineer.
+
+ Trying to connect to the server used to receive messages from this connection.
+ Versuche eine Verbindung mit dem Server aufzunehmen, der für den Empfang von Nachrichten dieser Verbindung genutzt wird.
+ subscription status explanation
Turkish interface
@@ -9180,16 +9190,21 @@ Repeat join request?
Verbindungsanfrage wiederholen?
new chat sheet title
-
- You are connected to the server used to receive messages from this contact.
- Sie sind mit dem Server verbunden, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird.
- No comment provided by engineer.
+
+ You are connected to the server used to receive messages from this connection.
+ Sie sind mit dem Server verbunden, der für den Empfang von Nachrichten dieser Verbindung genutzt wird.
+ subscription status explanation
You are invited to group
Sie sind zu der Gruppe eingeladen
No comment provided by engineer.
+
+ You are not connected to the server used to receive messages from this connection (no subscription).
+ Sie sind nicht mit dem Server verbunden, der für den Empfang von Nachrichten dieser Verbindung genutzt wird (kein Abonnement).
+ subscription status explanation
+
You are not connected to these servers. Private routing is used to deliver messages to them.
Sie sind nicht mit diesen Servern verbunden. Zur Auslieferung von Nachrichten an diese Server wird privates Routing genutzt.
@@ -9714,7 +9729,7 @@ Verbindungsanfrage wiederholen?
blocked %@
- %@ wurde blockiert
+ hat %@ blockiert
rcv group event chat item
@@ -10249,6 +10264,11 @@ pref value
Keine E2E-Verschlüsselung
No comment provided by engineer.
+
+ no subscription
+ Kein Abonnement
+ No comment provided by engineer.
+
no text
Kein Text
@@ -10493,7 +10513,7 @@ Zuletzt empfangene Nachricht: %2$@
unblocked %@
- %@ wurde freigegeben
+ hat %@ freigegeben
rcv group event chat item
@@ -10727,7 +10747,7 @@ Zuletzt empfangene Nachricht: %2$@
Copyright © 2022 SimpleX Chat. All rights reserved.
- Copyright © 2024 SimpleX Chat. All rights reserved.
+ Copyright © 2025 SimpleX Chat. All rights reserved.
Copyright (human-readable)