Compare commits
200 Commits
ep/rfc-use
...
av/android
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4947cf2c0c | ||
|
|
18041ae471 | ||
|
|
de3fdde2f6 | ||
|
|
0cf2af916b | ||
|
|
2ab938db60 | ||
|
|
9543af4784 | ||
|
|
38dc14f041 | ||
|
|
2e205b055d | ||
|
|
5353b466a9 | ||
|
|
b28a51106f | ||
|
|
b095c09283 | ||
|
|
1a567c88db | ||
|
|
d80ee14f77 | ||
|
|
b374b5b753 | ||
|
|
fde3c4f4e0 | ||
|
|
f17889b3e3 | ||
|
|
34c5658560 | ||
|
|
53662ef077 | ||
|
|
5a5876c258 | ||
|
|
4826a62d36 | ||
|
|
8cd362eed8 | ||
|
|
b7ac1b1b55 | ||
|
|
5952fd5290 | ||
|
|
353fe4539c | ||
|
|
b003d659e4 | ||
|
|
8f72328136 | ||
|
|
6d113ae2e2 | ||
|
|
f264470e14 | ||
|
|
65391756ef | ||
|
|
4e27a4ea4f | ||
|
|
b23b109b00 | ||
|
|
8bf830ced9 | ||
|
|
80a77a1104 | ||
|
|
760282cdfd | ||
|
|
497275646d | ||
|
|
105a6afb4b | ||
|
|
061125ab63 | ||
|
|
2f10633d1d | ||
|
|
e2d5ad0e48 | ||
|
|
e34a8ef719 | ||
|
|
e30f7695ab | ||
|
|
2bb2042d7d | ||
|
|
0a6133fe5b | ||
|
|
a4a6e2418a | ||
|
|
2b69103055 | ||
|
|
7344398826 | ||
|
|
7f662ec7cc | ||
|
|
298dd9744f | ||
|
|
6268f0a32b | ||
|
|
f0d64a30e9 | ||
|
|
d08df4cfbf | ||
|
|
8e84b9e85f | ||
|
|
c935e8aff3 | ||
|
|
a0a567f5f7 | ||
|
|
ddd97baf5a | ||
|
|
98e68c8e74 | ||
|
|
03edde18eb | ||
|
|
920b56e3d8 | ||
|
|
dd51f032d2 | ||
|
|
1bdbea4f6d | ||
|
|
90be54ff82 | ||
|
|
bd4b445cbf | ||
|
|
8e7e5209d3 | ||
|
|
af98e703ec | ||
|
|
fff8935b94 | ||
|
|
9e7a45c734 | ||
|
|
af33f4e2d9 | ||
|
|
98e53fb35b | ||
|
|
cb4aa29549 | ||
|
|
631dfff5e9 | ||
|
|
f69c842ba6 | ||
|
|
18c802159b | ||
|
|
45e557fd80 | ||
|
|
d50562cfee | ||
|
|
15d3d3b11a | ||
|
|
2b715a0d8c | ||
|
|
02d00944ff | ||
|
|
71d6410604 | ||
|
|
141611293f | ||
|
|
c9400fe932 | ||
|
|
445a8e75fe | ||
|
|
8fc3f5a0f7 | ||
|
|
9d30a3495e | ||
|
|
d77980e50e | ||
|
|
bb02f07370 | ||
|
|
a3cd7ca89e | ||
|
|
976fc68cc3 | ||
|
|
7c7e931aa9 | ||
|
|
e8e619effa | ||
|
|
bd0139eaab | ||
|
|
e9f77e1064 | ||
|
|
677b75f368 | ||
|
|
92d13591f3 | ||
|
|
6be8476f90 | ||
|
|
ae9b83515c | ||
|
|
26a233ab1a | ||
|
|
c7783a7039 | ||
|
|
80bd734cc1 | ||
|
|
0c34a545fa | ||
|
|
65c6c63024 | ||
|
|
f43fd57ec1 | ||
|
|
065b932e1f | ||
|
|
7ebb763889 | ||
|
|
eacfc4aa8c | ||
|
|
9c49b038cd | ||
|
|
1d4afe591e | ||
|
|
10ec3dd8b6 | ||
|
|
a715e847ad | ||
|
|
9ac0f30c5a | ||
|
|
b033fdbeee | ||
|
|
7996194f92 | ||
|
|
c2054b5ccf | ||
|
|
53dbe4b5d8 | ||
|
|
417eca74ad | ||
|
|
d25ef4e1a1 | ||
|
|
5d775a63c6 | ||
|
|
576c886ba0 | ||
|
|
511e3586d9 | ||
|
|
1cb500bc16 | ||
|
|
f5825d20e4 | ||
|
|
7a166e46a9 | ||
|
|
77d249cc37 | ||
|
|
3f905f59df | ||
|
|
87c35b037e | ||
|
|
d63c7d2abc | ||
|
|
ca5b3ddc0d | ||
|
|
4e2acbf456 | ||
|
|
202ecc369a | ||
|
|
e5cec7a68b | ||
|
|
05b292ac00 | ||
|
|
562bd197bb | ||
|
|
94321cfc36 | ||
|
|
7b863ef459 | ||
|
|
1aedfd6e5a | ||
|
|
572e3b7d32 | ||
|
|
ab708f8855 | ||
|
|
f5612504f5 | ||
|
|
94e25d9bb4 | ||
|
|
369d411fc1 | ||
|
|
94312ec6fa | ||
|
|
4b652b62da | ||
|
|
bf4df9ca58 | ||
|
|
27f4661ac4 | ||
|
|
2389e870b3 | ||
|
|
d61ff0f2a7 | ||
|
|
61334d7b77 | ||
|
|
9a714a0926 | ||
|
|
7ddd300fe5 | ||
|
|
6b663baf10 | ||
|
|
048ada79bb | ||
|
|
b69f422708 | ||
|
|
396abdbfab | ||
|
|
938bd56c3a | ||
|
|
d3b5bbe566 | ||
|
|
1bd8f66730 | ||
|
|
c2177f3684 | ||
|
|
72c0c61a86 | ||
|
|
f594752bb1 | ||
|
|
4a3c9366fd | ||
|
|
f5d61e7838 | ||
|
|
e762923410 | ||
|
|
d87b86199c | ||
|
|
a7a66c2b55 | ||
|
|
6ca76ec8a9 | ||
|
|
b089836efc | ||
|
|
90b616cd28 | ||
|
|
f970ef264a | ||
|
|
3793cd138e | ||
|
|
8dd90733b8 | ||
|
|
0f4473d272 | ||
|
|
0bdd96ae8a | ||
|
|
43ceb184c4 | ||
|
|
dd62b1cccb | ||
|
|
2e5a0fca1a | ||
|
|
38f40fec3d | ||
|
|
34c2303ef1 | ||
|
|
ced69e431c | ||
|
|
dcedbac379 | ||
|
|
a6a87cb7de | ||
|
|
416ae400eb | ||
|
|
b69916a3a3 | ||
|
|
62726e345c | ||
|
|
7a8db16791 | ||
|
|
ff7c22e114 | ||
|
|
e24564d7d6 | ||
|
|
ae17566a94 | ||
|
|
c329bf4ea1 | ||
|
|
7fea9c85bd | ||
|
|
313d3a732d | ||
|
|
5d9b6266ea | ||
|
|
c35ce29cc1 | ||
|
|
842bbf26c6 | ||
|
|
ebc5242932 | ||
|
|
be5e0d7f75 | ||
|
|
324a6ba38e | ||
|
|
7b67bc2d47 | ||
|
|
2f7ea909e2 | ||
|
|
9238ac3445 | ||
|
|
3bd5fc7463 | ||
|
|
30d4fc757c |
79
.github/workflows/build.yml
vendored
79
.github/workflows/build.yml
vendored
@@ -52,15 +52,19 @@ jobs:
|
||||
- os: ubuntu-20.04
|
||||
cache_path: ~/.cabal/store
|
||||
asset_name: simplex-chat-ubuntu-20_04-x86-64
|
||||
desktop_asset_name: simplex-desktop-ubuntu-20_04-x86_64.deb
|
||||
- os: ubuntu-22.04
|
||||
cache_path: ~/.cabal/store
|
||||
asset_name: simplex-chat-ubuntu-22_04-x86-64
|
||||
desktop_asset_name: simplex-desktop-ubuntu-22_04-x86_64.deb
|
||||
- os: macos-latest
|
||||
cache_path: ~/.cabal/store
|
||||
asset_name: simplex-chat-macos-x86-64
|
||||
desktop_asset_name: simplex-desktop-macos-x86_64.dmg
|
||||
- os: windows-latest
|
||||
cache_path: C:/cabal
|
||||
asset_name: simplex-chat-windows-x86-64
|
||||
desktop_asset_name: simplex-desktop-windows-x86_64.msi
|
||||
steps:
|
||||
- name: Configure pagefile (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
@@ -99,6 +103,10 @@ jobs:
|
||||
echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local
|
||||
echo " flags: +openssl" >> cabal.project.local
|
||||
|
||||
- name: Install AppImage dependencies
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
|
||||
run: sudo apt install -y desktop-file-utils
|
||||
|
||||
- name: Install pkg-config for Mac
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: brew install pkg-config
|
||||
@@ -111,23 +119,86 @@ jobs:
|
||||
echo "package direct-sqlcipher" >> cabal.project.local
|
||||
echo " flags: +openssl" >> cabal.project.local
|
||||
|
||||
- name: Unix build
|
||||
id: unix_build
|
||||
- name: Unix build CLI
|
||||
id: unix_cli_build
|
||||
if: matrix.os != 'windows-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
cabal build --enable-tests
|
||||
echo "::set-output name=bin_path::$(cabal list-bin simplex-chat)"
|
||||
|
||||
- name: Unix upload binary to release
|
||||
- name: Unix upload CLI binary to release
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ steps.unix_build.outputs.bin_path }}
|
||||
file: ${{ steps.unix_cli_build.outputs.bin_path }}
|
||||
asset_name: ${{ matrix.asset_name }}
|
||||
tag: ${{ github.ref }}
|
||||
|
||||
- name: Setup Java
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'corretto'
|
||||
java-version: '17'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Linux build desktop
|
||||
id: linux_desktop_build
|
||||
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
|
||||
shell: bash
|
||||
run: |
|
||||
scripts/desktop/build-lib-linux.sh
|
||||
cd apps/multiplatform
|
||||
./gradlew packageDeb
|
||||
echo "::set-output name=package_path::$(echo $PWD/release/main/deb/simplex_*_amd64.deb)"
|
||||
|
||||
- name: Linux make AppImage
|
||||
id: linux_appimage_build
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
|
||||
shell: bash
|
||||
run: |
|
||||
scripts/desktop/make-appimage-linux.sh
|
||||
echo "::set-output name=appimage_path::$(echo $PWD/apps/multiplatform/release/main/*imple*.AppImage)"
|
||||
|
||||
- name: Mac build desktop
|
||||
id: mac_desktop_build
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
scripts/desktop/build-lib-mac.sh
|
||||
cd apps/multiplatform
|
||||
./gradlew packageDmg
|
||||
echo "::set-output name=package_path::$(echo $PWD/release/main/dmg/SimpleX-*.dmg)"
|
||||
|
||||
- name: Linux upload desktop package to release
|
||||
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ steps.linux_desktop_build.outputs.package_path }}
|
||||
asset_name: ${{ matrix.desktop_asset_name }}
|
||||
tag: ${{ github.ref }}
|
||||
|
||||
- name: Linux upload AppImage to release
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ steps.linux_appimage_build.outputs.appimage_path }}
|
||||
asset_name: simplex-desktop-x86_64.AppImage
|
||||
tag: ${{ github.ref }}
|
||||
|
||||
- name: Mac upload desktop package to release
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest'
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ steps.mac_desktop_build.outputs.package_path }}
|
||||
asset_name: ${{ matrix.desktop_asset_name }}
|
||||
tag: ${{ github.ref }}
|
||||
|
||||
- name: Unix test
|
||||
if: matrix.os != 'windows-latest'
|
||||
timeout-minutes: 30
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -53,6 +53,7 @@ website/src/docs/
|
||||
website/translations.json
|
||||
website/src/img/images/
|
||||
website/src/images/
|
||||
website/src/js/lottie.min.js
|
||||
# Generated files
|
||||
website/package/generated*
|
||||
|
||||
|
||||
@@ -207,6 +207,8 @@ You can use SimpleX with your own servers and still communicate with people usin
|
||||
|
||||
Recent updates:
|
||||
|
||||
[July 22, 2023. SimpleX Chat: v5.2 released with message delivery receipts](./blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md).
|
||||
|
||||
[May 23, 2023. SimpleX Chat: v5.1 released with message reactions and self-destruct passcode](./blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md).
|
||||
|
||||
[Apr 22, 2023. SimpleX Chat: vision and funding, v5.0 released with videos and files up to 1gb](./blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md).
|
||||
@@ -337,8 +339,8 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A
|
||||
- ✅ Message reactions
|
||||
- ✅ Message editing history
|
||||
- ✅ Reduced battery and traffic usage in large groups.
|
||||
- ✅ Message delivery confirmation (with sender opt-out per contact).
|
||||
- 🏗 Desktop client.
|
||||
- 🏗 Message delivery confirmation (with sender opt-in or opt-out per contact, TBC).
|
||||
- SMP queue redundancy and rotation (manual is supported).
|
||||
- Include optional message into connection request sent via contact address.
|
||||
- Local app files encryption.
|
||||
|
||||
@@ -14,9 +14,28 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
logger.debug("AppDelegate: didFinishLaunchingWithOptions")
|
||||
application.registerForRemoteNotifications()
|
||||
if #available(iOS 17.0, *) { trackKeyboard() }
|
||||
return true
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
private func trackKeyboard() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
@objc func keyboardWillShow(_ notification: Notification) {
|
||||
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
|
||||
ChatModel.shared.keyboardHeight = keyboardFrame.cgRectValue.height
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
@objc func keyboardWillHide(_ notification: Notification) {
|
||||
ChatModel.shared.keyboardHeight = 0
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
let token = deviceToken.map { String(format: "%02hhx", $0) }.joined()
|
||||
logger.debug("AppDelegate: didRegisterForRemoteNotificationsWithDeviceToken \(token)")
|
||||
@@ -42,7 +61,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
m.notificationMode != .off {
|
||||
if let verification = ntfData["verification"] as? String,
|
||||
let nonce = ntfData["nonce"] as? String {
|
||||
if let token = ChatModel.shared.deviceToken {
|
||||
if let token = m.deviceToken {
|
||||
logger.debug("AppDelegate: didReceiveRemoteNotification: verification, confirming \(verification)")
|
||||
Task {
|
||||
do {
|
||||
@@ -62,7 +81,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
}
|
||||
} else if let checkMessages = ntfData["checkMessages"] as? Bool, checkMessages {
|
||||
logger.debug("AppDelegate: didReceiveRemoteNotification: checkMessages")
|
||||
if appStateGroupDefault.get().inactive {
|
||||
if appStateGroupDefault.get().inactive && m.ntfEnablePeriodic {
|
||||
receiveMessages(completionHandler)
|
||||
} else {
|
||||
completionHandler(.noData)
|
||||
@@ -76,7 +95,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
logger.debug("AppDelegate: applicationWillTerminate")
|
||||
logger.debug("DEBUGGING: AppDelegate: applicationWillTerminate")
|
||||
ChatModel.shared.filesToDelete.forEach {
|
||||
removeFile($0)
|
||||
}
|
||||
|
||||
@@ -28,6 +28,17 @@ struct ContentView: View {
|
||||
@State private var showWhatsNew = false
|
||||
@State private var showChooseLAMode = false
|
||||
@State private var showSetPasscode = false
|
||||
@State private var chatListActionSheet: ChatListActionSheet? = nil
|
||||
|
||||
private enum ChatListActionSheet: Identifiable {
|
||||
case connectViaUrl(action: ConnReqType, link: String)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .connectViaUrl: return "connectViaUrl \(link)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -80,6 +91,11 @@ struct ContentView: View {
|
||||
if case .onboardingComplete = step,
|
||||
chatModel.currentUser != nil {
|
||||
mainView()
|
||||
.actionSheet(item: $chatListActionSheet) { sheet in
|
||||
switch sheet {
|
||||
case let .connectViaUrl(action, link): return connectViaUrlSheet(action, link)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
OnboardingView(onboarding: step)
|
||||
}
|
||||
@@ -132,10 +148,15 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
prefShowLANotice = true
|
||||
connectViaUrl()
|
||||
}
|
||||
.onChange(of: chatModel.appOpenUrl) { _ in connectViaUrl() }
|
||||
.sheet(isPresented: $showWhatsNew) {
|
||||
WhatsNewView()
|
||||
}
|
||||
if chatModel.setDeliveryReceipts {
|
||||
SetDeliveryReceiptsView()
|
||||
}
|
||||
IncomingCallView()
|
||||
}
|
||||
.onContinueUserActivity("INStartCallIntent", perform: processUserActivity)
|
||||
@@ -176,10 +197,13 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
private func runAuthenticate() {
|
||||
logger.debug("DEBUGGING: runAuthenticate")
|
||||
if !prefPerformLA {
|
||||
userAuthorized = true
|
||||
} else {
|
||||
logger.debug("DEBUGGING: before dismissAllSheets")
|
||||
dismissAllSheets(animated: false) {
|
||||
logger.debug("DEBUGGING: in dismissAllSheets callback")
|
||||
chatModel.chatId = nil
|
||||
justAuthenticate()
|
||||
}
|
||||
@@ -190,7 +214,7 @@ struct ContentView: View {
|
||||
userAuthorized = false
|
||||
let laMode = privacyLocalAuthModeDefault.get()
|
||||
authenticate(reason: NSLocalizedString("Unlock app", comment: "authentication reason"), selfDestruct: true) { laResult in
|
||||
logger.debug("authenticate callback: \(String(describing: laResult))")
|
||||
logger.debug("DEBUGGING: authenticate callback: \(String(describing: laResult))")
|
||||
switch (laResult) {
|
||||
case .success:
|
||||
userAuthorized = true
|
||||
@@ -259,36 +283,38 @@ struct ContentView: View {
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func connectViaUrl() {
|
||||
let m = ChatModel.shared
|
||||
if let url = m.appOpenUrl {
|
||||
m.appOpenUrl = nil
|
||||
AlertManager.shared.showAlert(connectViaUrlAlert(url))
|
||||
func connectViaUrl() {
|
||||
let m = ChatModel.shared
|
||||
if let url = m.appOpenUrl {
|
||||
m.appOpenUrl = nil
|
||||
var path = url.path
|
||||
logger.debug("ContentView.connectViaUrl path: \(path)")
|
||||
if (path == "/contact" || path == "/invitation") {
|
||||
path.removeFirst()
|
||||
let action: ConnReqType = path == "contact" ? .contact : .invitation
|
||||
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
|
||||
chatListActionSheet = .connectViaUrl(action: action, link: link)
|
||||
} else {
|
||||
AlertManager.shared.showAlert(Alert(title: Text("Error: URL is invalid")))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func connectViaUrlAlert(_ url: URL) -> Alert {
|
||||
var path = url.path
|
||||
logger.debug("ChatListView.connectViaUrlAlert path: \(path)")
|
||||
if (path == "/contact" || path == "/invitation") {
|
||||
path.removeFirst()
|
||||
let action: ConnReqType = path == "contact" ? .contact : .invitation
|
||||
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
|
||||
private func connectViaUrlSheet(_ action: ConnReqType, _ link: String) -> ActionSheet {
|
||||
let title: LocalizedStringKey
|
||||
if case .contact = action { title = "Connect via contact link?" }
|
||||
else { title = "Connect via one-time link?" }
|
||||
return Alert(
|
||||
switch action {
|
||||
case .contact: title = "Connect via contact link"
|
||||
case .invitation: title = "Connect via one-time link"
|
||||
}
|
||||
return ActionSheet(
|
||||
title: Text(title),
|
||||
message: Text("Your profile will be sent to the contact that you received this link from"),
|
||||
primaryButton: .default(Text("Connect")) {
|
||||
connectViaLink(link)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
buttons: [
|
||||
.default(Text("Use current profile")) { connectViaLink(link, incognito: false) },
|
||||
.default(Text("Use new incognito profile")) { connectViaLink(link, incognito: true) },
|
||||
.cancel()
|
||||
]
|
||||
)
|
||||
} else {
|
||||
return Alert(title: Text("Error: URL is invalid"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ class BGManager {
|
||||
}
|
||||
|
||||
func schedule() {
|
||||
if !ChatModel.shared.ntfEnableLocal {
|
||||
logger.debug("BGManager.schedule: disabled")
|
||||
return
|
||||
}
|
||||
logger.debug("BGManager.schedule")
|
||||
let request = BGAppRefreshTaskRequest(identifier: receiveTaskId)
|
||||
request.earliestBeginDate = Date(timeIntervalSinceNow: bgRefreshInterval)
|
||||
@@ -45,6 +49,10 @@ class BGManager {
|
||||
}
|
||||
|
||||
private func handleRefresh(_ task: BGAppRefreshTask) {
|
||||
if !ChatModel.shared.ntfEnableLocal {
|
||||
logger.debug("BGManager.handleRefresh: disabled")
|
||||
return
|
||||
}
|
||||
logger.debug("BGManager.handleRefresh")
|
||||
schedule()
|
||||
if appStateGroupDefault.get().inactive {
|
||||
|
||||
@@ -13,6 +13,7 @@ import SimpleXChat
|
||||
|
||||
final class ChatModel: ObservableObject {
|
||||
@Published var onboardingStage: OnboardingStage?
|
||||
@Published var setDeliveryReceipts = false
|
||||
@Published var v3DBMigration: V3DBMigrationState = v3DBMigrationDefault.get()
|
||||
@Published var currentUser: User?
|
||||
@Published var users: [UserInfo] = []
|
||||
@@ -41,10 +42,9 @@ final class ChatModel: ObservableObject {
|
||||
@Published var tokenRegistered = false
|
||||
@Published var tokenStatus: NtfTknStatus?
|
||||
@Published var notificationMode = NotificationsMode.off
|
||||
@Published var notificationPreview: NotificationPreviewMode? = ntfPreviewModeGroupDefault.get()
|
||||
@Published var incognito: Bool = incognitoGroupDefault.get()
|
||||
@Published var notificationPreview: NotificationPreviewMode = ntfPreviewModeGroupDefault.get()
|
||||
// pending notification actions
|
||||
@Published var ntfContactRequest: ChatId?
|
||||
@Published var ntfContactRequest: NTFContactRequest?
|
||||
@Published var ntfCallInvitationAction: (ChatId, NtfCallAction)?
|
||||
// current WebRTC call
|
||||
@Published var callInvitations: Dictionary<ChatId, RcvCallInvitation> = [:]
|
||||
@@ -57,6 +57,8 @@ final class ChatModel: ObservableObject {
|
||||
@Published var stopPreviousRecPlay: URL? = nil // coordinates currently playing source
|
||||
@Published var draft: ComposeState?
|
||||
@Published var draftChatId: String?
|
||||
// tracks keyboard height via subscription in AppDelegate
|
||||
@Published var keyboardHeight: CGFloat = 0
|
||||
|
||||
var messageDelivery: Dictionary<Int64, () -> Void> = [:]
|
||||
|
||||
@@ -66,6 +68,14 @@ final class ChatModel: ObservableObject {
|
||||
|
||||
static var ok: Bool { ChatModel.shared.chatDbStatus == .ok }
|
||||
|
||||
var ntfEnableLocal: Bool {
|
||||
notificationMode == .off || ntfEnableLocalGroupDefault.get()
|
||||
}
|
||||
|
||||
var ntfEnablePeriodic: Bool {
|
||||
notificationMode == .periodic || ntfEnablePeriodicGroupDefault.get()
|
||||
}
|
||||
|
||||
func getUser(_ userId: Int64) -> User? {
|
||||
currentUser?.userId == userId
|
||||
? currentUser
|
||||
@@ -133,6 +143,14 @@ final class ChatModel: ObservableObject {
|
||||
updateChat(.direct(contact: contact), addMissing: contact.directOrUsed)
|
||||
}
|
||||
|
||||
func updateContactConnectionStats(_ contact: Contact, _ connectionStats: ConnectionStats) {
|
||||
var updatedConn = contact.activeConn
|
||||
updatedConn.connectionStats = connectionStats
|
||||
var updatedContact = contact
|
||||
updatedContact.activeConn = updatedConn
|
||||
updateContact(updatedContact)
|
||||
}
|
||||
|
||||
func updateGroup(_ groupInfo: GroupInfo) {
|
||||
updateChat(.group(groupInfo: groupInfo))
|
||||
}
|
||||
@@ -521,6 +539,16 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func updateGroupMemberConnectionStats(_ groupInfo: GroupInfo, _ member: GroupMember, _ connectionStats: ConnectionStats) {
|
||||
if let conn = member.activeConn {
|
||||
var updatedConn = conn
|
||||
updatedConn.connectionStats = connectionStats
|
||||
var updatedMember = member
|
||||
updatedMember.activeConn = updatedConn
|
||||
_ = upsertGroupMember(groupInfo, updatedMember)
|
||||
}
|
||||
}
|
||||
|
||||
func unreadChatItemCounts(itemsInView: Set<String>) -> UnreadChatItemCounts {
|
||||
var i = 0
|
||||
var totalBelow = 0
|
||||
@@ -560,6 +588,11 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
struct NTFContactRequest {
|
||||
var incognito: Bool
|
||||
var chatId: String
|
||||
}
|
||||
|
||||
struct UnreadChatItemCounts {
|
||||
var totalBelow: Int
|
||||
var unreadBelow: Int
|
||||
|
||||
@@ -12,6 +12,7 @@ import UIKit
|
||||
import SimpleXChat
|
||||
|
||||
let ntfActionAcceptContact = "NTF_ACT_ACCEPT_CONTACT"
|
||||
let ntfActionAcceptContactIncognito = "NTF_ACT_ACCEPT_CONTACT_INCOGNITO"
|
||||
let ntfActionAcceptCall = "NTF_ACT_ACCEPT_CALL"
|
||||
let ntfActionRejectCall = "NTF_ACT_REJECT_CALL"
|
||||
|
||||
@@ -41,12 +42,13 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
userId != chatModel.currentUser?.userId {
|
||||
changeActiveUser(userId, viewPwd: nil)
|
||||
}
|
||||
if content.categoryIdentifier == ntfCategoryContactRequest && action == ntfActionAcceptContact,
|
||||
if content.categoryIdentifier == ntfCategoryContactRequest && (action == ntfActionAcceptContact || action == ntfActionAcceptContactIncognito),
|
||||
let chatId = content.userInfo["chatId"] as? String {
|
||||
let incognito = action == ntfActionAcceptContactIncognito
|
||||
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
|
||||
Task { await acceptContactRequest(contactRequest) }
|
||||
Task { await acceptContactRequest(incognito: incognito, contactRequest: contactRequest) }
|
||||
} else {
|
||||
chatModel.ntfContactRequest = chatId
|
||||
chatModel.ntfContactRequest = NTFContactRequest(incognito: incognito, chatId: chatId)
|
||||
}
|
||||
} else if let (chatId, ntfAction) = ntfCallAction(content, action) {
|
||||
if let invitation = chatModel.callInvitations.removeValue(forKey: chatId) {
|
||||
@@ -134,11 +136,17 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
UNUserNotificationCenter.current().setNotificationCategories([
|
||||
UNNotificationCategory(
|
||||
identifier: ntfCategoryContactRequest,
|
||||
actions: [UNNotificationAction(
|
||||
identifier: ntfActionAcceptContact,
|
||||
title: NSLocalizedString("Accept", comment: "accept contact request via notification"),
|
||||
options: .foreground
|
||||
)],
|
||||
actions: [
|
||||
UNNotificationAction(
|
||||
identifier: ntfActionAcceptContact,
|
||||
title: NSLocalizedString("Accept", comment: "accept contact request via notification"),
|
||||
options: .foreground
|
||||
), UNNotificationAction(
|
||||
identifier: ntfActionAcceptContactIncognito,
|
||||
title: NSLocalizedString("Accept incognito", comment: "accept contact request via notification"),
|
||||
options: .foreground
|
||||
)
|
||||
],
|
||||
intentIdentifiers: [],
|
||||
hiddenPreviewsBodyPlaceholder: NSLocalizedString("New contact request", comment: "notification")
|
||||
),
|
||||
|
||||
@@ -159,6 +159,24 @@ func apiSetActiveUserAsync(_ userId: Int64, viewPwd: String?) async throws -> Us
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetAllContactReceipts(enable: Bool) async throws {
|
||||
let r = await chatSendCmd(.setAllContactReceipts(enable: enable))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetUserContactReceipts(_ userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) async throws {
|
||||
let r = await chatSendCmd(.apiSetUserContactReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetUserGroupReceipts(_ userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) async throws {
|
||||
let r = await chatSendCmd(.apiSetUserGroupReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiHideUser(_ userId: Int64, viewPwd: String) async throws -> User {
|
||||
try await setUserPrivacy_(.apiHideUser(userId: userId, viewPwd: viewPwd))
|
||||
}
|
||||
@@ -234,12 +252,6 @@ func setXFTPConfig(_ cfg: XFTPFileConfig?) throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetIncognito(incognito: Bool) throws {
|
||||
let r = chatSendCmdSync(.setIncognito(incognito: incognito))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiExportArchive(config: ArchiveConfig) async throws {
|
||||
try await sendCommandOkResp(.apiExportArchive(config: config))
|
||||
}
|
||||
@@ -464,6 +476,10 @@ func setNetworkConfig(_ cfg: NetCfg) throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func reconnectAllServers() async throws {
|
||||
try await sendCommandOkResp(.reconnectAllServers)
|
||||
}
|
||||
|
||||
func apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings) async throws {
|
||||
try await sendCommandOkResp(.apiSetChatSettings(type: type, id: id, chatSettings: chatSettings))
|
||||
}
|
||||
@@ -474,9 +490,9 @@ func apiContactInfo(_ contactId: Int64) async throws -> (ConnectionStats?, Profi
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) throws -> (ConnectionStats?) {
|
||||
func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) throws -> (GroupMember, ConnectionStats?) {
|
||||
let r = chatSendCmdSync(.apiGroupMemberInfo(groupId: groupId, groupMemberId: groupMemberId))
|
||||
if case let .groupMemberInfo(_, _, _, connStats_) = r { return (connStats_) }
|
||||
if case let .groupMemberInfo(_, _, member, connStats_) = r { return (member, connStats_) }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -504,6 +520,18 @@ func apiAbortSwitchGroupMember(_ groupId: Int64, _ groupMemberId: Int64) throws
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSyncContactRatchet(_ contactId: Int64, _ force: Bool) throws -> ConnectionStats {
|
||||
let r = chatSendCmdSync(.apiSyncContactRatchet(contactId: contactId, force: force))
|
||||
if case let .contactRatchetSyncStarted(_, _, connectionStats) = r { return connectionStats }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSyncGroupMemberRatchet(_ groupId: Int64, _ groupMemberId: Int64, _ force: Bool) throws -> (GroupMember, ConnectionStats) {
|
||||
let r = chatSendCmdSync(.apiSyncGroupMemberRatchet(groupId: groupId, groupMemberId: groupMemberId, force: force))
|
||||
if case let .groupMemberRatchetSyncStarted(_, _, member, connectionStats) = r { return (member, connectionStats) }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGetContactCode(_ contactId: Int64) async throws -> (Contact, String) {
|
||||
let r = await chatSendCmd(.apiGetContactCode(contactId: contactId))
|
||||
if case let .contactCode(_, contact, connectionCode) = r { return (contact, connectionCode) }
|
||||
@@ -530,19 +558,25 @@ func apiVerifyGroupMember(_ groupId: Int64, _ groupMemberId: Int64, connectionCo
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiAddContact() async -> String? {
|
||||
func apiAddContact(incognito: Bool) async -> (String, PendingContactConnection)? {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else {
|
||||
logger.error("apiAddContact: no current user")
|
||||
return nil
|
||||
}
|
||||
let r = await chatSendCmd(.apiAddContact(userId: userId), bgTask: false)
|
||||
if case let .invitation(_, connReqInvitation) = r { return connReqInvitation }
|
||||
let r = await chatSendCmd(.apiAddContact(userId: userId, incognito: incognito), bgTask: false)
|
||||
if case let .invitation(_, connReqInvitation, connection) = r { return (connReqInvitation, connection) }
|
||||
AlertManager.shared.showAlert(connectionErrorAlert(r))
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiConnect(connReq: String) async -> ConnReqType? {
|
||||
let (connReqType, alert) = await apiConnect_(connReq: connReq)
|
||||
func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> PendingContactConnection? {
|
||||
let r = await chatSendCmd(.apiSetConnectionIncognito(connId: connId, incognito: incognito))
|
||||
if case let .connectionIncognitoUpdated(_, toConnection) = r { return toConnection }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiConnect(incognito: Bool, connReq: String) async -> ConnReqType? {
|
||||
let (connReqType, alert) = await apiConnect_(incognito: incognito, connReq: connReq)
|
||||
if let alert = alert {
|
||||
AlertManager.shared.showAlert(alert)
|
||||
return nil
|
||||
@@ -551,12 +585,12 @@ func apiConnect(connReq: String) async -> ConnReqType? {
|
||||
}
|
||||
}
|
||||
|
||||
func apiConnect_(connReq: String) async -> (ConnReqType?, Alert?) {
|
||||
func apiConnect_(incognito: Bool, connReq: String) async -> (ConnReqType?, Alert?) {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else {
|
||||
logger.error("apiConnect: no current user")
|
||||
return (nil, nil)
|
||||
}
|
||||
let r = await chatSendCmd(.apiConnect(userId: userId, connReq: connReq))
|
||||
let r = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connReq: connReq))
|
||||
switch r {
|
||||
case .sentConfirmation: return (.invitation, nil)
|
||||
case .sentInvitation: return (.contact, nil)
|
||||
@@ -732,8 +766,8 @@ func userAddressAutoAccept(_ autoAccept: AutoAccept?) async throws -> UserContac
|
||||
}
|
||||
}
|
||||
|
||||
func apiAcceptContactRequest(contactReqId: Int64) async -> Contact? {
|
||||
let r = await chatSendCmd(.apiAcceptContact(contactReqId: contactReqId))
|
||||
func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Contact? {
|
||||
let r = await chatSendCmd(.apiAcceptContact(incognito: incognito, contactReqId: contactReqId))
|
||||
let am = AlertManager.shared
|
||||
|
||||
if case let .acceptingContactRequest(_, contact) = r { return contact }
|
||||
@@ -768,29 +802,35 @@ func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws {
|
||||
try await sendCommandOkResp(.apiChatUnread(type: type, id: id, unreadChat: unreadChat))
|
||||
}
|
||||
|
||||
func receiveFile(user: User, fileId: Int64) async {
|
||||
if let chatItem = await apiReceiveFile(fileId: fileId) {
|
||||
func receiveFile(user: User, fileId: Int64, auto: Bool = false) async {
|
||||
if let chatItem = await apiReceiveFile(fileId: fileId, auto: auto) {
|
||||
DispatchQueue.main.async { chatItemSimpleUpdate(user, chatItem) }
|
||||
}
|
||||
}
|
||||
|
||||
func apiReceiveFile(fileId: Int64, inline: Bool? = nil) async -> AChatItem? {
|
||||
func apiReceiveFile(fileId: Int64, inline: Bool? = nil, auto: Bool = false) async -> AChatItem? {
|
||||
let r = await chatSendCmd(.receiveFile(fileId: fileId, inline: inline))
|
||||
let am = AlertManager.shared
|
||||
if case let .rcvFileAccepted(_, chatItem) = r { return chatItem }
|
||||
if case .rcvFileAcceptedSndCancelled = r {
|
||||
am.showAlertMsg(
|
||||
title: "Cannot receive file",
|
||||
message: "Sender cancelled file transfer."
|
||||
)
|
||||
logger.debug("apiReceiveFile error: sender cancelled file transfer")
|
||||
if !auto {
|
||||
am.showAlertMsg(
|
||||
title: "Cannot receive file",
|
||||
message: "Sender cancelled file transfer."
|
||||
)
|
||||
}
|
||||
} else if let networkErrorAlert = networkErrorAlert(r) {
|
||||
logger.error("apiReceiveFile network error: \(String(describing: r))")
|
||||
am.showAlert(networkErrorAlert)
|
||||
} else {
|
||||
logger.error("apiReceiveFile error: \(String(describing: r))")
|
||||
switch r {
|
||||
case .chatCmdError(_, .error(.fileAlreadyReceiving)):
|
||||
switch chatError(r) {
|
||||
case .fileCancelled:
|
||||
logger.debug("apiReceiveFile ignoring fileCancelled error")
|
||||
case .fileAlreadyReceiving:
|
||||
logger.debug("apiReceiveFile ignoring fileAlreadyReceiving error")
|
||||
default:
|
||||
logger.error("apiReceiveFile error: \(String(describing: r))")
|
||||
am.showAlertMsg(
|
||||
title: "Error receiving file",
|
||||
message: "Error: \(String(describing: r))"
|
||||
@@ -835,8 +875,8 @@ func networkErrorAlert(_ r: ChatResponse) -> Alert? {
|
||||
}
|
||||
}
|
||||
|
||||
func acceptContactRequest(_ contactRequest: UserContactRequest) async {
|
||||
if let contact = await apiAcceptContactRequest(contactReqId: contactRequest.apiId) {
|
||||
func acceptContactRequest(incognito: Bool, contactRequest: UserContactRequest) async {
|
||||
if let contact = await apiAcceptContactRequest(incognito: incognito, contactReqId: contactRequest.apiId) {
|
||||
let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: [])
|
||||
DispatchQueue.main.async { ChatModel.shared.replaceChat(contactRequest.id, chat) }
|
||||
}
|
||||
@@ -1070,11 +1110,11 @@ func initializeChat(start: Bool, dbKey: String? = nil, refreshInvitations: Bool
|
||||
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
|
||||
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
|
||||
try setXFTPConfig(getXFTPCfg())
|
||||
try apiSetIncognito(incognito: incognitoGroupDefault.get())
|
||||
m.chatInitialized = true
|
||||
m.currentUser = try apiGetActiveUser()
|
||||
if m.currentUser == nil {
|
||||
onboardingStageDefault.set(.step1_SimpleXInfo)
|
||||
privacyDeliveryReceiptsSet.set(true)
|
||||
m.onboardingStage = .step1_SimpleXInfo
|
||||
} else if start {
|
||||
try startChat(refreshInvitations: refreshInvitations)
|
||||
@@ -1104,6 +1144,9 @@ func startChat(refreshInvitations: Bool = true) throws {
|
||||
m.onboardingStage = [.step1_SimpleXInfo, .step2_CreateProfile].contains(savedOnboardingStage) && m.users.count == 1
|
||||
? .step3_CreateSimpleXAddress
|
||||
: savedOnboardingStage
|
||||
if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() {
|
||||
m.setDeliveryReceipts = true
|
||||
}
|
||||
}
|
||||
}
|
||||
ChatReceiver.shared.start()
|
||||
@@ -1285,7 +1328,7 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
}
|
||||
if let file = cItem.autoReceiveFile() {
|
||||
Task {
|
||||
await receiveFile(user: user, fileId: file.fileId)
|
||||
await receiveFile(user: user, fileId: file.fileId, auto: true)
|
||||
}
|
||||
}
|
||||
if cItem.showNotification {
|
||||
@@ -1294,8 +1337,11 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
case let .chatItemStatusUpdated(user, aChatItem):
|
||||
let cInfo = aChatItem.chatInfo
|
||||
let cItem = aChatItem.chatItem
|
||||
if !cItem.isDeletedContent && (!active(user) || m.upsertChatItem(cInfo, cItem)) {
|
||||
NtfManager.shared.notifyMessageReceived(user, cInfo, cItem)
|
||||
if !cItem.isDeletedContent {
|
||||
let added = active(user) ? m.upsertChatItem(cInfo, cItem) : true
|
||||
if added && cItem.showNotification {
|
||||
NtfManager.shared.notifyMessageReceived(user, cInfo, cItem)
|
||||
}
|
||||
}
|
||||
if let endTask = m.messageDelivery[cItem.id] {
|
||||
switch cItem.meta.itemStatus {
|
||||
@@ -1449,6 +1495,14 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
}
|
||||
case .chatSuspended:
|
||||
chatSuspended()
|
||||
case let .contactSwitch(_, contact, switchProgress):
|
||||
m.updateContactConnectionStats(contact, switchProgress.connectionStats)
|
||||
case let .groupMemberSwitch(_, groupInfo, member, switchProgress):
|
||||
m.updateGroupMemberConnectionStats(groupInfo, member, switchProgress.connectionStats)
|
||||
case let .contactRatchetSync(_, contact, ratchetSyncProgress):
|
||||
m.updateContactConnectionStats(contact, ratchetSyncProgress.connectionStats)
|
||||
case let .groupMemberRatchetSync(_, groupInfo, member, ratchetSyncProgress):
|
||||
m.updateGroupMemberConnectionStats(groupInfo, member, ratchetSyncProgress.connectionStats)
|
||||
default:
|
||||
logger.debug("unsupported event: \(res.responseType)")
|
||||
}
|
||||
|
||||
@@ -76,9 +76,11 @@ private func _chatSuspended() {
|
||||
}
|
||||
|
||||
func activateChat(appState: AppState = .active) {
|
||||
logger.debug("DEBUGGING: activateChat")
|
||||
suspendLockQueue.sync {
|
||||
appStateGroupDefault.set(appState)
|
||||
if ChatModel.ok { apiActivateChat() }
|
||||
logger.debug("DEBUGGING: activateChat: after apiActivateChat")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +97,14 @@ func initChatAndMigrate(refreshInvitations: Bool = true) {
|
||||
}
|
||||
|
||||
func startChatAndActivate() {
|
||||
logger.debug("DEBUGGING: startChatAndActivate")
|
||||
if ChatModel.shared.chatRunning == true {
|
||||
ChatReceiver.shared.start()
|
||||
logger.debug("DEBUGGING: startChatAndActivate: after ChatReceiver.shared.start")
|
||||
}
|
||||
if .active != appStateGroupDefault.get() {
|
||||
logger.debug("DEBUGGING: startChatAndActivate: before activateChat")
|
||||
activateChat()
|
||||
logger.debug("DEBUGGING: startChatAndActivate: after activateChat")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,10 +139,10 @@ struct SimpleXApp: App {
|
||||
let chat = chatModel.getChat(id) {
|
||||
loadChat(chat: chat)
|
||||
}
|
||||
if let chatId = chatModel.ntfContactRequest {
|
||||
if let ncr = chatModel.ntfContactRequest {
|
||||
chatModel.ntfContactRequest = nil
|
||||
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
|
||||
Task { await acceptContactRequest(contactRequest) }
|
||||
if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo {
|
||||
Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) }
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
|
||||
@@ -57,6 +57,37 @@ private func serverHost(_ s: String) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
enum SendReceipts: Identifiable, Hashable {
|
||||
case yes
|
||||
case no
|
||||
case userDefault(Bool)
|
||||
|
||||
var id: Self { self }
|
||||
|
||||
var text: LocalizedStringKey {
|
||||
switch self {
|
||||
case .yes: return "yes"
|
||||
case .no: return "no"
|
||||
case let .userDefault(on): return on ? "default (yes)" : "default (no)"
|
||||
}
|
||||
}
|
||||
|
||||
func bool() -> Bool? {
|
||||
switch self {
|
||||
case .yes: return true
|
||||
case .no: return false
|
||||
case .userDefault: return nil
|
||||
}
|
||||
}
|
||||
|
||||
static func fromBool(_ enable: Bool?, userDefault def: Bool) -> SendReceipts {
|
||||
if let enable = enable {
|
||||
return enable ? .yes : .no
|
||||
}
|
||||
return .userDefault(def)
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatInfoView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@@ -68,6 +99,8 @@ struct ChatInfoView: View {
|
||||
@Binding var connectionCode: String?
|
||||
@FocusState private var aliasTextFieldFocused: Bool
|
||||
@State private var alert: ChatInfoViewAlert? = nil
|
||||
@State private var sendReceipts = SendReceipts.userDefault(true)
|
||||
@State private var sendReceiptsUserDefault = true
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
|
||||
enum ChatInfoViewAlert: Identifiable {
|
||||
@@ -76,6 +109,7 @@ struct ChatInfoView: View {
|
||||
case networkStatusAlert
|
||||
case switchAddressAlert
|
||||
case abortSwitchAddressAlert
|
||||
case syncConnectionForceAlert
|
||||
case error(title: LocalizedStringKey, error: LocalizedStringKey = "")
|
||||
|
||||
var id: String {
|
||||
@@ -85,6 +119,7 @@ struct ChatInfoView: View {
|
||||
case .networkStatusAlert: return "networkStatusAlert"
|
||||
case .switchAddressAlert: return "switchAddressAlert"
|
||||
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
|
||||
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
|
||||
case let .error(title, _): return "error \(title)"
|
||||
}
|
||||
}
|
||||
@@ -108,13 +143,26 @@ struct ChatInfoView: View {
|
||||
|
||||
if let customUserProfile = customUserProfile {
|
||||
Section("Incognito") {
|
||||
infoRow("Your random profile", customUserProfile.chatViewName)
|
||||
HStack {
|
||||
Text("Your random profile")
|
||||
Spacer()
|
||||
Text(customUserProfile.chatViewName)
|
||||
.foregroundStyle(.indigo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
if let code = connectionCode { verifyCodeButton(code) }
|
||||
contactPreferencesButton()
|
||||
sendReceiptsOption()
|
||||
if let connStats = connectionStats,
|
||||
connStats.ratchetSyncAllowed {
|
||||
synchronizeConnectionButton()
|
||||
}
|
||||
// } else if developerTools {
|
||||
// synchronizeConnectionButtonForce()
|
||||
// }
|
||||
}
|
||||
|
||||
if let contactLink = contact.contactLink {
|
||||
@@ -141,12 +189,18 @@ struct ChatInfoView: View {
|
||||
Button("Change receiving address") {
|
||||
alert = .switchAddressAlert
|
||||
}
|
||||
.disabled(connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil })
|
||||
if connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } {
|
||||
.disabled(
|
||||
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil }
|
||||
|| connStats.ratchetSyncSendProhibited
|
||||
)
|
||||
if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) {
|
||||
Button("Abort changing address") {
|
||||
alert = .abortSwitchAddressAlert
|
||||
}
|
||||
.disabled(connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch })
|
||||
.disabled(
|
||||
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch }
|
||||
|| connStats.ratchetSyncSendProhibited
|
||||
)
|
||||
}
|
||||
smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer })
|
||||
smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer })
|
||||
@@ -168,6 +222,12 @@ struct ChatInfoView: View {
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
.onAppear {
|
||||
if let currentUser = chatModel.currentUser {
|
||||
sendReceiptsUserDefault = currentUser.sendRcptsContacts
|
||||
}
|
||||
sendReceipts = SendReceipts.fromBool(contact.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault)
|
||||
}
|
||||
.alert(item: $alert) { alertItem in
|
||||
switch(alertItem) {
|
||||
case .deleteContactAlert: return deleteContactAlert()
|
||||
@@ -175,6 +235,7 @@ struct ChatInfoView: View {
|
||||
case .networkStatusAlert: return networkStatusAlert()
|
||||
case .switchAddressAlert: return switchAddressAlert(switchContactAddress)
|
||||
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress)
|
||||
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncContactConnection(force: true) })
|
||||
case let .error(title, error): return mkAlert(title: title, message: error)
|
||||
}
|
||||
}
|
||||
@@ -187,20 +248,30 @@ struct ChatInfoView: View {
|
||||
.frame(width: 192, height: 192)
|
||||
.padding(.top, 12)
|
||||
.padding()
|
||||
HStack {
|
||||
if contact.verified {
|
||||
Image(systemName: "checkmark.shield")
|
||||
if contact.verified {
|
||||
(
|
||||
Text(Image(systemName: "checkmark.shield"))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.font(.title2)
|
||||
+ Text(" ")
|
||||
+ Text(contact.profile.displayName)
|
||||
.font(.largeTitle)
|
||||
)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(2)
|
||||
.padding(.bottom, 2)
|
||||
} else {
|
||||
Text(contact.profile.displayName)
|
||||
.font(.largeTitle)
|
||||
.lineLimit(1)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(2)
|
||||
.padding(.bottom, 2)
|
||||
}
|
||||
if cInfo.fullName != "" && cInfo.fullName != cInfo.displayName && cInfo.fullName != contact.profile.displayName {
|
||||
Text(cInfo.fullName)
|
||||
.font(.title2)
|
||||
.lineLimit(2)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(4)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
@@ -280,6 +351,44 @@ struct ChatInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func sendReceiptsOption() -> some View {
|
||||
Picker(selection: $sendReceipts) {
|
||||
ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in
|
||||
Text(opt.text)
|
||||
}
|
||||
} label: {
|
||||
Label("Send receipts", systemImage: "checkmark.message")
|
||||
}
|
||||
.frame(height: 36)
|
||||
.onChange(of: sendReceipts) { _ in
|
||||
setSendReceipts()
|
||||
}
|
||||
}
|
||||
|
||||
private func setSendReceipts() {
|
||||
var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults
|
||||
chatSettings.sendRcpts = sendReceipts.bool()
|
||||
updateChatSettings(chat, chatSettings: chatSettings)
|
||||
}
|
||||
|
||||
private func synchronizeConnectionButton() -> some View {
|
||||
Button {
|
||||
syncContactConnection(force: false)
|
||||
} label: {
|
||||
Label("Fix connection", systemImage: "exclamationmark.arrow.triangle.2.circlepath")
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
}
|
||||
|
||||
private func synchronizeConnectionButtonForce() -> some View {
|
||||
Button {
|
||||
alert = .syncConnectionForceAlert
|
||||
} label: {
|
||||
Label("Renegotiate encryption", systemImage: "exclamationmark.triangle")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
private func networkStatusRow() -> some View {
|
||||
HStack {
|
||||
Text("Network status")
|
||||
@@ -370,6 +479,10 @@ struct ChatInfoView: View {
|
||||
do {
|
||||
let stats = try apiSwitchContact(contactId: contact.apiId)
|
||||
connectionStats = stats
|
||||
await MainActor.run {
|
||||
chatModel.updateContactConnectionStats(contact, stats)
|
||||
dismiss()
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("switchContactAddress apiSwitchContact error: \(responseError(error))")
|
||||
let a = getErrorAlert(error, "Error changing address")
|
||||
@@ -385,6 +498,9 @@ struct ChatInfoView: View {
|
||||
do {
|
||||
let stats = try apiAbortSwitchContact(contact.apiId)
|
||||
connectionStats = stats
|
||||
await MainActor.run {
|
||||
chatModel.updateContactConnectionStats(contact, stats)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("abortSwitchContactAddress apiAbortSwitchContact error: \(responseError(error))")
|
||||
let a = getErrorAlert(error, "Error aborting address change")
|
||||
@@ -394,6 +510,25 @@ struct ChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func syncContactConnection(force: Bool) {
|
||||
Task {
|
||||
do {
|
||||
let stats = try apiSyncContactRatchet(contact.apiId, force)
|
||||
connectionStats = stats
|
||||
await MainActor.run {
|
||||
chatModel.updateContactConnectionStats(contact, stats)
|
||||
dismiss()
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("syncContactConnection apiSyncContactRatchet error: \(responseError(error))")
|
||||
let a = getErrorAlert(error, "Error synchronizing connection")
|
||||
await MainActor.run {
|
||||
alert = .error(title: a.title, error: a.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func switchAddressAlert(_ switchAddress: @escaping () -> Void) -> Alert {
|
||||
@@ -414,6 +549,15 @@ func abortSwitchAddressAlert(_ abortSwitchAddress: @escaping () -> Void) -> Aler
|
||||
)
|
||||
}
|
||||
|
||||
func syncConnectionForceAlert(_ syncConnectionForce: @escaping () -> Void) -> Alert {
|
||||
Alert(
|
||||
title: Text("Renegotiate encryption?"),
|
||||
message: Text("The encryption is working and the new encryption agreement is not required. It may result in connection errors!"),
|
||||
primaryButton: .destructive(Text("Renegotiate"), action: syncConnectionForce),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
struct ChatInfoView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ChatInfoView(
|
||||
|
||||
@@ -62,6 +62,7 @@ struct CIFileView: View {
|
||||
case .rcvComplete: return true
|
||||
case .rcvCancelled: return false
|
||||
case .rcvError: return false
|
||||
case .invalid: return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -149,6 +150,7 @@ struct CIFileView: View {
|
||||
case .rcvComplete: fileIcon("doc.fill")
|
||||
case .rcvCancelled: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
|
||||
case .rcvError: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
|
||||
case .invalid: fileIcon("doc.fill", innerIcon: "questionmark", innerIconSize: 10)
|
||||
}
|
||||
} else {
|
||||
fileIcon("doc.fill")
|
||||
@@ -195,7 +197,7 @@ struct CIFileView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let sentFile: ChatItem = ChatItem(
|
||||
chatDir: .directSnd,
|
||||
meta: CIMeta.getSample(1, .now, "", .sndSent, itemEdited: true),
|
||||
meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true),
|
||||
content: .sndMsgContent(msgContent: .file("")),
|
||||
quotedItem: nil,
|
||||
file: CIFile.getSample(fileStatus: .sndComplete)
|
||||
|
||||
@@ -99,6 +99,7 @@ struct CIImageView: View {
|
||||
case .rcvTransfer: progressView()
|
||||
case .rcvCancelled: fileIcon("xmark", 10, 13)
|
||||
case .rcvError: fileIcon("xmark", 10, 13)
|
||||
case .invalid: fileIcon("questionmark", 10, 13)
|
||||
default: EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,47 @@ struct CIMetaView: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var metaColor = Color.secondary
|
||||
var paleMetaColor = Color(UIColor.tertiaryLabel)
|
||||
|
||||
var body: some View {
|
||||
if chatItem.isDeletedContent {
|
||||
chatItem.timestampText.font(.caption).foregroundColor(metaColor)
|
||||
} else {
|
||||
ciMetaText(chatItem.meta, chatTTL: chat.chatInfo.timedMessagesTTL, color: metaColor)
|
||||
let meta = chatItem.meta
|
||||
let ttl = chat.chatInfo.timedMessagesTTL
|
||||
switch meta.itemStatus {
|
||||
case let .sndSent(sndProgress):
|
||||
switch sndProgress {
|
||||
case .complete: ciMetaText(meta, chatTTL: ttl, color: metaColor, sent: .sent)
|
||||
case .partial: ciMetaText(meta, chatTTL: ttl, color: paleMetaColor, sent: .sent)
|
||||
}
|
||||
case let .sndRcvd(_, sndProgress):
|
||||
switch sndProgress {
|
||||
case .complete:
|
||||
ZStack {
|
||||
ciMetaText(meta, chatTTL: ttl, color: metaColor, sent: .rcvd1)
|
||||
ciMetaText(meta, chatTTL: ttl, color: metaColor, sent: .rcvd2)
|
||||
}
|
||||
case .partial:
|
||||
ZStack {
|
||||
ciMetaText(meta, chatTTL: ttl, color: paleMetaColor, sent: .rcvd1)
|
||||
ciMetaText(meta, chatTTL: ttl, color: paleMetaColor, sent: .rcvd2)
|
||||
}
|
||||
}
|
||||
default:
|
||||
ciMetaText(meta, chatTTL: ttl, color: metaColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ciMetaText(_ meta: CIMeta, chatTTL: Int?, color: Color = .clear, transparent: Bool = false) -> Text {
|
||||
enum SentCheckmark {
|
||||
case sent
|
||||
case rcvd1
|
||||
case rcvd2
|
||||
}
|
||||
|
||||
func ciMetaText(_ meta: CIMeta, chatTTL: Int?, color: Color = .clear, transparent: Bool = false, sent: SentCheckmark? = nil) -> Text {
|
||||
var r = Text("")
|
||||
if meta.itemEdited {
|
||||
r = r + statusIconText("pencil", color)
|
||||
@@ -37,7 +67,16 @@ func ciMetaText(_ meta: CIMeta, chatTTL: Int?, color: Color = .clear, transparen
|
||||
r = r + Text(" ")
|
||||
}
|
||||
if let (icon, statusColor) = meta.statusIcon(color) {
|
||||
r = r + statusIconText(icon, transparent ? .clear : statusColor) + Text(" ")
|
||||
let t = Text(Image(systemName: icon)).font(.caption2)
|
||||
let gap = Text(" ").kerning(-1.25)
|
||||
let t1 = t.foregroundColor(transparent ? .clear : statusColor.opacity(0.67))
|
||||
switch sent {
|
||||
case nil: r = r + t1
|
||||
case .sent: r = r + t1 + gap
|
||||
case .rcvd1: r = r + t.foregroundColor(transparent ? .clear : statusColor.opacity(0.67)) + gap
|
||||
case .rcvd2: r = r + gap + t1
|
||||
}
|
||||
r = r + Text(" ")
|
||||
} else if !meta.disappearing {
|
||||
r = r + statusIconText("circlebadge.fill", .clear) + Text(" ")
|
||||
}
|
||||
@@ -51,8 +90,12 @@ private func statusIconText(_ icon: String, _ color: Color) -> Text {
|
||||
struct CIMetaView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent, itemEdited: true))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete)))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .partial)))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .complete)))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .partial)))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .badMsgHash, sndProgress: .complete)))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), itemEdited: true))
|
||||
CIMetaView(chatItem: ChatItem.getDeletedContentSample())
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 100))
|
||||
|
||||
@@ -12,25 +12,215 @@ import SimpleXChat
|
||||
let decryptErrorReason: LocalizedStringKey = "It can happen when you or your connection used the old database backup."
|
||||
|
||||
struct CIRcvDecryptionError: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
var msgDecryptError: MsgDecryptError
|
||||
var msgCount: UInt32
|
||||
var chatItem: ChatItem
|
||||
var showMember = false
|
||||
@State private var alert: CIRcvDecryptionErrorAlert?
|
||||
|
||||
enum CIRcvDecryptionErrorAlert: Identifiable {
|
||||
case syncAllowedAlert(_ syncConnection: () -> Void)
|
||||
case syncNotSupportedContactAlert
|
||||
case syncNotSupportedMemberAlert
|
||||
case decryptionErrorAlert
|
||||
case error(title: LocalizedStringKey, error: LocalizedStringKey)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .syncAllowedAlert: return "syncAllowedAlert"
|
||||
case .syncNotSupportedContactAlert: return "syncNotSupportedContactAlert"
|
||||
case .syncNotSupportedMemberAlert: return "syncNotSupportedMemberAlert"
|
||||
case .decryptionErrorAlert: return "decryptionErrorAlert"
|
||||
case let .error(title, _): return "error \(title)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
CIMsgError(chatItem: chatItem, showMember: showMember) {
|
||||
var message: Text
|
||||
let why = Text(decryptErrorReason)
|
||||
let permanent = Text("This error is permanent for this connection, please re-connect.")
|
||||
switch msgDecryptError {
|
||||
case .ratchetHeader:
|
||||
message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why + Text("\n") + permanent
|
||||
case .tooManySkipped:
|
||||
message = Text("\(msgCount) messages skipped.") + Text("\n") + why + Text("\n") + permanent
|
||||
viewBody()
|
||||
.onAppear {
|
||||
// for direct chat ConnectionStats are populated on opening chat, see ChatView onAppear
|
||||
if case let .group(groupInfo) = chat.chatInfo,
|
||||
case let .groupRcv(groupMember) = chatItem.chatDir {
|
||||
do {
|
||||
let (member, stats) = try apiGroupMemberInfo(groupInfo.apiId, groupMember.groupMemberId)
|
||||
if let s = stats {
|
||||
ChatModel.shared.updateGroupMemberConnectionStats(groupInfo, member, s)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiGroupMemberInfo error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
AlertManager.shared.showAlert(Alert(title: Text("Decryption error"), message: message))
|
||||
.alert(item: $alert) { alertItem in
|
||||
switch(alertItem) {
|
||||
case let .syncAllowedAlert(syncConnection): return syncAllowedAlert(syncConnection)
|
||||
case .syncNotSupportedContactAlert: return Alert(title: Text("Fix not supported by contact"), message: message())
|
||||
case .syncNotSupportedMemberAlert: return Alert(title: Text("Fix not supported by group member"), message: message())
|
||||
case .decryptionErrorAlert: return Alert(title: Text("Decryption error"), message: message())
|
||||
case let .error(title, error): return Alert(title: Text(title), message: Text(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func viewBody() -> some View {
|
||||
if case let .direct(contact) = chat.chatInfo,
|
||||
let contactStats = contact.activeConn.connectionStats {
|
||||
if contactStats.ratchetSyncAllowed {
|
||||
decryptionErrorItemFixButton(syncSupported: true) {
|
||||
alert = .syncAllowedAlert { syncContactConnection(contact) }
|
||||
}
|
||||
} else if !contactStats.ratchetSyncSupported {
|
||||
decryptionErrorItemFixButton(syncSupported: false) {
|
||||
alert = .syncNotSupportedContactAlert
|
||||
}
|
||||
} else {
|
||||
basicDecryptionErrorItem()
|
||||
}
|
||||
} else if case let .group(groupInfo) = chat.chatInfo,
|
||||
case let .groupRcv(groupMember) = chatItem.chatDir,
|
||||
let modelMember = ChatModel.shared.groupMembers.first(where: { $0.id == groupMember.id }),
|
||||
let memberStats = modelMember.activeConn?.connectionStats {
|
||||
if memberStats.ratchetSyncAllowed {
|
||||
decryptionErrorItemFixButton(syncSupported: true) {
|
||||
alert = .syncAllowedAlert { syncMemberConnection(groupInfo, groupMember) }
|
||||
}
|
||||
} else if !memberStats.ratchetSyncSupported {
|
||||
decryptionErrorItemFixButton(syncSupported: false) {
|
||||
alert = .syncNotSupportedMemberAlert
|
||||
}
|
||||
} else {
|
||||
basicDecryptionErrorItem()
|
||||
}
|
||||
} else {
|
||||
basicDecryptionErrorItem()
|
||||
}
|
||||
}
|
||||
|
||||
private func basicDecryptionErrorItem() -> some View {
|
||||
decryptionErrorItem { alert = .decryptionErrorAlert }
|
||||
}
|
||||
|
||||
private func decryptionErrorItemFixButton(syncSupported: Bool, _ onClick: @escaping (() -> Void)) -> some View {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack {
|
||||
if showMember, let member = chatItem.memberDisplayName {
|
||||
Text(member).fontWeight(.medium) + Text(": ")
|
||||
}
|
||||
Text(chatItem.content.text)
|
||||
.foregroundColor(.red)
|
||||
.italic()
|
||||
}
|
||||
(
|
||||
Text(Image(systemName: "exclamationmark.arrow.triangle.2.circlepath"))
|
||||
.foregroundColor(syncSupported ? .accentColor : .secondary)
|
||||
.font(.callout)
|
||||
+ Text(" ")
|
||||
+ Text("Fix connection")
|
||||
.foregroundColor(syncSupported ? .accentColor : .secondary)
|
||||
.font(.callout)
|
||||
+ Text(" ")
|
||||
+ ciMetaText(chatItem.meta, chatTTL: nil, transparent: true)
|
||||
)
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
CIMetaView(chatItem: chatItem)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.onTapGesture(perform: { onClick() })
|
||||
.padding(.vertical, 6)
|
||||
.background(Color(uiColor: .tertiarySystemGroupedBackground))
|
||||
.cornerRadius(18)
|
||||
.textSelection(.disabled)
|
||||
}
|
||||
|
||||
private func decryptionErrorItem(_ onClick: @escaping (() -> Void)) -> some View {
|
||||
func text() -> Text {
|
||||
Text(chatItem.content.text)
|
||||
.foregroundColor(.red)
|
||||
.italic()
|
||||
+ Text(" ")
|
||||
+ ciMetaText(chatItem.meta, chatTTL: nil, transparent: true)
|
||||
}
|
||||
return ZStack(alignment: .bottomTrailing) {
|
||||
HStack {
|
||||
if showMember, let member = chatItem.memberDisplayName {
|
||||
Text(member).fontWeight(.medium) + Text(": ") + text()
|
||||
} else {
|
||||
text()
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
CIMetaView(chatItem: chatItem)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.onTapGesture(perform: { onClick() })
|
||||
.padding(.vertical, 6)
|
||||
.background(Color(uiColor: .tertiarySystemGroupedBackground))
|
||||
.cornerRadius(18)
|
||||
.textSelection(.disabled)
|
||||
}
|
||||
|
||||
private func message() -> Text {
|
||||
var message: Text
|
||||
let why = Text(decryptErrorReason)
|
||||
switch msgDecryptError {
|
||||
case .ratchetHeader:
|
||||
message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why
|
||||
case .tooManySkipped:
|
||||
message = Text("\(msgCount) messages skipped.") + Text("\n") + why
|
||||
case .ratchetEarlier:
|
||||
message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why
|
||||
case .other:
|
||||
message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
private func syncMemberConnection(_ groupInfo: GroupInfo, _ member: GroupMember) {
|
||||
Task {
|
||||
do {
|
||||
let (mem, stats) = try apiSyncGroupMemberRatchet(groupInfo.apiId, member.groupMemberId, false)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateGroupMemberConnectionStats(groupInfo, mem, stats)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("syncMemberConnection apiSyncGroupMemberRatchet error: \(responseError(error))")
|
||||
let a = getErrorAlert(error, "Error synchronizing connection")
|
||||
await MainActor.run {
|
||||
alert = .error(title: a.title, error: a.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func syncContactConnection(_ contact: Contact) {
|
||||
Task {
|
||||
do {
|
||||
let stats = try apiSyncContactRatchet(contact.apiId, false)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateContactConnectionStats(contact, stats)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("syncContactConnection apiSyncContactRatchet error: \(responseError(error))")
|
||||
let a = getErrorAlert(error, "Error synchronizing connection")
|
||||
await MainActor.run {
|
||||
alert = .error(title: a.title, error: a.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func syncAllowedAlert(_ syncConnection: @escaping () -> Void) -> Alert {
|
||||
Alert(
|
||||
title: Text("Fix connection?"),
|
||||
message: message(),
|
||||
primaryButton: .default(Text("Fix"), action: syncConnection),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//struct CIRcvDecryptionError_Previews: PreviewProvider {
|
||||
|
||||
@@ -212,6 +212,7 @@ struct CIVideoView: View {
|
||||
}
|
||||
case .rcvCancelled: fileIcon("xmark", 10, 13)
|
||||
case .rcvError: fileIcon("xmark", 10, 13)
|
||||
case .invalid: fileIcon("questionmark", 10, 13)
|
||||
default: EmptyView()
|
||||
}
|
||||
}
|
||||
@@ -246,10 +247,10 @@ struct CIVideoView: View {
|
||||
.padding([.trailing, .top], 11)
|
||||
}
|
||||
|
||||
private func receiveFileIfValidSize(file: CIFile, receiveFile: @escaping (User, Int64) async -> Void) {
|
||||
private func receiveFileIfValidSize(file: CIFile, receiveFile: @escaping (User, Int64, Bool) async -> Void) {
|
||||
Task {
|
||||
if let user = ChatModel.shared.currentUser {
|
||||
await receiveFile(user, file.fileId)
|
||||
await receiveFile(user, file.fileId, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ struct VoiceMessagePlayer: View {
|
||||
case .rcvComplete: playbackButton()
|
||||
case .rcvCancelled: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
|
||||
case .rcvError: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
|
||||
case .invalid: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
|
||||
}
|
||||
} else {
|
||||
playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
|
||||
@@ -268,7 +269,7 @@ struct CIVoiceView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let sentVoiceMessage: ChatItem = ChatItem(
|
||||
chatDir: .directSnd,
|
||||
meta: CIMeta.getSample(1, .now, "", .sndSent, itemEdited: true),
|
||||
meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true),
|
||||
content: .sndMsgContent(msgContent: .voice(text: "", duration: 30)),
|
||||
quotedItem: nil,
|
||||
file: CIFile.getSample(fileStatus: .sndComplete)
|
||||
|
||||
@@ -32,7 +32,7 @@ func emojiText(_ text: String) -> Text {
|
||||
struct EmojiItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group{
|
||||
EmojiItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent))
|
||||
EmojiItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete)))
|
||||
EmojiItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "👍"))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 70))
|
||||
|
||||
@@ -75,14 +75,14 @@ struct FramedCIVoiceView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let sentVoiceMessage: ChatItem = ChatItem(
|
||||
chatDir: .directSnd,
|
||||
meta: CIMeta.getSample(1, .now, "", .sndSent, itemEdited: true),
|
||||
meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true),
|
||||
content: .sndMsgContent(msgContent: .voice(text: "Hello there", duration: 30)),
|
||||
quotedItem: nil,
|
||||
file: CIFile.getSample(fileStatus: .sndComplete)
|
||||
)
|
||||
let voiceMessageWithQuote: ChatItem = ChatItem(
|
||||
chatDir: .directSnd,
|
||||
meta: CIMeta.getSample(1, .now, "", .sndSent, itemEdited: true),
|
||||
meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true),
|
||||
content: .sndMsgContent(msgContent: .voice(text: "", duration: 30)),
|
||||
quotedItem: CIQuote.getSample(1, .now, "Hi", chatDir: .directRcv),
|
||||
file: CIFile.getSample(fileStatus: .sndComplete)
|
||||
|
||||
@@ -349,8 +349,8 @@ struct FramedItemView_Previews: PreviewProvider {
|
||||
Group{
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent, quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent, quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
@@ -363,10 +363,10 @@ struct FramedItemView_Previews: PreviewProvider {
|
||||
struct FramedItemView_Edited_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemEdited: true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent, quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent, quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
@@ -381,10 +381,10 @@ struct FramedItemView_Edited_Previews: PreviewProvider {
|
||||
struct FramedItemView_Deleted_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent, quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent, quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
|
||||
@@ -46,7 +46,7 @@ struct MarkedDeletedItemView: View {
|
||||
struct MarkedDeletedItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
MarkedDeletedItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemDeleted: .deleted(deletedTs: .now)))
|
||||
MarkedDeletedItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 200))
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ struct MsgContentView: View {
|
||||
func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: String?, icon: String? = nil, preview: Bool = false) -> Text {
|
||||
let s = text
|
||||
var res: Text
|
||||
if let ft = formattedText, ft.count > 0 {
|
||||
if let ft = formattedText, ft.count > 0 && ft.count <= 200 {
|
||||
res = formatText(ft[0], preview)
|
||||
var i = 1
|
||||
while i < ft.count {
|
||||
|
||||
@@ -13,7 +13,25 @@ struct ChatItemInfoView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
var ci: ChatItem
|
||||
@Binding var chatItemInfo: ChatItemInfo?
|
||||
@State private var selection: CIInfoTab = .history
|
||||
@State private var alert: CIInfoViewAlert? = nil
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
|
||||
enum CIInfoTab {
|
||||
case history
|
||||
case quote
|
||||
case delivery
|
||||
}
|
||||
|
||||
enum CIInfoViewAlert: Identifiable {
|
||||
case alert(title: String, text: String)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .alert(title, text): return "alert \(title) \(text)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
@@ -25,6 +43,11 @@ struct ChatItemInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { a in
|
||||
switch(a) {
|
||||
case let .alert(title, text): return Alert(title: Text(title), message: Text(text))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,44 +57,92 @@ struct ChatItemInfoView: View {
|
||||
: NSLocalizedString("Received message", comment: "message info title")
|
||||
}
|
||||
|
||||
private var numTabs: Int {
|
||||
var numTabs = 1
|
||||
if chatItemInfo?.memberDeliveryStatuses != nil {
|
||||
numTabs += 1
|
||||
}
|
||||
if ci.quotedItem != nil {
|
||||
numTabs += 1
|
||||
}
|
||||
return numTabs
|
||||
}
|
||||
|
||||
@ViewBuilder private func itemInfoView() -> some View {
|
||||
if numTabs > 1 {
|
||||
TabView(selection: $selection) {
|
||||
if let mdss = chatItemInfo?.memberDeliveryStatuses {
|
||||
deliveryTab(mdss)
|
||||
.tabItem {
|
||||
Label("Delivery", systemImage: "checkmark.message")
|
||||
}
|
||||
.tag(CIInfoTab.delivery)
|
||||
}
|
||||
historyTab()
|
||||
.tabItem {
|
||||
Label("History", systemImage: "clock")
|
||||
}
|
||||
.tag(CIInfoTab.history)
|
||||
if let qi = ci.quotedItem {
|
||||
quoteTab(qi)
|
||||
.tabItem {
|
||||
Label("In reply to", systemImage: "arrowshape.turn.up.left")
|
||||
}
|
||||
.tag(CIInfoTab.quote)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if chatItemInfo?.memberDeliveryStatuses != nil {
|
||||
selection = .delivery
|
||||
}
|
||||
}
|
||||
} else {
|
||||
historyTab()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func details() -> some View {
|
||||
let meta = ci.meta
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text(title)
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.padding(.bottom)
|
||||
|
||||
infoRow("Sent at", localTimestamp(meta.itemTs))
|
||||
if !ci.chatDir.sent {
|
||||
infoRow("Received at", localTimestamp(meta.createdAt))
|
||||
}
|
||||
switch (meta.itemDeleted) {
|
||||
case let .deleted(deletedTs):
|
||||
if let deletedTs = deletedTs {
|
||||
infoRow("Deleted at", localTimestamp(deletedTs))
|
||||
}
|
||||
case let .moderated(deletedTs, _):
|
||||
if let deletedTs = deletedTs {
|
||||
infoRow("Moderated at", localTimestamp(deletedTs))
|
||||
}
|
||||
default: EmptyView()
|
||||
}
|
||||
if let deleteAt = meta.itemTimed?.deleteAt {
|
||||
infoRow("Disappears at", localTimestamp(deleteAt))
|
||||
}
|
||||
if developerTools {
|
||||
infoRow("Database ID", "\(meta.itemId)")
|
||||
infoRow("Record updated at", localTimestamp(meta.updatedAt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func historyTab() -> some View {
|
||||
GeometryReader { g in
|
||||
let maxWidth = (g.size.width - 32) * 0.84
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text(title)
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.padding(.bottom)
|
||||
|
||||
let maxWidth = (g.size.width - 32) * 0.84
|
||||
infoRow("Sent at", localTimestamp(meta.itemTs))
|
||||
if !ci.chatDir.sent {
|
||||
infoRow("Received at", localTimestamp(meta.createdAt))
|
||||
}
|
||||
switch (meta.itemDeleted) {
|
||||
case let .deleted(deletedTs):
|
||||
if let deletedTs = deletedTs {
|
||||
infoRow("Deleted at", localTimestamp(deletedTs))
|
||||
}
|
||||
case let .moderated(deletedTs, _):
|
||||
if let deletedTs = deletedTs {
|
||||
infoRow("Moderated at", localTimestamp(deletedTs))
|
||||
}
|
||||
default: EmptyView()
|
||||
}
|
||||
if let deleteAt = meta.itemTimed?.deleteAt {
|
||||
infoRow("Disappears at", localTimestamp(deleteAt))
|
||||
}
|
||||
if developerTools {
|
||||
infoRow("Database ID", "\(meta.itemId)")
|
||||
infoRow("Record updated at", localTimestamp(meta.updatedAt))
|
||||
}
|
||||
|
||||
details()
|
||||
Divider().padding(.vertical)
|
||||
if let chatItemInfo = chatItemInfo,
|
||||
!chatItemInfo.itemVersions.isEmpty {
|
||||
Divider().padding(.vertical)
|
||||
|
||||
Text("History")
|
||||
.font(.title2)
|
||||
.padding(.bottom, 4)
|
||||
@@ -81,16 +152,21 @@ struct ChatItemInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Text("No history")
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder private func itemVersionView(_ itemVersion: ChatItemVersion, _ maxWidth: CGFloat, current: Bool) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
versionText(itemVersion)
|
||||
textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil)
|
||||
.allowsHitTesting(false)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
@@ -119,9 +195,9 @@ struct ChatItemInfoView: View {
|
||||
.frame(maxWidth: maxWidth, alignment: .leading)
|
||||
}
|
||||
|
||||
@ViewBuilder private func versionText(_ itemVersion: ChatItemVersion) -> some View {
|
||||
if itemVersion.msgContent.text != "" {
|
||||
messageText(itemVersion.msgContent.text, itemVersion.formattedText, nil)
|
||||
@ViewBuilder private func textBubble(_ text: String, _ formattedText: [FormattedText]?, _ sender: String? = nil) -> some View {
|
||||
if text != "" {
|
||||
messageText(text, formattedText, sender)
|
||||
} else {
|
||||
Text("no text")
|
||||
.italic()
|
||||
@@ -129,9 +205,141 @@ struct ChatItemInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func quoteTab(_ qi: CIQuote) -> some View {
|
||||
GeometryReader { g in
|
||||
let maxWidth = (g.size.width - 32) * 0.84
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
details()
|
||||
Divider().padding(.vertical)
|
||||
Text("In reply to")
|
||||
.font(.title2)
|
||||
.padding(.bottom, 4)
|
||||
quotedMsgView(qi, maxWidth)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func quotedMsgView(_ qi: CIQuote, _ maxWidth: CGFloat) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
textBubble(qi.text, qi.formattedText, qi.getSender(nil))
|
||||
.allowsHitTesting(false)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(quotedMsgFrameColor(qi, colorScheme))
|
||||
.cornerRadius(18)
|
||||
.contextMenu {
|
||||
if qi.text != "" {
|
||||
Button {
|
||||
showShareSheet(items: [qi.text])
|
||||
} label: {
|
||||
Label("Share", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
Button {
|
||||
UIPasteboard.general.string = qi.text
|
||||
} label: {
|
||||
Label("Copy", systemImage: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(localTimestamp(qi.sentAt))
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.frame(maxWidth: maxWidth, alignment: .leading)
|
||||
}
|
||||
|
||||
func quotedMsgFrameColor(_ qi: CIQuote, _ colorScheme: ColorScheme) -> Color {
|
||||
(qi.chatDir?.sent ?? false)
|
||||
? (colorScheme == .light ? sentColorLight : sentColorDark)
|
||||
: Color(uiColor: .tertiarySystemGroupedBackground)
|
||||
}
|
||||
|
||||
@ViewBuilder private func deliveryTab(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
details()
|
||||
Divider().padding(.vertical)
|
||||
Text("Delivery")
|
||||
.font(.title2)
|
||||
.padding(.bottom, 4)
|
||||
memberDeliveryStatusesView(memberDeliveryStatuses)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
}
|
||||
|
||||
@ViewBuilder private func memberDeliveryStatusesView(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
let mss = membersStatuses(memberDeliveryStatuses)
|
||||
if !mss.isEmpty {
|
||||
ForEach(mss, id: \.0.groupMemberId) { memberStatus in
|
||||
memberDeliveryStatusView(memberStatus.0, memberStatus.1)
|
||||
}
|
||||
} else {
|
||||
Text("No delivery information")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func membersStatuses(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> [(GroupMember, CIStatus)] {
|
||||
memberDeliveryStatuses.compactMap({ mds in
|
||||
if let mem = ChatModel.shared.groupMembers.first(where: { $0.groupMemberId == mds.groupMemberId }) {
|
||||
return (mem, mds.memberDeliveryStatus)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func memberDeliveryStatusView(_ member: GroupMember, _ status: CIStatus) -> some View {
|
||||
HStack{
|
||||
ProfileImage(imageStr: member.image)
|
||||
.frame(width: 30, height: 30)
|
||||
.padding(.trailing, 2)
|
||||
Text(member.chatViewName)
|
||||
.lineLimit(1)
|
||||
Spacer()
|
||||
let v = Group {
|
||||
if let (icon, statusColor) = status.statusIcon(Color.secondary) {
|
||||
switch status {
|
||||
case .sndRcvd:
|
||||
ZStack(alignment: .trailing) {
|
||||
Image(systemName: icon)
|
||||
.foregroundColor(statusColor.opacity(0.67))
|
||||
.padding(.trailing, 6)
|
||||
Image(systemName: icon)
|
||||
.foregroundColor(statusColor.opacity(0.67))
|
||||
}
|
||||
default:
|
||||
Image(systemName: icon)
|
||||
.foregroundColor(statusColor)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "ellipsis")
|
||||
.foregroundColor(Color.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
if let (title, text) = status.statusInfo {
|
||||
v.onTapGesture {
|
||||
alert = .alert(title: title, text: text)
|
||||
}
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func itemInfoShareText() -> String {
|
||||
let meta = ci.meta
|
||||
var shareText: [String] = [title, ""]
|
||||
var shareText: [String] = [String.localizedStringWithFormat(NSLocalizedString("# %@", comment: "copied message info title, # <title>"), title), ""]
|
||||
shareText += [String.localizedStringWithFormat(NSLocalizedString("Sent at: %@", comment: "copied message info"), localTimestamp(meta.itemTs))]
|
||||
if !ci.chatDir.sent {
|
||||
shareText += [String.localizedStringWithFormat(NSLocalizedString("Received at: %@", comment: "copied message info"), localTimestamp(meta.createdAt))]
|
||||
@@ -156,9 +364,27 @@ struct ChatItemInfoView: View {
|
||||
String.localizedStringWithFormat(NSLocalizedString("Record updated at: %@", comment: "copied message info"), localTimestamp(meta.updatedAt))
|
||||
]
|
||||
}
|
||||
if let qi = ci.quotedItem {
|
||||
shareText += ["", NSLocalizedString("## In reply to", comment: "copied message info")]
|
||||
let t = qi.text
|
||||
shareText += [""]
|
||||
if let sender = qi.getSender(nil) {
|
||||
shareText += [String.localizedStringWithFormat(
|
||||
NSLocalizedString("%@ at %@:", comment: "copied message info, <sender> at <time>"),
|
||||
sender,
|
||||
localTimestamp(qi.sentAt)
|
||||
)]
|
||||
} else {
|
||||
shareText += [String.localizedStringWithFormat(
|
||||
NSLocalizedString("%@:", comment: "copied message info"),
|
||||
localTimestamp(qi.sentAt)
|
||||
)]
|
||||
}
|
||||
shareText += [t != "" ? t : NSLocalizedString("no text", comment: "copied message info in history")]
|
||||
}
|
||||
if let chatItemInfo = chatItemInfo,
|
||||
!chatItemInfo.itemVersions.isEmpty {
|
||||
shareText += ["", NSLocalizedString("History", comment: "copied message info")]
|
||||
shareText += ["", NSLocalizedString("## History", comment: "copied message info")]
|
||||
for (index, itemVersion) in chatItemInfo.itemVersions.enumerated() {
|
||||
let t = itemVersion.msgContent.text
|
||||
shareText += [
|
||||
|
||||
@@ -125,9 +125,9 @@ struct ChatItemView_Previews: PreviewProvider {
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getDeletedContentSample(), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent, itemLive: true), revealed: Binding.constant(true))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemLive: true), revealed: Binding.constant(true))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 70))
|
||||
.environmentObject(Chat.sampleData)
|
||||
|
||||
@@ -22,7 +22,7 @@ struct ChatView: View {
|
||||
@State private var showAddMembersSheet: Bool = false
|
||||
@State private var composeState = ComposeState()
|
||||
@State private var deletingItem: ChatItem? = nil
|
||||
@FocusState private var keyboardVisible: Bool
|
||||
@State private var keyboardVisible = false
|
||||
@State private var showDeleteMessage = false
|
||||
@State private var connectionStats: ConnectionStats?
|
||||
@State private var customUserProfile: Profile?
|
||||
@@ -39,6 +39,16 @@ struct ChatView: View {
|
||||
@State private var selectedMember: GroupMember? = nil
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
viewBody
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.keyboardPadding()
|
||||
} else {
|
||||
viewBody
|
||||
}
|
||||
}
|
||||
|
||||
private var viewBody: some View {
|
||||
let cInfo = chat.chatInfo
|
||||
return VStack(spacing: 0) {
|
||||
if searchMode {
|
||||
@@ -65,17 +75,14 @@ struct ChatView: View {
|
||||
.navigationTitle(cInfo.chatViewName)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.onAppear {
|
||||
if chatModel.draftChatId == cInfo.id, let draft = chatModel.draft {
|
||||
composeState = draft
|
||||
}
|
||||
if chat.chatStats.unreadChat {
|
||||
Task {
|
||||
await markChatUnread(chat, unreadChat: false)
|
||||
}
|
||||
}
|
||||
initChatView()
|
||||
}
|
||||
.onChange(of: chatModel.chatId) { _ in
|
||||
if chatModel.chatId == nil { dismiss() }
|
||||
.onChange(of: chatModel.chatId) { cId in
|
||||
if cId != nil {
|
||||
initChatView()
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
VideoPlayerView.players.removeAll()
|
||||
@@ -185,6 +192,32 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func initChatView() {
|
||||
let cInfo = chat.chatInfo
|
||||
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)
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiContactInfo error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
if chatModel.draftChatId == cInfo.id, let draft = chatModel.draft {
|
||||
composeState = draft
|
||||
}
|
||||
if chat.chatStats.unreadChat {
|
||||
Task {
|
||||
await markChatUnread(chat, unreadChat: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func searchToolbar() -> some View {
|
||||
HStack {
|
||||
HStack {
|
||||
@@ -594,6 +627,7 @@ struct ChatView: View {
|
||||
menu.append(viewInfoUIAction())
|
||||
menu.append(deleteUIAction())
|
||||
} else if ci.isDeletedContent {
|
||||
menu.append(viewInfoUIAction())
|
||||
menu.append(deleteUIAction())
|
||||
}
|
||||
return menu
|
||||
@@ -616,7 +650,7 @@ struct ChatView: View {
|
||||
|
||||
private func reactionUIMenuPreiOS16(_ rs: [UIAction]) -> UIMenu {
|
||||
UIMenu(
|
||||
title: NSLocalizedString("React...", comment: "chat item menu"),
|
||||
title: NSLocalizedString("React…", comment: "chat item menu"),
|
||||
image: UIImage(systemName: "face.smiling"),
|
||||
children: rs
|
||||
)
|
||||
@@ -736,6 +770,12 @@ struct ChatView: View {
|
||||
await MainActor.run {
|
||||
chatItemInfo = ciInfo
|
||||
}
|
||||
if case let .group(gInfo) = chat.chatInfo {
|
||||
let groupMembers = await apiListMembers(gInfo.groupId)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.groupMembers = groupMembers
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiGetChatItemInfo error: \(responseError(error))")
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ struct ComposeView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@ObservedObject var chat: Chat
|
||||
@Binding var composeState: ComposeState
|
||||
@FocusState.Binding var keyboardVisible: Bool
|
||||
@Binding var keyboardVisible: Bool
|
||||
|
||||
@State var linkUrl: URL? = nil
|
||||
@State var prevLinkUrl: URL? = nil
|
||||
@@ -665,17 +665,21 @@ struct ComposeView: View {
|
||||
if let oldMsgContent = ei.content.msgContent {
|
||||
do {
|
||||
let mc = updateMsgContent(oldMsgContent)
|
||||
let chatItem = try await apiUpdateChatItem(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
itemId: ei.id,
|
||||
msg: mc,
|
||||
live: live
|
||||
)
|
||||
await MainActor.run {
|
||||
_ = self.chatModel.upsertChatItem(self.chat.chatInfo, chatItem)
|
||||
if mc != oldMsgContent || (ei.meta.itemLive ?? false) {
|
||||
let chatItem = try await apiUpdateChatItem(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
itemId: ei.id,
|
||||
msg: mc,
|
||||
live: live
|
||||
)
|
||||
await MainActor.run {
|
||||
_ = self.chatModel.upsertChatItem(self.chat.chatInfo, chatItem)
|
||||
}
|
||||
return chatItem
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return chatItem
|
||||
} catch {
|
||||
logger.error("ChatView.sendMessage error: \(error.localizedDescription)")
|
||||
AlertManager.shared.showAlertMsg(title: "Error updating message", message: "Error: \(responseError(error))")
|
||||
@@ -943,19 +947,18 @@ struct ComposeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
|
||||
@State var composeState = ComposeState(message: "hello")
|
||||
@FocusState var keyboardVisible: Bool
|
||||
|
||||
return Group {
|
||||
ComposeView(
|
||||
chat: chat,
|
||||
composeState: $composeState,
|
||||
keyboardVisible: $keyboardVisible
|
||||
keyboardVisible: Binding.constant(true)
|
||||
)
|
||||
.environmentObject(ChatModel())
|
||||
ComposeView(
|
||||
chat: chat,
|
||||
composeState: $composeState,
|
||||
keyboardVisible: $keyboardVisible
|
||||
keyboardVisible: Binding.constant(true)
|
||||
)
|
||||
.environmentObject(ChatModel())
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ struct NativeTextEditor: UIViewRepresentable {
|
||||
@Binding var disableEditing: Bool
|
||||
let height: CGFloat
|
||||
let font: UIFont
|
||||
@FocusState.Binding var focused: Bool
|
||||
@Binding var focused: Bool
|
||||
let alignment: TextAlignment
|
||||
let onImagesAdded: ([UploadContent]) -> Void
|
||||
|
||||
@@ -144,13 +144,12 @@ private class CustomUITextField: UITextView, UITextViewDelegate {
|
||||
|
||||
struct NativeTextEditor_Previews: PreviewProvider{
|
||||
static var previews: some View {
|
||||
@FocusState var keyboardVisible: Bool
|
||||
return NativeTextEditor(
|
||||
text: Binding.constant("Hello, world!"),
|
||||
disableEditing: Binding.constant(false),
|
||||
height: 100,
|
||||
font: UIFont.preferredFont(forTextStyle: .body),
|
||||
focused: $keyboardVisible,
|
||||
focused: Binding.constant(false),
|
||||
alignment: TextAlignment.leading,
|
||||
onImagesAdded: { _ in }
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ struct SendMessageView: View {
|
||||
var onMediaAdded: ([UploadContent]) -> Void
|
||||
@State private var holdingVMR = false
|
||||
@Namespace var namespace
|
||||
@FocusState.Binding var keyboardVisible: Bool
|
||||
@Binding var keyboardVisible: Bool
|
||||
@State private var teHeight: CGFloat = 42
|
||||
@State private var teFont: Font = .body
|
||||
@State private var teUiFont: UIFont = UIFont.preferredFont(forTextStyle: .body)
|
||||
@@ -401,7 +401,6 @@ struct SendMessageView_Previews: PreviewProvider {
|
||||
@State var composeStateNew = ComposeState()
|
||||
let ci = ChatItem.getSample(1, .directSnd, .now, "hello")
|
||||
@State var composeStateEditing = ComposeState(editingItem: ci)
|
||||
@FocusState var keyboardVisible: Bool
|
||||
@State var sendEnabled: Bool = true
|
||||
|
||||
return Group {
|
||||
@@ -412,7 +411,7 @@ struct SendMessageView_Previews: PreviewProvider {
|
||||
composeState: $composeStateNew,
|
||||
sendMessage: { _ in },
|
||||
onMediaAdded: { _ in },
|
||||
keyboardVisible: $keyboardVisible
|
||||
keyboardVisible: Binding.constant(true)
|
||||
)
|
||||
}
|
||||
VStack {
|
||||
@@ -422,7 +421,7 @@ struct SendMessageView_Previews: PreviewProvider {
|
||||
composeState: $composeStateEditing,
|
||||
sendMessage: { _ in },
|
||||
onMediaAdded: { _ in },
|
||||
keyboardVisible: $keyboardVisible
|
||||
keyboardVisible: Binding.constant(true)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
let SMALL_GROUPS_RCPS_MEM_LIMIT: Int = 20
|
||||
|
||||
struct GroupChatInfoView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@@ -21,6 +23,8 @@ struct GroupChatInfoView: View {
|
||||
@State private var showAddMembersSheet: Bool = false
|
||||
@State private var connectionStats: ConnectionStats?
|
||||
@State private var connectionCode: String?
|
||||
@State private var sendReceipts = SendReceipts.userDefault(true)
|
||||
@State private var sendReceiptsUserDefault = true
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@State private var searchText: String = ""
|
||||
@FocusState private var searchFocussed
|
||||
@@ -30,6 +34,7 @@ struct GroupChatInfoView: View {
|
||||
case clearChatAlert
|
||||
case leaveGroupAlert
|
||||
case cantInviteIncognitoAlert
|
||||
case largeGroupReceiptsDisabled
|
||||
|
||||
var id: GroupChatInfoViewAlert { get { self } }
|
||||
}
|
||||
@@ -52,6 +57,11 @@ struct GroupChatInfoView: View {
|
||||
addOrEditWelcomeMessage()
|
||||
}
|
||||
groupPreferencesButton($groupInfo)
|
||||
if members.filter({ $0.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
|
||||
sendReceiptsOption()
|
||||
} else {
|
||||
sendReceiptsOptionDisabled()
|
||||
}
|
||||
} header: {
|
||||
Text("")
|
||||
} footer: {
|
||||
@@ -115,9 +125,14 @@ struct GroupChatInfoView: View {
|
||||
case .clearChatAlert: return clearChatAlert()
|
||||
case .leaveGroupAlert: return leaveGroupAlert()
|
||||
case .cantInviteIncognitoAlert: return cantInviteIncognitoAlert()
|
||||
case .largeGroupReceiptsDisabled: return largeGroupReceiptsDisabledAlert()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if let currentUser = chatModel.currentUser {
|
||||
sendReceiptsUserDefault = currentUser.sendRcptsSmallGroups
|
||||
}
|
||||
sendReceipts = SendReceipts.fromBool(groupInfo.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault)
|
||||
do {
|
||||
if let link = try apiGetGroupLink(groupInfo.groupId) {
|
||||
(groupLink, groupLinkMemberRole) = link
|
||||
@@ -126,6 +141,7 @@ struct GroupChatInfoView: View {
|
||||
logger.error("GroupChatInfoView apiGetGroupLink: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
.keyboardPadding()
|
||||
}
|
||||
|
||||
private func groupInfoHeader() -> some View {
|
||||
@@ -137,12 +153,14 @@ struct GroupChatInfoView: View {
|
||||
.padding()
|
||||
Text(cInfo.displayName)
|
||||
.font(.largeTitle)
|
||||
.lineLimit(1)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(4)
|
||||
.padding(.bottom, 2)
|
||||
if cInfo.fullName != "" && cInfo.fullName != cInfo.displayName {
|
||||
Text(cInfo.fullName)
|
||||
.font(.title2)
|
||||
.lineLimit(2)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(8)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
@@ -325,6 +343,38 @@ struct GroupChatInfoView: View {
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
private func sendReceiptsOption() -> some View {
|
||||
Picker(selection: $sendReceipts) {
|
||||
ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in
|
||||
Text(opt.text)
|
||||
}
|
||||
} label: {
|
||||
Label("Send receipts", systemImage: "checkmark.message")
|
||||
}
|
||||
.frame(height: 36)
|
||||
.onChange(of: sendReceipts) { _ in
|
||||
setSendReceipts()
|
||||
}
|
||||
}
|
||||
|
||||
private func setSendReceipts() {
|
||||
var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults
|
||||
chatSettings.sendRcpts = sendReceipts.bool()
|
||||
updateChatSettings(chat, chatSettings: chatSettings)
|
||||
}
|
||||
|
||||
private func sendReceiptsOptionDisabled() -> some View {
|
||||
HStack {
|
||||
Label("Send receipts", systemImage: "checkmark.message")
|
||||
Spacer()
|
||||
Text("disabled")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.onTapGesture {
|
||||
alert = .largeGroupReceiptsDisabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func groupPreferencesButton(_ groupInfo: Binding<GroupInfo>, _ creatingGroup: Bool = false) -> some View {
|
||||
@@ -353,6 +403,13 @@ func cantInviteIncognitoAlert() -> Alert {
|
||||
)
|
||||
}
|
||||
|
||||
func largeGroupReceiptsDisabledAlert() -> Alert {
|
||||
Alert(
|
||||
title: Text("Receipts are disabled"),
|
||||
message: Text("This group has over \(SMALL_GROUPS_RCPS_MEM_LIMIT) members, delivery receipts are not sent.")
|
||||
)
|
||||
}
|
||||
|
||||
struct GroupChatInfoView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
GroupChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []), groupInfo: GroupInfo.sampleData)
|
||||
|
||||
@@ -19,6 +19,7 @@ struct GroupMemberInfoView: View {
|
||||
@State private var connectionCode: String? = nil
|
||||
@State private var newRole: GroupMemberRole = .member
|
||||
@State private var alert: GroupMemberInfoViewAlert?
|
||||
@State private var connectToMemberDialog: Bool = false
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@State private var justOpened = true
|
||||
|
||||
@@ -27,6 +28,7 @@ struct GroupMemberInfoView: View {
|
||||
case changeMemberRoleAlert(mem: GroupMember, role: GroupMemberRole)
|
||||
case switchAddressAlert
|
||||
case abortSwitchAddressAlert
|
||||
case syncConnectionForceAlert
|
||||
case connRequestSentAlert(type: ConnReqType)
|
||||
case error(title: LocalizedStringKey, error: LocalizedStringKey)
|
||||
case other(alert: Alert)
|
||||
@@ -37,6 +39,7 @@ struct GroupMemberInfoView: View {
|
||||
case let .changeMemberRoleAlert(_, role): return "changeMemberRoleAlert \(role.rawValue)"
|
||||
case .switchAddressAlert: return "switchAddressAlert"
|
||||
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
|
||||
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
|
||||
case .connRequestSentAlert: return "connRequestSentAlert"
|
||||
case let .error(title, _): return "error \(title)"
|
||||
case let .other(alert): return "other \(alert)"
|
||||
@@ -77,6 +80,13 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
}
|
||||
if let code = connectionCode { verifyCodeButton(code) }
|
||||
if let connStats = connectionStats,
|
||||
connStats.ratchetSyncAllowed {
|
||||
synchronizeConnectionButton()
|
||||
}
|
||||
// } else if developerTools {
|
||||
// synchronizeConnectionButtonForce()
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,12 +139,18 @@ struct GroupMemberInfoView: View {
|
||||
Button("Change receiving address") {
|
||||
alert = .switchAddressAlert
|
||||
}
|
||||
.disabled(connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil })
|
||||
if connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } {
|
||||
.disabled(
|
||||
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil }
|
||||
|| connStats.ratchetSyncSendProhibited
|
||||
)
|
||||
if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) {
|
||||
Button("Abort changing address") {
|
||||
alert = .abortSwitchAddressAlert
|
||||
}
|
||||
.disabled(connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch })
|
||||
.disabled(
|
||||
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch }
|
||||
|| connStats.ratchetSyncSendProhibited
|
||||
)
|
||||
}
|
||||
smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer })
|
||||
smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer })
|
||||
@@ -162,7 +178,7 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
newRole = member.memberRole
|
||||
do {
|
||||
let stats = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId)
|
||||
let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId)
|
||||
let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil)
|
||||
member = mem
|
||||
connectionStats = stats
|
||||
@@ -185,6 +201,7 @@ struct GroupMemberInfoView: View {
|
||||
case let .changeMemberRoleAlert(mem, _): return changeMemberRoleAlert(mem)
|
||||
case .switchAddressAlert: return switchAddressAlert(switchMemberAddress)
|
||||
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchMemberAddress)
|
||||
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) })
|
||||
case let .connRequestSentAlert(type): return connReqSentAlert(type)
|
||||
case let .error(title, error): return Alert(title: Text(title), message: Text(error))
|
||||
case let .other(alert): return alert
|
||||
@@ -194,15 +211,19 @@ struct GroupMemberInfoView: View {
|
||||
|
||||
func connectViaAddressButton(_ contactLink: String) -> some View {
|
||||
Button {
|
||||
connectViaAddress(contactLink)
|
||||
connectToMemberDialog = true
|
||||
} label: {
|
||||
Label("Connect", systemImage: "link")
|
||||
}
|
||||
.confirmationDialog("Connect directly", isPresented: $connectToMemberDialog, titleVisibility: .visible) {
|
||||
Button("Use current profile") { connectViaAddress(incognito: false, contactLink: contactLink) }
|
||||
Button("Use new incognito profile") { connectViaAddress(incognito: true, contactLink: contactLink) }
|
||||
}
|
||||
}
|
||||
|
||||
func connectViaAddress(_ contactLink: String) {
|
||||
func connectViaAddress(incognito: Bool, contactLink: String) {
|
||||
Task {
|
||||
let (connReqType, connectAlert) = await apiConnect_(connReq: contactLink)
|
||||
let (connReqType, connectAlert) = await apiConnect_(incognito: incognito, connReq: contactLink)
|
||||
if let connReqType = connReqType {
|
||||
alert = .connRequestSentAlert(type: connReqType)
|
||||
} else if let connectAlert = connectAlert {
|
||||
@@ -245,19 +266,30 @@ struct GroupMemberInfoView: View {
|
||||
.frame(width: 192, height: 192)
|
||||
.padding(.top, 12)
|
||||
.padding()
|
||||
HStack {
|
||||
if mem.verified {
|
||||
Image(systemName: "checkmark.shield")
|
||||
}
|
||||
if mem.verified {
|
||||
(
|
||||
Text(Image(systemName: "checkmark.shield"))
|
||||
.foregroundColor(.secondary)
|
||||
.font(.title2)
|
||||
+ Text(" ")
|
||||
+ Text(mem.displayName)
|
||||
.font(.largeTitle)
|
||||
)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(2)
|
||||
.padding(.bottom, 2)
|
||||
} else {
|
||||
Text(mem.displayName)
|
||||
.font(.largeTitle)
|
||||
.lineLimit(1)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(2)
|
||||
.padding(.bottom, 2)
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
if mem.fullName != "" && mem.fullName != mem.displayName {
|
||||
Text(mem.fullName)
|
||||
.font(.title2)
|
||||
.lineLimit(2)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(4)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
@@ -291,7 +323,24 @@ struct GroupMemberInfoView: View {
|
||||
systemImage: member.verified ? "checkmark.shield" : "shield"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func synchronizeConnectionButton() -> some View {
|
||||
Button {
|
||||
syncMemberConnection(force: false)
|
||||
} label: {
|
||||
Label("Fix connection", systemImage: "exclamationmark.arrow.triangle.2.circlepath")
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
}
|
||||
|
||||
private func synchronizeConnectionButtonForce() -> some View {
|
||||
Button {
|
||||
alert = .syncConnectionForceAlert
|
||||
} label: {
|
||||
Label("Renegotiate encryption", systemImage: "exclamationmark.triangle")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
private func removeMemberButton(_ mem: GroupMember) -> some View {
|
||||
@@ -357,7 +406,11 @@ struct GroupMemberInfoView: View {
|
||||
Task {
|
||||
do {
|
||||
let stats = try apiSwitchGroupMember(groupInfo.apiId, member.groupMemberId)
|
||||
connectionStats = stats
|
||||
connectionStats = stats
|
||||
await MainActor.run {
|
||||
chatModel.updateGroupMemberConnectionStats(groupInfo, member, stats)
|
||||
dismiss()
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("switchMemberAddress apiSwitchGroupMember error: \(responseError(error))")
|
||||
let a = getErrorAlert(error, "Error changing address")
|
||||
@@ -373,6 +426,9 @@ struct GroupMemberInfoView: View {
|
||||
do {
|
||||
let stats = try apiAbortSwitchGroupMember(groupInfo.apiId, member.groupMemberId)
|
||||
connectionStats = stats
|
||||
await MainActor.run {
|
||||
chatModel.updateGroupMemberConnectionStats(groupInfo, member, stats)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("abortSwitchMemberAddress apiAbortSwitchGroupMember error: \(responseError(error))")
|
||||
let a = getErrorAlert(error, "Error aborting address change")
|
||||
@@ -382,6 +438,25 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func syncMemberConnection(force: Bool) {
|
||||
Task {
|
||||
do {
|
||||
let (mem, stats) = try apiSyncGroupMemberRatchet(groupInfo.apiId, member.groupMemberId, force)
|
||||
connectionStats = stats
|
||||
await MainActor.run {
|
||||
chatModel.updateGroupMemberConnectionStats(groupInfo, mem, stats)
|
||||
dismiss()
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("syncMemberConnection apiSyncGroupMemberRatchet error: \(responseError(error))")
|
||||
let a = getErrorAlert(error, "Error synchronizing connection")
|
||||
await MainActor.run {
|
||||
alert = .error(title: a.title, error: a.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GroupMemberInfoView_Previews: PreviewProvider {
|
||||
|
||||
@@ -19,7 +19,7 @@ struct ScanCodeView: View {
|
||||
VStack(alignment: .leading) {
|
||||
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.border(.gray)
|
||||
.cornerRadius(12)
|
||||
Text("Scan security code from your contact's app.")
|
||||
.padding(.top)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ struct VerifyCodeView: View {
|
||||
HStack {
|
||||
NavigationLink {
|
||||
ScanCodeView(connectionVerified: $connectionVerified, verify: verify)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.navigationTitle("Scan code")
|
||||
} label: {
|
||||
Label("Scan code", systemImage: "qrcode")
|
||||
|
||||
@@ -222,9 +222,15 @@ struct ChatListNavLink: View {
|
||||
ContactRequestView(contactRequest: contactRequest, chat: chat)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button {
|
||||
Task { await acceptContactRequest(contactRequest) }
|
||||
} label: { Label("Accept", systemImage: chatModel.incognito ? "theatermasks" : "checkmark") }
|
||||
.tint(chatModel.incognito ? .indigo : .accentColor)
|
||||
Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) }
|
||||
} label: { Label("Accept", systemImage: "checkmark") }
|
||||
.tint(.accentColor)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) }
|
||||
} label: {
|
||||
Label("Accept incognito", systemImage: "theatermasks")
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequest))
|
||||
} label: {
|
||||
@@ -234,9 +240,10 @@ struct ChatListNavLink: View {
|
||||
}
|
||||
.frame(height: rowHeights[dynamicTypeSize])
|
||||
.onTapGesture { showContactRequestDialog = true }
|
||||
.confirmationDialog("Connection request", isPresented: $showContactRequestDialog, titleVisibility: .visible) {
|
||||
Button(chatModel.incognito ? "Accept incognito" : "Accept contact") { Task { await acceptContactRequest(contactRequest) } }
|
||||
Button("Reject contact (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest) } }
|
||||
.confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) {
|
||||
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } }
|
||||
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) } }
|
||||
Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest) } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +270,7 @@ struct ChatListNavLink: View {
|
||||
.sheet(isPresented: $showContactConnectionInfo) {
|
||||
if case let .contactConnection(contactConnection) = chat.chatInfo {
|
||||
ContactConnectionInfo(contactConnection: contactConnection)
|
||||
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
@@ -379,6 +387,7 @@ struct ChatListNavLink: View {
|
||||
.onTapGesture { showInvalidJSON = true }
|
||||
.sheet(isPresented: $showInvalidJSON) {
|
||||
invalidJSONView(json)
|
||||
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,14 @@ struct ChatListView: View {
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
viewBody.scrollDismissesKeyboard(.immediately)
|
||||
} else {
|
||||
viewBody
|
||||
}
|
||||
}
|
||||
|
||||
private var viewBody: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
NavStackCompat(
|
||||
isActive: Binding(
|
||||
@@ -52,15 +60,41 @@ struct ChatListView: View {
|
||||
chatList
|
||||
}
|
||||
}
|
||||
.onChange(of: chatModel.appOpenUrl) { _ in connectViaUrl() }
|
||||
.onAppear() { connectViaUrl() }
|
||||
.onDisappear() { withAnimation { userPickerVisible = false } }
|
||||
.refreshable {
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
title: Text("Reconnect servers?"),
|
||||
message: Text("Reconnect all connected servers to force message delivery. It uses additional traffic."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
Task {
|
||||
do {
|
||||
try await reconnectAllServers()
|
||||
} catch let error {
|
||||
AlertManager.shared.showAlertMsg(title: "Error", message: "\(responseError(error))")
|
||||
}
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
))
|
||||
}
|
||||
.offset(x: -8)
|
||||
.listStyle(.plain)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
let user = chatModel.currentUser ?? User.sampleData
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ProfileImage(imageStr: user.image, color: Color(uiColor: .quaternaryLabel))
|
||||
.frame(width: 32, height: 32)
|
||||
.padding(.trailing, 4)
|
||||
let allRead = chatModel.users
|
||||
.filter { u in !u.user.activeUser && !u.user.hidden }
|
||||
.allSatisfy { u in u.unreadCount == 0 }
|
||||
if !allRead {
|
||||
unreadBadge(size: 12)
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
if chatModel.users.filter({ u in u.user.activeUser || !u.user.hidden }).count > 1 {
|
||||
withAnimation {
|
||||
userPickerVisible.toggle()
|
||||
@@ -68,28 +102,10 @@ struct ChatListView: View {
|
||||
} else {
|
||||
showSettings = true
|
||||
}
|
||||
} label: {
|
||||
let user = chatModel.currentUser ?? User.sampleData
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ProfileImage(imageStr: user.image, color: Color(uiColor: .quaternaryLabel))
|
||||
.frame(width: 32, height: 32)
|
||||
.padding(.trailing, 4)
|
||||
let allRead = chatModel.users
|
||||
.filter { u in !u.user.activeUser && !u.user.hidden }
|
||||
.allSatisfy { u in u.unreadCount == 0 }
|
||||
if !allRead {
|
||||
unreadBadge(size: 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .principal) {
|
||||
HStack(spacing: 4) {
|
||||
if (chatModel.incognito) {
|
||||
Image(systemName: "theatermasks")
|
||||
.foregroundColor(.indigo)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
Text("Chats")
|
||||
.font(.headline)
|
||||
if chatModel.chats.count > 0 {
|
||||
|
||||
@@ -41,11 +41,9 @@ struct ChatPreviewView: View {
|
||||
|
||||
ZStack(alignment: .topTrailing) {
|
||||
chatMessagePreview(cItem)
|
||||
if case .direct = chat.chatInfo {
|
||||
chatStatusImage()
|
||||
.padding(.top, 24)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
chatStatusImage()
|
||||
.padding(.top, 26)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.padding(.trailing, 8)
|
||||
|
||||
@@ -59,12 +57,9 @@ struct ChatPreviewView: View {
|
||||
@ViewBuilder private func chatPreviewImageOverlayIcon() -> some View {
|
||||
if case let .group(groupInfo) = chat.chatInfo {
|
||||
switch (groupInfo.membership.memberStatus) {
|
||||
case .memLeft:
|
||||
groupInactiveIcon()
|
||||
case .memRemoved:
|
||||
groupInactiveIcon()
|
||||
case .memGroupDeleted:
|
||||
groupInactiveIcon()
|
||||
case .memLeft: groupInactiveIcon()
|
||||
case .memRemoved: groupInactiveIcon()
|
||||
case .memGroupDeleted: groupInactiveIcon()
|
||||
default: EmptyView()
|
||||
}
|
||||
} else {
|
||||
@@ -74,7 +69,7 @@ struct ChatPreviewView: View {
|
||||
|
||||
@ViewBuilder private func groupInactiveIcon() -> some View {
|
||||
Image(systemName: "multiply.circle.fill")
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(.secondary.opacity(0.65))
|
||||
.background(Circle().foregroundColor(Color(uiColor: .systemBackground)))
|
||||
}
|
||||
|
||||
@@ -198,10 +193,7 @@ struct ChatPreviewView: View {
|
||||
@ViewBuilder private func groupInvitationPreviewText(_ groupInfo: GroupInfo) -> some View {
|
||||
groupInfo.membership.memberIncognito
|
||||
? chatPreviewInfoText("join as \(groupInfo.membership.memberProfile.displayName)")
|
||||
: (chatModel.incognito
|
||||
? chatPreviewInfoText("join as \(chatModel.currentUser?.profile.displayName ?? "yourself")")
|
||||
: chatPreviewInfoText("you are invited to group")
|
||||
)
|
||||
: chatPreviewInfoText("you are invited to group")
|
||||
}
|
||||
|
||||
@ViewBuilder private func chatPreviewInfoText(_ text: LocalizedStringKey) -> some View {
|
||||
@@ -229,7 +221,7 @@ struct ChatPreviewView: View {
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact):
|
||||
switch (chatModel.contactNetworkStatus(contact)) {
|
||||
case .connected: EmptyView()
|
||||
case .connected: incognitoIcon(chat.chatInfo.incognito)
|
||||
case .error:
|
||||
Image(systemName: "exclamationmark.circle")
|
||||
.resizable()
|
||||
@@ -240,11 +232,23 @@ struct ChatPreviewView: View {
|
||||
ProgressView()
|
||||
}
|
||||
default:
|
||||
EmptyView()
|
||||
incognitoIcon(chat.chatInfo.incognito)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func incognitoIcon(_ incognito: Bool) -> some View {
|
||||
if incognito {
|
||||
Image(systemName: "theatermasks")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 22, height: 22)
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
func unreadCountText(_ n: Int) -> Text {
|
||||
Text(n > 999 ? "\(n / 1000)k" : n > 0 ? "\(n)" : "")
|
||||
}
|
||||
@@ -258,20 +262,20 @@ struct ChatPreviewView_Previews: PreviewProvider {
|
||||
))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent)]
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete))]
|
||||
))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent)],
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete))],
|
||||
chatStats: ChatStats(unreadCount: 11, minUnreadItemId: 0)
|
||||
))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemDeleted: .deleted(deletedTs: .now))]
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now))]
|
||||
))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent)],
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete))],
|
||||
chatStats: ChatStats(unreadCount: 3, minUnreadItemId: 0)
|
||||
))
|
||||
ChatPreviewView(chat: Chat(
|
||||
|
||||
@@ -15,6 +15,7 @@ struct ContactConnectionInfo: View {
|
||||
@State var contactConnection: PendingContactConnection
|
||||
@State private var alert: CCInfoAlert?
|
||||
@State private var localAlias = ""
|
||||
@State private var showIncognitoSheet = false
|
||||
@FocusState private var aliasTextFieldFocused: Bool
|
||||
|
||||
enum CCInfoAlert: Identifiable {
|
||||
@@ -31,19 +32,14 @@ struct ContactConnectionInfo: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
let v = List {
|
||||
Group {
|
||||
Text(contactConnection.initiated ? "You invited your contact" : "You accepted connection")
|
||||
Text(contactConnection.initiated ? "You invited a contact" : "You accepted connection")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.padding(.bottom, 16)
|
||||
.padding(.bottom)
|
||||
|
||||
Text(contactConnectionText(contactConnection))
|
||||
.padding(.bottom, 16)
|
||||
|
||||
if let connReqInv = contactConnection.connReqInv {
|
||||
OneTimeLinkProfileText(contactConnection: contactConnection, connReqInvitation: connReqInv)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
@@ -65,10 +61,16 @@ struct ContactConnectionInfo: View {
|
||||
|
||||
if contactConnection.initiated,
|
||||
let connReqInv = contactConnection.connReqInv {
|
||||
oneTimeLinkSection(contactConnection: contactConnection, connReqInvitation: connReqInv)
|
||||
QRCode(uri: connReqInv)
|
||||
incognitoEnabled()
|
||||
shareLinkButton(connReqInv)
|
||||
oneTimeLinkLearnMoreButton()
|
||||
} else {
|
||||
incognitoEnabled()
|
||||
oneTimeLinkLearnMoreButton()
|
||||
}
|
||||
} footer: {
|
||||
sharedProfileInfo(contactConnection.incognito)
|
||||
}
|
||||
|
||||
Section {
|
||||
@@ -80,6 +82,14 @@ struct ContactConnectionInfo: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
if #available(iOS 16, *) {
|
||||
v
|
||||
} else {
|
||||
// navigationBarHidden is added conditionally,
|
||||
// because the view jumps in iOS 17 if this is added,
|
||||
// and on iOS 16+ it is hidden without it.
|
||||
v.navigationBarHidden(true)
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { _alert in
|
||||
switch _alert {
|
||||
@@ -128,6 +138,30 @@ struct ContactConnectionInfo: View {
|
||||
)
|
||||
: "You will be connected when your contact's device is online, please wait or check later!"
|
||||
}
|
||||
|
||||
@ViewBuilder private func incognitoEnabled() -> some View {
|
||||
if contactConnection.incognito {
|
||||
ZStack(alignment: .leading) {
|
||||
Image(systemName: "theatermasks.fill")
|
||||
.frame(maxWidth: 24, maxHeight: 24, alignment: .center)
|
||||
.foregroundColor(Color.indigo)
|
||||
.font(.system(size: 14))
|
||||
HStack(spacing: 6) {
|
||||
Text("Incognito")
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
.onTapGesture {
|
||||
showIncognitoSheet = true
|
||||
}
|
||||
.padding(.leading, 36)
|
||||
}
|
||||
.sheet(isPresented: $showIncognitoSheet) {
|
||||
IncognitoHelp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContactConnectionInfo_Previews: PreviewProvider {
|
||||
|
||||
@@ -58,10 +58,14 @@ struct ContactConnectionView: View {
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
|
||||
Text(contactConnection.description)
|
||||
.frame(alignment: .topLeading)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 2)
|
||||
ZStack(alignment: .topTrailing) {
|
||||
Text(contactConnection.description)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
incognitoIcon(contactConnection.incognito)
|
||||
.padding(.top, 26)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ struct ContactRequestView: View {
|
||||
Text(contactRequest.chatViewName)
|
||||
.font(.title3)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(chatModel.incognito ? .indigo : .accentColor)
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(.leading, 8)
|
||||
.frame(alignment: .topLeading)
|
||||
Spacer()
|
||||
|
||||
@@ -65,7 +65,7 @@ struct MigrateToAppGroupView: View {
|
||||
case .exporting:
|
||||
center {
|
||||
ProgressView(value: 0.33)
|
||||
Text("Exporting database archive...")
|
||||
Text("Exporting database archive…")
|
||||
}
|
||||
migrationProgress()
|
||||
case .export_error:
|
||||
@@ -82,7 +82,7 @@ struct MigrateToAppGroupView: View {
|
||||
case .migrating:
|
||||
center {
|
||||
ProgressView(value: 0.67)
|
||||
Text("Migrating database archive...")
|
||||
Text("Migrating database archive…")
|
||||
}
|
||||
migrationProgress()
|
||||
case .migration_error:
|
||||
|
||||
9
apps/ios/Shared/Views/Helpers/Keyboard.swift
Normal file
9
apps/ios/Shared/Views/Helpers/Keyboard.swift
Normal file
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// Keyboard.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 10/07/2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
21
apps/ios/Shared/Views/Helpers/KeyboardPadding.swift
Normal file
21
apps/ios/Shared/Views/Helpers/KeyboardPadding.swift
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// KeyboardPadding.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 10/07/2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
@ViewBuilder func keyboardPadding() -> some View {
|
||||
if #available(iOS 17.0, *) {
|
||||
GeometryReader { g in
|
||||
self.padding(.bottom, max(0, ChatModel.shared.keyboardHeight - g.safeAreaInsets.bottom))
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ struct LocalAuthRequest {
|
||||
}
|
||||
|
||||
func authenticate(title: LocalizedStringKey? = nil, reason: String, selfDestruct: Bool = false, completed: @escaping (LAResult) -> Void) {
|
||||
logger.debug("authenticate")
|
||||
logger.debug("DEBUGGING: authenticate")
|
||||
switch privacyLocalAuthModeDefault.get() {
|
||||
case .system: systemAuthenticate(reason, completed)
|
||||
case .passcode:
|
||||
@@ -58,21 +58,24 @@ func authenticate(title: LocalizedStringKey? = nil, reason: String, selfDestruct
|
||||
}
|
||||
|
||||
func systemAuthenticate(_ reason: String, _ completed: @escaping (LAResult) -> Void) {
|
||||
logger.debug("DEBUGGING: systemAuthenticate")
|
||||
let laContext = LAContext()
|
||||
var authAvailabilityError: NSError?
|
||||
if laContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: &authAvailabilityError) {
|
||||
logger.debug("DEBUGGING: systemAuthenticate: canEvaluatePolicy callback")
|
||||
laContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, authError in
|
||||
logger.debug("DEBUGGING: systemAuthenticate evaluatePolicy callback")
|
||||
DispatchQueue.main.async {
|
||||
if success {
|
||||
completed(LAResult.success)
|
||||
} else {
|
||||
logger.error("authentication error: \(authError.debugDescription)")
|
||||
logger.error("DEBUGGING: systemAuthenticate authentication error: \(authError.debugDescription)")
|
||||
completed(LAResult.failed(authError: authError?.localizedDescription))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.error("authentication availability error: \(authAvailabilityError.debugDescription)")
|
||||
logger.error("DEBUGGING: authentication availability error: \(authAvailabilityError.debugDescription)")
|
||||
completed(LAResult.unavailable(authError: authAvailabilityError?.localizedDescription))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,38 +12,92 @@ import SimpleXChat
|
||||
|
||||
struct AddContactView: View {
|
||||
@EnvironmentObject private var chatModel: ChatModel
|
||||
var contactConnection: PendingContactConnection? = nil
|
||||
@Binding var contactConnection: PendingContactConnection?
|
||||
var connReqInvitation: String
|
||||
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
OneTimeLinkProfileText(contactConnection: contactConnection, connReqInvitation: connReqInvitation)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
Section("1-time link") {
|
||||
oneTimeLinkSection(contactConnection: contactConnection, connReqInvitation: connReqInvitation)
|
||||
VStack {
|
||||
List {
|
||||
Section {
|
||||
if connReqInvitation != "" {
|
||||
QRCode(uri: connReqInvitation)
|
||||
} else {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.scaleEffect(2)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical)
|
||||
}
|
||||
IncognitoToggle(incognitoEnabled: $incognitoDefault)
|
||||
.disabled(contactConnection == nil)
|
||||
shareLinkButton(connReqInvitation)
|
||||
oneTimeLinkLearnMoreButton()
|
||||
} header: {
|
||||
Text("1-time link")
|
||||
} footer: {
|
||||
sharedProfileInfo(incognitoDefault)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear { chatModel.connReqInv = connReqInvitation }
|
||||
.onChange(of: incognitoDefault) { incognito in
|
||||
Task {
|
||||
do {
|
||||
if let contactConn = contactConnection,
|
||||
let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) {
|
||||
await MainActor.run {
|
||||
contactConnection = conn
|
||||
ChatModel.shared.updateContactConnection(conn)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.error("apiSetConnectionIncognito error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func oneTimeLinkSection(contactConnection: PendingContactConnection? = nil, connReqInvitation: String) -> some View {
|
||||
if connReqInvitation != "" {
|
||||
QRCode(uri: connReqInvitation)
|
||||
} else {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.scaleEffect(2)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical)
|
||||
struct IncognitoToggle: View {
|
||||
@Binding var incognitoEnabled: Bool
|
||||
@State private var showIncognitoSheet = false
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .leading) {
|
||||
Image(systemName: incognitoEnabled ? "theatermasks.fill" : "theatermasks")
|
||||
.frame(maxWidth: 24, maxHeight: 24, alignment: .center)
|
||||
.foregroundColor(incognitoEnabled ? Color.indigo : .secondary)
|
||||
.font(.system(size: 14))
|
||||
Toggle(isOn: $incognitoEnabled) {
|
||||
HStack(spacing: 6) {
|
||||
Text("Incognito")
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
.onTapGesture {
|
||||
showIncognitoSheet = true
|
||||
}
|
||||
}
|
||||
.padding(.leading, 36)
|
||||
}
|
||||
.sheet(isPresented: $showIncognitoSheet) {
|
||||
IncognitoHelp()
|
||||
}
|
||||
}
|
||||
shareLinkButton(connReqInvitation)
|
||||
oneTimeLinkLearnMoreButton()
|
||||
}
|
||||
|
||||
private func shareLinkButton(_ connReqInvitation: String) -> some View {
|
||||
func sharedProfileInfo(_ incognito: Bool) -> Text {
|
||||
let name = ChatModel.shared.currentUser?.displayName ?? ""
|
||||
return Text(
|
||||
incognito
|
||||
? "A new random profile will be shared."
|
||||
: "Your profile **\(name)** will be shared."
|
||||
)
|
||||
}
|
||||
|
||||
func shareLinkButton(_ connReqInvitation: String) -> some View {
|
||||
Button {
|
||||
showShareSheet(items: [connReqInvitation])
|
||||
} label: {
|
||||
@@ -65,26 +119,11 @@ func oneTimeLinkLearnMoreButton() -> some View {
|
||||
}
|
||||
}
|
||||
|
||||
struct OneTimeLinkProfileText: View {
|
||||
@EnvironmentObject private var chatModel: ChatModel
|
||||
var contactConnection: PendingContactConnection? = nil
|
||||
var connReqInvitation: String
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
if (contactConnection?.incognito ?? chatModel.incognito) {
|
||||
Image(systemName: "theatermasks").foregroundColor(.indigo)
|
||||
Text("A random profile will be sent to your contact")
|
||||
} else {
|
||||
Image(systemName: "info.circle").foregroundColor(.secondary)
|
||||
Text("Your chat profile will be sent to your contact")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddContactView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddContactView(connReqInvitation: "https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D")
|
||||
AddContactView(
|
||||
contactConnection: Binding.constant(PendingContactConnection.getSampleData()),
|
||||
connReqInvitation: "https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ struct AddGroupView: View {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createGroupView()
|
||||
createGroupView().keyboardPadding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,21 +47,13 @@ struct AddGroupView: View {
|
||||
.padding(.vertical, 4)
|
||||
Text("The group is fully decentralized – it is visible only to the members.")
|
||||
.padding(.bottom, 4)
|
||||
if (m.incognito) {
|
||||
HStack {
|
||||
Image(systemName: "info.circle").foregroundColor(.orange).font(.footnote)
|
||||
Spacer().frame(width: 8)
|
||||
Text("Incognito mode is not supported here - your main profile will be sent to group members").font(.footnote)
|
||||
}
|
||||
.padding(.bottom)
|
||||
} else {
|
||||
HStack {
|
||||
Image(systemName: "info.circle").foregroundColor(.secondary).font(.footnote)
|
||||
Spacer().frame(width: 8)
|
||||
Text("Your chat profile will be sent to group members").font(.footnote)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
HStack {
|
||||
Image(systemName: "info.circle").foregroundColor(.secondary).font(.footnote)
|
||||
Spacer().frame(width: 8)
|
||||
Text("Your chat profile will be sent to group members").font(.footnote)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
ZStack(alignment: .center) {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
enum CreateLinkTab {
|
||||
case oneTime
|
||||
@@ -24,6 +25,7 @@ struct CreateLinkView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@State var selection: CreateLinkTab
|
||||
@State var connReqInvitation: String = ""
|
||||
@State var contactConnection: PendingContactConnection? = nil
|
||||
@State private var creatingConnReq = false
|
||||
var viaNavLink = false
|
||||
|
||||
@@ -39,7 +41,7 @@ struct CreateLinkView: View {
|
||||
|
||||
private func createLinkView() -> some View {
|
||||
TabView(selection: $selection) {
|
||||
AddContactView(connReqInvitation: connReqInvitation)
|
||||
AddContactView(contactConnection: $contactConnection, connReqInvitation: connReqInvitation)
|
||||
.tabItem {
|
||||
Label(
|
||||
connReqInvitation == ""
|
||||
@@ -56,7 +58,7 @@ struct CreateLinkView: View {
|
||||
.tag(CreateLinkTab.longTerm)
|
||||
}
|
||||
.onChange(of: selection) { _ in
|
||||
if case .oneTime = selection, connReqInvitation == "" && !creatingConnReq {
|
||||
if case .oneTime = selection, connReqInvitation == "", contactConnection == nil && !creatingConnReq {
|
||||
createInvitation()
|
||||
}
|
||||
}
|
||||
@@ -69,12 +71,14 @@ struct CreateLinkView: View {
|
||||
private func createInvitation() {
|
||||
creatingConnReq = true
|
||||
Task {
|
||||
let connReq = await apiAddContact()
|
||||
await MainActor.run {
|
||||
if let connReq = connReq {
|
||||
if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) {
|
||||
await MainActor.run {
|
||||
connReqInvitation = connReq
|
||||
contactConnection = pcc
|
||||
m.connReqInv = connReq
|
||||
} else {
|
||||
}
|
||||
} else {
|
||||
await MainActor.run {
|
||||
creatingConnReq = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
enum NewChatAction: Identifiable {
|
||||
case createLink(link: String)
|
||||
case createLink(link: String, connection: PendingContactConnection)
|
||||
case connectViaLink
|
||||
case createGroup
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .createLink(link): return "createLink \(link)"
|
||||
case let .createLink(link, _): return "createLink \(link)"
|
||||
case .connectViaLink: return "connectViaLink"
|
||||
case .createGroup: return "createGroup"
|
||||
}
|
||||
@@ -41,8 +41,8 @@ struct NewChatButton: View {
|
||||
}
|
||||
.sheet(item: $actionSheet) { sheet in
|
||||
switch sheet {
|
||||
case let .createLink(link):
|
||||
CreateLinkView(selection: .oneTime, connReqInvitation: link)
|
||||
case let .createLink(link, pcc):
|
||||
CreateLinkView(selection: .oneTime, connReqInvitation: link, contactConnection: pcc)
|
||||
case .connectViaLink: ConnectViaLinkView()
|
||||
case .createGroup: AddGroupView()
|
||||
}
|
||||
@@ -51,8 +51,8 @@ struct NewChatButton: View {
|
||||
|
||||
func addContactAction() {
|
||||
Task {
|
||||
if let connReq = await apiAddContact() {
|
||||
actionSheet = .createLink(link: connReq)
|
||||
if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) {
|
||||
actionSheet = .createLink(link: connReq, connection: pcc)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,9 +63,9 @@ enum ConnReqType: Equatable {
|
||||
case invitation
|
||||
}
|
||||
|
||||
func connectViaLink(_ connectionLink: String, _ dismiss: DismissAction? = nil) {
|
||||
func connectViaLink(_ connectionLink: String, dismiss: DismissAction? = nil, incognito: Bool) {
|
||||
Task {
|
||||
if let connReqType = await apiConnect(connReq: connectionLink) {
|
||||
if let connReqType = await apiConnect(incognito: incognito, connReq: connectionLink) {
|
||||
DispatchQueue.main.async {
|
||||
dismiss?()
|
||||
AlertManager.shared.showAlert(connReqSentAlert(connReqType))
|
||||
@@ -100,12 +100,12 @@ func checkCRDataGroup(_ crData: CReqClientData) -> Bool {
|
||||
return crData.type == "group" && crData.groupLinkId != nil
|
||||
}
|
||||
|
||||
func groupLinkAlert(_ connectionLink: String) -> Alert {
|
||||
func groupLinkAlert(_ connectionLink: String, incognito: Bool) -> Alert {
|
||||
return Alert(
|
||||
title: Text("Connect via group link?"),
|
||||
message: Text("You will join a group this link refers to and connect to its group members."),
|
||||
primaryButton: .default(Text("Connect")) {
|
||||
connectViaLink(connectionLink)
|
||||
primaryButton: .default(Text(incognito ? "Connect incognito" : "Connect")) {
|
||||
connectViaLink(connectionLink, incognito: incognito)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
|
||||
@@ -7,76 +7,77 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct PasteToConnectView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@State private var connectionLink: String = ""
|
||||
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
|
||||
@FocusState private var linkEditorFocused: Bool
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Connect via link")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.vertical)
|
||||
Text("Paste the link you received into the box below to connect with your contact.")
|
||||
.padding(.bottom, 4)
|
||||
if (chatModel.incognito) {
|
||||
HStack {
|
||||
Image(systemName: "theatermasks").foregroundColor(.indigo).font(.footnote)
|
||||
Spacer().frame(width: 8)
|
||||
Text("A random profile will be sent to the contact that you received this link from").font(.footnote)
|
||||
List {
|
||||
Text("Connect via link")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
.onTapGesture { linkEditorFocused = false }
|
||||
|
||||
Section {
|
||||
linkEditor()
|
||||
|
||||
Button {
|
||||
if connectionLink == "" {
|
||||
connectionLink = UIPasteboard.general.string ?? ""
|
||||
} else {
|
||||
connectionLink = ""
|
||||
}
|
||||
.padding(.bottom)
|
||||
} else {
|
||||
HStack {
|
||||
Image(systemName: "info.circle").foregroundColor(.secondary).font(.footnote)
|
||||
Spacer().frame(width: 8)
|
||||
Text("Your profile will be sent to the contact that you received this link from").font(.footnote)
|
||||
} label: {
|
||||
if connectionLink == "" {
|
||||
settingsRow("doc.plaintext") { Text("Paste") }
|
||||
} else {
|
||||
settingsRow("multiply") { Text("Clear") }
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
|
||||
Button {
|
||||
connect()
|
||||
} label: {
|
||||
settingsRow("link") { Text("Connect") }
|
||||
}
|
||||
.disabled(connectionLink == "" || connectionLink.trimmingCharacters(in: .whitespaces).firstIndex(of: " ") != nil)
|
||||
|
||||
IncognitoToggle(incognitoEnabled: $incognitoDefault)
|
||||
} footer: {
|
||||
sharedProfileInfo(incognitoDefault)
|
||||
+ Text(String("\n\n"))
|
||||
+ Text("You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func linkEditor() -> some View {
|
||||
ZStack {
|
||||
Group {
|
||||
if connectionLink.isEmpty {
|
||||
TextEditor(text: Binding.constant(NSLocalizedString("Paste the link you received to connect with your contact.", comment: "placeholder")))
|
||||
.foregroundColor(.secondary)
|
||||
.disabled(true)
|
||||
}
|
||||
TextEditor(text: $connectionLink)
|
||||
.onSubmit(connect)
|
||||
.textInputAutocapitalization(.never)
|
||||
.disableAutocorrection(true)
|
||||
.allowsTightening(false)
|
||||
.frame(height: 180)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.strokeBorder(.secondary, lineWidth: 0.3, antialiased: true)
|
||||
)
|
||||
|
||||
HStack(spacing: 20) {
|
||||
if connectionLink == "" {
|
||||
Button {
|
||||
connectionLink = UIPasteboard.general.string ?? ""
|
||||
} label: {
|
||||
Label("Paste", systemImage: "doc.plaintext")
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
connectionLink = ""
|
||||
} label: {
|
||||
Label("Clear", systemImage: "multiply")
|
||||
}
|
||||
|
||||
}
|
||||
Spacer()
|
||||
Button(action: connect, label: {
|
||||
Label("Connect", systemImage: "link")
|
||||
})
|
||||
.disabled(connectionLink == "" || connectionLink.trimmingCharacters(in: .whitespaces).firstIndex(of: " ") != nil)
|
||||
}
|
||||
.frame(height: 48)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button.")
|
||||
.focused($linkEditorFocused)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
.allowsTightening(false)
|
||||
.padding(.horizontal, -5)
|
||||
.padding(.top, -8)
|
||||
.frame(height: 180, alignment: .topLeading)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +86,9 @@ struct PasteToConnectView: View {
|
||||
if let crData = parseLinkQueryData(link),
|
||||
checkCRDataGroup(crData) {
|
||||
dismiss()
|
||||
AlertManager.shared.showAlert(groupLinkAlert(link))
|
||||
AlertManager.shared.showAlert(groupLinkAlert(link, incognito: incognitoDefault))
|
||||
} else {
|
||||
connectViaLink(link, dismiss)
|
||||
connectViaLink(link, dismiss: dismiss, incognito: incognitoDefault)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
import CodeScanner
|
||||
|
||||
struct ScanToConnectView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
@@ -19,34 +20,35 @@ struct ScanToConnectView: View {
|
||||
Text("Scan QR code")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.vertical)
|
||||
if (chatModel.incognito) {
|
||||
HStack {
|
||||
Image(systemName: "theatermasks").foregroundColor(.indigo).font(.footnote)
|
||||
Spacer().frame(width: 8)
|
||||
Text("A random profile will be sent to your contact").font(.footnote)
|
||||
}
|
||||
.padding(.bottom)
|
||||
} else {
|
||||
HStack {
|
||||
Image(systemName: "info.circle").foregroundColor(.secondary).font(.footnote)
|
||||
Spacer().frame(width: 8)
|
||||
Text("Your chat profile will be sent to your contact").font(.footnote)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.cornerRadius(12)
|
||||
|
||||
IncognitoToggle(incognitoEnabled: $incognitoDefault)
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 6)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(Color(uiColor: .systemBackground))
|
||||
)
|
||||
.padding(.top)
|
||||
|
||||
Group {
|
||||
sharedProfileInfo(incognitoDefault)
|
||||
+ Text(String("\n\n"))
|
||||
+ Text("If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.")
|
||||
}
|
||||
ZStack {
|
||||
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.border(.gray)
|
||||
}
|
||||
.padding(.bottom)
|
||||
Text("If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.")
|
||||
.padding(.bottom)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
}
|
||||
|
||||
func processQRCode(_ resp: Result<ScanResult, ScanError>) {
|
||||
@@ -55,9 +57,9 @@ struct ScanToConnectView: View {
|
||||
if let crData = parseLinkQueryData(r.string),
|
||||
checkCRDataGroup(crData) {
|
||||
dismiss()
|
||||
AlertManager.shared.showAlert(groupLinkAlert(r.string))
|
||||
AlertManager.shared.showAlert(groupLinkAlert(r.string, incognito: incognitoDefault))
|
||||
} else {
|
||||
Task { connectViaLink(r.string, dismiss) }
|
||||
Task { connectViaLink(r.string, dismiss: dismiss, incognito: incognitoDefault) }
|
||||
}
|
||||
case let .failure(e):
|
||||
logger.error("ConnectContactView.processQRCode QR code error: \(e.localizedDescription)")
|
||||
|
||||
@@ -104,6 +104,7 @@ struct CreateProfile: View {
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.keyboardPadding()
|
||||
}
|
||||
|
||||
func textField(_ placeholder: LocalizedStringKey, text: Binding<String>) -> some View {
|
||||
|
||||
@@ -220,6 +220,37 @@ private let versionDescriptions: [VersionDescription] = [
|
||||
description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"
|
||||
),
|
||||
]
|
||||
),
|
||||
VersionDescription(
|
||||
version: "v5.2",
|
||||
post: URL(string: "https://simplex.chat/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.html"),
|
||||
features: [
|
||||
FeatureDescription(
|
||||
icon: "checkmark",
|
||||
title: "Message delivery receipts!",
|
||||
description: "The second tick we missed! ✅"
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "star",
|
||||
title: "Find chats faster",
|
||||
description: "Filter unread and favorite chats."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "exclamationmark.arrow.triangle.2.circlepath",
|
||||
title: "Keep your connections",
|
||||
description: "Fix encryption after restoring backups."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "stopwatch",
|
||||
title: "Make one message disappear",
|
||||
description: "Even when disabled in the conversation."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "gift",
|
||||
title: "A few more things",
|
||||
description: "- more stable message delivery.\n- a bit better groups.\n- and more!"
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ struct TerminalView: View {
|
||||
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@State var composeState: ComposeState = ComposeState()
|
||||
@FocusState private var keyboardVisible: Bool
|
||||
@State private var keyboardVisible = false
|
||||
@State var authorized = !UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)
|
||||
@State private var terminalItem: TerminalItem?
|
||||
@State private var scrolled = false
|
||||
|
||||
@@ -51,8 +51,9 @@ struct AdvancedNetworkSettings: View {
|
||||
}
|
||||
.disabled(currentNetCfg == NetCfg.proxyDefaults)
|
||||
|
||||
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [2_500000, 5_000000, 7_500000, 10_000000, 15_000000, 20_000000], label: secondsLabel)
|
||||
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [1_500000, 3_000000, 5_000000, 7_000000, 10_000000, 15_000000], label: secondsLabel)
|
||||
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [5_000000, 7_500000, 10_000000, 15_000000, 20_000000, 30_000000, 45_000000], label: secondsLabel)
|
||||
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [3_000000, 5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel)
|
||||
timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [10_000, 20_000, 40_000, 75_000, 100_000], label: secondsLabel)
|
||||
timeoutSettingPicker("PING interval", selection: $netCfg.smpPingInterval, values: [120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000, 3600_000000], label: secondsLabel)
|
||||
intSettingPicker("PING count", selection: $netCfg.smpPingCount, values: [1, 2, 3, 5, 8], label: "")
|
||||
Toggle("Enable TCP keep-alive", isOn: $enableKeepAlive)
|
||||
@@ -152,7 +153,9 @@ struct AdvancedNetworkSettings: View {
|
||||
|
||||
private func timeoutSettingPicker(_ title: LocalizedStringKey, selection: Binding<Int>, values: [Int], label: String) -> some View {
|
||||
Picker(title, selection: selection) {
|
||||
ForEach(values, id: \.self) { value in
|
||||
let v = selection.wrappedValue
|
||||
let vs = values.contains(v) ? values : values + [v]
|
||||
ForEach(vs, id: \.self) { value in
|
||||
Text("\(String(format: "%g", (Double(value) / 1000000))) \(secondsLabel)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,9 @@ struct IncognitoHelp: View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
Group {
|
||||
Text("Incognito mode protects the privacy of your main profile name and image — for each new contact a new random profile is created.")
|
||||
Text("Incognito mode protects your privacy by using a new random profile for each contact.")
|
||||
Text("It allows having many anonymous connections without any shared data between them in a single chat profile.")
|
||||
Text("When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.")
|
||||
Text("To find the profile used for an incognito connection, tap the contact or group name on top of the chat.")
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
|
||||
@@ -11,9 +11,12 @@ import SimpleXChat
|
||||
|
||||
struct NotificationsView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@State private var notificationMode: NotificationsMode?
|
||||
@State private var notificationMode: NotificationsMode = ChatModel.shared.notificationMode
|
||||
@State private var showAlert: NotificationAlert?
|
||||
@State private var legacyDatabase = dbContainerGroupDefault.get() == .documents
|
||||
// @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
// @AppStorage(GROUP_DEFAULT_NTF_ENABLE_LOCAL, store: groupDefaults) private var ntfEnableLocal = false
|
||||
// @AppStorage(GROUP_DEFAULT_NTF_ENABLE_PERIODIC, store: groupDefaults) private var ntfEnablePeriodic = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
@@ -26,9 +29,7 @@ struct NotificationsView: View {
|
||||
}
|
||||
} footer: {
|
||||
VStack(alignment: .leading) {
|
||||
if let mode = notificationMode {
|
||||
Text(ntfModeDescription(mode))
|
||||
}
|
||||
Text(ntfModeDescription(notificationMode))
|
||||
}
|
||||
.font(.callout)
|
||||
.padding(.top, 1)
|
||||
@@ -43,7 +44,6 @@ struct NotificationsView: View {
|
||||
return Alert(title: Text("No device token!"))
|
||||
}
|
||||
}
|
||||
.onAppear { notificationMode = m.notificationMode }
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Send notifications")
|
||||
@@ -76,7 +76,7 @@ struct NotificationsView: View {
|
||||
HStack {
|
||||
Text("Show preview")
|
||||
Spacer()
|
||||
Text(m.notificationPreview?.label ?? "")
|
||||
Text(m.notificationPreview.label)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
@@ -88,8 +88,15 @@ struct NotificationsView: View {
|
||||
.padding(.top, 1)
|
||||
}
|
||||
}
|
||||
.disabled(legacyDatabase)
|
||||
|
||||
// if developerTools {
|
||||
// Section(String("Experimental")) {
|
||||
// Toggle(String("Always enable local"), isOn: $ntfEnableLocal)
|
||||
// Toggle(String("Always enable periodic"), isOn: $ntfEnablePeriodic)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
.disabled(legacyDatabase)
|
||||
}
|
||||
|
||||
private func notificationAlert(_ alert: NotificationAlert, _ token: DeviceToken) -> Alert {
|
||||
@@ -166,7 +173,7 @@ func ntfModeDescription(_ mode: NotificationsMode) -> LocalizedStringKey {
|
||||
|
||||
struct SelectionListView<Item: SelectableItem>: View {
|
||||
var list: [Item]
|
||||
@Binding var selection: Item?
|
||||
@Binding var selection: Item
|
||||
var onSelection: ((Item) -> Void)?
|
||||
@State private var tapped: Item? = nil
|
||||
|
||||
|
||||
@@ -10,12 +10,32 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct PrivacySettings: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@AppStorage(DEFAULT_PRIVACY_ACCEPT_IMAGES) private var autoAcceptImages = true
|
||||
@AppStorage(DEFAULT_PRIVACY_LINK_PREVIEWS) private var useLinkPreviews = true
|
||||
@State private var simplexLinkMode = privacySimplexLinkModeDefault.get()
|
||||
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
|
||||
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
|
||||
@State private var currentLAMode = privacyLocalAuthModeDefault.get()
|
||||
@State private var contactReceipts = false
|
||||
@State private var contactReceiptsReset = false
|
||||
@State private var contactReceiptsOverrides = 0
|
||||
@State private var contactReceiptsDialogue = false
|
||||
@State private var groupReceipts = false
|
||||
@State private var groupReceiptsReset = false
|
||||
@State private var groupReceiptsOverrides = 0
|
||||
@State private var groupReceiptsDialogue = false
|
||||
@State private var alert: PrivacySettingsViewAlert?
|
||||
|
||||
enum PrivacySettingsViewAlert: Identifiable {
|
||||
case error(title: LocalizedStringKey, error: LocalizedStringKey = "")
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .error(title, _): return "error \(title)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
@@ -68,6 +88,175 @@ struct PrivacySettings: View {
|
||||
Text("Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.")
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
settingsRow("person") {
|
||||
Toggle("Contacts", isOn: $contactReceipts)
|
||||
}
|
||||
settingsRow("person.2") {
|
||||
Toggle("Small groups (max 20)", isOn: $groupReceipts)
|
||||
}
|
||||
} header: {
|
||||
Text("Send delivery receipts to")
|
||||
} footer: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("These settings are for your current profile **\(ChatModel.shared.currentUser?.displayName ?? "")**.")
|
||||
Text("They can be overridden in contact and group settings.")
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.confirmationDialog(contactReceiptsDialogTitle, isPresented: $contactReceiptsDialogue, titleVisibility: .visible) {
|
||||
Button(contactReceipts ? "Enable (keep overrides)" : "Disable (keep overrides)") {
|
||||
setSendReceiptsContacts(contactReceipts, clearOverrides: false)
|
||||
}
|
||||
Button(contactReceipts ? "Enable for all" : "Disable for all", role: .destructive) {
|
||||
setSendReceiptsContacts(contactReceipts, clearOverrides: true)
|
||||
}
|
||||
Button("Cancel", role: .cancel) {
|
||||
contactReceiptsReset = true
|
||||
contactReceipts.toggle()
|
||||
}
|
||||
}
|
||||
.confirmationDialog(groupReceiptsDialogTitle, isPresented: $groupReceiptsDialogue, titleVisibility: .visible) {
|
||||
Button(groupReceipts ? "Enable (keep overrides)" : "Disable (keep overrides)") {
|
||||
setSendReceiptsGroups(groupReceipts, clearOverrides: false)
|
||||
}
|
||||
Button(contactReceipts ? "Enable for all" : "Disable for all", role: .destructive) {
|
||||
setSendReceiptsGroups(groupReceipts, clearOverrides: true)
|
||||
}
|
||||
Button("Cancel", role: .cancel) {
|
||||
groupReceiptsReset = true
|
||||
groupReceipts.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: contactReceipts) { _ in
|
||||
if contactReceiptsReset {
|
||||
contactReceiptsReset = false
|
||||
} else {
|
||||
setOrAskSendReceiptsContacts(contactReceipts)
|
||||
}
|
||||
}
|
||||
.onChange(of: groupReceipts) { _ in
|
||||
if groupReceiptsReset {
|
||||
groupReceiptsReset = false
|
||||
} else {
|
||||
setOrAskSendReceiptsGroups(groupReceipts)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if let u = m.currentUser {
|
||||
if contactReceipts != u.sendRcptsContacts {
|
||||
contactReceiptsReset = true
|
||||
contactReceipts = u.sendRcptsContacts
|
||||
}
|
||||
if groupReceipts != u.sendRcptsSmallGroups {
|
||||
groupReceiptsReset = true
|
||||
groupReceipts = u.sendRcptsSmallGroups
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { alert in
|
||||
switch alert {
|
||||
case let .error(title, error):
|
||||
return Alert(title: Text(title), message: Text(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setOrAskSendReceiptsContacts(_ enable: Bool) {
|
||||
contactReceiptsOverrides = m.chats.reduce(0) { count, chat in
|
||||
let sendRcpts = chat.chatInfo.contact?.chatSettings.sendRcpts
|
||||
return count + (sendRcpts == nil || sendRcpts == enable ? 0 : 1)
|
||||
}
|
||||
if contactReceiptsOverrides == 0 {
|
||||
setSendReceiptsContacts(enable, clearOverrides: false)
|
||||
} else {
|
||||
contactReceiptsDialogue = true
|
||||
}
|
||||
}
|
||||
|
||||
private var contactReceiptsDialogTitle: LocalizedStringKey {
|
||||
contactReceipts
|
||||
? "Sending receipts is disabled for \(contactReceiptsOverrides) contacts"
|
||||
: "Sending receipts is enabled for \(contactReceiptsOverrides) contacts"
|
||||
}
|
||||
|
||||
private func setSendReceiptsContacts(_ enable: Bool, clearOverrides: Bool) {
|
||||
Task {
|
||||
do {
|
||||
if let currentUser = m.currentUser {
|
||||
let userMsgReceiptSettings = UserMsgReceiptSettings(enable: enable, clearOverrides: clearOverrides)
|
||||
try await apiSetUserContactReceipts(currentUser.userId, userMsgReceiptSettings: userMsgReceiptSettings)
|
||||
privacyDeliveryReceiptsSet.set(true)
|
||||
await MainActor.run {
|
||||
var updatedUser = currentUser
|
||||
updatedUser.sendRcptsContacts = enable
|
||||
m.updateUser(updatedUser)
|
||||
if clearOverrides {
|
||||
m.chats.forEach { chat in
|
||||
if var contact = chat.chatInfo.contact {
|
||||
let sendRcpts = contact.chatSettings.sendRcpts
|
||||
if sendRcpts != nil && sendRcpts != enable {
|
||||
contact.chatSettings.sendRcpts = nil
|
||||
m.updateContact(contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
alert = .error(title: "Error setting delivery receipts!", error: "Error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setOrAskSendReceiptsGroups(_ enable: Bool) {
|
||||
groupReceiptsOverrides = m.chats.reduce(0) { count, chat in
|
||||
let sendRcpts = chat.chatInfo.groupInfo?.chatSettings.sendRcpts
|
||||
return count + (sendRcpts == nil || sendRcpts == enable ? 0 : 1)
|
||||
}
|
||||
if groupReceiptsOverrides == 0 {
|
||||
setSendReceiptsGroups(enable, clearOverrides: false)
|
||||
} else {
|
||||
groupReceiptsDialogue = true
|
||||
}
|
||||
}
|
||||
|
||||
private var groupReceiptsDialogTitle: LocalizedStringKey {
|
||||
groupReceipts
|
||||
? "Sending receipts is disabled for \(groupReceiptsOverrides) groups"
|
||||
: "Sending receipts is enabled for \(groupReceiptsOverrides) groups"
|
||||
}
|
||||
|
||||
private func setSendReceiptsGroups(_ enable: Bool, clearOverrides: Bool) {
|
||||
Task {
|
||||
do {
|
||||
if let currentUser = m.currentUser {
|
||||
let userMsgReceiptSettings = UserMsgReceiptSettings(enable: enable, clearOverrides: clearOverrides)
|
||||
try await apiSetUserGroupReceipts(currentUser.userId, userMsgReceiptSettings: userMsgReceiptSettings)
|
||||
privacyDeliveryReceiptsSet.set(true)
|
||||
await MainActor.run {
|
||||
var updatedUser = currentUser
|
||||
updatedUser.sendRcptsSmallGroups = enable
|
||||
m.updateUser(updatedUser)
|
||||
if clearOverrides {
|
||||
m.chats.forEach { chat in
|
||||
if var groupInfo = chat.chatInfo.groupInfo {
|
||||
let sendRcpts = groupInfo.chatSettings.sendRcpts
|
||||
if sendRcpts != nil && sendRcpts != enable {
|
||||
groupInfo.chatSettings.sendRcpts = nil
|
||||
m.updateGroup(groupInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
alert = .error(title: "Error setting delivery receipts!", error: "Error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,10 @@ struct ScanProtocolServer: View {
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.padding(.vertical)
|
||||
ZStack {
|
||||
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.border(.gray)
|
||||
}
|
||||
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.cornerRadius(12)
|
||||
.padding(.top)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
|
||||
100
apps/ios/Shared/Views/UserSettings/SetDeliveryReceiptsView.swift
Normal file
100
apps/ios/Shared/Views/UserSettings/SetDeliveryReceiptsView.swift
Normal file
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// SetDeliveryReceiptsView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 12/07/2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct SetDeliveryReceiptsView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
Text("Delivery receipts!")
|
||||
.font(.title)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.vertical)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Enable") {
|
||||
Task {
|
||||
do {
|
||||
if let currentUser = m.currentUser {
|
||||
try await apiSetAllContactReceipts(enable: true)
|
||||
await MainActor.run {
|
||||
var updatedUser = currentUser
|
||||
updatedUser.sendRcptsContacts = true
|
||||
m.updateUser(updatedUser)
|
||||
m.setDeliveryReceipts = false
|
||||
privacyDeliveryReceiptsSet.set(true)
|
||||
}
|
||||
do {
|
||||
let users = try await listUsersAsync()
|
||||
await MainActor.run { m.users = users }
|
||||
} catch let error {
|
||||
logger.debug("listUsers error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
title: Text("Error enabling delivery receipts!"),
|
||||
message: Text("Error: \(responseError(error))")
|
||||
))
|
||||
await MainActor.run {
|
||||
m.setDeliveryReceipts = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.font(.largeTitle)
|
||||
Group {
|
||||
if m.users.count > 1 {
|
||||
Text("Sending delivery receipts will be enabled for all contacts in all visible chat profiles.")
|
||||
} else {
|
||||
Text("Sending delivery receipts will be enabled for all contacts.")
|
||||
}
|
||||
}
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
title: Text("Delivery receipts are disabled!"),
|
||||
message: Text("You can enable them later via app Privacy & Security settings."),
|
||||
primaryButton: .default(Text("Don't show again")) {
|
||||
m.setDeliveryReceipts = false
|
||||
privacyDeliveryReceiptsSet.set(true)
|
||||
},
|
||||
secondaryButton: .default(Text("Ok")) {
|
||||
m.setDeliveryReceipts = false
|
||||
}
|
||||
))
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Don't enable")
|
||||
Image(systemName: "chevron.right")
|
||||
}
|
||||
}
|
||||
Text("You can enable later via Settings").font(.footnote)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.padding(.horizontal)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color(uiColor: .systemBackground))
|
||||
}
|
||||
}
|
||||
|
||||
struct SetDeliveryReceiptsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SetDeliveryReceiptsView()
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages"
|
||||
let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews"
|
||||
let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode"
|
||||
let DEFAULT_PRIVACY_PROTECT_SCREEN = "privacyProtectScreen"
|
||||
let DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET = "privacyDeliveryReceiptsSet"
|
||||
let DEFAULT_EXPERIMENTAL_CALLS = "experimentalCalls"
|
||||
let DEFAULT_CHAT_ARCHIVE_NAME = "chatArchiveName"
|
||||
let DEFAULT_CHAT_ARCHIVE_TIME = "chatArchiveTime"
|
||||
@@ -65,6 +66,7 @@ let appDefaults: [String: Any] = [
|
||||
DEFAULT_PRIVACY_LINK_PREVIEWS: true,
|
||||
DEFAULT_PRIVACY_SIMPLEX_LINK_MODE: SimpleXLinkMode.description.rawValue,
|
||||
DEFAULT_PRIVACY_PROTECT_SCREEN: false,
|
||||
DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET: false,
|
||||
DEFAULT_EXPERIMENTAL_CALLS: false,
|
||||
DEFAULT_CHAT_V3_DB_MIGRATION: V3DBMigrationState.offer.rawValue,
|
||||
DEFAULT_DEVELOPER_TOOLS: false,
|
||||
@@ -114,6 +116,8 @@ let privacySimplexLinkModeDefault = EnumDefault<SimpleXLinkMode>(defaults: UserD
|
||||
|
||||
let privacyLocalAuthModeDefault = EnumDefault<LAMode>(defaults: UserDefaults.standard, forKey: DEFAULT_LA_MODE, withDefault: .system)
|
||||
|
||||
let privacyDeliveryReceiptsSet = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET)
|
||||
|
||||
let onboardingStageDefault = EnumDefault<OnboardingStage>(defaults: UserDefaults.standard, forKey: DEFAULT_ONBOARDING_STAGE, withDefault: .onboardingComplete)
|
||||
|
||||
let customDisappearingMessageTimeDefault = IntDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CUSTOM_DISAPPEARING_MESSAGE_TIME)
|
||||
@@ -127,7 +131,6 @@ struct SettingsView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@EnvironmentObject var sceneDelegate: SceneDelegate
|
||||
@Binding var showSettings: Bool
|
||||
@State private var settingsSheet: SettingsSheet?
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -157,8 +160,6 @@ struct SettingsView: View {
|
||||
settingsRow("person.crop.rectangle.stack") { Text("Your chat profiles") }
|
||||
}
|
||||
|
||||
incognitoRow()
|
||||
|
||||
NavigationLink {
|
||||
UserAddressView(shareViaProfile: chatModel.currentUser!.addressShared)
|
||||
.navigationTitle("SimpleX address")
|
||||
@@ -294,39 +295,6 @@ struct SettingsView: View {
|
||||
}
|
||||
.navigationTitle("Your settings")
|
||||
}
|
||||
.sheet(item: $settingsSheet) { sheet in
|
||||
switch sheet {
|
||||
case .incognitoInfo: IncognitoHelp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func incognitoRow() -> some View {
|
||||
ZStack(alignment: .leading) {
|
||||
Image(systemName: chatModel.incognito ? "theatermasks.fill" : "theatermasks")
|
||||
.frame(maxWidth: 24, maxHeight: 24, alignment: .center)
|
||||
.foregroundColor(chatModel.incognito ? Color.indigo : .secondary)
|
||||
Toggle(isOn: $chatModel.incognito) {
|
||||
HStack(spacing: 6) {
|
||||
Text("Incognito")
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
.onTapGesture {
|
||||
settingsSheet = .incognitoInfo
|
||||
}
|
||||
}
|
||||
.onChange(of: chatModel.incognito) { incognito in
|
||||
incognitoGroupDefault.set(incognito)
|
||||
do {
|
||||
try apiSetIncognito(incognito: incognito)
|
||||
} catch {
|
||||
logger.error("apiSetIncognito: cannot set incognito \(responseError(error))")
|
||||
}
|
||||
}
|
||||
.padding(.leading, indent)
|
||||
}
|
||||
}
|
||||
|
||||
private func chatDatabaseRow() -> some View {
|
||||
@@ -347,12 +315,6 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private enum SettingsSheet: Identifiable {
|
||||
case incognitoInfo
|
||||
|
||||
var id: SettingsSheet { get { self } }
|
||||
}
|
||||
|
||||
private enum NotificationAlert {
|
||||
case enable
|
||||
case error(LocalizedStringKey, String)
|
||||
|
||||
@@ -3630,6 +3630,31 @@ SimpleX servers cannot see your profile.</source>
|
||||
<source>\~strike~</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ servers" xml:space="preserve" approved="no">
|
||||
<source>%@ servers</source>
|
||||
<target state="translated">%@ الخوادم</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ (current)" xml:space="preserve" approved="no">
|
||||
<source>%@ (current)</source>
|
||||
<target state="translated">%@ (الحالي)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ (current):" xml:space="preserve" approved="no">
|
||||
<source>%@ (current):</source>
|
||||
<target state="translated">%@ (الحالي):</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@:" xml:space="preserve" approved="no">
|
||||
<source>%@:</source>
|
||||
<target state="needs-translation">%@:</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ at %@:" xml:space="preserve" approved="no">
|
||||
<source>%1$@ at %2$@:</source>
|
||||
<target state="translated">%1$@ في %2$@:</target>
|
||||
<note>copied message info, <sender> at <time></note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="ar" datatype="plaintext">
|
||||
|
||||
4862
apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
Normal file
4862
apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
Normal file
File diff suppressed because it is too large
Load Diff
4655
apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff
Normal file
4655
apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "cs",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolBuildNumber" : "15A5219j",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "de",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolBuildNumber" : "15A5219j",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "en",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolBuildNumber" : "15A5219j",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "es",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolBuildNumber" : "15A5219j",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "fr",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolBuildNumber" : "15A5219j",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "it",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolBuildNumber" : "15A5219j",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
|
||||
<file original="en.lproj/Localizable.strings" source-language="en" target-language="ja" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
@@ -42,6 +42,18 @@
|
||||
<target>!1 色付き!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="# %@" xml:space="preserve">
|
||||
<source># %@</source>
|
||||
<note>copied message info title, # <title></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="## History" xml:space="preserve">
|
||||
<source>## History</source>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="## In reply to" xml:space="preserve">
|
||||
<source>## In reply to</source>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="#secret#" xml:space="preserve">
|
||||
<source>#secret#</source>
|
||||
<target>シークレット</target>
|
||||
@@ -72,6 +84,10 @@
|
||||
<target>%@ / %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ at %@:" xml:space="preserve">
|
||||
<source>%1$@ at %2$@:</source>
|
||||
<note>copied message info, <sender> at <time></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is connected!" xml:space="preserve">
|
||||
<source>%@ is connected!</source>
|
||||
<target>%@ 接続中!</target>
|
||||
@@ -297,6 +313,12 @@
|
||||
<target>, </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- more stable message delivery. - a bit better groups. - and more!" xml:space="preserve">
|
||||
<source>- more stable message delivery.
|
||||
- a bit better groups.
|
||||
- and more!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- voice messages up to 5 minutes. - custom time to disappear. - editing history." xml:space="preserve">
|
||||
<source>- voice messages up to 5 minutes.
|
||||
- custom time to disappear.
|
||||
@@ -373,19 +395,17 @@
|
||||
<p><a href="%@">SimpleX Chatでつながろう</a></p></target>
|
||||
<note>email text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A few more things" xml:space="preserve">
|
||||
<source>A few more things</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A new contact" xml:space="preserve">
|
||||
<source>A new contact</source>
|
||||
<target>新しい連絡先</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A random profile will be sent to the contact that you received this link from" xml:space="preserve">
|
||||
<source>A random profile will be sent to the contact that you received this link from</source>
|
||||
<target>このリンクの送信元にランダムなプロフィール(ダミー)が送られます</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A random profile will be sent to your contact" xml:space="preserve">
|
||||
<source>A random profile will be sent to your contact</source>
|
||||
<target>連絡先にランダムなプロフィール(ダミー)が送られます</target>
|
||||
<trans-unit id="A new random profile will be shared." xml:space="preserve">
|
||||
<source>A new random profile will be shared.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A separate TCP connection will be used **for each chat profile you have in the app**." xml:space="preserve">
|
||||
@@ -438,8 +458,8 @@
|
||||
<note>accept contact request via notification
|
||||
accept incoming call via notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Accept contact" xml:space="preserve">
|
||||
<source>Accept contact</source>
|
||||
<trans-unit id="Accept connection request?" xml:space="preserve">
|
||||
<source>Accept connection request?</source>
|
||||
<target>連絡を受け入れる</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -451,7 +471,7 @@
|
||||
<trans-unit id="Accept incognito" xml:space="preserve">
|
||||
<source>Accept incognito</source>
|
||||
<target>シークレットモードで承諾</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>accept contact request via notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." xml:space="preserve">
|
||||
<source>Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts.</source>
|
||||
@@ -587,6 +607,10 @@
|
||||
<target>送信済みメッセージの永久削除を許可する。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to send files and media." xml:space="preserve">
|
||||
<source>Allow to send files and media.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to send voice messages." xml:space="preserve">
|
||||
<source>Allow to send voice messages.</source>
|
||||
<target>音声メッセージの送信を許可する。</target>
|
||||
@@ -1017,8 +1041,16 @@
|
||||
<target>接続</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact link?" xml:space="preserve">
|
||||
<source>Connect via contact link?</source>
|
||||
<trans-unit id="Connect directly" xml:space="preserve">
|
||||
<source>Connect directly</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect incognito" xml:space="preserve">
|
||||
<source>Connect incognito</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact link" xml:space="preserve">
|
||||
<source>Connect via contact link</source>
|
||||
<target>連絡先リンク経由で接続しますか?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -1037,8 +1069,8 @@
|
||||
<target>リンク・QRコード経由で接続</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via one-time link?" xml:space="preserve">
|
||||
<source>Connect via one-time link?</source>
|
||||
<trans-unit id="Connect via one-time link" xml:space="preserve">
|
||||
<source>Connect via one-time link</source>
|
||||
<target>使い捨てリンク経由で接続しますか?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -1067,11 +1099,6 @@
|
||||
<target>接続エラー (AUTH)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection request" xml:space="preserve">
|
||||
<source>Connection request</source>
|
||||
<target>接続のリクエスト</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection request sent!" xml:space="preserve">
|
||||
<source>Connection request sent!</source>
|
||||
<target>接続リクエストを送信しました!</target>
|
||||
@@ -1122,6 +1149,10 @@
|
||||
<target>連絡先の設定</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contacts" xml:space="preserve">
|
||||
<source>Contacts</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contacts can mark messages for deletion; you will be able to view them." xml:space="preserve">
|
||||
<source>Contacts can mark messages for deletion; you will be able to view them.</source>
|
||||
<target>連絡先はメッセージを削除対象とすることができます。あなたには閲覧可能です。</target>
|
||||
@@ -1328,7 +1359,7 @@
|
||||
<trans-unit id="Decryption error" xml:space="preserve">
|
||||
<source>Decryption error</source>
|
||||
<target>復号化エラー</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>message decrypt error item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete" xml:space="preserve">
|
||||
<source>Delete</source>
|
||||
@@ -1515,6 +1546,18 @@
|
||||
<target>削除完了: %@</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delivery" xml:space="preserve">
|
||||
<source>Delivery</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delivery receipts are disabled!" xml:space="preserve">
|
||||
<source>Delivery receipts are disabled!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delivery receipts!" xml:space="preserve">
|
||||
<source>Delivery receipts!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Description" xml:space="preserve">
|
||||
<source>Description</source>
|
||||
<target>説明</target>
|
||||
@@ -1560,11 +1603,19 @@
|
||||
<target>このグループではメンバー間のダイレクトメッセージが使用禁止です。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disable (keep overrides)" xml:space="preserve">
|
||||
<source>Disable (keep overrides)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disable SimpleX Lock" xml:space="preserve">
|
||||
<source>Disable SimpleX Lock</source>
|
||||
<target>SimpleXロックを無効にする</target>
|
||||
<note>authentication reason</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disable for all" xml:space="preserve">
|
||||
<source>Disable for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing message" xml:space="preserve">
|
||||
<source>Disappearing message</source>
|
||||
<target>消えるメッセージ</target>
|
||||
@@ -1625,6 +1676,10 @@
|
||||
<target>アドレスを作成しないでください</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Don't enable" xml:space="preserve">
|
||||
<source>Don't enable</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Don't show again" xml:space="preserve">
|
||||
<source>Don't show again</source>
|
||||
<target>次から表示しない</target>
|
||||
@@ -1665,6 +1720,10 @@
|
||||
<target>有効</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable (keep overrides)" xml:space="preserve">
|
||||
<source>Enable (keep overrides)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable SimpleX Lock" xml:space="preserve">
|
||||
<source>Enable SimpleX Lock</source>
|
||||
<target>SimpleXロックを有効にする</target>
|
||||
@@ -1680,6 +1739,10 @@
|
||||
<target>自動メッセージ削除を有効にしますか?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable for all" xml:space="preserve">
|
||||
<source>Enable for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable instant notifications?" xml:space="preserve">
|
||||
<source>Enable instant notifications?</source>
|
||||
<target>即時通知を有効にしますか?</target>
|
||||
@@ -1889,6 +1952,10 @@
|
||||
<target>ユーザのプロフィール削除にエラー発生</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error enabling delivery receipts!" xml:space="preserve">
|
||||
<source>Error enabling delivery receipts!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error enabling notifications" xml:space="preserve">
|
||||
<source>Error enabling notifications</source>
|
||||
<target>通知の有効化にエラー発生</target>
|
||||
@@ -1969,6 +2036,10 @@
|
||||
<target>メッセージ送信にエラー発生</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error setting delivery receipts!" xml:space="preserve">
|
||||
<source>Error setting delivery receipts!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error starting chat" xml:space="preserve">
|
||||
<source>Error starting chat</source>
|
||||
<target>チャット開始にエラー発生</target>
|
||||
@@ -1984,6 +2055,10 @@
|
||||
<target>プロフィール切り替えにエラー発生!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error synchronizing connection" xml:space="preserve">
|
||||
<source>Error synchronizing connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error updating group link" xml:space="preserve">
|
||||
<source>Error updating group link</source>
|
||||
<target>グループのリンクのアップデートにエラー発生</target>
|
||||
@@ -2024,6 +2099,10 @@
|
||||
<target>エラー: データベースが存在しません</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Even when disabled in the conversation." xml:space="preserve">
|
||||
<source>Even when disabled in the conversation.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Exit without saving" xml:space="preserve">
|
||||
<source>Exit without saving</source>
|
||||
<target>保存せずに閉じる</target>
|
||||
@@ -2044,9 +2123,9 @@
|
||||
<target>データベースのアーカイブをエクスポートします。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Exporting database archive..." xml:space="preserve">
|
||||
<source>Exporting database archive...</source>
|
||||
<target>データベース アーカイブをエクスポートしています...</target>
|
||||
<trans-unit id="Exporting database archive…" xml:space="preserve">
|
||||
<source>Exporting database archive…</source>
|
||||
<target>データベース アーカイブをエクスポートしています…</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Failed to remove passphrase" xml:space="preserve">
|
||||
@@ -2088,10 +2167,54 @@
|
||||
<target>ファイルとメディア</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Files and media" xml:space="preserve">
|
||||
<source>Files and media</source>
|
||||
<note>chat feature</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Files and media are prohibited in this group." xml:space="preserve">
|
||||
<source>Files and media are prohibited in this group.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Files and media prohibited!" xml:space="preserve">
|
||||
<source>Files and media prohibited!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter unread and favorite chats." xml:space="preserve">
|
||||
<source>Filter unread and favorite chats.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Finally, we have them! 🚀" xml:space="preserve">
|
||||
<source>Finally, we have them! 🚀</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Find chats faster" xml:space="preserve">
|
||||
<source>Find chats faster</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix" xml:space="preserve">
|
||||
<source>Fix</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix connection" xml:space="preserve">
|
||||
<source>Fix connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix connection?" xml:space="preserve">
|
||||
<source>Fix connection?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix encryption after restoring backups." xml:space="preserve">
|
||||
<source>Fix encryption after restoring backups.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix not supported by contact" xml:space="preserve">
|
||||
<source>Fix not supported by contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix not supported by group member" xml:space="preserve">
|
||||
<source>Fix not supported by group member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="For console" xml:space="preserve">
|
||||
<source>For console</source>
|
||||
<target>コンソール</target>
|
||||
@@ -2197,6 +2320,10 @@
|
||||
<target>グループのメンバーが消えるメッセージを送信できます。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can send files and media." xml:space="preserve">
|
||||
<source>Group members can send files and media.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can send voice messages." xml:space="preserve">
|
||||
<source>Group members can send voice messages.</source>
|
||||
<target>グループのメンバーが音声メッセージを送信できます。</target>
|
||||
@@ -2285,7 +2412,7 @@
|
||||
<trans-unit id="History" xml:space="preserve">
|
||||
<source>History</source>
|
||||
<target>履歴</target>
|
||||
<note>copied message info</note>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How SimpleX works" xml:space="preserve">
|
||||
<source>How SimpleX works</source>
|
||||
@@ -2392,6 +2519,10 @@
|
||||
<target>サーバ設定の向上</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="In reply to" xml:space="preserve">
|
||||
<source>In reply to</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito" xml:space="preserve">
|
||||
<source>Incognito</source>
|
||||
<target>シークレットモード</target>
|
||||
@@ -2402,14 +2533,8 @@
|
||||
<target>シークレットモード</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito mode is not supported here - your main profile will be sent to group members" xml:space="preserve">
|
||||
<source>Incognito mode is not supported here - your main profile will be sent to group members</source>
|
||||
<target>ここではシークレットモードが無効です。メインのプロフィールがグループのメンバーに送られます</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito mode protects the privacy of your main profile name and image — for each new contact a new random profile is created." xml:space="preserve">
|
||||
<source>Incognito mode protects the privacy of your main profile name and image — for each new contact a new random profile is created.</source>
|
||||
<target>シークレットモードとは、メインのプロフィールとプロフィール画像を守るために、新しい連絡先を追加する時に、その連絡先に対してランダムなプロフィールが作成されます。</target>
|
||||
<trans-unit id="Incognito mode protects your privacy by using a new random profile for each contact." xml:space="preserve">
|
||||
<source>Incognito mode protects your privacy by using a new random profile for each contact.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incoming audio call" xml:space="preserve">
|
||||
@@ -2484,6 +2609,10 @@
|
||||
<target>無効なサーバアドレス!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid status" xml:space="preserve">
|
||||
<source>Invalid status</source>
|
||||
<note>item status text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invitation expired!" xml:space="preserve">
|
||||
<source>Invitation expired!</source>
|
||||
<target>招待が期限切れました!</target>
|
||||
@@ -2575,6 +2704,10 @@
|
||||
<target>グループに参加</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep your connections" xml:space="preserve">
|
||||
<source>Keep your connections</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="KeyChain error" xml:space="preserve">
|
||||
<source>KeyChain error</source>
|
||||
<target>キーチェーンのエラー</target>
|
||||
@@ -2665,6 +2798,10 @@
|
||||
<target>プライベートな接続をする</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Make one message disappear" xml:space="preserve">
|
||||
<source>Make one message disappear</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Make profile private!" xml:space="preserve">
|
||||
<source>Make profile private!</source>
|
||||
<target>プロフィールを非表示にできます!</target>
|
||||
@@ -2733,6 +2870,10 @@
|
||||
<trans-unit id="Message delivery error" xml:space="preserve">
|
||||
<source>Message delivery error</source>
|
||||
<target>メッセージ送信エラー</target>
|
||||
<note>item status text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message delivery receipts!" xml:space="preserve">
|
||||
<source>Message delivery receipts!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message draft" xml:space="preserve">
|
||||
@@ -2770,9 +2911,9 @@
|
||||
<target>メッセージ & ファイル</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating database archive..." xml:space="preserve">
|
||||
<source>Migrating database archive...</source>
|
||||
<target>データベースのアーカイブを移行しています...</target>
|
||||
<trans-unit id="Migrating database archive…" xml:space="preserve">
|
||||
<source>Migrating database archive…</source>
|
||||
<target>データベースのアーカイブを移行しています…</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migration error:" xml:space="preserve">
|
||||
@@ -2815,6 +2956,10 @@
|
||||
<target>まだまだ改善してまいります!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Most likely this connection is deleted." xml:space="preserve">
|
||||
<source>Most likely this connection is deleted.</source>
|
||||
<note>item status description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Most likely this contact has deleted the connection with you." xml:space="preserve">
|
||||
<source>Most likely this contact has deleted the connection with you.</source>
|
||||
<target>恐らくこの連絡先があなたとの接続を削除しました。</target>
|
||||
@@ -2920,16 +3065,28 @@
|
||||
<target>追加できる連絡先がありません</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No delivery information" xml:space="preserve">
|
||||
<source>No delivery information</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No device token!" xml:space="preserve">
|
||||
<source>No device token!</source>
|
||||
<target>デバイストークンがありません!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No filtered chats" xml:space="preserve">
|
||||
<source>No filtered chats</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No group!" xml:space="preserve">
|
||||
<source>Group not found!</source>
|
||||
<target>グループが見つかりません!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No history" xml:space="preserve">
|
||||
<source>No history</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No permission to record voice message" xml:space="preserve">
|
||||
<source>No permission to record voice message</source>
|
||||
<target>音声メッセージを録音する権限がありません</target>
|
||||
@@ -3014,6 +3171,10 @@
|
||||
<target>グループ設定を変えられるのはグループのオーナーだけです。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only group owners can enable files and media." xml:space="preserve">
|
||||
<source>Only group owners can enable files and media.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only group owners can enable voice messages." xml:space="preserve">
|
||||
<source>Only group owners can enable voice messages.</source>
|
||||
<target>音声メッセージを利用可能に設定できるのはグループのオーナーだけです。</target>
|
||||
@@ -3159,10 +3320,10 @@
|
||||
<target>頂いたリンクを貼り付ける</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste the link you received into the box below to connect with your contact." xml:space="preserve">
|
||||
<source>Paste the link you received into the box below to connect with your contact.</source>
|
||||
<trans-unit id="Paste the link you received to connect with your contact." xml:space="preserve">
|
||||
<source>Paste the link you received to connect with your contact.</source>
|
||||
<target>連絡相手から頂いたリンクを以下の入力欄に貼り付けて繋がります。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>placeholder</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
|
||||
<source>People can connect to you only via the links you share.</source>
|
||||
@@ -3334,6 +3495,10 @@
|
||||
<target>消えるメッセージを使用禁止にする。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Prohibit sending files and media." xml:space="preserve">
|
||||
<source>Prohibit sending files and media.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Prohibit sending voice messages." xml:space="preserve">
|
||||
<source>Prohibit sending voice messages.</source>
|
||||
<target>音声メッセージを使用禁止にする。</target>
|
||||
@@ -3354,6 +3519,10 @@
|
||||
<target>プロトコル・タイムアウト</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Protocol timeout per KB" xml:space="preserve">
|
||||
<source>Protocol timeout per KB</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Push notifications" xml:space="preserve">
|
||||
<source>Push notifications</source>
|
||||
<target>プッシュ通知</target>
|
||||
@@ -3364,9 +3533,8 @@
|
||||
<target>アプリを評価</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="React..." xml:space="preserve">
|
||||
<source>React...</source>
|
||||
<target>リアクション...</target>
|
||||
<trans-unit id="React…" xml:space="preserve">
|
||||
<source>React…</source>
|
||||
<note>chat item menu</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Read" xml:space="preserve">
|
||||
@@ -3399,6 +3567,10 @@
|
||||
<target>詳しくは[GitHubリポジトリ](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)をご覧ください。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Receipts are disabled" xml:space="preserve">
|
||||
<source>Receipts are disabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Received at" xml:space="preserve">
|
||||
<source>Received at</source>
|
||||
<target>受信</target>
|
||||
@@ -3438,6 +3610,14 @@
|
||||
<target>受信者には、入力時に更新内容が表示されます。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reconnect all connected servers to force message delivery. It uses additional traffic." xml:space="preserve">
|
||||
<source>Reconnect all connected servers to force message delivery. It uses additional traffic.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reconnect servers?" xml:space="preserve">
|
||||
<source>Reconnect servers?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Record updated at" xml:space="preserve">
|
||||
<source>Record updated at</source>
|
||||
<target>レコード更新日時</target>
|
||||
@@ -3458,8 +3638,8 @@
|
||||
<target>拒否</target>
|
||||
<note>reject incoming call via notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reject contact (sender NOT notified)" xml:space="preserve">
|
||||
<source>Reject contact (sender NOT notified)</source>
|
||||
<trans-unit id="Reject (sender NOT notified)" xml:space="preserve">
|
||||
<source>Reject (sender NOT notified)</source>
|
||||
<target>連絡を拒否(送信者には通知されません)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -3498,6 +3678,18 @@
|
||||
<target>キーチェーンからパスフレーズを削除しますか?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Renegotiate" xml:space="preserve">
|
||||
<source>Renegotiate</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Renegotiate encryption" xml:space="preserve">
|
||||
<source>Renegotiate encryption</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Renegotiate encryption?" xml:space="preserve">
|
||||
<source>Renegotiate encryption?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
<source>Reply</source>
|
||||
<target>返信</target>
|
||||
@@ -3753,6 +3945,10 @@
|
||||
<target>ライブメッセージを送信 (入力しながら宛先の画面で更新される)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send delivery receipts to" xml:space="preserve">
|
||||
<source>Send delivery receipts to</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send direct message" xml:space="preserve">
|
||||
<source>Send direct message</source>
|
||||
<target>ダイレクトメッセージを送信</target>
|
||||
@@ -3788,6 +3984,10 @@
|
||||
<target>質問やアイデアを送る</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send receipts" xml:space="preserve">
|
||||
<source>Send receipts</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send them from gallery or custom keyboards." xml:space="preserve">
|
||||
<source>Send them from gallery or custom keyboards.</source>
|
||||
<target>ギャラリーまたはカスタム キーボードから送信します。</target>
|
||||
@@ -3803,11 +4003,35 @@
|
||||
<target>送信元が繋がりリクエストを削除したかもしれません。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending delivery receipts will be enabled for all contacts in all visible chat profiles." xml:space="preserve">
|
||||
<source>Sending delivery receipts will be enabled for all contacts in all visible chat profiles.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending delivery receipts will be enabled for all contacts." xml:space="preserve">
|
||||
<source>Sending delivery receipts will be enabled for all contacts.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending file will be stopped." xml:space="preserve">
|
||||
<source>Sending file will be stopped.</source>
|
||||
<target>ファイルの送信を停止します。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending receipts is disabled for %lld contacts" xml:space="preserve">
|
||||
<source>Sending receipts is disabled for %lld contacts</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending receipts is disabled for %lld groups" xml:space="preserve">
|
||||
<source>Sending receipts is disabled for %lld groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending receipts is enabled for %lld contacts" xml:space="preserve">
|
||||
<source>Sending receipts is enabled for %lld contacts</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending receipts is enabled for %lld groups" xml:space="preserve">
|
||||
<source>Sending receipts is enabled for %lld groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending via" xml:space="preserve">
|
||||
<source>Sending via</source>
|
||||
<target>経由で送信</target>
|
||||
@@ -4028,6 +4252,10 @@
|
||||
<target>飛ばしたメッセージ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Small groups (max 20)" xml:space="preserve">
|
||||
<source>Small groups (max 20)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -4244,6 +4472,10 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>作成されたアーカイブは、アプリの設定/データベース/過去のデータベースアーカイブから利用できます。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The encryption is working and the new encryption agreement is not required. It may result in connection errors!" xml:space="preserve">
|
||||
<source>The encryption is working and the new encryption agreement is not required. It may result in connection errors!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The group is fully decentralized – it is visible only to the members." xml:space="preserve">
|
||||
<source>The group is fully decentralized – it is visible only to the members.</source>
|
||||
<target>グループは完全分散型で、メンバーしか内容を見れません。</target>
|
||||
@@ -4279,6 +4511,10 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>プロフィールは連絡先にしか共有されません。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The second tick we missed! ✅" xml:space="preserve">
|
||||
<source>The second tick we missed! ✅</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The sender will NOT be notified" xml:space="preserve">
|
||||
<source>The sender will NOT be notified</source>
|
||||
<target>送信者には通知されません</target>
|
||||
@@ -4304,6 +4540,14 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>少なくとも1つのユーザープロフィールが表示されている必要があります。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="These settings are for your current profile **%@**." xml:space="preserve">
|
||||
<source>These settings are for your current profile **%@**.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="They can be overridden in contact and group settings." xml:space="preserve">
|
||||
<source>They can be overridden in contact and group settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." xml:space="preserve">
|
||||
<source>This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain.</source>
|
||||
<target>ファイルとメディアが全て削除されます (※元に戻せません※)。低解像度の画像が残ります。</target>
|
||||
@@ -4319,9 +4563,8 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>あなたのプロフィール、連絡先、メッセージ、ファイルが完全削除されます (※元に戻せません※)。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This error is permanent for this connection, please re-connect." xml:space="preserve">
|
||||
<source>This error is permanent for this connection, please re-connect.</source>
|
||||
<target>このエラーはこの接続では永続的なものです。再接続してください。</target>
|
||||
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
|
||||
<source>This group has over %lld members, delivery receipts are not sent.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This group no longer exists." xml:space="preserve">
|
||||
@@ -4344,11 +4587,6 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>接続するにはQRコードを読み込むか、アプリ内のリンクを使用する必要があります。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To find the profile used for an incognito connection, tap the contact or group name on top of the chat." xml:space="preserve">
|
||||
<source>To find the profile used for an incognito connection, tap the contact or group name on top of the chat.</source>
|
||||
<target>シークレットモード接続のプロフィールを確認するには、チャットの上部の連絡先、またはグループ名をタップします。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To make a new connection" xml:space="preserve">
|
||||
<source>To make a new connection</source>
|
||||
<target>新規に接続する場合</target>
|
||||
@@ -4429,7 +4667,7 @@ You will be prompted to complete authentication before this feature is enabled.<
|
||||
<trans-unit id="Unexpected error: %@" xml:space="preserve">
|
||||
<source>Unexpected error: %@</source>
|
||||
<target>予期しないエラー: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>item status description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unexpected migration state" xml:space="preserve">
|
||||
<source>Unexpected migration state</source>
|
||||
@@ -4567,6 +4805,10 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>チャット</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use current profile" xml:space="preserve">
|
||||
<source>Use current profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use for new connections" xml:space="preserve">
|
||||
<source>Use for new connections</source>
|
||||
<target>新しい接続に使う</target>
|
||||
@@ -4577,6 +4819,10 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>iOS通話インターフェースを使用する</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use new incognito profile" xml:space="preserve">
|
||||
<source>Use new incognito profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use server" xml:space="preserve">
|
||||
<source>Use server</source>
|
||||
<target>サーバを使う</target>
|
||||
@@ -4787,6 +5033,14 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>後からでも作成できます</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can enable later via Settings" xml:space="preserve">
|
||||
<source>You can enable later via Settings</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can enable them later via app Privacy & Security settings." xml:space="preserve">
|
||||
<source>You can enable them later via app Privacy & Security settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can hide or mute a user profile - swipe it to the right." xml:space="preserve">
|
||||
<source>You can hide or mute a user profile - swipe it to the right.</source>
|
||||
<target>ユーザープロファイルを右にスワイプすると、非表示またはミュートにすることができます。</target>
|
||||
@@ -4857,8 +5111,8 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>アプリ起動時にパスフレーズを入力しなければなりません。端末に保存されてません。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You invited your contact" xml:space="preserve">
|
||||
<source>You invited your contact</source>
|
||||
<trans-unit id="You invited a contact" xml:space="preserve">
|
||||
<source>You invited a contact</source>
|
||||
<target>連絡先に招待を送りました</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -4987,11 +5241,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>あなたのチャットプロフィールが他のグループメンバーに送られます</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat profile will be sent to your contact" xml:space="preserve">
|
||||
<source>Your chat profile will be sent to your contact</source>
|
||||
<target>あなたのチャットプロフィールが連絡相手に送られます</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat profiles" xml:space="preserve">
|
||||
<source>Your chat profiles</source>
|
||||
<target>あなたのチャットプロフィール</target>
|
||||
@@ -5046,6 +5295,10 @@ You can change it in Settings.</source>
|
||||
<target>あなたのプライバシー</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
|
||||
<source>Your profile **%@** will be shared.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." xml:space="preserve">
|
||||
<source>Your profile is stored on your device and shared only with your contacts.
|
||||
SimpleX servers cannot see your profile.</source>
|
||||
@@ -5053,11 +5306,6 @@ SimpleX servers cannot see your profile.</source>
|
||||
SimpleX サーバーはあなたのプロファイルを参照できません。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile will be sent to the contact that you received this link from" xml:space="preserve">
|
||||
<source>Your profile will be sent to the contact that you received this link from</source>
|
||||
<target>あなたのプロフィールは、このリンクを受け取った連絡先に送信されます</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile, contacts and delivered messages are stored on your device." xml:space="preserve">
|
||||
<source>Your profile, contacts and delivered messages are stored on your device.</source>
|
||||
<target>あなたのプロフィール、連絡先、送信したメッセージがご自分の端末に保存されます。</target>
|
||||
@@ -5123,6 +5371,14 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
|
||||
<target>管理者</target>
|
||||
<note>member role</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="agreeing encryption for %@…" xml:space="preserve">
|
||||
<source>agreeing encryption for %@…</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="agreeing encryption…" xml:space="preserve">
|
||||
<source>agreeing encryption…</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="always" xml:space="preserve">
|
||||
<source>always</source>
|
||||
<target>常に</target>
|
||||
@@ -5183,14 +5439,12 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
|
||||
<target>あなたの役割を %@ に変更しました</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="changing address for %@..." xml:space="preserve">
|
||||
<source>changing address for %@...</source>
|
||||
<target>%@ のアドレスを変更しています...</target>
|
||||
<trans-unit id="changing address for %@…" xml:space="preserve">
|
||||
<source>changing address for %@…</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="changing address..." xml:space="preserve">
|
||||
<source>changing address...</source>
|
||||
<target>アドレスを変更しています…</target>
|
||||
<trans-unit id="changing address…" xml:space="preserve">
|
||||
<source>changing address…</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="colored" xml:space="preserve">
|
||||
@@ -5293,6 +5547,14 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
|
||||
<target>デフォルト (%@)</target>
|
||||
<note>pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="default (no)" xml:space="preserve">
|
||||
<source>default (no)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="default (yes)" xml:space="preserve">
|
||||
<source>default (yes)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="deleted" xml:space="preserve">
|
||||
<source>deleted</source>
|
||||
<target>削除完了</target>
|
||||
@@ -5313,6 +5575,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
|
||||
<target>直接</target>
|
||||
<note>connection level description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="disabled" xml:space="preserve">
|
||||
<source>disabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="duplicate message" xml:space="preserve">
|
||||
<source>duplicate message</source>
|
||||
<target>重複メッセージ</target>
|
||||
@@ -5338,6 +5604,38 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
|
||||
<target>あなたに有効</target>
|
||||
<note>enabled status</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption agreed" xml:space="preserve">
|
||||
<source>encryption agreed</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption agreed for %@" xml:space="preserve">
|
||||
<source>encryption agreed for %@</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption ok" xml:space="preserve">
|
||||
<source>encryption ok</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption ok for %@" xml:space="preserve">
|
||||
<source>encryption ok for %@</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption re-negotiation allowed" xml:space="preserve">
|
||||
<source>encryption re-negotiation allowed</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption re-negotiation allowed for %@" xml:space="preserve">
|
||||
<source>encryption re-negotiation allowed for %@</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption re-negotiation required" xml:space="preserve">
|
||||
<source>encryption re-negotiation required</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption re-negotiation required for %@" xml:space="preserve">
|
||||
<source>encryption re-negotiation required for %@</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ended" xml:space="preserve">
|
||||
<source>ended</source>
|
||||
<target>終了</target>
|
||||
@@ -5608,6 +5906,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
|
||||
<target>シークレット</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="security code changed" xml:space="preserve">
|
||||
<source>security code changed</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="starting…" xml:space="preserve">
|
||||
<source>starting…</source>
|
||||
<target>接続中…</target>
|
||||
@@ -5752,7 +6054,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
|
||||
</file>
|
||||
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="ja" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||
@@ -5784,7 +6086,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
|
||||
</file>
|
||||
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="ja" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "ja",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolBuildNumber" : "15A5219j",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "nl",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolBuildNumber" : "15A5219j",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "pl",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolBuildNumber" : "15A5219j",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "ru",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolBuildNumber" : "15A5219j",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
4945
apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
Normal file
4945
apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
|
||||
<file original="en.lproj/Localizable.strings" source-language="en" target-language="zh-Hans" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
@@ -42,6 +42,18 @@
|
||||
<target>!1 种彩色!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="# %@" xml:space="preserve">
|
||||
<source># %@</source>
|
||||
<note>copied message info title, # <title></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="## History" xml:space="preserve">
|
||||
<source>## History</source>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="## In reply to" xml:space="preserve">
|
||||
<source>## In reply to</source>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="#secret#" xml:space="preserve">
|
||||
<source>#secret#</source>
|
||||
<target>#秘密#</target>
|
||||
@@ -72,6 +84,10 @@
|
||||
<target>%@ / %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ at %@:" xml:space="preserve">
|
||||
<source>%1$@ at %2$@:</source>
|
||||
<note>copied message info, <sender> at <time></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is connected!" xml:space="preserve">
|
||||
<source>%@ is connected!</source>
|
||||
<target>%@ 已连接!</target>
|
||||
@@ -297,6 +313,12 @@
|
||||
<target>, </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- more stable message delivery. - a bit better groups. - and more!" xml:space="preserve">
|
||||
<source>- more stable message delivery.
|
||||
- a bit better groups.
|
||||
- and more!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- voice messages up to 5 minutes. - custom time to disappear. - editing history." xml:space="preserve">
|
||||
<source>- voice messages up to 5 minutes.
|
||||
- custom time to disappear.
|
||||
@@ -373,19 +395,17 @@
|
||||
<p><a href="%@">通过 SimpleX Chat </a></p>与我联系</target>
|
||||
<note>email text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A few more things" xml:space="preserve">
|
||||
<source>A few more things</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A new contact" xml:space="preserve">
|
||||
<source>A new contact</source>
|
||||
<target>新联系人</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A random profile will be sent to the contact that you received this link from" xml:space="preserve">
|
||||
<source>A random profile will be sent to the contact that you received this link from</source>
|
||||
<target>一个随机个人资料将被发送至给予您链接的联系人那里</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A random profile will be sent to your contact" xml:space="preserve">
|
||||
<source>A random profile will be sent to your contact</source>
|
||||
<target>一个随机资料将发送给您的联系人</target>
|
||||
<trans-unit id="A new random profile will be shared." xml:space="preserve">
|
||||
<source>A new random profile will be shared.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A separate TCP connection will be used **for each chat profile you have in the app**." xml:space="preserve">
|
||||
@@ -402,14 +422,17 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Abort" xml:space="preserve">
|
||||
<source>Abort</source>
|
||||
<target>中止</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Abort changing address" xml:space="preserve">
|
||||
<source>Abort changing address</source>
|
||||
<target>中止地址更改</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Abort changing address?" xml:space="preserve">
|
||||
<source>Abort changing address?</source>
|
||||
<target>中止地址更改?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="About SimpleX" xml:space="preserve">
|
||||
@@ -438,8 +461,8 @@
|
||||
<note>accept contact request via notification
|
||||
accept incoming call via notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Accept contact" xml:space="preserve">
|
||||
<source>Accept contact</source>
|
||||
<trans-unit id="Accept connection request?" xml:space="preserve">
|
||||
<source>Accept connection request?</source>
|
||||
<target>接受联系人</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -451,7 +474,7 @@
|
||||
<trans-unit id="Accept incognito" xml:space="preserve">
|
||||
<source>Accept incognito</source>
|
||||
<target>接受隐身聊天</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>accept contact request via notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." xml:space="preserve">
|
||||
<source>Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts.</source>
|
||||
@@ -495,6 +518,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Address change will be aborted. Old receiving address will be used." xml:space="preserve">
|
||||
<source>Address change will be aborted. Old receiving address will be used.</source>
|
||||
<target>将中止地址更改。将使用旧接收地址。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Admins can create the links to join groups." xml:space="preserve">
|
||||
@@ -587,6 +611,11 @@
|
||||
<target>允许不可撤回地删除已发送消息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to send files and media." xml:space="preserve">
|
||||
<source>Allow to send files and media.</source>
|
||||
<target>允许发送文件和媒体。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to send voice messages." xml:space="preserve">
|
||||
<source>Allow to send voice messages.</source>
|
||||
<target>允许发送语音消息。</target>
|
||||
@@ -694,7 +723,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio and video calls" xml:space="preserve">
|
||||
<source>Audio and video calls</source>
|
||||
<target>音视频通话</target>
|
||||
<target>语音和视频通话</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio/video calls" xml:space="preserve">
|
||||
@@ -1018,8 +1047,16 @@
|
||||
<target>连接</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact link?" xml:space="preserve">
|
||||
<source>Connect via contact link?</source>
|
||||
<trans-unit id="Connect directly" xml:space="preserve">
|
||||
<source>Connect directly</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect incognito" xml:space="preserve">
|
||||
<source>Connect incognito</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact link" xml:space="preserve">
|
||||
<source>Connect via contact link</source>
|
||||
<target>通过联系人链接进行连接?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -1038,8 +1075,8 @@
|
||||
<target>通过群组链接/二维码连接</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via one-time link?" xml:space="preserve">
|
||||
<source>Connect via one-time link?</source>
|
||||
<trans-unit id="Connect via one-time link" xml:space="preserve">
|
||||
<source>Connect via one-time link</source>
|
||||
<target>通过一次性链接连接?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -1068,11 +1105,6 @@
|
||||
<target>连接错误(AUTH)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection request" xml:space="preserve">
|
||||
<source>Connection request</source>
|
||||
<target>连接请求</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection request sent!" xml:space="preserve">
|
||||
<source>Connection request sent!</source>
|
||||
<target>已发送连接请求!</target>
|
||||
@@ -1123,6 +1155,10 @@
|
||||
<target>联系人偏好设置</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contacts" xml:space="preserve">
|
||||
<source>Contacts</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contacts can mark messages for deletion; you will be able to view them." xml:space="preserve">
|
||||
<source>Contacts can mark messages for deletion; you will be able to view them.</source>
|
||||
<target>联系人可以将信息标记为删除;您将可以查看这些信息。</target>
|
||||
@@ -1329,7 +1365,7 @@
|
||||
<trans-unit id="Decryption error" xml:space="preserve">
|
||||
<source>Decryption error</source>
|
||||
<target>解密错误</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>message decrypt error item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete" xml:space="preserve">
|
||||
<source>Delete</source>
|
||||
@@ -1516,6 +1552,18 @@
|
||||
<target>已删除于:%@</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delivery" xml:space="preserve">
|
||||
<source>Delivery</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delivery receipts are disabled!" xml:space="preserve">
|
||||
<source>Delivery receipts are disabled!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delivery receipts!" xml:space="preserve">
|
||||
<source>Delivery receipts!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Description" xml:space="preserve">
|
||||
<source>Description</source>
|
||||
<target>描述</target>
|
||||
@@ -1561,11 +1609,19 @@
|
||||
<target>此群中禁止成员之间私信。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disable (keep overrides)" xml:space="preserve">
|
||||
<source>Disable (keep overrides)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disable SimpleX Lock" xml:space="preserve">
|
||||
<source>Disable SimpleX Lock</source>
|
||||
<target>禁用 SimpleX 锁定</target>
|
||||
<note>authentication reason</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disable for all" xml:space="preserve">
|
||||
<source>Disable for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing message" xml:space="preserve">
|
||||
<source>Disappearing message</source>
|
||||
<target>限时消息</target>
|
||||
@@ -1626,6 +1682,10 @@
|
||||
<target>不创建地址</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Don't enable" xml:space="preserve">
|
||||
<source>Don't enable</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Don't show again" xml:space="preserve">
|
||||
<source>Don't show again</source>
|
||||
<target>不再显示</target>
|
||||
@@ -1666,6 +1726,10 @@
|
||||
<target>启用</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable (keep overrides)" xml:space="preserve">
|
||||
<source>Enable (keep overrides)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable SimpleX Lock" xml:space="preserve">
|
||||
<source>Enable SimpleX Lock</source>
|
||||
<target>启用 SimpleX 锁定</target>
|
||||
@@ -1681,6 +1745,10 @@
|
||||
<target>启用自动删除消息?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable for all" xml:space="preserve">
|
||||
<source>Enable for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable instant notifications?" xml:space="preserve">
|
||||
<source>Enable instant notifications?</source>
|
||||
<target>启用即时通知?</target>
|
||||
@@ -1798,6 +1866,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Error aborting address change" xml:space="preserve">
|
||||
<source>Error aborting address change</source>
|
||||
<target>中止地址更改错误</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error accepting contact request" xml:space="preserve">
|
||||
@@ -1890,6 +1959,10 @@
|
||||
<target>删除用户资料错误</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error enabling delivery receipts!" xml:space="preserve">
|
||||
<source>Error enabling delivery receipts!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error enabling notifications" xml:space="preserve">
|
||||
<source>Error enabling notifications</source>
|
||||
<target>启用通知错误</target>
|
||||
@@ -1970,6 +2043,10 @@
|
||||
<target>发送消息错误</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error setting delivery receipts!" xml:space="preserve">
|
||||
<source>Error setting delivery receipts!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error starting chat" xml:space="preserve">
|
||||
<source>Error starting chat</source>
|
||||
<target>启动聊天错误</target>
|
||||
@@ -1985,6 +2062,10 @@
|
||||
<target>切换资料错误!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error synchronizing connection" xml:space="preserve">
|
||||
<source>Error synchronizing connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error updating group link" xml:space="preserve">
|
||||
<source>Error updating group link</source>
|
||||
<target>更新群组链接错误</target>
|
||||
@@ -2025,6 +2106,10 @@
|
||||
<target>错误:没有数据库文件</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Even when disabled in the conversation." xml:space="preserve">
|
||||
<source>Even when disabled in the conversation.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Exit without saving" xml:space="preserve">
|
||||
<source>Exit without saving</source>
|
||||
<target>退出而不保存</target>
|
||||
@@ -2045,9 +2130,9 @@
|
||||
<target>导出数据库归档。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Exporting database archive..." xml:space="preserve">
|
||||
<source>Exporting database archive...</source>
|
||||
<target>导出数据库档案中……</target>
|
||||
<trans-unit id="Exporting database archive…" xml:space="preserve">
|
||||
<source>Exporting database archive…</source>
|
||||
<target>导出数据库档案中…</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Failed to remove passphrase" xml:space="preserve">
|
||||
@@ -2062,6 +2147,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Favorite" xml:space="preserve">
|
||||
<source>Favorite</source>
|
||||
<target>最喜欢</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="File will be deleted from servers." xml:space="preserve">
|
||||
@@ -2089,11 +2175,58 @@
|
||||
<target>文件和媒体</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Files and media" xml:space="preserve">
|
||||
<source>Files and media</source>
|
||||
<target>文件和媒体</target>
|
||||
<note>chat feature</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Files and media are prohibited in this group." xml:space="preserve">
|
||||
<source>Files and media are prohibited in this group.</source>
|
||||
<target>此群组中禁止文件和媒体。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Files and media prohibited!" xml:space="preserve">
|
||||
<source>Files and media prohibited!</source>
|
||||
<target>禁止文件和媒体!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter unread and favorite chats." xml:space="preserve">
|
||||
<source>Filter unread and favorite chats.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Finally, we have them! 🚀" xml:space="preserve">
|
||||
<source>Finally, we have them! 🚀</source>
|
||||
<target>终于我们有它们了! 🚀</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Find chats faster" xml:space="preserve">
|
||||
<source>Find chats faster</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix" xml:space="preserve">
|
||||
<source>Fix</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix connection" xml:space="preserve">
|
||||
<source>Fix connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix connection?" xml:space="preserve">
|
||||
<source>Fix connection?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix encryption after restoring backups." xml:space="preserve">
|
||||
<source>Fix encryption after restoring backups.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix not supported by contact" xml:space="preserve">
|
||||
<source>Fix not supported by contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fix not supported by group member" xml:space="preserve">
|
||||
<source>Fix not supported by group member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="For console" xml:space="preserve">
|
||||
<source>For console</source>
|
||||
<target>用于控制台</target>
|
||||
@@ -2199,6 +2332,11 @@
|
||||
<target>群组成员可以发送限时消息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can send files and media." xml:space="preserve">
|
||||
<source>Group members can send files and media.</source>
|
||||
<target>群组成员可以发送文件和媒体。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can send voice messages." xml:space="preserve">
|
||||
<source>Group members can send voice messages.</source>
|
||||
<target>群组成员可以发送语音消息。</target>
|
||||
@@ -2287,7 +2425,7 @@
|
||||
<trans-unit id="History" xml:space="preserve">
|
||||
<source>History</source>
|
||||
<target>历史记录</target>
|
||||
<note>copied message info</note>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How SimpleX works" xml:space="preserve">
|
||||
<source>How SimpleX works</source>
|
||||
@@ -2394,6 +2532,10 @@
|
||||
<target>改进的服务器配置</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="In reply to" xml:space="preserve">
|
||||
<source>In reply to</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito" xml:space="preserve">
|
||||
<source>Incognito</source>
|
||||
<target>隐身聊天</target>
|
||||
@@ -2404,14 +2546,8 @@
|
||||
<target>隐身模式</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito mode is not supported here - your main profile will be sent to group members" xml:space="preserve">
|
||||
<source>Incognito mode is not supported here - your main profile will be sent to group members</source>
|
||||
<target>此处不支持隐身模式——您的主要个人资料将发送给群组成员</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito mode protects the privacy of your main profile name and image — for each new contact a new random profile is created." xml:space="preserve">
|
||||
<source>Incognito mode protects the privacy of your main profile name and image — for each new contact a new random profile is created.</source>
|
||||
<target>隐身模式可以保护你的主要个人资料名称和图像的隐私——对于每个新的联系人,都会创建一个新的随机个人资料。</target>
|
||||
<trans-unit id="Incognito mode protects your privacy by using a new random profile for each contact." xml:space="preserve">
|
||||
<source>Incognito mode protects your privacy by using a new random profile for each contact.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incoming audio call" xml:space="preserve">
|
||||
@@ -2486,6 +2622,10 @@
|
||||
<target>无效的服务器地址!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid status" xml:space="preserve">
|
||||
<source>Invalid status</source>
|
||||
<note>item status text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invitation expired!" xml:space="preserve">
|
||||
<source>Invitation expired!</source>
|
||||
<target>邀请已过期!</target>
|
||||
@@ -2574,7 +2714,11 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Joining group" xml:space="preserve">
|
||||
<source>Joining group</source>
|
||||
<target>加入群组</target>
|
||||
<target>加入群组中</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep your connections" xml:space="preserve">
|
||||
<source>Keep your connections</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="KeyChain error" xml:space="preserve">
|
||||
@@ -2667,6 +2811,10 @@
|
||||
<target>建立私密连接</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Make one message disappear" xml:space="preserve">
|
||||
<source>Make one message disappear</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Make profile private!" xml:space="preserve">
|
||||
<source>Make profile private!</source>
|
||||
<target>将个人资料设为私密!</target>
|
||||
@@ -2735,6 +2883,10 @@
|
||||
<trans-unit id="Message delivery error" xml:space="preserve">
|
||||
<source>Message delivery error</source>
|
||||
<target>消息传递错误</target>
|
||||
<note>item status text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message delivery receipts!" xml:space="preserve">
|
||||
<source>Message delivery receipts!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message draft" xml:space="preserve">
|
||||
@@ -2772,9 +2924,9 @@
|
||||
<target>消息</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating database archive..." xml:space="preserve">
|
||||
<source>Migrating database archive...</source>
|
||||
<target>迁移数据库档案中……</target>
|
||||
<trans-unit id="Migrating database archive…" xml:space="preserve">
|
||||
<source>Migrating database archive…</source>
|
||||
<target>迁移数据库档案中…</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migration error:" xml:space="preserve">
|
||||
@@ -2817,6 +2969,10 @@
|
||||
<target>更多改进即将推出!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Most likely this connection is deleted." xml:space="preserve">
|
||||
<source>Most likely this connection is deleted.</source>
|
||||
<note>item status description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Most likely this contact has deleted the connection with you." xml:space="preserve">
|
||||
<source>Most likely this contact has deleted the connection with you.</source>
|
||||
<target>很可能此联系人已经删除了与您的联系。</target>
|
||||
@@ -2922,16 +3078,29 @@
|
||||
<target>没有联系人可添加</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No delivery information" xml:space="preserve">
|
||||
<source>No delivery information</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No device token!" xml:space="preserve">
|
||||
<source>No device token!</source>
|
||||
<target>无设备令牌!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No filtered chats" xml:space="preserve">
|
||||
<source>No filtered chats</source>
|
||||
<target>无过滤聊天</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No group!" xml:space="preserve">
|
||||
<source>Group not found!</source>
|
||||
<target>未找到群组!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No history" xml:space="preserve">
|
||||
<source>No history</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No permission to record voice message" xml:space="preserve">
|
||||
<source>No permission to record voice message</source>
|
||||
<target>没有录制语音消息的权限</target>
|
||||
@@ -3016,6 +3185,11 @@
|
||||
<target>只有群主可以改变群组偏好设置。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only group owners can enable files and media." xml:space="preserve">
|
||||
<source>Only group owners can enable files and media.</source>
|
||||
<target>只有组主可以启用文件和媒体。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only group owners can enable voice messages." xml:space="preserve">
|
||||
<source>Only group owners can enable voice messages.</source>
|
||||
<target>只有群主可以启用语音信息。</target>
|
||||
@@ -3161,10 +3335,10 @@
|
||||
<target>粘贴收到的链接</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste the link you received into the box below to connect with your contact." xml:space="preserve">
|
||||
<source>Paste the link you received into the box below to connect with your contact.</source>
|
||||
<trans-unit id="Paste the link you received to connect with your contact." xml:space="preserve">
|
||||
<source>Paste the link you received to connect with your contact.</source>
|
||||
<target>将您收到的链接粘贴到下面的框中以与您的联系人联系。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>placeholder</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
|
||||
<source>People can connect to you only via the links you share.</source>
|
||||
@@ -3336,6 +3510,11 @@
|
||||
<target>禁止发送限时消息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Prohibit sending files and media." xml:space="preserve">
|
||||
<source>Prohibit sending files and media.</source>
|
||||
<target>禁止发送文件和媒体。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Prohibit sending voice messages." xml:space="preserve">
|
||||
<source>Prohibit sending voice messages.</source>
|
||||
<target>禁止发送语音消息。</target>
|
||||
@@ -3356,6 +3535,10 @@
|
||||
<target>协议超时</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Protocol timeout per KB" xml:space="preserve">
|
||||
<source>Protocol timeout per KB</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Push notifications" xml:space="preserve">
|
||||
<source>Push notifications</source>
|
||||
<target>推送通知</target>
|
||||
@@ -3366,9 +3549,8 @@
|
||||
<target>评价此应用程序</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="React..." xml:space="preserve">
|
||||
<source>React...</source>
|
||||
<target>回应……</target>
|
||||
<trans-unit id="React…" xml:space="preserve">
|
||||
<source>React…</source>
|
||||
<note>chat item menu</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Read" xml:space="preserve">
|
||||
@@ -3401,6 +3583,10 @@
|
||||
<target>在我们的 [GitHub 仓库](https://github.com/simplex-chat/simplex-chat#readme) 中阅读更多信息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Receipts are disabled" xml:space="preserve">
|
||||
<source>Receipts are disabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Received at" xml:space="preserve">
|
||||
<source>Received at</source>
|
||||
<target>已收到于</target>
|
||||
@@ -3423,6 +3609,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Receiving address will be changed to a different server. Address change will complete after sender comes online." xml:space="preserve">
|
||||
<source>Receiving address will be changed to a different server. Address change will complete after sender comes online.</source>
|
||||
<target>接收地址将变更到不同的服务器。地址更改将在发件人上线后完成。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Receiving file will be stopped." xml:space="preserve">
|
||||
@@ -3440,6 +3627,14 @@
|
||||
<target>对方会在您键入时看到更新。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reconnect all connected servers to force message delivery. It uses additional traffic." xml:space="preserve">
|
||||
<source>Reconnect all connected servers to force message delivery. It uses additional traffic.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reconnect servers?" xml:space="preserve">
|
||||
<source>Reconnect servers?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Record updated at" xml:space="preserve">
|
||||
<source>Record updated at</source>
|
||||
<target>记录更新于</target>
|
||||
@@ -3460,8 +3655,8 @@
|
||||
<target>拒绝</target>
|
||||
<note>reject incoming call via notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reject contact (sender NOT notified)" xml:space="preserve">
|
||||
<source>Reject contact (sender NOT notified)</source>
|
||||
<trans-unit id="Reject (sender NOT notified)" xml:space="preserve">
|
||||
<source>Reject (sender NOT notified)</source>
|
||||
<target>拒绝联系人(发送者不会被通知)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -3500,6 +3695,18 @@
|
||||
<target>从钥匙串中删除密码?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Renegotiate" xml:space="preserve">
|
||||
<source>Renegotiate</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Renegotiate encryption" xml:space="preserve">
|
||||
<source>Renegotiate encryption</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Renegotiate encryption?" xml:space="preserve">
|
||||
<source>Renegotiate encryption?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
<source>Reply</source>
|
||||
<target>回复</target>
|
||||
@@ -3755,6 +3962,10 @@
|
||||
<target>发送实时消息——它会在您键入时为收件人更新</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send delivery receipts to" xml:space="preserve">
|
||||
<source>Send delivery receipts to</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send direct message" xml:space="preserve">
|
||||
<source>Send direct message</source>
|
||||
<target>发送私信</target>
|
||||
@@ -3790,6 +4001,10 @@
|
||||
<target>发送问题和想法</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send receipts" xml:space="preserve">
|
||||
<source>Send receipts</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send them from gallery or custom keyboards." xml:space="preserve">
|
||||
<source>Send them from gallery or custom keyboards.</source>
|
||||
<target>发送它们来自图库或自定义键盘。</target>
|
||||
@@ -3805,11 +4020,35 @@
|
||||
<target>发送人可能已删除连接请求。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending delivery receipts will be enabled for all contacts in all visible chat profiles." xml:space="preserve">
|
||||
<source>Sending delivery receipts will be enabled for all contacts in all visible chat profiles.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending delivery receipts will be enabled for all contacts." xml:space="preserve">
|
||||
<source>Sending delivery receipts will be enabled for all contacts.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending file will be stopped." xml:space="preserve">
|
||||
<source>Sending file will be stopped.</source>
|
||||
<target>即将停止发送文件。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending receipts is disabled for %lld contacts" xml:space="preserve">
|
||||
<source>Sending receipts is disabled for %lld contacts</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending receipts is disabled for %lld groups" xml:space="preserve">
|
||||
<source>Sending receipts is disabled for %lld groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending receipts is enabled for %lld contacts" xml:space="preserve">
|
||||
<source>Sending receipts is enabled for %lld contacts</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending receipts is enabled for %lld groups" xml:space="preserve">
|
||||
<source>Sending receipts is enabled for %lld groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending via" xml:space="preserve">
|
||||
<source>Sending via</source>
|
||||
<target>发送通过</target>
|
||||
@@ -4030,6 +4269,10 @@
|
||||
<target>已跳过消息</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Small groups (max 20)" xml:space="preserve">
|
||||
<source>Small groups (max 20)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
|
||||
<target>导入过程中发生了一些非致命错误——您可以查看聊天控制台了解更多详细信息。</target>
|
||||
@@ -4247,6 +4490,10 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>创建的归档文件可以通过应用设置/数据库/旧数据库归档访问。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The encryption is working and the new encryption agreement is not required. It may result in connection errors!" xml:space="preserve">
|
||||
<source>The encryption is working and the new encryption agreement is not required. It may result in connection errors!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The group is fully decentralized – it is visible only to the members." xml:space="preserve">
|
||||
<source>The group is fully decentralized – it is visible only to the members.</source>
|
||||
<target>该小组是完全分散式的——它只对成员可见。</target>
|
||||
@@ -4282,6 +4529,10 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>该资料仅与您的联系人共享。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The second tick we missed! ✅" xml:space="preserve">
|
||||
<source>The second tick we missed! ✅</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The sender will NOT be notified" xml:space="preserve">
|
||||
<source>The sender will NOT be notified</source>
|
||||
<target>发送者将不会收到通知</target>
|
||||
@@ -4307,6 +4558,14 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>应该至少有一个可见的用户资料。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="These settings are for your current profile **%@**." xml:space="preserve">
|
||||
<source>These settings are for your current profile **%@**.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="They can be overridden in contact and group settings." xml:space="preserve">
|
||||
<source>They can be overridden in contact and group settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." xml:space="preserve">
|
||||
<source>This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain.</source>
|
||||
<target>此操作无法撤消——所有接收和发送的文件和媒体都将被删除。 低分辨率图片将保留。</target>
|
||||
@@ -4322,9 +4581,8 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>此操作无法撤消——您的个人资料、联系人、消息和文件将不可撤回地丢失。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This error is permanent for this connection, please re-connect." xml:space="preserve">
|
||||
<source>This error is permanent for this connection, please re-connect.</source>
|
||||
<target>此错误对于此连接是永久性的,请重新连接。</target>
|
||||
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
|
||||
<source>This group has over %lld members, delivery receipts are not sent.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This group no longer exists." xml:space="preserve">
|
||||
@@ -4347,11 +4605,6 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>您的联系人可以扫描二维码或使用应用程序中的链接来建立连接。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To find the profile used for an incognito connection, tap the contact or group name on top of the chat." xml:space="preserve">
|
||||
<source>To find the profile used for an incognito connection, tap the contact or group name on top of the chat.</source>
|
||||
<target>要查找用于隐身聊天连接的资料,点击聊天顶部的联系人或群组名。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To make a new connection" xml:space="preserve">
|
||||
<source>To make a new connection</source>
|
||||
<target>建立新连接</target>
|
||||
@@ -4432,7 +4685,7 @@ You will be prompted to complete authentication before this feature is enabled.<
|
||||
<trans-unit id="Unexpected error: %@" xml:space="preserve">
|
||||
<source>Unexpected error: %@</source>
|
||||
<target>意外错误: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>item status description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unexpected migration state" xml:space="preserve">
|
||||
<source>Unexpected migration state</source>
|
||||
@@ -4441,6 +4694,7 @@ You will be prompted to complete authentication before this feature is enabled.<
|
||||
</trans-unit>
|
||||
<trans-unit id="Unfav." xml:space="preserve">
|
||||
<source>Unfav.</source>
|
||||
<target>取消最喜欢</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unhide" xml:space="preserve">
|
||||
@@ -4570,6 +4824,10 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>使用聊天</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use current profile" xml:space="preserve">
|
||||
<source>Use current profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use for new connections" xml:space="preserve">
|
||||
<source>Use for new connections</source>
|
||||
<target>用于新连接</target>
|
||||
@@ -4580,6 +4838,10 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>使用 iOS 通话界面</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use new incognito profile" xml:space="preserve">
|
||||
<source>Use new incognito profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use server" xml:space="preserve">
|
||||
<source>Use server</source>
|
||||
<target>使用服务器</target>
|
||||
@@ -4790,6 +5052,14 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>您可以以后创建它</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can enable later via Settings" xml:space="preserve">
|
||||
<source>You can enable later via Settings</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can enable them later via app Privacy & Security settings." xml:space="preserve">
|
||||
<source>You can enable them later via app Privacy & Security settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can hide or mute a user profile - swipe it to the right." xml:space="preserve">
|
||||
<source>You can hide or mute a user profile - swipe it to the right.</source>
|
||||
<target>您可以隐藏或静音用户个人资料——只需向右滑动。</target>
|
||||
@@ -4860,8 +5130,8 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>您必须在每次应用程序启动时输入密码——它不存储在设备上。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You invited your contact" xml:space="preserve">
|
||||
<source>You invited your contact</source>
|
||||
<trans-unit id="You invited a contact" xml:space="preserve">
|
||||
<source>You invited a contact</source>
|
||||
<target>您邀请了您的联系人</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -4990,11 +5260,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>您的聊天资料将被发送给群组成员</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat profile will be sent to your contact" xml:space="preserve">
|
||||
<source>Your chat profile will be sent to your contact</source>
|
||||
<target>您的聊天资料将被发送给您的联系人</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat profiles" xml:space="preserve">
|
||||
<source>Your chat profiles</source>
|
||||
<target>您的聊天资料</target>
|
||||
@@ -5049,6 +5314,10 @@ You can change it in Settings.</source>
|
||||
<target>您的隐私设置</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
|
||||
<source>Your profile **%@** will be shared.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." xml:space="preserve">
|
||||
<source>Your profile is stored on your device and shared only with your contacts.
|
||||
SimpleX servers cannot see your profile.</source>
|
||||
@@ -5056,11 +5325,6 @@ SimpleX servers cannot see your profile.</source>
|
||||
SimpleX 服务器无法看到您的资料。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile will be sent to the contact that you received this link from" xml:space="preserve">
|
||||
<source>Your profile will be sent to the contact that you received this link from</source>
|
||||
<target>您的个人资料将发送给您收到此链接的联系人</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile, contacts and delivered messages are stored on your device." xml:space="preserve">
|
||||
<source>Your profile, contacts and delivered messages are stored on your device.</source>
|
||||
<target>您的资料、联系人和发送的消息存储在您的设备上。</target>
|
||||
@@ -5126,6 +5390,14 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>管理员</target>
|
||||
<note>member role</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="agreeing encryption for %@…" xml:space="preserve">
|
||||
<source>agreeing encryption for %@…</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="agreeing encryption…" xml:space="preserve">
|
||||
<source>agreeing encryption…</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="always" xml:space="preserve">
|
||||
<source>always</source>
|
||||
<target>始终</target>
|
||||
@@ -5186,14 +5458,12 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>更改您的角色为 %@</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="changing address for %@..." xml:space="preserve">
|
||||
<source>changing address for %@...</source>
|
||||
<target>更改 %@... 的地址中</target>
|
||||
<trans-unit id="changing address for %@…" xml:space="preserve">
|
||||
<source>changing address for %@…</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="changing address..." xml:space="preserve">
|
||||
<source>changing address...</source>
|
||||
<target>更改地址中……</target>
|
||||
<trans-unit id="changing address…" xml:space="preserve">
|
||||
<source>changing address…</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="colored" xml:space="preserve">
|
||||
@@ -5296,6 +5566,14 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>默认 (%@)</target>
|
||||
<note>pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="default (no)" xml:space="preserve">
|
||||
<source>default (no)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="default (yes)" xml:space="preserve">
|
||||
<source>default (yes)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="deleted" xml:space="preserve">
|
||||
<source>deleted</source>
|
||||
<target>已删除</target>
|
||||
@@ -5316,6 +5594,10 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>直接</target>
|
||||
<note>connection level description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="disabled" xml:space="preserve">
|
||||
<source>disabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="duplicate message" xml:space="preserve">
|
||||
<source>duplicate message</source>
|
||||
<target>重复的消息</target>
|
||||
@@ -5341,6 +5623,38 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>为您启用</target>
|
||||
<note>enabled status</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption agreed" xml:space="preserve">
|
||||
<source>encryption agreed</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption agreed for %@" xml:space="preserve">
|
||||
<source>encryption agreed for %@</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption ok" xml:space="preserve">
|
||||
<source>encryption ok</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption ok for %@" xml:space="preserve">
|
||||
<source>encryption ok for %@</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption re-negotiation allowed" xml:space="preserve">
|
||||
<source>encryption re-negotiation allowed</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption re-negotiation allowed for %@" xml:space="preserve">
|
||||
<source>encryption re-negotiation allowed for %@</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption re-negotiation required" xml:space="preserve">
|
||||
<source>encryption re-negotiation required</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="encryption re-negotiation required for %@" xml:space="preserve">
|
||||
<source>encryption re-negotiation required for %@</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ended" xml:space="preserve">
|
||||
<source>ended</source>
|
||||
<target>已结束</target>
|
||||
@@ -5612,6 +5926,10 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>秘密</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="security code changed" xml:space="preserve">
|
||||
<source>security code changed</source>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="starting…" xml:space="preserve">
|
||||
<source>starting…</source>
|
||||
<target>启动中……</target>
|
||||
@@ -5756,7 +6074,7 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
</file>
|
||||
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="zh-Hans" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||
@@ -5788,7 +6106,7 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
</file>
|
||||
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="zh-Hans" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.0" build-num="15A5219j"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "zh-Hans",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14C18",
|
||||
"toolBuildNumber" : "15A5219j",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.2"
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
@@ -76,7 +76,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
var badgeCount: Int = 0
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
logger.debug("NotificationService.didReceive")
|
||||
logger.debug("DEBUGGING: NotificationService.didReceive")
|
||||
if let ntf = request.content.mutableCopy() as? UNMutableNotificationContent {
|
||||
setBestAttemptNtf(ntf)
|
||||
}
|
||||
@@ -149,7 +149,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
logger.debug("NotificationService.serviceExtensionTimeWillExpire")
|
||||
logger.debug("DEBUGGING: NotificationService.serviceExtensionTimeWillExpire")
|
||||
deliverBestAttemptNtf()
|
||||
}
|
||||
|
||||
@@ -219,7 +219,6 @@ func startChat() -> DBMigrationResult? {
|
||||
let justStarted = try apiStartChat()
|
||||
chatStarted = true
|
||||
if justStarted {
|
||||
try apiSetIncognito(incognito: incognitoGroupDefault.get())
|
||||
chatLastStartGroupDefault.set(Date.now)
|
||||
Task { await receiveMessages() }
|
||||
}
|
||||
@@ -275,7 +274,7 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
|
||||
cItem = autoReceiveFile(file) ?? cItem
|
||||
}
|
||||
let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(notification: createMessageReceivedNtf(user, cInfo, cItem)) : .empty
|
||||
return cItem.showMutableNotification ? (aChatItem.chatId, ntf) : nil
|
||||
return cItem.showNotification ? (aChatItem.chatId, ntf) : nil
|
||||
case let .rcvFileSndCancelled(_, aChatItem, _):
|
||||
cleanupFile(aChatItem)
|
||||
return nil
|
||||
@@ -352,12 +351,6 @@ func setXFTPConfig(_ cfg: XFTPFileConfig?) throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetIncognito(incognito: Bool) throws {
|
||||
let r = sendSimpleXCmd(.setIncognito(incognito: incognito))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? {
|
||||
guard apiGetActiveUser() != nil else {
|
||||
logger.debug("no active user")
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
5C00168128C4FE760094D739 /* KeyChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00168028C4FE760094D739 /* KeyChain.swift */; };
|
||||
5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA72837DBB3004A9677 /* CICallItemView.swift */; };
|
||||
5C029EAA283942EA004A9677 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA9283942EA004A9677 /* CallController.swift */; };
|
||||
5C0403922A7EAA41006ACFE8 /* libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C04038D2A7EAA41006ACFE8 /* libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5-ghc8.10.7.a */; };
|
||||
5C0403932A7EAA41006ACFE8 /* libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C04038E2A7EAA41006ACFE8 /* libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5.a */; };
|
||||
5C0403942A7EAA41006ACFE8 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C04038F2A7EAA41006ACFE8 /* libffi.a */; };
|
||||
5C0403952A7EAA41006ACFE8 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0403902A7EAA41006ACFE8 /* libgmp.a */; };
|
||||
5C0403962A7EAA41006ACFE8 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0403912A7EAA41006ACFE8 /* libgmpxx.a */; };
|
||||
5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C05DF522840AA1D00C683F9 /* CallSettings.swift */; };
|
||||
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; };
|
||||
5C10D88828EED12E00E58BF0 /* ContactConnectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */; };
|
||||
@@ -141,6 +146,8 @@
|
||||
5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; };
|
||||
5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCE227DE9246000BD591 /* ComposeView.swift */; };
|
||||
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
|
||||
5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; };
|
||||
5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; };
|
||||
5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; };
|
||||
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */; };
|
||||
5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
|
||||
@@ -165,11 +172,6 @@
|
||||
648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; };
|
||||
649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; };
|
||||
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; };
|
||||
64A353102A4C84CE007CD71D /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A3530B2A4C84CE007CD71D /* libgmp.a */; };
|
||||
64A353112A4C84CE007CD71D /* libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A3530C2A4C84CE007CD71D /* libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm.a */; };
|
||||
64A353122A4C84CE007CD71D /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A3530D2A4C84CE007CD71D /* libffi.a */; };
|
||||
64A353132A4C84CE007CD71D /* libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A3530E2A4C84CE007CD71D /* libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm-ghc8.10.7.a */; };
|
||||
64A353142A4C84CE007CD71D /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A3530F2A4C84CE007CD71D /* libgmpxx.a */; };
|
||||
64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; };
|
||||
64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; };
|
||||
64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */; };
|
||||
@@ -261,6 +263,11 @@
|
||||
5C00168028C4FE760094D739 /* KeyChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyChain.swift; sourceTree = "<group>"; };
|
||||
5C029EA72837DBB3004A9677 /* CICallItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CICallItemView.swift; sourceTree = "<group>"; };
|
||||
5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = "<group>"; };
|
||||
5C04038D2A7EAA41006ACFE8 /* libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5C04038E2A7EAA41006ACFE8 /* libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5.a"; sourceTree = "<group>"; };
|
||||
5C04038F2A7EAA41006ACFE8 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C0403902A7EAA41006ACFE8 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C0403912A7EAA41006ACFE8 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = "<group>"; };
|
||||
5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = "<group>"; };
|
||||
5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionInfo.swift; sourceTree = "<group>"; };
|
||||
@@ -417,6 +424,8 @@
|
||||
5CE4407827ADB701007B033A /* EmojiItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItemView.swift; sourceTree = "<group>"; };
|
||||
5CEACCE227DE9246000BD591 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = "<group>"; };
|
||||
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = "<group>"; };
|
||||
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = "<group>"; };
|
||||
5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = "<group>"; };
|
||||
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
|
||||
5CFA59CF286477B400863A68 /* ChatArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatArchiveView.swift; sourceTree = "<group>"; };
|
||||
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
|
||||
@@ -441,11 +450,6 @@
|
||||
6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = "<group>"; };
|
||||
649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = "<group>"; };
|
||||
64A3530B2A4C84CE007CD71D /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
64A3530C2A4C84CE007CD71D /* libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm.a"; sourceTree = "<group>"; };
|
||||
64A3530D2A4C84CE007CD71D /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
64A3530E2A4C84CE007CD71D /* libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
64A3530F2A4C84CE007CD71D /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = "<group>"; };
|
||||
64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = "<group>"; };
|
||||
64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemInfoView.swift; sourceTree = "<group>"; };
|
||||
@@ -497,13 +501,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C0403932A7EAA41006ACFE8 /* libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5.a in Frameworks */,
|
||||
5C0403922A7EAA41006ACFE8 /* libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5-ghc8.10.7.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
64A353132A4C84CE007CD71D /* libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm-ghc8.10.7.a in Frameworks */,
|
||||
64A353102A4C84CE007CD71D /* libgmp.a in Frameworks */,
|
||||
64A353112A4C84CE007CD71D /* libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm.a in Frameworks */,
|
||||
64A353122A4C84CE007CD71D /* libffi.a in Frameworks */,
|
||||
5C0403942A7EAA41006ACFE8 /* libffi.a in Frameworks */,
|
||||
5C0403952A7EAA41006ACFE8 /* libgmp.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
64A353142A4C84CE007CD71D /* libgmpxx.a in Frameworks */,
|
||||
5C0403962A7EAA41006ACFE8 /* libgmpxx.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -564,11 +568,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
64A3530D2A4C84CE007CD71D /* libffi.a */,
|
||||
64A3530B2A4C84CE007CD71D /* libgmp.a */,
|
||||
64A3530F2A4C84CE007CD71D /* libgmpxx.a */,
|
||||
64A3530E2A4C84CE007CD71D /* libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm-ghc8.10.7.a */,
|
||||
64A3530C2A4C84CE007CD71D /* libHSsimplex-chat-5.2.0.0-ESKsZ4YorLH7yFQuFvHeIm.a */,
|
||||
5C04038F2A7EAA41006ACFE8 /* libffi.a */,
|
||||
5C0403902A7EAA41006ACFE8 /* libgmp.a */,
|
||||
5C0403912A7EAA41006ACFE8 /* libgmpxx.a */,
|
||||
5C04038D2A7EAA41006ACFE8 /* libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5-ghc8.10.7.a */,
|
||||
5C04038E2A7EAA41006ACFE8 /* libHSsimplex-chat-5.3.0.2-57EsBXX08D1H5qwhz1zMA5.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -619,6 +623,7 @@
|
||||
18415DAAAD1ADBEDB0EDA852 /* VideoPlayerView.swift */,
|
||||
64466DCB29FFE3E800E3D48D /* MailView.swift */,
|
||||
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */,
|
||||
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
@@ -741,6 +746,7 @@
|
||||
5C65DAF829D0CC20003CEE45 /* DeveloperView.swift */,
|
||||
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */,
|
||||
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */,
|
||||
5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */,
|
||||
);
|
||||
path = UserSettings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1142,6 +1148,7 @@
|
||||
647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */,
|
||||
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */,
|
||||
5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */,
|
||||
5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */,
|
||||
5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */,
|
||||
5CB634B129E5EFEA0066AD6B /* PasscodeView.swift in Sources */,
|
||||
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */,
|
||||
@@ -1190,6 +1197,7 @@
|
||||
5C9CC7A928C532AB00BEF955 /* DatabaseErrorView.swift in Sources */,
|
||||
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */,
|
||||
64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */,
|
||||
5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */,
|
||||
5C9C2DA7289957AE00CC63B1 /* AdvancedNetworkSettings.swift in Sources */,
|
||||
5CADE79A29211BB900072E13 /* PreferencesView.swift in Sources */,
|
||||
644EFFE42937BE9700525D5B /* MarkedDeletedItemView.swift in Sources */,
|
||||
@@ -1470,7 +1478,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 151;
|
||||
CURRENT_PROJECT_VERSION = 164;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1491,7 +1499,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.2;
|
||||
MARKETING_VERSION = 5.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1512,7 +1520,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 151;
|
||||
CURRENT_PROJECT_VERSION = 164;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1533,7 +1541,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.2;
|
||||
MARKETING_VERSION = 5.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1592,7 +1600,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 151;
|
||||
CURRENT_PROJECT_VERSION = 164;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1605,7 +1613,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.2;
|
||||
MARKETING_VERSION = 5.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1624,7 +1632,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 151;
|
||||
CURRENT_PROJECT_VERSION = 164;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1637,7 +1645,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.2;
|
||||
MARKETING_VERSION = 5.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1656,7 +1664,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 71;
|
||||
CURRENT_PROJECT_VERSION = 164;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1680,7 +1688,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 4.0;
|
||||
MARKETING_VERSION = 5.2.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1702,7 +1710,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 71;
|
||||
CURRENT_PROJECT_VERSION = 164;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1726,7 +1734,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 4.0;
|
||||
MARKETING_VERSION = 5.2.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
||||
@@ -206,7 +206,7 @@ public func responseError(_ err: Error) -> String {
|
||||
switch r {
|
||||
case let .chatCmdError(_, chatError): return chatErrorString(chatError)
|
||||
case let .chatError(_, chatError): return chatErrorString(chatError)
|
||||
default: return String(describing: r)
|
||||
default: return "\(String(describing: r.responseType)), details: \(String(describing: r.details))"
|
||||
}
|
||||
} else {
|
||||
return String(describing: err)
|
||||
|
||||
@@ -17,6 +17,9 @@ public enum ChatCommand {
|
||||
case createActiveUser(profile: Profile?, sameServers: Bool, pastTimestamp: Bool)
|
||||
case listUsers
|
||||
case apiSetActiveUser(userId: Int64, viewPwd: String?)
|
||||
case setAllContactReceipts(enable: Bool)
|
||||
case apiSetUserContactReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings)
|
||||
case apiSetUserGroupReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings)
|
||||
case apiHideUser(userId: Int64, viewPwd: String)
|
||||
case apiUnhideUser(userId: Int64, viewPwd: String)
|
||||
case apiMuteUser(userId: Int64)
|
||||
@@ -29,7 +32,6 @@ public enum ChatCommand {
|
||||
case setTempFolder(tempFolder: String)
|
||||
case setFilesFolder(filesFolder: String)
|
||||
case apiSetXFTPConfig(config: XFTPFileConfig?)
|
||||
case setIncognito(incognito: Bool)
|
||||
case apiExportArchive(config: ArchiveConfig)
|
||||
case apiImportArchive(config: ArchiveConfig)
|
||||
case apiDeleteStorage
|
||||
@@ -66,6 +68,7 @@ public enum ChatCommand {
|
||||
case apiGetChatItemTTL(userId: Int64)
|
||||
case apiSetNetworkConfig(networkConfig: NetCfg)
|
||||
case apiGetNetworkConfig
|
||||
case reconnectAllServers
|
||||
case apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings)
|
||||
case apiContactInfo(contactId: Int64)
|
||||
case apiGroupMemberInfo(groupId: Int64, groupMemberId: Int64)
|
||||
@@ -73,12 +76,15 @@ public enum ChatCommand {
|
||||
case apiSwitchGroupMember(groupId: Int64, groupMemberId: Int64)
|
||||
case apiAbortSwitchContact(contactId: Int64)
|
||||
case apiAbortSwitchGroupMember(groupId: Int64, groupMemberId: Int64)
|
||||
case apiSyncContactRatchet(contactId: Int64, force: Bool)
|
||||
case apiSyncGroupMemberRatchet(groupId: Int64, groupMemberId: Int64, force: Bool)
|
||||
case apiGetContactCode(contactId: Int64)
|
||||
case apiGetGroupMemberCode(groupId: Int64, groupMemberId: Int64)
|
||||
case apiVerifyContact(contactId: Int64, connectionCode: String?)
|
||||
case apiVerifyGroupMember(groupId: Int64, groupMemberId: Int64, connectionCode: String?)
|
||||
case apiAddContact(userId: Int64)
|
||||
case apiConnect(userId: Int64, connReq: String)
|
||||
case apiAddContact(userId: Int64, incognito: Bool)
|
||||
case apiSetConnectionIncognito(connId: Int64, incognito: Bool)
|
||||
case apiConnect(userId: Int64, incognito: Bool, connReq: String)
|
||||
case apiDeleteChat(type: ChatType, id: Int64)
|
||||
case apiClearChat(type: ChatType, id: Int64)
|
||||
case apiListContacts(userId: Int64)
|
||||
@@ -91,7 +97,7 @@ public enum ChatCommand {
|
||||
case apiShowMyAddress(userId: Int64)
|
||||
case apiSetProfileAddress(userId: Int64, on: Bool)
|
||||
case apiAddressAutoAccept(userId: Int64, autoAccept: AutoAccept?)
|
||||
case apiAcceptContact(contactReqId: Int64)
|
||||
case apiAcceptContact(incognito: Bool, contactReqId: Int64)
|
||||
case apiRejectContact(contactReqId: Int64)
|
||||
// WebRTC calls
|
||||
case apiSendCallInvitation(contact: Contact, callType: CallType)
|
||||
@@ -119,6 +125,13 @@ public enum ChatCommand {
|
||||
return "/_create user \(encodeJSON(user))"
|
||||
case .listUsers: return "/users"
|
||||
case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))"
|
||||
case let .setAllContactReceipts(enable): return "/set receipts all \(onOff(enable))"
|
||||
case let .apiSetUserContactReceipts(userId, userMsgReceiptSettings):
|
||||
let umrs = userMsgReceiptSettings
|
||||
return "/_set receipts contacts \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))"
|
||||
case let .apiSetUserGroupReceipts(userId, userMsgReceiptSettings):
|
||||
let umrs = userMsgReceiptSettings
|
||||
return "/_set receipts groups \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))"
|
||||
case let .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))"
|
||||
case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId) \(encodeJSON(viewPwd))"
|
||||
case let .apiMuteUser(userId): return "/_mute user \(userId)"
|
||||
@@ -135,7 +148,6 @@ public enum ChatCommand {
|
||||
} else {
|
||||
return "/_xftp off"
|
||||
}
|
||||
case let .setIncognito(incognito): return "/incognito \(onOff(incognito))"
|
||||
case let .apiExportArchive(cfg): return "/_db export \(encodeJSON(cfg))"
|
||||
case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))"
|
||||
case .apiDeleteStorage: return "/_db delete"
|
||||
@@ -176,6 +188,7 @@ public enum ChatCommand {
|
||||
case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)"
|
||||
case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))"
|
||||
case .apiGetNetworkConfig: return "/network"
|
||||
case .reconnectAllServers: return "/reconnect"
|
||||
case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id)) \(encodeJSON(chatSettings))"
|
||||
case let .apiContactInfo(contactId): return "/_info @\(contactId)"
|
||||
case let .apiGroupMemberInfo(groupId, groupMemberId): return "/_info #\(groupId) \(groupMemberId)"
|
||||
@@ -183,14 +196,25 @@ public enum ChatCommand {
|
||||
case let .apiSwitchGroupMember(groupId, groupMemberId): return "/_switch #\(groupId) \(groupMemberId)"
|
||||
case let .apiAbortSwitchContact(contactId): return "/_abort switch @\(contactId)"
|
||||
case let .apiAbortSwitchGroupMember(groupId, groupMemberId): return "/_abort switch #\(groupId) \(groupMemberId)"
|
||||
case let .apiSyncContactRatchet(contactId, force): if force {
|
||||
return "/_sync @\(contactId) force=on"
|
||||
} else {
|
||||
return "/_sync @\(contactId)"
|
||||
}
|
||||
case let .apiSyncGroupMemberRatchet(groupId, groupMemberId, force): if force {
|
||||
return "/_sync #\(groupId) \(groupMemberId) force=on"
|
||||
} else {
|
||||
return "/_sync #\(groupId) \(groupMemberId)"
|
||||
}
|
||||
case let .apiGetContactCode(contactId): return "/_get code @\(contactId)"
|
||||
case let .apiGetGroupMemberCode(groupId, groupMemberId): return "/_get code #\(groupId) \(groupMemberId)"
|
||||
case let .apiVerifyContact(contactId, .some(connectionCode)): return "/_verify code @\(contactId) \(connectionCode)"
|
||||
case let .apiVerifyContact(contactId, .none): return "/_verify code @\(contactId)"
|
||||
case let .apiVerifyGroupMember(groupId, groupMemberId, .some(connectionCode)): return "/_verify code #\(groupId) \(groupMemberId) \(connectionCode)"
|
||||
case let .apiVerifyGroupMember(groupId, groupMemberId, .none): return "/_verify code #\(groupId) \(groupMemberId)"
|
||||
case let .apiAddContact(userId): return "/_connect \(userId)"
|
||||
case let .apiConnect(userId, connReq): return "/_connect \(userId) \(connReq)"
|
||||
case let .apiAddContact(userId, incognito): return "/_connect \(userId) incognito=\(onOff(incognito))"
|
||||
case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))"
|
||||
case let .apiConnect(userId, incognito, connReq): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connReq)"
|
||||
case let .apiDeleteChat(type, id): return "/_delete \(ref(type, id))"
|
||||
case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))"
|
||||
case let .apiListContacts(userId): return "/_contacts \(userId)"
|
||||
@@ -203,7 +227,7 @@ public enum ChatCommand {
|
||||
case let .apiShowMyAddress(userId): return "/_show_address \(userId)"
|
||||
case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))"
|
||||
case let .apiAddressAutoAccept(userId, autoAccept): return "/_auto_accept \(userId) \(AutoAccept.cmdString(autoAccept))"
|
||||
case let .apiAcceptContact(contactReqId): return "/_accept \(contactReqId)"
|
||||
case let .apiAcceptContact(incognito, contactReqId): return "/_accept incognito=\(onOff(incognito)) \(contactReqId)"
|
||||
case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)"
|
||||
case let .apiSendCallInvitation(contact, callType): return "/_call invite @\(contact.apiId) \(encodeJSON(callType))"
|
||||
case let .apiRejectCall(contact): return "/_call reject @\(contact.apiId)"
|
||||
@@ -235,6 +259,9 @@ public enum ChatCommand {
|
||||
case .createActiveUser: return "createActiveUser"
|
||||
case .listUsers: return "listUsers"
|
||||
case .apiSetActiveUser: return "apiSetActiveUser"
|
||||
case .setAllContactReceipts: return "setAllContactReceipts"
|
||||
case .apiSetUserContactReceipts: return "apiSetUserContactReceipts"
|
||||
case .apiSetUserGroupReceipts: return "apiSetUserGroupReceipts"
|
||||
case .apiHideUser: return "apiHideUser"
|
||||
case .apiUnhideUser: return "apiUnhideUser"
|
||||
case .apiMuteUser: return "apiMuteUser"
|
||||
@@ -247,7 +274,6 @@ public enum ChatCommand {
|
||||
case .setTempFolder: return "setTempFolder"
|
||||
case .setFilesFolder: return "setFilesFolder"
|
||||
case .apiSetXFTPConfig: return "apiSetXFTPConfig"
|
||||
case .setIncognito: return "setIncognito"
|
||||
case .apiExportArchive: return "apiExportArchive"
|
||||
case .apiImportArchive: return "apiImportArchive"
|
||||
case .apiDeleteStorage: return "apiDeleteStorage"
|
||||
@@ -284,6 +310,7 @@ public enum ChatCommand {
|
||||
case .apiGetChatItemTTL: return "apiGetChatItemTTL"
|
||||
case .apiSetNetworkConfig: return "apiSetNetworkConfig"
|
||||
case .apiGetNetworkConfig: return "apiGetNetworkConfig"
|
||||
case .reconnectAllServers: return "reconnectAllServers"
|
||||
case .apiSetChatSettings: return "apiSetChatSettings"
|
||||
case .apiContactInfo: return "apiContactInfo"
|
||||
case .apiGroupMemberInfo: return "apiGroupMemberInfo"
|
||||
@@ -291,11 +318,14 @@ public enum ChatCommand {
|
||||
case .apiSwitchGroupMember: return "apiSwitchGroupMember"
|
||||
case .apiAbortSwitchContact: return "apiAbortSwitchContact"
|
||||
case .apiAbortSwitchGroupMember: return "apiAbortSwitchGroupMember"
|
||||
case .apiSyncContactRatchet: return "apiSyncContactRatchet"
|
||||
case .apiSyncGroupMemberRatchet: return "apiSyncGroupMemberRatchet"
|
||||
case .apiGetContactCode: return "apiGetContactCode"
|
||||
case .apiGetGroupMemberCode: return "apiGetGroupMemberCode"
|
||||
case .apiVerifyContact: return "apiVerifyContact"
|
||||
case .apiVerifyGroupMember: return "apiVerifyGroupMember"
|
||||
case .apiAddContact: return "apiAddContact"
|
||||
case .apiSetConnectionIncognito: return "apiSetConnectionIncognito"
|
||||
case .apiConnect: return "apiConnect"
|
||||
case .apiDeleteChat: return "apiDeleteChat"
|
||||
case .apiClearChat: return "apiClearChat"
|
||||
@@ -407,13 +437,23 @@ public enum ChatResponse: Decodable, Error {
|
||||
case groupMemberSwitchStarted(user: User, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats)
|
||||
case contactSwitchAborted(user: User, contact: Contact, connectionStats: ConnectionStats)
|
||||
case groupMemberSwitchAborted(user: User, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats)
|
||||
case contactSwitch(user: User, contact: Contact, switchProgress: SwitchProgress)
|
||||
case groupMemberSwitch(user: User, groupInfo: GroupInfo, member: GroupMember, switchProgress: SwitchProgress)
|
||||
case contactRatchetSyncStarted(user: User, contact: Contact, connectionStats: ConnectionStats)
|
||||
case groupMemberRatchetSyncStarted(user: User, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats)
|
||||
case contactRatchetSync(user: User, contact: Contact, ratchetSyncProgress: RatchetSyncProgress)
|
||||
case groupMemberRatchetSync(user: User, groupInfo: GroupInfo, member: GroupMember, ratchetSyncProgress: RatchetSyncProgress)
|
||||
case contactVerificationReset(user: User, contact: Contact)
|
||||
case groupMemberVerificationReset(user: User, groupInfo: GroupInfo, member: GroupMember)
|
||||
case contactCode(user: User, contact: Contact, connectionCode: String)
|
||||
case groupMemberCode(user: User, groupInfo: GroupInfo, member: GroupMember, connectionCode: String)
|
||||
case connectionVerified(user: User, verified: Bool, expectedCode: String)
|
||||
case invitation(user: User, connReqInvitation: String)
|
||||
case invitation(user: User, connReqInvitation: String, connection: PendingContactConnection)
|
||||
case connectionIncognitoUpdated(user: User, toConnection: PendingContactConnection)
|
||||
case sentConfirmation(user: User)
|
||||
case sentInvitation(user: User)
|
||||
case contactAlreadyExists(user: User, contact: Contact)
|
||||
case contactRequestAlreadyAccepted(user: User, contact: Contact)
|
||||
case contactDeleted(user: User, contact: Contact)
|
||||
case chatCleared(user: User, chatInfo: ChatInfo)
|
||||
case userProfileNoChange(user: User)
|
||||
@@ -443,6 +483,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case newChatItem(user: User, chatItem: AChatItem)
|
||||
case chatItemStatusUpdated(user: User, chatItem: AChatItem)
|
||||
case chatItemUpdated(user: User, chatItem: AChatItem)
|
||||
case chatItemNotChanged(user: User, chatItem: AChatItem)
|
||||
case chatItemReaction(user: User, added: Bool, reaction: ACIReaction)
|
||||
case chatItemDeleted(user: User, deletedChatItem: AChatItem, toChatItem: AChatItem?, byUser: Bool)
|
||||
case contactsList(user: User, contacts: [Contact])
|
||||
@@ -530,13 +571,23 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .groupMemberSwitchStarted: return "groupMemberSwitchStarted"
|
||||
case .contactSwitchAborted: return "contactSwitchAborted"
|
||||
case .groupMemberSwitchAborted: return "groupMemberSwitchAborted"
|
||||
case .contactSwitch: return "contactSwitch"
|
||||
case .groupMemberSwitch: return "groupMemberSwitch"
|
||||
case .contactRatchetSyncStarted: return "contactRatchetSyncStarted"
|
||||
case .groupMemberRatchetSyncStarted: return "groupMemberRatchetSyncStarted"
|
||||
case .contactRatchetSync: return "contactRatchetSync"
|
||||
case .groupMemberRatchetSync: return "groupMemberRatchetSync"
|
||||
case .contactVerificationReset: return "contactVerificationReset"
|
||||
case .groupMemberVerificationReset: return "groupMemberVerificationReset"
|
||||
case .contactCode: return "contactCode"
|
||||
case .groupMemberCode: return "groupMemberCode"
|
||||
case .connectionVerified: return "connectionVerified"
|
||||
case .invitation: return "invitation"
|
||||
case .connectionIncognitoUpdated: return "connectionIncognitoUpdated"
|
||||
case .sentConfirmation: return "sentConfirmation"
|
||||
case .sentInvitation: return "sentInvitation"
|
||||
case .contactAlreadyExists: return "contactAlreadyExists"
|
||||
case .contactRequestAlreadyAccepted: return "contactRequestAlreadyAccepted"
|
||||
case .contactDeleted: return "contactDeleted"
|
||||
case .chatCleared: return "chatCleared"
|
||||
case .userProfileNoChange: return "userProfileNoChange"
|
||||
@@ -566,6 +617,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .newChatItem: return "newChatItem"
|
||||
case .chatItemStatusUpdated: return "chatItemStatusUpdated"
|
||||
case .chatItemUpdated: return "chatItemUpdated"
|
||||
case .chatItemNotChanged: return "chatItemNotChanged"
|
||||
case .chatItemReaction: return "chatItemReaction"
|
||||
case .chatItemDeleted: return "chatItemDeleted"
|
||||
case .contactsList: return "contactsList"
|
||||
@@ -652,13 +704,23 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .groupMemberSwitchStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))")
|
||||
case let .contactSwitchAborted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))")
|
||||
case let .groupMemberSwitchAborted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))")
|
||||
case let .contactSwitch(u, contact, switchProgress): return withUser(u, "contact: \(String(describing: contact))\nswitchProgress: \(String(describing: switchProgress))")
|
||||
case let .groupMemberSwitch(u, groupInfo, member, switchProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nswitchProgress: \(String(describing: switchProgress))")
|
||||
case let .contactRatchetSyncStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))")
|
||||
case let .groupMemberRatchetSyncStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))")
|
||||
case let .contactRatchetSync(u, contact, ratchetSyncProgress): return withUser(u, "contact: \(String(describing: contact))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))")
|
||||
case let .groupMemberRatchetSync(u, groupInfo, member, ratchetSyncProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))")
|
||||
case let .contactVerificationReset(u, contact): return withUser(u, "contact: \(String(describing: contact))")
|
||||
case let .groupMemberVerificationReset(u, groupInfo, member): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))")
|
||||
case let .contactCode(u, contact, connectionCode): return withUser(u, "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)")
|
||||
case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)")
|
||||
case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)")
|
||||
case let .invitation(u, connReqInvitation): return withUser(u, connReqInvitation)
|
||||
case let .invitation(u, connReqInvitation, _): return withUser(u, connReqInvitation)
|
||||
case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection))
|
||||
case .sentConfirmation: return noDetails
|
||||
case .sentInvitation: return noDetails
|
||||
case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact))
|
||||
case let .contactRequestAlreadyAccepted(u, contact): return withUser(u, String(describing: contact))
|
||||
case let .contactDeleted(u, contact): return withUser(u, String(describing: contact))
|
||||
case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo))
|
||||
case .userProfileNoChange: return noDetails
|
||||
@@ -688,6 +750,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .newChatItem(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||
case let .chatItemStatusUpdated(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||
case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||
case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||
case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))")
|
||||
case let .chatItemDeleted(u, deletedChatItem, toChatItem, byUser): return withUser(u, "deletedChatItem:\n\(String(describing: deletedChatItem))\ntoChatItem:\n\(String(describing: toChatItem))\nbyUser: \(byUser)")
|
||||
case let .contactsList(u, contacts): return withUser(u, String(describing: contacts))
|
||||
@@ -761,6 +824,14 @@ public enum ChatResponse: Decodable, Error {
|
||||
}
|
||||
}
|
||||
|
||||
public func chatError(_ chatResponse: ChatResponse) -> ChatErrorType? {
|
||||
switch chatResponse {
|
||||
case let .chatCmdError(_, .error(error)): return error
|
||||
case let .chatError(_, .error(error)): return error
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
struct NewUser: Encodable {
|
||||
var profile: Profile?
|
||||
var sameServers: Bool
|
||||
@@ -994,6 +1065,7 @@ public struct NetCfg: Codable, Equatable {
|
||||
public var sessionMode: TransportSessionMode
|
||||
public var tcpConnectTimeout: Int // microseconds
|
||||
public var tcpTimeout: Int // microseconds
|
||||
public var tcpTimeoutPerKb: Int // microseconds
|
||||
public var tcpKeepAlive: KeepAliveOpts?
|
||||
public var smpPingInterval: Int // microseconds
|
||||
public var smpPingCount: Int // times
|
||||
@@ -1002,8 +1074,9 @@ public struct NetCfg: Codable, Equatable {
|
||||
public static let defaults: NetCfg = NetCfg(
|
||||
socksProxy: nil,
|
||||
sessionMode: TransportSessionMode.user,
|
||||
tcpConnectTimeout: 10_000_000,
|
||||
tcpTimeout: 7_000_000,
|
||||
tcpConnectTimeout: 15_000_000,
|
||||
tcpTimeout: 10_000_000,
|
||||
tcpTimeoutPerKb: 20_000,
|
||||
tcpKeepAlive: KeepAliveOpts.defaults,
|
||||
smpPingInterval: 1200_000_000,
|
||||
smpPingCount: 3,
|
||||
@@ -1013,8 +1086,9 @@ public struct NetCfg: Codable, Equatable {
|
||||
public static let proxyDefaults: NetCfg = NetCfg(
|
||||
socksProxy: nil,
|
||||
sessionMode: TransportSessionMode.user,
|
||||
tcpConnectTimeout: 20_000_000,
|
||||
tcpTimeout: 15_000_000,
|
||||
tcpConnectTimeout: 30_000_000,
|
||||
tcpTimeout: 20_000_000,
|
||||
tcpTimeoutPerKb: 40_000,
|
||||
tcpKeepAlive: KeepAliveOpts.defaults,
|
||||
smpPingInterval: 1200_000_000,
|
||||
smpPingCount: 3,
|
||||
@@ -1090,19 +1164,42 @@ public struct KeepAliveOpts: Codable, Equatable {
|
||||
|
||||
public struct ChatSettings: Codable {
|
||||
public var enableNtfs: Bool
|
||||
public var sendRcpts: Bool?
|
||||
public var favorite: Bool
|
||||
|
||||
public init(enableNtfs: Bool, favorite: Bool) {
|
||||
public init(enableNtfs: Bool, sendRcpts: Bool?, favorite: Bool) {
|
||||
self.enableNtfs = enableNtfs
|
||||
self.sendRcpts = sendRcpts
|
||||
self.favorite = favorite
|
||||
}
|
||||
|
||||
public static let defaults: ChatSettings = ChatSettings(enableNtfs: true, favorite: false)
|
||||
public static let defaults: ChatSettings = ChatSettings(enableNtfs: true, sendRcpts: nil, favorite: false)
|
||||
}
|
||||
|
||||
public struct ConnectionStats: Codable {
|
||||
public struct UserMsgReceiptSettings: Codable {
|
||||
public var enable: Bool
|
||||
public var clearOverrides: Bool
|
||||
|
||||
public init(enable: Bool, clearOverrides: Bool) {
|
||||
self.enable = enable
|
||||
self.clearOverrides = clearOverrides
|
||||
}
|
||||
}
|
||||
|
||||
public struct ConnectionStats: Decodable {
|
||||
public var connAgentVersion: Int
|
||||
public var rcvQueuesInfo: [RcvQueueInfo]
|
||||
public var sndQueuesInfo: [SndQueueInfo]
|
||||
public var ratchetSyncState: RatchetSyncState
|
||||
public var ratchetSyncSupported: Bool
|
||||
|
||||
public var ratchetSyncAllowed: Bool {
|
||||
ratchetSyncSupported && [.allowed, .required].contains(ratchetSyncState)
|
||||
}
|
||||
|
||||
public var ratchetSyncSendProhibited: Bool {
|
||||
[.required, .started, .agreed].contains(ratchetSyncState)
|
||||
}
|
||||
}
|
||||
|
||||
public struct RcvQueueInfo: Codable {
|
||||
@@ -1128,6 +1225,30 @@ public enum SndSwitchStatus: String, Codable {
|
||||
case sendingQTEST = "sending_qtest"
|
||||
}
|
||||
|
||||
public enum QueueDirection: String, Decodable {
|
||||
case rcv
|
||||
case snd
|
||||
}
|
||||
|
||||
public struct SwitchProgress: Decodable {
|
||||
public var queueDirection: QueueDirection
|
||||
public var switchPhase: SwitchPhase
|
||||
public var connectionStats: ConnectionStats
|
||||
}
|
||||
|
||||
public struct RatchetSyncProgress: Decodable {
|
||||
public var ratchetSyncStatus: RatchetSyncState
|
||||
public var connectionStats: ConnectionStats
|
||||
}
|
||||
|
||||
public enum RatchetSyncState: String, Decodable {
|
||||
case ok
|
||||
case allowed
|
||||
case required
|
||||
case started
|
||||
case agreed
|
||||
}
|
||||
|
||||
public struct UserContactLink: Decodable {
|
||||
public var connReqContact: String
|
||||
public var autoAccept: AutoAccept?
|
||||
@@ -1263,14 +1384,32 @@ public enum ChatError: Decodable {
|
||||
|
||||
public enum ChatErrorType: Decodable {
|
||||
case noActiveUser
|
||||
case noConnectionUser(agentConnId: String)
|
||||
case noSndFileUser(agentSndFileId: String)
|
||||
case noRcvFileUser(agentRcvFileId: String)
|
||||
case userUnknown
|
||||
case activeUserExists
|
||||
case userExists
|
||||
case differentActiveUser
|
||||
case differentActiveUser(commandUserId: Int64, activeUserId: Int64)
|
||||
case cantDeleteActiveUser(userId: Int64)
|
||||
case cantDeleteLastUser(userId: Int64)
|
||||
case cantHideLastUser(userId: Int64)
|
||||
case hiddenUserAlwaysMuted(userId: Int64)
|
||||
case emptyUserPassword(userId: Int64)
|
||||
case userAlreadyHidden(userId: Int64)
|
||||
case userNotHidden(userId: Int64)
|
||||
case chatNotStarted
|
||||
case chatNotStopped
|
||||
case chatStoreChanged
|
||||
case invalidConnReq
|
||||
case invalidChatMessage(message: String)
|
||||
case invalidChatMessage(connection: Connection, message: String)
|
||||
case contactNotReady(contact: Contact)
|
||||
case groupUserRole
|
||||
case contactDisabled(contact: Contact)
|
||||
case connectionDisabled(connection: Connection)
|
||||
case groupUserRole(groupInfo: GroupInfo, requiredRole: GroupMemberRole)
|
||||
case groupMemberInitialRole(groupInfo: GroupInfo, initialRole: GroupMemberRole)
|
||||
case contactIncognitoCantInvite
|
||||
case groupIncognitoCantInvite
|
||||
case groupContactRole(contactName: ContactName)
|
||||
case groupDuplicateMember(contactName: ContactName)
|
||||
case groupDuplicateMemberId
|
||||
@@ -1282,23 +1421,50 @@ public enum ChatErrorType: Decodable {
|
||||
case groupCantResendInvitation(groupInfo: GroupInfo, contactName: ContactName)
|
||||
case groupInternal(message: String)
|
||||
case fileNotFound(message: String)
|
||||
case fileSize(filePath: String)
|
||||
case fileAlreadyReceiving(message: String)
|
||||
case fileCancelled(message: String)
|
||||
case fileCancel(fileId: Int64, message: String)
|
||||
case fileAlreadyExists(filePath: String)
|
||||
case fileRead(filePath: String, message: String)
|
||||
case fileWrite(filePath: String, message: String)
|
||||
case fileSend(fileId: Int64, agentError: String)
|
||||
case fileRcvChunk(message: String)
|
||||
case fileInternal(message: String)
|
||||
case fileImageType(filePath: String)
|
||||
case fileImageSize(filePath: String)
|
||||
case fileNotReceived(fileId: Int64)
|
||||
// case xFTPRcvFile
|
||||
// case xFTPSndFile
|
||||
case fallbackToSMPProhibited(fileId: Int64)
|
||||
case inlineFileProhibited(fileId: Int64)
|
||||
case invalidQuote
|
||||
case invalidChatItemUpdate
|
||||
case invalidChatItemDelete
|
||||
case hasCurrentCall
|
||||
case noCurrentCall
|
||||
case callContact(contactId: Int64)
|
||||
case callState
|
||||
case directMessagesProhibited(contact: Contact)
|
||||
case agentVersion
|
||||
case agentNoSubResult(agentConnId: String)
|
||||
case commandError(message: String)
|
||||
case serverProtocol
|
||||
case agentCommandError(message: String)
|
||||
case invalidFileDescription(message: String)
|
||||
case connectionIncognitoChangeProhibited
|
||||
case internalError(message: String)
|
||||
case exception(message: String)
|
||||
}
|
||||
|
||||
public enum StoreError: Decodable {
|
||||
case duplicateName
|
||||
case userNotFound(userId: Int64)
|
||||
case userNotFoundByName(contactName: ContactName)
|
||||
case userNotFoundByContactId(contactId: Int64)
|
||||
case userNotFoundByGroupId(groupId: Int64)
|
||||
case userNotFoundByFileId(fileId: Int64)
|
||||
case userNotFoundByContactRequestId(contactRequestId: Int64)
|
||||
case contactNotFound(contactId: Int64)
|
||||
case contactNotFoundByName(contactName: ContactName)
|
||||
case contactNotReady(contactName: ContactName)
|
||||
@@ -1308,6 +1474,9 @@ public enum StoreError: Decodable {
|
||||
case contactRequestNotFoundByName(contactName: ContactName)
|
||||
case groupNotFound(groupId: Int64)
|
||||
case groupNotFoundByName(groupName: GroupName)
|
||||
case groupMemberNameNotFound(groupId: Int64, groupMemberName: ContactName)
|
||||
case groupMemberNotFound(groupMemberId: Int64)
|
||||
case groupMemberNotFoundByMemberId(memberId: String)
|
||||
case groupWithoutUser
|
||||
case duplicateGroupMember
|
||||
case groupAlreadyJoined
|
||||
@@ -1315,9 +1484,16 @@ public enum StoreError: Decodable {
|
||||
case sndFileNotFound(fileId: Int64)
|
||||
case sndFileInvalid(fileId: Int64)
|
||||
case rcvFileNotFound(fileId: Int64)
|
||||
case rcvFileDescrNotFound(fileId: Int64)
|
||||
case fileNotFound(fileId: Int64)
|
||||
case rcvFileInvalid(fileId: Int64)
|
||||
case rcvFileInvalidDescrPart
|
||||
case sharedMsgIdNotFoundByFileId(fileId: Int64)
|
||||
case fileIdNotFoundBySharedMsgId(sharedMsgId: String)
|
||||
case sndFileNotFoundXFTP(agentSndFileId: String)
|
||||
case rcvFileNotFoundXFTP(agentRcvFileId: String)
|
||||
case connectionNotFound(agentConnId: String)
|
||||
case connectionNotFoundById(connId: Int64)
|
||||
case pendingConnectionNotFound(connId: Int64)
|
||||
case introNotFound
|
||||
case uniqueID
|
||||
@@ -1325,11 +1501,16 @@ public enum StoreError: Decodable {
|
||||
case noMsgDelivery(connId: Int64, agentMsgId: String)
|
||||
case badChatItem(itemId: Int64)
|
||||
case chatItemNotFound(itemId: Int64)
|
||||
case quotedChatItemNotFound
|
||||
case chatItemNotFoundByText(text: String)
|
||||
case chatItemSharedMsgIdNotFound(sharedMsgId: String)
|
||||
case chatItemNotFoundByFileId(fileId: Int64)
|
||||
case chatItemNotFoundByGroupId(groupId: Int64)
|
||||
case profileNotFound(profileId: Int64)
|
||||
case duplicateGroupLink(groupInfo: GroupInfo)
|
||||
case groupLinkNotFound(groupInfo: GroupInfo)
|
||||
case hostMemberIdNotFound(groupId: Int64)
|
||||
case contactNotFoundByFileId(fileId: Int64)
|
||||
case noGroupSndStatus(itemId: Int64, groupMemberId: Int64)
|
||||
}
|
||||
|
||||
public enum DatabaseError: Decodable {
|
||||
@@ -1349,11 +1530,12 @@ public enum AgentErrorType: Decodable {
|
||||
case CMD(cmdErr: CommandErrorType)
|
||||
case CONN(connErr: ConnectionErrorType)
|
||||
case SMP(smpErr: ProtocolErrorType)
|
||||
case XFTP(xftpErr: XFTPErrorType)
|
||||
case NTF(ntfErr: ProtocolErrorType)
|
||||
case XFTP(xftpErr: XFTPErrorType)
|
||||
case BROKER(brokerAddress: String, brokerErr: BrokerErrorType)
|
||||
case AGENT(agentErr: SMPAgentError)
|
||||
case INTERNAL(internalErr: String)
|
||||
case INACTIVE
|
||||
}
|
||||
|
||||
public enum CommandErrorType: Decodable {
|
||||
@@ -1373,9 +1555,10 @@ public enum ConnectionErrorType: Decodable {
|
||||
}
|
||||
|
||||
public enum BrokerErrorType: Decodable {
|
||||
case RESPONSE(smpErr: ProtocolErrorType)
|
||||
case RESPONSE(smpErr: String)
|
||||
case UNEXPECTED
|
||||
case NETWORK
|
||||
case HOST
|
||||
case TRANSPORT(transportErr: ProtocolTransportError)
|
||||
case TIMEOUT
|
||||
}
|
||||
@@ -1409,6 +1592,7 @@ public enum XFTPErrorType: Decodable {
|
||||
public enum ProtocolCommandError: Decodable {
|
||||
case UNKNOWN
|
||||
case SYNTAX
|
||||
case PROHIBITED
|
||||
case NO_AUTH
|
||||
case HAS_AUTH
|
||||
case NO_ENTITY
|
||||
@@ -1431,7 +1615,9 @@ public enum SMPAgentError: Decodable {
|
||||
case A_MESSAGE
|
||||
case A_PROHIBITED
|
||||
case A_VERSION
|
||||
case A_ENCRYPTION
|
||||
case A_CRYPTO
|
||||
case A_DUPLICATE
|
||||
case A_QUEUE(queueErr: String)
|
||||
}
|
||||
|
||||
public enum ArchiveError: Decodable {
|
||||
|
||||
@@ -13,6 +13,8 @@ let GROUP_DEFAULT_APP_STATE = "appState"
|
||||
let GROUP_DEFAULT_DB_CONTAINER = "dbContainer"
|
||||
public let GROUP_DEFAULT_CHAT_LAST_START = "chatLastStart"
|
||||
let GROUP_DEFAULT_NTF_PREVIEW_MODE = "ntfPreviewMode"
|
||||
public let GROUP_DEFAULT_NTF_ENABLE_LOCAL = "ntfEnableLocal"
|
||||
public let GROUP_DEFAULT_NTF_ENABLE_PERIODIC = "ntfEnablePeriodic"
|
||||
let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages"
|
||||
public let GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE = "privacyTransferImagesInline" // no longer used
|
||||
let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount"
|
||||
@@ -20,13 +22,14 @@ let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts"
|
||||
let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode"
|
||||
let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout"
|
||||
let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout"
|
||||
let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB = "networkTCPTimeoutPerKb"
|
||||
let GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL = "networkSMPPingInterval"
|
||||
let GROUP_DEFAULT_NETWORK_SMP_PING_COUNT = "networkSMPPingCount"
|
||||
let GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE = "networkEnableKeepAlive"
|
||||
let GROUP_DEFAULT_NETWORK_TCP_KEEP_IDLE = "networkTCPKeepIdle"
|
||||
let GROUP_DEFAULT_NETWORK_TCP_KEEP_INTVL = "networkTCPKeepIntvl"
|
||||
let GROUP_DEFAULT_NETWORK_TCP_KEEP_CNT = "networkTCPKeepCnt"
|
||||
let GROUP_DEFAULT_INCOGNITO = "incognito"
|
||||
public let GROUP_DEFAULT_INCOGNITO = "incognito"
|
||||
let GROUP_DEFAULT_STORE_DB_PASSPHRASE = "storeDBPassphrase"
|
||||
let GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE = "initialRandomDBPassphrase"
|
||||
public let GROUP_DEFAULT_CONFIRM_DB_UPGRADES = "confirmDBUpgrades"
|
||||
@@ -38,10 +41,13 @@ public let groupDefaults = UserDefaults(suiteName: APP_GROUP_NAME)!
|
||||
|
||||
public func registerGroupDefaults() {
|
||||
groupDefaults.register(defaults: [
|
||||
GROUP_DEFAULT_NTF_ENABLE_LOCAL: false,
|
||||
GROUP_DEFAULT_NTF_ENABLE_PERIODIC: false,
|
||||
GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS: OnionHosts.no.rawValue,
|
||||
GROUP_DEFAULT_NETWORK_SESSION_MODE: TransportSessionMode.user.rawValue,
|
||||
GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT: NetCfg.defaults.tcpConnectTimeout,
|
||||
GROUP_DEFAULT_NETWORK_TCP_TIMEOUT: NetCfg.defaults.tcpTimeout,
|
||||
GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB: NetCfg.defaults.tcpTimeoutPerKb,
|
||||
GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL: NetCfg.defaults.smpPingInterval,
|
||||
GROUP_DEFAULT_NETWORK_SMP_PING_COUNT: NetCfg.defaults.smpPingCount,
|
||||
GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE: NetCfg.defaults.enableKeepAlive,
|
||||
@@ -101,6 +107,10 @@ public let ntfPreviewModeGroupDefault = EnumDefault<NotificationPreviewMode>(
|
||||
|
||||
public let incognitoGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_INCOGNITO)
|
||||
|
||||
public let ntfEnableLocalGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_NTF_ENABLE_LOCAL)
|
||||
|
||||
public let ntfEnablePeriodicGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_NTF_ENABLE_PERIODIC)
|
||||
|
||||
public let privacyAcceptImagesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES)
|
||||
|
||||
public let privacyTransferImagesInlineGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE)
|
||||
@@ -209,6 +219,7 @@ public func getNetCfg() -> NetCfg {
|
||||
let sessionMode = networkSessionModeGroupDefault.get()
|
||||
let tcpConnectTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT)
|
||||
let tcpTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT)
|
||||
let tcpTimeoutPerKb = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB)
|
||||
let smpPingInterval = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL)
|
||||
let smpPingCount = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_SMP_PING_COUNT)
|
||||
let enableKeepAlive = groupDefaults.bool(forKey: GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE)
|
||||
@@ -227,6 +238,7 @@ public func getNetCfg() -> NetCfg {
|
||||
sessionMode: sessionMode,
|
||||
tcpConnectTimeout: tcpConnectTimeout,
|
||||
tcpTimeout: tcpTimeout,
|
||||
tcpTimeoutPerKb: tcpTimeoutPerKb,
|
||||
tcpKeepAlive: tcpKeepAlive,
|
||||
smpPingInterval: smpPingInterval,
|
||||
smpPingCount: smpPingCount,
|
||||
@@ -239,6 +251,7 @@ public func setNetCfg(_ cfg: NetCfg) {
|
||||
networkSessionModeGroupDefault.set(cfg.sessionMode)
|
||||
groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT)
|
||||
groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT)
|
||||
groupDefaults.set(cfg.tcpTimeoutPerKb, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB)
|
||||
groupDefaults.set(cfg.smpPingInterval, forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL)
|
||||
groupDefaults.set(cfg.smpPingCount, forKey: GROUP_DEFAULT_NETWORK_SMP_PING_COUNT)
|
||||
if let tcpKeepAlive = cfg.tcpKeepAlive {
|
||||
|
||||
@@ -23,6 +23,8 @@ public struct User: Decodable, NamedChat, Identifiable {
|
||||
public var localAlias: String { get { "" } }
|
||||
|
||||
public var showNtfs: Bool
|
||||
public var sendRcptsContacts: Bool
|
||||
public var sendRcptsSmallGroups: Bool
|
||||
public var viewPwdHash: UserPwdHash?
|
||||
|
||||
public var id: Int64 { userId }
|
||||
@@ -44,7 +46,9 @@ public struct User: Decodable, NamedChat, Identifiable {
|
||||
profile: LocalProfile.sampleData,
|
||||
fullPreferences: FullPreferences.sampleData,
|
||||
activeUser: true,
|
||||
showNtfs: true
|
||||
showNtfs: true,
|
||||
sendRcptsContacts: true,
|
||||
sendRcptsSmallGroups: false
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1196,6 +1200,13 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
}
|
||||
}
|
||||
|
||||
public var groupInfo: GroupInfo? {
|
||||
switch self {
|
||||
case let .group(groupInfo): return groupInfo
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
// this works for features that are common for contacts and groups
|
||||
public func featureEnabled(_ feature: ChatFeature) -> Bool {
|
||||
switch self {
|
||||
@@ -1353,7 +1364,7 @@ public struct Contact: Identifiable, Decodable, NamedChat {
|
||||
public var id: ChatId { get { "@\(contactId)" } }
|
||||
public var apiId: Int64 { get { contactId } }
|
||||
public var ready: Bool { get { activeConn.connStatus == .ready } }
|
||||
public var sendMsgEnabled: Bool { get { true } }
|
||||
public var sendMsgEnabled: Bool { get { !(activeConn.connectionStats?.ratchetSyncSendProhibited ?? false) } }
|
||||
public var displayName: String { localAlias == "" ? profile.displayName : localAlias }
|
||||
public var fullName: String { get { profile.fullName } }
|
||||
public var image: String? { get { profile.image } }
|
||||
@@ -1426,6 +1437,12 @@ public struct Connection: Decodable {
|
||||
public var customUserProfileId: Int64?
|
||||
public var connectionCode: SecurityCode?
|
||||
|
||||
public var connectionStats: ConnectionStats? = nil
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case connId, agentConnId, connStatus, connLevel, viaGroupLink, customUserProfileId, connectionCode
|
||||
}
|
||||
|
||||
public var id: ChatId { get { ":\(connId)" } }
|
||||
|
||||
static let sampleData = Connection(
|
||||
@@ -2054,14 +2071,6 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
return nil
|
||||
}
|
||||
|
||||
public var showMutableNotification: Bool {
|
||||
switch content {
|
||||
case .rcvCall: return false
|
||||
case .rcvChatFeature: return false
|
||||
default: return showNtfDir
|
||||
}
|
||||
}
|
||||
|
||||
public var memberDisplayName: String? {
|
||||
get {
|
||||
if case let .groupRcv(groupMember) = chatDir {
|
||||
@@ -2261,13 +2270,7 @@ public struct CIMeta: Decodable {
|
||||
}
|
||||
|
||||
public func statusIcon(_ metaColor: Color = .secondary) -> (String, Color)? {
|
||||
switch itemStatus {
|
||||
case .sndSent: return ("checkmark", metaColor)
|
||||
case .sndErrorAuth: return ("multiply", .red)
|
||||
case .sndError: return ("exclamationmark.triangle.fill", .yellow)
|
||||
case .rcvNew: return ("circlebadge.fill", Color.accentColor)
|
||||
default: return nil
|
||||
}
|
||||
itemStatus.statusIcon(metaColor)
|
||||
}
|
||||
|
||||
public static func getSample(_ id: Int64, _ ts: Date, _ text: String, _ status: CIStatus = .sndNew, itemDeleted: CIDeleted? = nil, itemEdited: Bool = false, itemLive: Bool = false, editable: Bool = true) -> CIMeta {
|
||||
@@ -2330,22 +2333,75 @@ private func recent(_ date: Date) -> Bool {
|
||||
|
||||
public enum CIStatus: Decodable {
|
||||
case sndNew
|
||||
case sndSent
|
||||
case sndSent(sndProgress: SndCIStatusProgress)
|
||||
case sndRcvd(msgRcptStatus: MsgReceiptStatus, sndProgress: SndCIStatusProgress)
|
||||
case sndErrorAuth
|
||||
case sndError(agentError: String)
|
||||
case rcvNew
|
||||
case rcvRead
|
||||
case invalid(text: String)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .sndNew: return "sndNew"
|
||||
case .sndSent: return "sndSent"
|
||||
case .sndErrorAuth: return "sndErrorAuth"
|
||||
case .sndError: return "sndError"
|
||||
case .rcvNew: return "rcvNew"
|
||||
case .sndNew: return "sndNew"
|
||||
case .sndSent: return "sndSent"
|
||||
case .sndRcvd: return "sndRcvd"
|
||||
case .sndErrorAuth: return "sndErrorAuth"
|
||||
case .sndError: return "sndError"
|
||||
case .rcvNew: return "rcvNew"
|
||||
case .rcvRead: return "rcvRead"
|
||||
case .invalid: return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
public func statusIcon(_ metaColor: Color = .secondary) -> (String, Color)? {
|
||||
switch self {
|
||||
case .sndNew: return nil
|
||||
case .sndSent: return ("checkmark", metaColor)
|
||||
case let .sndRcvd(msgRcptStatus, _):
|
||||
switch msgRcptStatus {
|
||||
case .ok: return ("checkmark", metaColor)
|
||||
case .badMsgHash: return ("checkmark", .red)
|
||||
}
|
||||
case .sndErrorAuth: return ("multiply", .red)
|
||||
case .sndError: return ("exclamationmark.triangle.fill", .yellow)
|
||||
case .rcvNew: return ("circlebadge.fill", Color.accentColor)
|
||||
case .rcvRead: return nil
|
||||
case .invalid: return ("questionmark", metaColor)
|
||||
}
|
||||
}
|
||||
|
||||
public var statusInfo: (String, String)? {
|
||||
switch self {
|
||||
case .sndNew: return nil
|
||||
case .sndSent: return nil
|
||||
case .sndRcvd: return nil
|
||||
case .sndErrorAuth: return (
|
||||
NSLocalizedString("Message delivery error", comment: "item status text"),
|
||||
NSLocalizedString("Most likely this connection is deleted.", comment: "item status description")
|
||||
)
|
||||
case let .sndError(agentError): return (
|
||||
NSLocalizedString("Message delivery error", comment: "item status text"),
|
||||
String.localizedStringWithFormat(NSLocalizedString("Unexpected error: %@", comment: "item status description"), agentError)
|
||||
)
|
||||
case .rcvNew: return nil
|
||||
case .rcvRead: return nil
|
||||
case let .invalid(text): return (
|
||||
NSLocalizedString("Invalid status", comment: "item status text"),
|
||||
text
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum MsgReceiptStatus: String, Decodable {
|
||||
case ok
|
||||
case badMsgHash
|
||||
}
|
||||
|
||||
public enum SndCIStatusProgress: String, Decodable {
|
||||
case partial
|
||||
case complete
|
||||
}
|
||||
|
||||
public enum CIDeleted: Decodable {
|
||||
@@ -2456,20 +2512,24 @@ public enum CIContent: Decodable, ItemContent {
|
||||
public enum MsgDecryptError: String, Decodable {
|
||||
case ratchetHeader
|
||||
case tooManySkipped
|
||||
case ratchetEarlier
|
||||
case other
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case .ratchetHeader: return NSLocalizedString("Permanent decryption error", comment: "message decrypt error item")
|
||||
case .tooManySkipped: return NSLocalizedString("Permanent decryption error", comment: "message decrypt error item")
|
||||
case .ratchetEarlier: return NSLocalizedString("Decryption error", comment: "message decrypt error item")
|
||||
case .other: return NSLocalizedString("Decryption error", comment: "message decrypt error item")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CIQuote: Decodable, ItemContent {
|
||||
var chatDir: CIDirection?
|
||||
public var chatDir: CIDirection?
|
||||
public var itemId: Int64?
|
||||
var sharedMsgId: String? = nil
|
||||
var sentAt: Date
|
||||
public var sentAt: Date
|
||||
public var content: MsgContent
|
||||
public var formattedText: [FormattedText]?
|
||||
|
||||
@@ -2484,7 +2544,7 @@ public struct CIQuote: Decodable, ItemContent {
|
||||
switch (chatDir) {
|
||||
case .directSnd: return "you"
|
||||
case .directRcv: return nil
|
||||
case .groupSnd: return membership?.displayName
|
||||
case .groupSnd: return membership?.displayName ?? "you"
|
||||
case let .groupRcv(member): return member.displayName
|
||||
case nil: return nil
|
||||
}
|
||||
@@ -2597,6 +2657,7 @@ public struct CIFile: Decodable {
|
||||
case .rcvCancelled: return false
|
||||
case .rcvComplete: return true
|
||||
case .rcvError: return false
|
||||
case .invalid: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2620,6 +2681,7 @@ public struct CIFile: Decodable {
|
||||
case .rcvCancelled: return nil
|
||||
case .rcvComplete: return nil
|
||||
case .rcvError: return nil
|
||||
case .invalid: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2680,6 +2742,7 @@ public enum CIFileStatus: Decodable, Equatable {
|
||||
case rcvComplete
|
||||
case rcvCancelled
|
||||
case rcvError
|
||||
case invalid(text: String)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
@@ -2694,11 +2757,12 @@ public enum CIFileStatus: Decodable, Equatable {
|
||||
case .rcvComplete: return "rcvComplete"
|
||||
case .rcvCancelled: return "rcvCancelled"
|
||||
case .rcvError: return "rcvError"
|
||||
case .invalid: return "invalid"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum MsgContent {
|
||||
public enum MsgContent: Equatable {
|
||||
case text(String)
|
||||
case link(text: String, preview: LinkPreview)
|
||||
case image(text: String, image: String)
|
||||
@@ -2759,6 +2823,19 @@ public enum MsgContent {
|
||||
case image
|
||||
case duration
|
||||
}
|
||||
|
||||
public static func == (lhs: MsgContent, rhs: MsgContent) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.text(lt), .text(rt)): return lt == rt
|
||||
case let (.link(lt, lp), .link(rt, rp)): return lt == rt && lp == rp
|
||||
case let (.image(lt, li), .image(rt, ri)): return lt == rt && li == ri
|
||||
case let (.video(lt, li, ld), .video(rt, ri, rd)): return lt == rt && li == ri && ld == rd
|
||||
case let (.voice(lt, ld), .voice(rt, rd)): return lt == rt && ld == rd
|
||||
case let (.file(lf), .file(rf)): return lf == rf
|
||||
case let (.unknown(lType, lt), .unknown(rType, rt)): return lType == rType && lt == rt
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MsgContent: Decodable {
|
||||
@@ -3057,6 +3134,8 @@ public enum SndGroupEvent: Decodable {
|
||||
|
||||
public enum RcvConnEvent: Decodable {
|
||||
case switchQueue(phase: SwitchPhase)
|
||||
case ratchetSync(syncStatus: RatchetSyncState)
|
||||
case verificationCodeReset
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
@@ -3064,25 +3143,51 @@ public enum RcvConnEvent: Decodable {
|
||||
if case .completed = phase {
|
||||
return NSLocalizedString("changed address for you", comment: "chat item text")
|
||||
}
|
||||
return NSLocalizedString("changing address...", comment: "chat item text")
|
||||
return NSLocalizedString("changing address…", comment: "chat item text")
|
||||
case let .ratchetSync(syncStatus):
|
||||
return ratchetSyncStatusToText(syncStatus)
|
||||
case .verificationCodeReset:
|
||||
return NSLocalizedString("security code changed", comment: "chat item text")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ratchetSyncStatusToText(_ ratchetSyncStatus: RatchetSyncState) -> String {
|
||||
switch ratchetSyncStatus {
|
||||
case .ok: return NSLocalizedString("encryption ok", comment: "chat item text")
|
||||
case .allowed: return NSLocalizedString("encryption re-negotiation allowed", comment: "chat item text")
|
||||
case .required: return NSLocalizedString("encryption re-negotiation required", comment: "chat item text")
|
||||
case .started: return NSLocalizedString("agreeing encryption…", comment: "chat item text")
|
||||
case .agreed: return NSLocalizedString("encryption agreed", comment: "chat item text")
|
||||
}
|
||||
}
|
||||
|
||||
public enum SndConnEvent: Decodable {
|
||||
case switchQueue(phase: SwitchPhase, member: GroupMemberRef?)
|
||||
|
||||
case ratchetSync(syncStatus: RatchetSyncState, member: GroupMemberRef?)
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case let .switchQueue(phase, member):
|
||||
if let name = member?.profile.profileViewName {
|
||||
return phase == .completed
|
||||
? String.localizedStringWithFormat(NSLocalizedString("you changed address for %@", comment: "chat item text"), name)
|
||||
: String.localizedStringWithFormat(NSLocalizedString("changing address for %@...", comment: "chat item text"), name)
|
||||
? String.localizedStringWithFormat(NSLocalizedString("you changed address for %@", comment: "chat item text"), name)
|
||||
: String.localizedStringWithFormat(NSLocalizedString("changing address for %@…", comment: "chat item text"), name)
|
||||
}
|
||||
return phase == .completed
|
||||
? NSLocalizedString("you changed address", comment: "chat item text")
|
||||
: NSLocalizedString("changing address...", comment: "chat item text")
|
||||
? NSLocalizedString("you changed address", comment: "chat item text")
|
||||
: NSLocalizedString("changing address…", comment: "chat item text")
|
||||
case let .ratchetSync(syncStatus, member):
|
||||
if let name = member?.profile.profileViewName {
|
||||
switch syncStatus {
|
||||
case .ok: return String.localizedStringWithFormat(NSLocalizedString("encryption ok for %@", comment: "chat item text"), name)
|
||||
case .allowed: return String.localizedStringWithFormat(NSLocalizedString("encryption re-negotiation allowed for %@", comment: "chat item text"), name)
|
||||
case .required: return String.localizedStringWithFormat(NSLocalizedString("encryption re-negotiation required for %@", comment: "chat item text"), name)
|
||||
case .started: return String.localizedStringWithFormat(NSLocalizedString("agreeing encryption for %@…", comment: "chat item text"), name)
|
||||
case .agreed: return String.localizedStringWithFormat(NSLocalizedString("encryption agreed for %@", comment: "chat item text"), name)
|
||||
}
|
||||
}
|
||||
return ratchetSyncStatusToText(syncStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3146,6 +3251,7 @@ public enum ChatItemTTL: Hashable, Identifiable, Comparable {
|
||||
|
||||
public struct ChatItemInfo: Decodable {
|
||||
public var itemVersions: [ChatItemVersion]
|
||||
public var memberDeliveryStatuses: [MemberDeliveryStatus]?
|
||||
}
|
||||
|
||||
public struct ChatItemVersion: Decodable {
|
||||
@@ -3155,3 +3261,8 @@ public struct ChatItemVersion: Decodable {
|
||||
public var itemVersionTs: Date
|
||||
public var createdAt: Date
|
||||
}
|
||||
|
||||
public struct MemberDeliveryStatus: Decodable {
|
||||
public var groupMemberId: Int64
|
||||
public var memberDeliveryStatus: CIStatus
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"_italic_" = "\\_kurzíva_";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- více stabilní doručování zpráv.\n- o trochu lepší skupiny.\n- a více!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 5 minutové hlasové zprávy.\n- vlastní čas mizení.\n- historie úprav.";
|
||||
|
||||
@@ -103,6 +106,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"%@ %@" = "%@ %@";
|
||||
|
||||
/* copied message info, <sender> at <time> */
|
||||
"%@ at %@:" = "%1$@ na %2$@:";
|
||||
|
||||
/* notification title */
|
||||
"%@ is connected!" = "%@ je připojen!";
|
||||
|
||||
@@ -232,21 +238,27 @@
|
||||
/* No comment provided by engineer. */
|
||||
"30 seconds" = "30 vteřin";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A few more things" = "Ještě pár věcí";
|
||||
|
||||
/* notification title */
|
||||
"A new contact" = "Nový kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A random profile will be sent to the contact that you received this link from" = "Náhodný profil bude zaslán kontaktu, od kterého jste obdrželi tento odkaz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A random profile will be sent to your contact" = "Vašemu kontaktu bude zaslán náhodný profil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A separate TCP connection will be used **for each chat profile you have in the app**." = "Samostatné připojení TCP bude použito **pro každý chat profil, který máte v aplikaci**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "**pro každý kontakt a člena skupiny** bude použito samostatné připojení TCP.\n**Upozornění**: Pokud máte mnoho připojení, spotřeba baterie a provozu může být podstatně vyšší a některá připojení mohou selhat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort" = "Přerušit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort changing address" = "Přerušit změnu adresy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort changing address?" = "Přerušit změnu adresy?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"About SimpleX" = "O SimpleX";
|
||||
|
||||
@@ -267,12 +279,12 @@
|
||||
"Accept" = "Přijmout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Accept contact" = "Přijmout kontakt";
|
||||
"Accept connection request?" = "Přijmout kontakt";
|
||||
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "Přijmout žádost o kontakt od %@?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* accept contact request via notification */
|
||||
"Accept incognito" = "Přijmout inkognito";
|
||||
|
||||
/* call status */
|
||||
@@ -302,6 +314,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Address" = "Adresa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Address change will be aborted. Old receiving address will be used." = "Změna adresy bude přerušena. Budou použity staré přijímací adresy.";
|
||||
|
||||
/* member role */
|
||||
"admin" = "správce";
|
||||
|
||||
@@ -311,6 +326,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Advanced network settings" = "Pokročilá nastavení sítě";
|
||||
|
||||
/* chat item text */
|
||||
"agreeing encryption for %@…" = "povoluji šifrování pro %@…";
|
||||
|
||||
/* chat item text */
|
||||
"agreeing encryption…" = "povoluji šifrování…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All app data is deleted." = "Všechna data aplikace jsou smazána.";
|
||||
|
||||
@@ -359,6 +380,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages." = "Povolit nevratné smazání odeslaných zpráv.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send files and media." = "Povolit odesílání souborů a médii.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send voice messages." = "Povolit odesílání hlasových zpráv.";
|
||||
|
||||
@@ -574,10 +598,10 @@
|
||||
"changed your role to %@" = "změnil vaši roli na %@";
|
||||
|
||||
/* chat item text */
|
||||
"changing address for %@..." = "změna adresy pro %@...";
|
||||
"changing address for %@…" = "změna adresy pro %@…";
|
||||
|
||||
/* chat item text */
|
||||
"changing address..." = "změna adresy...";
|
||||
"changing address…" = "změna adresy…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat archive" = "Chat se archivuje";
|
||||
@@ -670,7 +694,7 @@
|
||||
"connect to SimpleX Chat developers." = "připojit se k vývojářům SimpleX Chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via contact link?" = "Připojit se přes kontaktní odkaz?";
|
||||
"Connect via contact link" = "Připojit se přes kontaktní odkaz?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via group link?" = "Připojit se přes odkaz skupiny?";
|
||||
@@ -682,7 +706,7 @@
|
||||
"Connect via link / QR code" = "Připojit se prostřednictvím odkazu / QR kódu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via one-time link?" = "Připojit se jednorázovým odkazem?";
|
||||
"Connect via one-time link" = "Připojit se jednorázovým odkazem?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connected" = "připojeno";
|
||||
@@ -726,9 +750,6 @@
|
||||
/* chat list item title (it should not be shown */
|
||||
"connection established" = "spojení navázáno";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection request" = "Žádost o připojení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection request sent!" = "Požadavek na připojení byl odeslán!";
|
||||
|
||||
@@ -768,6 +789,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact preferences" = "Předvolby kontaktů";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contacts" = "Kontakty";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contacts can mark messages for deletion; you will be able to view them." = "Kontakty mohou označit zprávy ke smazání; vy je budete moci zobrazit.";
|
||||
|
||||
@@ -897,12 +921,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "Decentralizované";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "Chyba dešifrování";
|
||||
|
||||
/* pref value */
|
||||
"default (%@)" = "výchozí (%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"default (no)" = "výchozí (ne)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "výchozí (ano)";
|
||||
|
||||
/* chat item action */
|
||||
"Delete" = "Smazat";
|
||||
|
||||
@@ -1020,6 +1050,12 @@
|
||||
/* rcv group event chat item */
|
||||
"deleted group" = "odstraněna skupina";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delivery receipts are disabled!" = "Potvrzení o doručení jsou vypnuté!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delivery receipts!" = "Potvrzení o doručení!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Description" = "Popis";
|
||||
|
||||
@@ -1053,6 +1089,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Direct messages between members are prohibited in this group." = "Přímé zprávy mezi členy jsou v této skupině zakázány.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disable (keep overrides)" = "Vypnout (zachovat přepsání)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disable for all" = "Vypnout pro všechny";
|
||||
|
||||
/* authentication reason */
|
||||
"Disable SimpleX Lock" = "Vypnutí zámku SimpleX";
|
||||
|
||||
@@ -1092,6 +1134,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Don't create address" = "Nevytvářet adresu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Don't enable" = "Nepovolovat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Don't show again" = "Znovu neukazuj";
|
||||
|
||||
@@ -1122,9 +1167,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Enable" = "Zapnout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable (keep overrides)" = "Povolit (zachovat přepsání)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable automatic message deletion?" = "Povolit automatické mazání zpráv?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable for all" = "Povolit pro všechny";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable instant notifications?" = "Povolit okamžitá oznámení?";
|
||||
|
||||
@@ -1185,6 +1236,30 @@
|
||||
/* notification */
|
||||
"Encrypted message: unexpected error" = "Šifrovaná zpráva: neočekávaná chyba";
|
||||
|
||||
/* chat item text */
|
||||
"encryption agreed" = "šifrování povoleno";
|
||||
|
||||
/* chat item text */
|
||||
"encryption agreed for %@" = "šifrování povoleno pro %@";
|
||||
|
||||
/* chat item text */
|
||||
"encryption ok" = "šifrování ok";
|
||||
|
||||
/* chat item text */
|
||||
"encryption ok for %@" = "šifrování ok pro %@";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation allowed" = "opětovné vyjednávání šifrování povoleno";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation allowed for %@" = "opětovné vyjednávání šifrování povoleno pro %@";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation required" = "vyžadováno opětovné vyjednávání šifrování";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation required for %@" = "vyžadováno opětovné vyjednávání šifrování pro %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"ended" = "ukončeno";
|
||||
|
||||
@@ -1218,6 +1293,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error" = "Chyba";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error aborting address change" = "Chyba přerušení změny adresy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error accepting contact request" = "Chyba při přijímání žádosti o kontakt";
|
||||
|
||||
@@ -1272,6 +1350,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting user profile" = "Chyba mazání uživatelského profilu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error enabling delivery receipts!" = "Chyba povolení potvrzení o doručení!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error enabling notifications" = "Chyba při aktivaci oznámení";
|
||||
|
||||
@@ -1320,6 +1401,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending message" = "Chyba při odesílání zprávy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error setting delivery receipts!" = "Chyba nastavování potvrzení o doručení!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error starting chat" = "Chyba při spuštění chatu";
|
||||
|
||||
@@ -1329,6 +1413,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error switching profile!" = "Chyba při přepínání profilu!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error synchronizing connection" = "Chyba synchronizace připojení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error updating group link" = "Chyba aktualizace odkazu skupiny";
|
||||
|
||||
@@ -1353,6 +1440,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error: URL is invalid" = "Chyba: Adresa URL je neplatná";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Even when disabled in the conversation." = "I při vypnutí v konverzaci.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Exit without saving" = "Ukončit bez uložení";
|
||||
|
||||
@@ -1366,7 +1456,7 @@
|
||||
"Exported database archive." = "Exportovaný archiv databáze.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Exporting database archive..." = "Exportuji archiv databáze...";
|
||||
"Exporting database archive…" = "Exportuji archiv databáze…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Failed to remove passphrase" = "Přístupovou frázi se nepodařilo odstranit";
|
||||
@@ -1374,6 +1464,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Fast and no wait until the sender is online!" = "Rychle a bez čekání, než bude odesílatel online!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Favorite" = "Oblíbené";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"File will be deleted from servers." = "Soubor bude smazán ze serverů.";
|
||||
|
||||
@@ -1389,9 +1482,42 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Files & media" = "Soubory a média";
|
||||
|
||||
/* chat feature */
|
||||
"Files and media" = "Soubory a média";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Files and media are prohibited in this group." = "Soubory a média jsou zakázány v této skupině.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Files and media prohibited!" = "Soubory a média jsou zakázány!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Filter unread and favorite chats." = "Filtrovat nepřečtené a oblíbené chaty.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Finally, we have them! 🚀" = "Konečně je máme! 🚀";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Find chats faster" = "Najděte chaty rychleji";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix" = "Opravit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix connection" = "Opravit připojení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix connection?" = "Opravit připojení?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix encryption after restoring backups." = "Opravit šifrování po obnovení zálohy.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix not supported by contact" = "Opravit nepodporované kontaktem";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix not supported by group member" = "Opravit nepodporované členem skupiny";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"For console" = "Pro konzoli";
|
||||
|
||||
@@ -1402,7 +1528,7 @@
|
||||
"Full link" = "Úplný odkaz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Full name (optional)" = "Celé jméno (volitelné)";
|
||||
"Full name (optional)" = "Celé jméno (volitelně)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Full name:" = "Celé jméno:";
|
||||
@@ -1458,6 +1584,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send disappearing messages." = "Členové skupiny mohou posílat mizící zprávy.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send files and media." = "Členové skupiny mohou posílat soubory a média.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send voice messages." = "Členové skupiny mohou posílat hlasové zprávy.";
|
||||
|
||||
@@ -1512,7 +1641,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Hide:" = "Skrýt:";
|
||||
|
||||
/* copied message info */
|
||||
/* No comment provided by engineer. */
|
||||
"History" = "Historie";
|
||||
|
||||
/* time unit */
|
||||
@@ -1581,18 +1710,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Improved server configuration" = "Vylepšená konfigurace serveru";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"In reply to" = "V odpovědi na";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito" = "Inkognito";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito mode" = "Režim inkognito";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito mode is not supported here - your main profile will be sent to group members" = "Zde není podporován režim inkognito - členům skupiny bude zaslán váš hlavní profil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito mode protects the privacy of your main profile name and image — for each new contact a new random profile is created." = "Režim inkognito chrání soukromí vašeho hlavního profilového jména a obrázku - pro každý nový kontakt je vytvořen nový náhodný profil.";
|
||||
|
||||
/* chat list item description */
|
||||
"incognito via contact address link" = "inkognito přes odkaz na kontaktní adresu";
|
||||
|
||||
@@ -1734,6 +1860,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Joining group" = "Připojení ke skupině";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep your connections" = "Zachovat vaše připojení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keychain error" = "Chyba klíčenky";
|
||||
|
||||
@@ -1791,6 +1920,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Make a private connection" = "Vytvořte si soukromé připojení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Make one message disappear" = "Nechat jednu zprávu zmizet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Make profile private!" = "Změnit profil na soukromý!";
|
||||
|
||||
@@ -1839,9 +1971,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Member will be removed from group - this cannot be undone!" = "Člen bude odstraněn ze skupiny - toto nelze vzít zpět!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* item status text */
|
||||
"Message delivery error" = "Chyba doručení zprávy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message delivery receipts!" = "Potvrzení o doručení zprávy!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message draft" = "Návrh zprávy";
|
||||
|
||||
@@ -1867,7 +2002,7 @@
|
||||
"Messages & files" = "Zprávy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating database archive..." = "Přenášení archivu databáze...";
|
||||
"Migrating database archive…" = "Přenášení archivu databáze…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migration error:" = "Chyba přenášení:";
|
||||
@@ -1986,9 +2121,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"no e2e encryption" = "bez šifrování e2e";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No filtered chats" = "Žádné filtrované chaty";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No group!" = "Skupina nebyla nalezena!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No history" = "Žádná historie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No permission to record voice message" = "Nemáte oprávnění nahrávat hlasové zprávy";
|
||||
|
||||
@@ -2056,6 +2197,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can change group preferences." = "Předvolby skupiny mohou měnit pouze vlastníci skupiny.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can enable files and media." = "Pouze majitelé skupiny mohou povolit soubory a média.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can enable voice messages." = "Pouze majitelé skupin mohou povolit zasílání hlasových zpráv.";
|
||||
|
||||
@@ -2143,8 +2287,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Paste received link" = "Vložení přijatého odkazu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste the link you received into the box below to connect with your contact." = "Vložte odkaz, který jste obdrželi, do pole níže a spojte se se svým kontaktem.";
|
||||
/* placeholder */
|
||||
"Paste the link you received to connect with your contact." = "Vložte odkaz, který jste obdrželi, do pole níže a spojte se se svým kontaktem.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"peer-to-peer" = "peer-to-peer";
|
||||
@@ -2257,6 +2401,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending disappearing messages." = "Zakázat posílání mizících zpráv.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending files and media." = "Zakázat odesílání souborů a médií.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending voice messages." = "Zakázat odesílání hlasových zpráv.";
|
||||
|
||||
@@ -2269,6 +2416,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Protocol timeout" = "Časový limit protokolu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Protocol timeout per KB" = "Časový limit protokolu na KB";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Push notifications" = "Nabízená oznámení";
|
||||
|
||||
@@ -2276,7 +2426,7 @@
|
||||
"Rate the app" = "Ohodnoťte aplikaci";
|
||||
|
||||
/* chat item menu */
|
||||
"React..." = "Reagovat...";
|
||||
"React…" = "Reagovat…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read" = "Číst";
|
||||
@@ -2314,6 +2464,9 @@
|
||||
/* message info title */
|
||||
"Received message" = "Přijatá zpráva";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Přijímací adresa bude změněna na jiný server. Změna adresy bude dokončena po připojení odesílatele.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receiving file will be stopped." = "Příjem souboru bude zastaven.";
|
||||
|
||||
@@ -2323,6 +2476,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Recipients see updates as you type them." = "Příjemci uvidí aktualizace během jejich psaní.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reconnect all connected servers to force message delivery. It uses additional traffic." = "Znovu připojte všechny připojené servery a vynuťte doručení zprávy. Využívá další provoz.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reconnect servers?" = "Znovu připojit servery?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Record updated at" = "Záznam aktualizován v";
|
||||
|
||||
@@ -2336,7 +2495,7 @@
|
||||
"Reject" = "Odmítnout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reject contact (sender NOT notified)" = "Odmítnout kontakt (odesílatel NEBUDE upozorněn)";
|
||||
"Reject (sender NOT notified)" = "Odmítnout kontakt (odesílatel NEBUDE upozorněn)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reject contact request" = "Odmítnout žádost o kontakt";
|
||||
@@ -2371,6 +2530,15 @@
|
||||
/* rcv group event chat item */
|
||||
"removed you" = "odstranil vás";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Renegotiate" = "Znovu vyjednat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Renegotiate encryption" = "Znovu vyjednat šifrování";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Renegotiate encryption?" = "Znovu vyjednat šifrování?";
|
||||
|
||||
/* chat item action */
|
||||
"Reply" = "Odpověď";
|
||||
|
||||
@@ -2509,6 +2677,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Security code" = "Bezpečnostní kód";
|
||||
|
||||
/* chat item text */
|
||||
"security code changed" = "bezpečnostní kód změněn";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Select" = "Vybrat";
|
||||
|
||||
@@ -2530,6 +2701,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send a live message - it will update for the recipient(s) as you type it" = "Poslat živou zprávu - zpráva se bude aktualizovat pro příjemce během psaní";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send delivery receipts to" = "Potvrzení o doručení zasílat na";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Odeslat přímou zprávu";
|
||||
|
||||
@@ -2551,6 +2725,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send questions and ideas" = "Zasílání otázek a nápadů";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send receipts" = "Odeslat potvrzení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send them from gallery or custom keyboards." = "Odeslat je z galerie nebo vlastní klávesnice.";
|
||||
|
||||
@@ -2560,9 +2737,21 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Sender may have deleted the connection request." = "Odesílatel možná smazal požadavek připojení.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "Odesílání potvrzení o doručení bude povoleno pro všechny kontakty ve všech viditelných chat profilech.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending delivery receipts will be enabled for all contacts." = "Odesílání potvrzení o doručení bude povoleno pro všechny kontakty.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending file will be stopped." = "Odesílání souboru bude zastaveno.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending receipts is disabled for %lld contacts" = "Odesílání potvrzení o doručení je vypnuto pro %lld kontakty";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending receipts is enabled for %lld contacts" = "Odesílání potvrzení o doručení je povoleno pro %lld kontakty";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending via" = "Odesílání přes";
|
||||
|
||||
@@ -2830,6 +3019,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The created archive is available via app Settings / Database / Old database archive." = "Vytvořený archiv je k dispozici v aplikaci Nastavení / Databáze / Archiv staré databáze.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Šifrování funguje a nové povolení šifrování není vyžadováno. To může vyvolat chybu v připojení!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The group is fully decentralized – it is visible only to the members." = "Skupina je plně decentralizovaná - je viditelná pouze pro členy.";
|
||||
|
||||
@@ -2854,6 +3046,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The profile is only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The second tick we missed! ✅" = "Druhé zaškrtnutí jsme přehlédli! ✅";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The sender will NOT be notified" = "Odesílatel NEBUDE informován";
|
||||
|
||||
@@ -2869,6 +3064,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"There should be at least one visible user profile." = "Měl by tam být alespoň jeden viditelný uživatelský profil.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"These settings are for your current profile **%@**." = "Toto nastavení je pro váš aktuální profil **%@**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"They can be overridden in contact and group settings." = "Mohou být přepsány v nastavení kontaktů";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Tuto akci nelze vrátit zpět - všechny přijaté a odeslané soubory a média budou smazány. Obrázky s nízkým rozlišením zůstanou zachovány.";
|
||||
|
||||
@@ -2881,9 +3082,6 @@
|
||||
/* notification title */
|
||||
"this contact" = "tento kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This error is permanent for this connection, please re-connect." = "Tato chyba je pro toto připojení trvalá, připojte se znovu.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This group no longer exists." = "Tato skupina již neexistuje.";
|
||||
|
||||
@@ -2896,9 +3094,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"To connect, your contact can scan QR code or use the link in the app." = "Pro připojení může váš kontakt naskenovat QR kód, nebo použít odkaz v aplikaci.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To find the profile used for an incognito connection, tap the contact or group name on top of the chat." = "Chcete-li najít profil použitý pro inkognito připojení, klepněte na název kontaktu nebo skupiny v horní části chatu.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To make a new connection" = "Vytvoření nového připojení";
|
||||
|
||||
@@ -2944,12 +3139,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unable to record voice message" = "Nelze nahrát hlasovou zprávu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* item status description */
|
||||
"Unexpected error: %@" = "Neočekávaná chyba: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "Neočekávaný stav přenášení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unfav." = "Odobl.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unhide" = "Odkrýt";
|
||||
|
||||
@@ -3202,6 +3400,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can create it later" = "Můžete vytvořit později";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can enable later via Settings" = "Můžete povolit později v Nastavení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can enable them later via app Privacy & Security settings." = "Můžete je povolit později v nastavení Soukromí & Bezpečnosti aplikace";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can hide or mute a user profile - swipe it to the right." = "Profil uživatele můžete skrýt nebo ztlumit - přejeďte prstem doprava.";
|
||||
|
||||
@@ -3257,7 +3461,7 @@
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Musíte zadat přístupovou frázi při každém spuštění aplikace - není uložena v zařízení.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You invited your contact" = "Pozvali jste svůj kontakt";
|
||||
"You invited a contact" = "Pozvali jste svůj kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You joined this group" = "Připojili jste se k této skupině";
|
||||
@@ -3337,9 +3541,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profile will be sent to group members" = "Váš chat profil bude zaslán členům skupiny";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profile will be sent to your contact" = "Váš chat profil bude odeslán vašemu kontaktu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Vaše chat profily";
|
||||
|
||||
@@ -3376,9 +3577,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty.\nServery SimpleX nevidí váš profil.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile will be sent to the contact that you received this link from" = "Váš profil bude zaslán kontaktu, od kterého jste obdrželi tento odkaz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile, contacts and delivered messages are stored on your device." = "Váš profil, kontakty a doručené zprávy jsou uloženy ve vašem zařízení.";
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"_italic_" = "\\_kursiv_";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- stabilere Zustellung von Nachrichten.\n- ein bisschen verbesserte Gruppen.\n- und mehr!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- Bis zu 5 Minuten lange Sprachnachrichten.\n- Zeitdauer für verschwindende Nachrichten anpassen.\n- Nachrichten-Historie bearbeiten.";
|
||||
|
||||
@@ -103,6 +106,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"%@ %@" = "%@ %@";
|
||||
|
||||
/* copied message info, <sender> at <time> */
|
||||
"%@ at %@:" = "%1$@ an %2$@:";
|
||||
|
||||
/* notification title */
|
||||
"%@ is connected!" = "%@ ist mit Ihnen verbunden!";
|
||||
|
||||
@@ -232,21 +238,27 @@
|
||||
/* No comment provided by engineer. */
|
||||
"30 seconds" = "30 Sekunden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A few more things" = "Ein paar weitere Dinge";
|
||||
|
||||
/* notification title */
|
||||
"A new contact" = "Ein neuer Kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A random profile will be sent to the contact that you received this link from" = "Ein zufälliges Profil wird an den Kontakt gesendet, von dem Sie diesen Link erhalten haben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A random profile will be sent to your contact" = "Ein zufälliges Profil wird an Ihren Kontakt gesendet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A separate TCP connection will be used **for each chat profile you have in the app**." = "**Für jedes von Ihnen in der App genutzte Chat-Profil** wird eine separate TCP-Verbindung genutzt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "**Für jeden Kontakt und jedes Gruppenmitglied** wird eine separate TCP-Verbindung genutzt.\n**Bitte beachten Sie**: Wenn Sie viele Verbindungen haben, kann der Batterieverbrauch und die Datennutzung wesentlich höher sein und einige Verbindungen können scheitern.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort" = "Abbrechen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort changing address" = "Wechsel der Adresse abbrechen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort changing address?" = "Wechsel der Adresse abbrechen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"About SimpleX" = "Über SimpleX";
|
||||
|
||||
@@ -267,12 +279,12 @@
|
||||
"Accept" = "Annehmen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Accept contact" = "Kontakt annehmen";
|
||||
"Accept connection request?" = "Kontakt annehmen";
|
||||
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "Die Kontaktanfrage von %@ annehmen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* accept contact request via notification */
|
||||
"Accept incognito" = "Inkognito akzeptieren";
|
||||
|
||||
/* call status */
|
||||
@@ -302,6 +314,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Address" = "Adresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Address change will be aborted. Old receiving address will be used." = "Der Wechsel der Adresse wird abgebrochen. Die bisherige Adresse wird weiter verwendet.";
|
||||
|
||||
/* member role */
|
||||
"admin" = "Admin";
|
||||
|
||||
@@ -311,6 +326,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Advanced network settings" = "Erweiterte Netzwerkeinstellungen";
|
||||
|
||||
/* chat item text */
|
||||
"agreeing encryption for %@…" = "Verschlüsselung von %@ zustimmen…";
|
||||
|
||||
/* chat item text */
|
||||
"agreeing encryption…" = "Verschlüsselung zustimmen…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All app data is deleted." = "Werden die App-Daten komplett gelöscht.";
|
||||
|
||||
@@ -318,7 +339,7 @@
|
||||
"All chats and messages will be deleted - this cannot be undone!" = "Alle Chats und Nachrichten werden gelöscht! Dies kann nicht rückgängig gemacht werden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All data is erased when it is entered." = "Alle Daten werden gelöscht, sobald diese eingegeben wird.";
|
||||
"All data is erased when it is entered." = "Alle Daten werden gelöscht, sobald dieser eingegeben wird.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All group members will remain connected." = "Alle Gruppenmitglieder bleiben verbunden.";
|
||||
@@ -336,16 +357,16 @@
|
||||
"Allow" = "Erlauben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow calls only if your contact allows them." = "Anrufe sind nur erlaubt, wenn Ihr Kontakt das ebenfalls erlaubt.";
|
||||
"Allow calls only if your contact allows them." = "Erlauben Sie Anrufe nur dann, wenn es Ihr Kontakt ebenfalls erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow disappearing messages only if your contact allows it to you." = "Verschwindende Nachrichten nur erlauben, wenn Ihr Kontakt das ebenfalls erlaubt.";
|
||||
"Allow disappearing messages only if your contact allows it to you." = "Erlauben Sie verschwindende Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow irreversible message deletion only if your contact allows it to you." = "Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow message reactions only if your contact allows them." = "Reaktionen auf Nachrichten sind nur möglich, falls Ihr Kontakt dies erlaubt.";
|
||||
"Allow message reactions only if your contact allows them." = "Erlauben Sie Reaktionen auf Nachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow message reactions." = "Reaktionen auf Nachrichten erlauben.";
|
||||
@@ -359,11 +380,14 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages." = "Unwiederbringliches löschen von gesendeten Nachrichten erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send files and media." = "Das Senden von Dateien und Medien erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send voice messages." = "Das Senden von Sprachnachrichten erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow voice messages only if your contact allows them." = "Erlauben Sie Sprachnachrichten nur dann, wenn Ihr Kontakt diese ebenfalls erlaubt.";
|
||||
"Allow voice messages only if your contact allows them." = "Erlauben Sie Sprachnachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow voice messages?" = "Sprachnachricht erlauben?";
|
||||
@@ -574,10 +598,10 @@
|
||||
"changed your role to %@" = "änderte Ihre Rolle auf %@";
|
||||
|
||||
/* chat item text */
|
||||
"changing address for %@..." = "Wechseln der Adresse für %@ ...";
|
||||
"changing address for %@…" = "Adresse von %@ wechseln…";
|
||||
|
||||
/* chat item text */
|
||||
"changing address..." = "Wechseln der Adresse ...";
|
||||
"changing address…" = "Wechsel der Adresse…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat archive" = "Datenbank Archiv";
|
||||
@@ -670,7 +694,7 @@
|
||||
"connect to SimpleX Chat developers." = "Mit den SimpleX Chat Entwicklern verbinden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via contact link?" = "Über den Kontakt-Link verbinden?";
|
||||
"Connect via contact link" = "Über den Kontakt-Link verbinden?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via group link?" = "Über den Gruppen-Link verbinden?";
|
||||
@@ -682,7 +706,7 @@
|
||||
"Connect via link / QR code" = "Über einen Link / QR-Code verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via one-time link?" = "Über einen Einmal-Link verbinden?";
|
||||
"Connect via one-time link" = "Über einen Einmal-Link verbinden?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connected" = "Verbunden";
|
||||
@@ -726,9 +750,6 @@
|
||||
/* chat list item title (it should not be shown */
|
||||
"connection established" = "Verbindung hergestellt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection request" = "Verbindungsanfrage";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection request sent!" = "Verbindungsanfrage wurde gesendet!";
|
||||
|
||||
@@ -768,6 +789,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact preferences" = "Kontakt Präferenzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contacts" = "Kontakte";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contacts can mark messages for deletion; you will be able to view them." = "Ihre Kontakte können Nachrichten zum Löschen markieren. Sie können diese Nachrichten trotzdem anschauen.";
|
||||
|
||||
@@ -841,7 +865,7 @@
|
||||
"Database encrypted!" = "Datenbank verschlüsselt!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database encryption passphrase will be updated and stored in the keychain.\n" = "Das Passwort für die Datenbankverschlüsselung wird aktualisiert und im Keychain gespeichert.\n";
|
||||
"Database encryption passphrase will be updated and stored in the keychain.\n" = "Das Passwort für die Datenbankverschlüsselung wird aktualisiert und im Schlüsselbund gespeichert.\n";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database encryption passphrase will be updated.\n" = "Das Passwort für die Datenbankverschlüsselung wird aktualisiert.\n";
|
||||
@@ -871,7 +895,7 @@
|
||||
"Database passphrase & export" = "Datenbank-Passwort & -Export";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is different from saved in the keychain." = "Das Datenbank-Passwort unterscheidet sich von dem im Keychain gespeicherten.";
|
||||
"Database passphrase is different from saved in the keychain." = "Das Datenbank-Passwort unterscheidet sich von dem im Schlüsselbund gespeicherten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is required to open chat." = "Das Datenbank-Passwort ist erforderlich, um den Chat zu öffnen.";
|
||||
@@ -883,7 +907,7 @@
|
||||
"database version is newer than the app, but no down migration for: %@" = "Die Datenbank-Version ist neuer als die App, keine Abwärts-Migration für: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database will be encrypted and the passphrase stored in the keychain.\n" = "Die Datenbank wird verschlüsselt, und das Passwort im Keychain gespeichert.\n";
|
||||
"Database will be encrypted and the passphrase stored in the keychain.\n" = "Die Datenbank wird verschlüsselt, und das Passwort im Schlüsselbund gespeichert.\n";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database will be encrypted.\n" = "Die Datenbank wird verschlüsselt.\n";
|
||||
@@ -897,12 +921,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "Dezentral";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "Entschlüsselungsfehler";
|
||||
|
||||
/* pref value */
|
||||
"default (%@)" = "Voreinstellung (%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"default (no)" = "Voreinstellung (Nein)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "Voreinstellung (Ja)";
|
||||
|
||||
/* chat item action */
|
||||
"Delete" = "Löschen";
|
||||
|
||||
@@ -1020,6 +1050,12 @@
|
||||
/* rcv group event chat item */
|
||||
"deleted group" = "Gruppe gelöscht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delivery receipts are disabled!" = "Empfangsbestätigungen sind deaktiviert!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delivery receipts!" = "Empfangsbestätigungen!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Description" = "Beschreibung";
|
||||
|
||||
@@ -1053,6 +1089,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Direct messages between members are prohibited in this group." = "In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disable (keep overrides)" = "Deaktivieren (vorgenommene Einstellungen bleiben erhalten)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disable for all" = "Für Alle deaktivieren";
|
||||
|
||||
/* authentication reason */
|
||||
"Disable SimpleX Lock" = "SimpleX Sperre deaktivieren";
|
||||
|
||||
@@ -1092,6 +1134,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Don't create address" = "Keine Adresse erstellt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Don't enable" = "Nicht aktivieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Don't show again" = "Nicht nochmals anzeigen";
|
||||
|
||||
@@ -1122,9 +1167,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Enable" = "Aktivieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable (keep overrides)" = "Aktivieren (vorgenommene Einstellungen bleiben erhalten)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable automatic message deletion?" = "Automatisches Löschen von Nachrichten aktivieren?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable for all" = "Für Alle aktivieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable instant notifications?" = "Sofortige Benachrichtigungen aktivieren?";
|
||||
|
||||
@@ -1185,6 +1236,30 @@
|
||||
/* notification */
|
||||
"Encrypted message: unexpected error" = "Verschlüsselte Nachricht: Unerwarteter Fehler";
|
||||
|
||||
/* chat item text */
|
||||
"encryption agreed" = "Verschlüsselung wurde zugestimmt";
|
||||
|
||||
/* chat item text */
|
||||
"encryption agreed for %@" = "Verschlüsselung von %@ wurde zugestimmt";
|
||||
|
||||
/* chat item text */
|
||||
"encryption ok" = "Verschlüsselung ist in Ordnung";
|
||||
|
||||
/* chat item text */
|
||||
"encryption ok for %@" = "Verschlüsselung für %@ ist in Ordnung";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation allowed" = "Neuaushandlung der Verschlüsselung erlaubt";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation allowed for %@" = "Neuaushandlung der Verschlüsselung von %@ erlaubt";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation required" = "Neuaushandlung der Verschlüsselung notwendig";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation required for %@" = "Neuaushandlung der Verschlüsselung von %@ notwendig";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"ended" = "beendet";
|
||||
|
||||
@@ -1218,6 +1293,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error" = "Fehler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error aborting address change" = "Fehler beim Abbrechen des Adresswechsels";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error accepting contact request" = "Fehler beim Annehmen der Kontaktanfrage";
|
||||
|
||||
@@ -1272,6 +1350,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting user profile" = "Fehler beim Löschen des Benutzerprofils";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error enabling delivery receipts!" = "Fehler beim Aktivieren der Empfangsbestätigungen!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error enabling notifications" = "Fehler beim Aktivieren der Benachrichtigungen";
|
||||
|
||||
@@ -1320,6 +1401,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending message" = "Fehler beim Senden der Nachricht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error setting delivery receipts!" = "Fehler beim Setzen der Empfangsbestätigungen!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error starting chat" = "Fehler beim Starten des Chats";
|
||||
|
||||
@@ -1329,6 +1413,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error switching profile!" = "Fehler beim Umschalten des Profils!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error synchronizing connection" = "Fehler beim Synchronisieren der Verbindung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error updating group link" = "Fehler beim Aktualisieren des Gruppen-Links";
|
||||
|
||||
@@ -1353,6 +1440,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error: URL is invalid" = "Fehler: URL ist ungültig";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Even when disabled in the conversation." = "Auch wenn sie im Chat deaktiviert sind.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Exit without saving" = "Beenden ohne Speichern";
|
||||
|
||||
@@ -1366,7 +1456,7 @@
|
||||
"Exported database archive." = "Exportiertes Datenbankarchiv.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Exporting database archive..." = "Export des Datenbankarchivs...";
|
||||
"Exporting database archive…" = "Exportieren des Datenbank-Archivs…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Failed to remove passphrase" = "Das Entfernen des Passworts ist fehlgeschlagen";
|
||||
@@ -1374,6 +1464,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Fast and no wait until the sender is online!" = "Schnell und ohne warten auf den Absender, bis er online ist!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Favorite" = "Favorit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"File will be deleted from servers." = "Die Datei wird von den Servern gelöscht.";
|
||||
|
||||
@@ -1389,9 +1482,42 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Files & media" = "Dateien & Medien";
|
||||
|
||||
/* chat feature */
|
||||
"Files and media" = "Dateien und Medien";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Files and media are prohibited in this group." = "In dieser Gruppe sind Dateien und Medien nicht erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Files and media prohibited!" = "Dateien und Medien sind nicht erlaubt!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Filter unread and favorite chats." = "Nach ungelesenen und favorisierten Chats filtern.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Finally, we have them! 🚀" = "Endlich haben wir sie! 🚀";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Find chats faster" = "Chats schneller finden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix" = "Reparieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix connection" = "Verbindung reparieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix connection?" = "Verbindung reparieren?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix encryption after restoring backups." = "Reparatur der Verschlüsselung nach wiedereinspielen von Backups.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix not supported by contact" = "Reparatur wird vom Kontakt nicht unterstützt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix not supported by group member" = "Reparatur wird vom Gruppenmitglied nicht unterstützt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"For console" = "Für Konsole";
|
||||
|
||||
@@ -1458,6 +1584,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send disappearing messages." = "Gruppenmitglieder können verschwindende Nachrichten senden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send files and media." = "Gruppenmitglieder können Dateien und Medien senden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send voice messages." = "Gruppenmitglieder können Sprachnachrichten versenden.";
|
||||
|
||||
@@ -1512,7 +1641,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Hide:" = "Verberge:";
|
||||
|
||||
/* copied message info */
|
||||
/* No comment provided by engineer. */
|
||||
"History" = "Vergangenheit";
|
||||
|
||||
/* time unit */
|
||||
@@ -1581,18 +1710,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Improved server configuration" = "Verbesserte Serverkonfiguration";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"In reply to" = "Als Antwort auf";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito" = "Inkognito";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito mode" = "Inkognito Modus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito mode is not supported here - your main profile will be sent to group members" = "Der Inkognito-Modus wird hier nicht unterstützt - Ihr Hauptprofil wird an die Gruppenmitglieder gesendet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito mode protects the privacy of your main profile name and image — for each new contact a new random profile is created." = "Der Inkognito-Modus schützt die Privatsphäre Ihres Hauptprofilnamens und -bildes – für jeden neuen Kontakt wird ein neues Zufallsprofil erstellt.";
|
||||
|
||||
/* chat list item description */
|
||||
"incognito via contact address link" = "Inkognito über einen Kontaktadressen-Link";
|
||||
|
||||
@@ -1735,10 +1861,13 @@
|
||||
"Joining group" = "Der Gruppe beitreten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keychain error" = "Schlüsselbundfehler";
|
||||
"Keep your connections" = "Ihre Verbindungen beibehalten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"KeyChain error" = "Keystore Fehler";
|
||||
"Keychain error" = "KeyChain Fehler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"KeyChain error" = "KeyChain Fehler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Large file!" = "Große Datei!";
|
||||
@@ -1791,6 +1920,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Make a private connection" = "Stellen Sie eine private Verbindung her";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Make one message disappear" = "Eine verschwindende Nachricht verfassen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Make profile private!" = "Privates Profil erzeugen!";
|
||||
|
||||
@@ -1839,9 +1971,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt - dies kann nicht rückgängig gemacht werden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* item status text */
|
||||
"Message delivery error" = "Fehler bei der Nachrichtenzustellung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message delivery receipts!" = "Empfangsbestätigungen für Nachrichten!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message draft" = "Nachrichtenentwurf";
|
||||
|
||||
@@ -1867,7 +2002,7 @@
|
||||
"Messages & files" = "Nachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating database archive..." = "Das Datenbankarchiv wird migriert...";
|
||||
"Migrating database archive…" = "Datenbank-Archiv wird migriert…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migration error:" = "Fehler bei der Migration:";
|
||||
@@ -1986,9 +2121,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"no e2e encryption" = "Keine E2E-Verschlüsselung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No filtered chats" = "Keine gefilterten Chats";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No group!" = "Die Gruppe wurde nicht gefunden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No history" = "Keine Vergangenheit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No permission to record voice message" = "Keine Berechtigung für das Aufnehmen von Sprachnachrichten";
|
||||
|
||||
@@ -2056,6 +2197,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can change group preferences." = "Gruppenpräferenzen können nur von Gruppen-Eigentümern geändert werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can enable files and media." = "Nur Gruppenbesitzer können Dateien und Medien aktivieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can enable voice messages." = "Sprachnachrichten können nur von Gruppen-Eigentümern aktiviert werden.";
|
||||
|
||||
@@ -2143,8 +2287,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Paste received link" = "Fügen Sie den erhaltenen Link ein";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste the link you received into the box below to connect with your contact." = "Um sich mit Ihrem Kontakt zu verbinden, fügen Sie den erhaltenen Link in das Feld unten ein.";
|
||||
/* placeholder */
|
||||
"Paste the link you received to connect with your contact." = "Um sich mit Ihrem Kontakt zu verbinden, fügen Sie den erhaltenen Link in das Feld unten ein.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"peer-to-peer" = "Peer-to-Peer";
|
||||
@@ -2257,6 +2401,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending disappearing messages." = "Das Senden von verschwindenden Nachrichten verbieten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending files and media." = "Das Senden von Dateien und Medien nicht erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending voice messages." = "Das Senden von Sprachnachrichten nicht erlauben.";
|
||||
|
||||
@@ -2269,6 +2416,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Protocol timeout" = "Protokollzeitüberschreitung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Protocol timeout per KB" = "Protokollzeitüberschreitung pro kB";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Push notifications" = "Push-Benachrichtigungen";
|
||||
|
||||
@@ -2276,7 +2426,7 @@
|
||||
"Rate the app" = "Bewerten Sie die App";
|
||||
|
||||
/* chat item menu */
|
||||
"React..." = "Reaktion...";
|
||||
"React…" = "Reagiere…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read" = "Gelesen";
|
||||
@@ -2314,6 +2464,9 @@
|
||||
/* message info title */
|
||||
"Received message" = "Empfangene Nachricht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Die Empfängeradresse wird auf einen anderen Server geändert. Der Adresswechsel wird abgeschlossen, wenn der Absender wieder online ist.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receiving file will be stopped." = "Der Empfang der Datei wird beendet.";
|
||||
|
||||
@@ -2323,6 +2476,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Recipients see updates as you type them." = "Die Empfänger sehen Nachrichtenaktualisierungen, während Sie sie eingeben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reconnect all connected servers to force message delivery. It uses additional traffic." = "Alle verbundenen Server werden neu verbunden, um die Zustellung der Nachricht zu erzwingen. Dies verursacht zusätzlichen Datenverkehr.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reconnect servers?" = "Die Server neu verbinden?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Record updated at" = "Datensatz aktualisiert um";
|
||||
|
||||
@@ -2336,7 +2495,7 @@
|
||||
"Reject" = "Ablehnen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reject contact (sender NOT notified)" = "Kontakt ablehnen (der Absender wird NICHT benachrichtigt)";
|
||||
"Reject (sender NOT notified)" = "Kontakt ablehnen (der Absender wird NICHT benachrichtigt)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reject contact request" = "Kontaktanfrage ablehnen";
|
||||
@@ -2371,6 +2530,15 @@
|
||||
/* rcv group event chat item */
|
||||
"removed you" = "hat Sie aus der Gruppe entfernt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Renegotiate" = "Neu aushandeln";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Renegotiate encryption" = "Verschlüsselung neu aushandeln";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Renegotiate encryption?" = "Verschlüsselung neu aushandeln?";
|
||||
|
||||
/* chat item action */
|
||||
"Reply" = "Antwort";
|
||||
|
||||
@@ -2384,7 +2552,7 @@
|
||||
"Reset colors" = "Farben zurücksetzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reset to defaults" = "Auf Standardwerte zurücksetzen";
|
||||
"Reset to defaults" = "Auf Voreinstellungen zurücksetzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Restart the app to create a new chat profile" = "Um ein neues Chat-Profil zu erstellen, starten Sie die App neu";
|
||||
@@ -2509,6 +2677,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Security code" = "Sicherheitscode";
|
||||
|
||||
/* chat item text */
|
||||
"security code changed" = "Sicherheitscode wurde geändert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Select" = "Auswählen";
|
||||
|
||||
@@ -2530,6 +2701,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send a live message - it will update for the recipient(s) as you type it" = "Eine Live Nachricht senden - der/die Empfänger sieht/sehen Nachrichtenaktualisierungen, während Sie sie eingeben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send delivery receipts to" = "Empfangsbestätigungen senden an";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Direktnachricht senden";
|
||||
|
||||
@@ -2551,6 +2725,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send questions and ideas" = "Senden Sie Fragen und Ideen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send receipts" = "Quittierungen versenden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send them from gallery or custom keyboards." = "Senden Sie diese aus dem Fotoalbum oder von individuellen Tastaturen.";
|
||||
|
||||
@@ -2560,9 +2737,21 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Sender may have deleted the connection request." = "Der Absender hat möglicherweise die Verbindungsanfrage gelöscht.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "Das Senden von Empfangsbestätigungen an alle Kontakte in allen sichtbaren Chat-Profilen wird aktiviert.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending delivery receipts will be enabled for all contacts." = "Das Senden von Empfangsbestätigungen an alle Kontakte wird aktiviert.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending file will be stopped." = "Das Senden der Datei wird beendet.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending receipts is disabled for %lld contacts" = "Das Senden von Empfangsbestätigungen an %lld Kontakte ist deaktiviert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending receipts is enabled for %lld contacts" = "Das Senden von Empfangsbestätigungen an %lld Kontakte ist aktiviert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending via" = "Senden über";
|
||||
|
||||
@@ -2830,6 +3019,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The created archive is available via app Settings / Database / Old database archive." = "Das erzeugte Archiv ist über Einstellungen / Datenbank / Altes Datenbankarchiv verfügbar.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Die Verschlüsselung funktioniert und ein neues Verschlüsselungsabkommen ist nicht erforderlich. Es kann zu Verbindungsfehlern kommen!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The group is fully decentralized – it is visible only to the members." = "Die Gruppe ist vollständig dezentralisiert – sie ist nur für Mitglieder sichtbar.";
|
||||
|
||||
@@ -2854,6 +3046,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The profile is only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The second tick we missed! ✅" = "Wir haben das zweite Häkchen vermisst! ✅";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The sender will NOT be notified" = "Der Absender wird NICHT benachrichtigt";
|
||||
|
||||
@@ -2869,6 +3064,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"There should be at least one visible user profile." = "Es muss mindestens ein sichtbares Benutzer-Profil vorhanden sein.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"These settings are for your current profile **%@**." = "Diese Einstellungen betreffen Ihr aktuelles Profil **%@**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"They can be overridden in contact and group settings." = "Sie können in den Kontakteinstellungen überschrieben werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Diese Aktion kann nicht rückgängig gemacht werden! Alle empfangenen und gesendeten Dateien und Medien werden gelöscht. Bilder mit niedriger Auflösung bleiben erhalten.";
|
||||
|
||||
@@ -2881,9 +3082,6 @@
|
||||
/* notification title */
|
||||
"this contact" = "Dieser Kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This error is permanent for this connection, please re-connect." = "Es handelt sich um einen permanenten Fehler für diese Verbindung - bitte verbinden Sie sich neu.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This group no longer exists." = "Diese Gruppe existiert nicht mehr.";
|
||||
|
||||
@@ -2896,9 +3094,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"To connect, your contact can scan QR code or use the link in the app." = "Um eine Verbindung herzustellen, kann Ihr Kontakt den QR-Code scannen oder den Link in der App verwenden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To find the profile used for an incognito connection, tap the contact or group name on top of the chat." = "Um das für eine Inkognito-Verbindung verwendete Profil zu finden, tippen Sie oben im Chat auf den Kontakt- oder Gruppennamen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To make a new connection" = "Um eine Verbindung mit einem neuen Kontakt zu erstellen";
|
||||
|
||||
@@ -2944,12 +3139,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unable to record voice message" = "Die Aufnahme einer Sprachnachricht ist nicht möglich";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* item status description */
|
||||
"Unexpected error: %@" = "Unerwarteter Fehler: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "Unerwarteter Migrationsstatus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unfav." = "Fav. entf.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unhide" = "Verbergen aufheben";
|
||||
|
||||
@@ -3202,6 +3400,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can create it later" = "Sie können dies später erstellen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can enable later via Settings" = "Sie können diese später in den Einstellungen aktivieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can enable them later via app Privacy & Security settings." = "Sie können diese später in den Datenschutz & Sicherheits-Einstellungen der App aktivieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can hide or mute a user profile - swipe it to the right." = "Sie können ein Benutzerprofil verbergen oder stummschalten - wischen Sie es nach rechts.";
|
||||
|
||||
@@ -3257,7 +3461,7 @@
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Sie müssen das Passwort jedes Mal eingeben, wenn die App startet. Es wird nicht auf dem Gerät gespeichert.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You invited your contact" = "Sie haben Ihren Kontakt eingeladen";
|
||||
"You invited a contact" = "Sie haben Ihren Kontakt eingeladen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You joined this group" = "Sie sind dieser Gruppe beigetreten";
|
||||
@@ -3337,9 +3541,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profile will be sent to group members" = "Ihr Chat-Profil wird an Gruppenmitglieder gesendet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profile will be sent to your contact" = "Ihr Chat-Profil wird an Ihren Kontakt gesendet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Meine Chat-Profile";
|
||||
|
||||
@@ -3376,9 +3577,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt.\nSimpleX-Server können Ihr Profil nicht einsehen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile will be sent to the contact that you received this link from" = "Ihr Profil wird an den Kontakt gesendet, von dem Sie diesen Link erhalten haben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile, contacts and delivered messages are stored on your device." = "Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert.";
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"_italic_" = "\\_italique_";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- une diffusion plus stable des messages.\n- des groupes un peu plus performants.\n- et bien d'autres choses encore !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- messages vocaux pouvant durer jusqu'à 5 minutes.\n- délai personnalisé de disparition.\n- l'historique de modification.";
|
||||
|
||||
@@ -103,6 +106,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"%@ %@" = "%@ %@";
|
||||
|
||||
/* copied message info, <sender> at <time> */
|
||||
"%@ at %@:" = "%1$@ à %2$@ :";
|
||||
|
||||
/* notification title */
|
||||
"%@ is connected!" = "%@ est connecté·e !";
|
||||
|
||||
@@ -232,21 +238,27 @@
|
||||
/* No comment provided by engineer. */
|
||||
"30 seconds" = "30 secondes";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A few more things" = "Encore quelques points";
|
||||
|
||||
/* notification title */
|
||||
"A new contact" = "Un nouveau contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A random profile will be sent to the contact that you received this link from" = "Un profil aléatoire sera envoyé au contact qui vous a envoyé ce lien";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A random profile will be sent to your contact" = "Un profil aléatoire sera envoyé à votre contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A separate TCP connection will be used **for each chat profile you have in the app**." = "Une connexion TCP distincte sera utilisée **pour chaque profil de chat que vous avez dans l'application**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "Une connexion TCP distincte sera utilisée **pour chaque contact et membre de groupe**.\n**Veuillez noter** : si vous avez de nombreuses connexions, votre consommation de batterie et de réseau peut être nettement plus élevée et certaines liaisons peuvent échouer.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort" = "Annuler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort changing address" = "Annuler le changement d'adresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort changing address?" = "Abandonner le changement d'adresse ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"About SimpleX" = "À propos de SimpleX";
|
||||
|
||||
@@ -267,12 +279,12 @@
|
||||
"Accept" = "Accepter";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Accept contact" = "Accepter le contact";
|
||||
"Accept connection request?" = "Accepter le contact";
|
||||
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "Accepter la demande de contact de %@ ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* accept contact request via notification */
|
||||
"Accept incognito" = "Accepter en incognito";
|
||||
|
||||
/* call status */
|
||||
@@ -302,6 +314,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Address" = "Adresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Address change will be aborted. Old receiving address will be used." = "Le changement d'adresse sera annulé. L'ancienne adresse de réception sera utilisée.";
|
||||
|
||||
/* member role */
|
||||
"admin" = "admin";
|
||||
|
||||
@@ -311,6 +326,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Advanced network settings" = "Paramètres réseau avancés";
|
||||
|
||||
/* chat item text */
|
||||
"agreeing encryption for %@…" = "acceptant le chiffrement pour %@…";
|
||||
|
||||
/* chat item text */
|
||||
"agreeing encryption…" = "accord sur le chiffrement…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All app data is deleted." = "Toutes les données de l'application sont supprimées.";
|
||||
|
||||
@@ -359,6 +380,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages." = "Autoriser la suppression irréversible de messages envoyés.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send files and media." = "Permet l'envoi de fichiers et de médias.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send voice messages." = "Autoriser l'envoi de messages vocaux.";
|
||||
|
||||
@@ -435,7 +459,7 @@
|
||||
"Audio/video calls" = "Appels audio/vidéo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Audio/video calls are prohibited." = "Interdire les appels audio/vidéo.";
|
||||
"Audio/video calls are prohibited." = "Les appels audio/vidéo sont interdits.";
|
||||
|
||||
/* PIN entry */
|
||||
"Authentication cancelled" = "Authentification interrompue";
|
||||
@@ -450,7 +474,7 @@
|
||||
"Authentication unavailable" = "Authentification indisponible";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Auto-accept" = "Auto-acceptation";
|
||||
"Auto-accept" = "Auto-accepter";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Auto-accept contact requests" = "Demandes de contact auto-acceptées";
|
||||
@@ -574,10 +598,10 @@
|
||||
"changed your role to %@" = "a modifié votre rôle pour %@";
|
||||
|
||||
/* chat item text */
|
||||
"changing address for %@..." = "changement d'adresse pour %@...";
|
||||
"changing address for %@…" = "changement d'adresse pour %@…";
|
||||
|
||||
/* chat item text */
|
||||
"changing address..." = "changement d'adresse...";
|
||||
"changing address…" = "changement d'adresse…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat archive" = "Archives du chat";
|
||||
@@ -670,7 +694,7 @@
|
||||
"connect to SimpleX Chat developers." = "se connecter aux developpeurs de SimpleX Chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via contact link?" = "Se connecter via le lien du contact ?";
|
||||
"Connect via contact link" = "Se connecter via le lien du contact ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via group link?" = "Se connecter via le lien du groupe ?";
|
||||
@@ -682,7 +706,7 @@
|
||||
"Connect via link / QR code" = "Se connecter via un lien / code QR";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via one-time link?" = "Se connecter via un lien unique ?";
|
||||
"Connect via one-time link" = "Se connecter via le lien du contact ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connected" = "connecté";
|
||||
@@ -726,9 +750,6 @@
|
||||
/* chat list item title (it should not be shown */
|
||||
"connection established" = "connexion établie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection request" = "Demande de connexion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection request sent!" = "Demande de connexion envoyée !";
|
||||
|
||||
@@ -768,6 +789,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact preferences" = "Préférences de contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contacts" = "Contacts";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contacts can mark messages for deletion; you will be able to view them." = "Vos contacts peuvent marquer les messages pour les supprimer ; vous pourrez les consulter.";
|
||||
|
||||
@@ -856,7 +880,7 @@
|
||||
"Database ID: %d" = "ID de base de données : %d";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database IDs and Transport isolation option." = "IDs de base de données et option d'isolation du transport.";
|
||||
"Database IDs and Transport isolation option." = "IDs de base de données et option d'isolement du transport.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database is encrypted using a random passphrase, you can change it." = "La base de données est chiffrée à l'aide d'une phrase secrète aléatoire, que vous pouvez modifier.";
|
||||
@@ -897,12 +921,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "Décentralisé";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "Erreur de déchiffrement";
|
||||
|
||||
/* pref value */
|
||||
"default (%@)" = "défaut (%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"default (no)" = "par défaut (non)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "par défaut (oui)";
|
||||
|
||||
/* chat item action */
|
||||
"Delete" = "Supprimer";
|
||||
|
||||
@@ -1020,6 +1050,12 @@
|
||||
/* rcv group event chat item */
|
||||
"deleted group" = "groupe supprimé";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delivery receipts are disabled!" = "Les accusés de réception sont désactivés !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delivery receipts!" = "Justificatifs de réception!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Description" = "Description";
|
||||
|
||||
@@ -1042,7 +1078,7 @@
|
||||
"different migration in the app/database: %@ / %@" = "migration différente dans l'app/la base de données : %@ / %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Different names, avatars and transport isolation." = "Différents noms, avatars et mode d'isolation de transport.";
|
||||
"Different names, avatars and transport isolation." = "Différents noms, avatars et modes d'isolement de transport.";
|
||||
|
||||
/* connection level description */
|
||||
"direct" = "direct";
|
||||
@@ -1053,6 +1089,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Direct messages between members are prohibited in this group." = "Les messages directs entre membres sont interdits dans ce groupe.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disable (keep overrides)" = "Désactiver (conserver les remplacements)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disable for all" = "Désactiver pour tous";
|
||||
|
||||
/* authentication reason */
|
||||
"Disable SimpleX Lock" = "Désactiver SimpleX Lock";
|
||||
|
||||
@@ -1092,6 +1134,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Don't create address" = "Ne pas créer d'adresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Don't enable" = "Ne pas activer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Don't show again" = "Ne plus afficher";
|
||||
|
||||
@@ -1122,9 +1167,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Enable" = "Activer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable (keep overrides)" = "Activer (conserver les remplacements)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable automatic message deletion?" = "Activer la suppression automatique des messages ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable for all" = "Activer pour tous";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable instant notifications?" = "Activer les notifications instantanées ?";
|
||||
|
||||
@@ -1185,6 +1236,30 @@
|
||||
/* notification */
|
||||
"Encrypted message: unexpected error" = "Message chiffrée : erreur inattendue";
|
||||
|
||||
/* chat item text */
|
||||
"encryption agreed" = "chiffrement accepté";
|
||||
|
||||
/* chat item text */
|
||||
"encryption agreed for %@" = "chiffrement accepté pour %@";
|
||||
|
||||
/* chat item text */
|
||||
"encryption ok" = "chiffrement ok";
|
||||
|
||||
/* chat item text */
|
||||
"encryption ok for %@" = "chiffrement ok pour %@";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation allowed" = "renégociation de chiffrement autorisée";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation allowed for %@" = "renégociation de chiffrement autorisée pour %@";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation required" = "renégociation de chiffrement requise";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation required for %@" = "renégociation de chiffrement requise pour %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"ended" = "terminé";
|
||||
|
||||
@@ -1218,6 +1293,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error" = "Erreur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error aborting address change" = "Erreur lors de l'annulation du changement d'adresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error accepting contact request" = "Erreur de validation de la demande de contact";
|
||||
|
||||
@@ -1272,6 +1350,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting user profile" = "Erreur lors de la suppression du profil utilisateur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error enabling delivery receipts!" = "Erreur lors de l'activation des accusés de réception !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error enabling notifications" = "Erreur lors de l'activation des notifications";
|
||||
|
||||
@@ -1320,6 +1401,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending message" = "Erreur lors de l'envoi du message";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error setting delivery receipts!" = "Erreur lors de la configuration des accusés de réception !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error starting chat" = "Erreur lors du démarrage du chat";
|
||||
|
||||
@@ -1329,6 +1413,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error switching profile!" = "Erreur lors du changement de profil !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error synchronizing connection" = "Erreur de synchronisation de connexion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error updating group link" = "Erreur lors de la mise à jour du lien de groupe";
|
||||
|
||||
@@ -1353,6 +1440,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error: URL is invalid" = "Erreur : URL invalide";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Even when disabled in the conversation." = "Même s'il est désactivé dans la conversation.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Exit without saving" = "Quitter sans sauvegarder";
|
||||
|
||||
@@ -1366,7 +1456,7 @@
|
||||
"Exported database archive." = "Archive de la base de données exportée.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Exporting database archive..." = "Exportation de l'archive de la base de données...";
|
||||
"Exporting database archive…" = "Exportation de l'archive de la base de données…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Failed to remove passphrase" = "Échec de la suppression de la phrase secrète";
|
||||
@@ -1374,6 +1464,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Fast and no wait until the sender is online!" = "Rapide et ne nécessitant pas d'attendre que l'expéditeur soit en ligne !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Favorite" = "Favoris";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"File will be deleted from servers." = "Le fichier sera supprimé des serveurs.";
|
||||
|
||||
@@ -1389,9 +1482,42 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Files & media" = "Fichiers & médias";
|
||||
|
||||
/* chat feature */
|
||||
"Files and media" = "Fichiers et médias";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Files and media are prohibited in this group." = "Les fichiers et les médias sont interdits dans ce groupe.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Files and media prohibited!" = "Fichiers et médias interdits !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Filter unread and favorite chats." = "Filtrer les messages non lus et favoris.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Finally, we have them! 🚀" = "Enfin, les voilà ! 🚀";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Find chats faster" = "Trouver des messages plus rapidement";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix" = "Réparer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix connection" = "Réparer la connexion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix connection?" = "Réparer la connexion?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix encryption after restoring backups." = "Réparer le chiffrement après la restauration des sauvegardes.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix not supported by contact" = "Correction non prise en charge par le contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fix not supported by group member" = "Correction non prise en charge par un membre du groupe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"For console" = "Pour la console";
|
||||
|
||||
@@ -1458,6 +1584,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send disappearing messages." = "Les membres du groupes peuvent envoyer des messages éphémères.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send files and media." = "Les membres du groupe peuvent envoyer des fichiers et des médias.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send voice messages." = "Les membres du groupe peuvent envoyer des messages vocaux.";
|
||||
|
||||
@@ -1512,7 +1641,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Hide:" = "Cacher :";
|
||||
|
||||
/* copied message info */
|
||||
/* No comment provided by engineer. */
|
||||
"History" = "Historique";
|
||||
|
||||
/* time unit */
|
||||
@@ -1581,18 +1710,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Improved server configuration" = "Configuration de serveur améliorée";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"In reply to" = "En réponse à";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito" = "Incognito";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito mode" = "Mode Incognito";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito mode is not supported here - your main profile will be sent to group members" = "Le mode Incognito n'est pas supporté ici - votre profil principal sera envoyé aux membres du groupe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito mode protects the privacy of your main profile name and image — for each new contact a new random profile is created." = "Le mode Incognito protège la confidentialité de votre profil principal — pour chaque nouveau contact un nouveau profil aléatoire est créé.";
|
||||
|
||||
/* chat list item description */
|
||||
"incognito via contact address link" = "mode incognito via le lien d'adresse du contact";
|
||||
|
||||
@@ -1734,6 +1860,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Joining group" = "Entrain de rejoindre le groupe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep your connections" = "Conserver vos connexions";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keychain error" = "Erreur de la keychain";
|
||||
|
||||
@@ -1791,6 +1920,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Make a private connection" = "Établir une connexion privée";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Make one message disappear" = "Rendre un message éphémère";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Make profile private!" = "Rendre un profil privé !";
|
||||
|
||||
@@ -1839,9 +1971,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Member will be removed from group - this cannot be undone!" = "Ce membre sera retiré du groupe - impossible de revenir en arrière !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* item status text */
|
||||
"Message delivery error" = "Erreur de distribution du message";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message delivery receipts!" = "Accusés de réception des messages !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message draft" = "Brouillon de message";
|
||||
|
||||
@@ -1867,7 +2002,7 @@
|
||||
"Messages & files" = "Messages";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating database archive..." = "Migration de l'archive de la base de données...";
|
||||
"Migrating database archive…" = "Migration de l'archive de la base de données…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migration error:" = "Erreur de migration :";
|
||||
@@ -1986,9 +2121,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"no e2e encryption" = "sans chiffrement de bout en bout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No filtered chats" = "Pas de chats filtrés";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No group!" = "Groupe introuvable !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No history" = "Aucun historique";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No permission to record voice message" = "Pas l'autorisation d'enregistrer un message vocal";
|
||||
|
||||
@@ -2056,6 +2197,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can change group preferences." = "Seuls les propriétaires du groupe peuvent modifier les préférences du groupe.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can enable files and media." = "Seuls les propriétaires du groupe peuvent activer les fichiers et les médias.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can enable voice messages." = "Seuls les propriétaires de groupes peuvent activer les messages vocaux.";
|
||||
|
||||
@@ -2143,8 +2287,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Paste received link" = "Coller le lien reçu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste the link you received into the box below to connect with your contact." = "Collez le lien que vous avez reçu dans le cadre ci-dessous pour vous connecter avec votre contact.";
|
||||
/* placeholder */
|
||||
"Paste the link you received to connect with your contact." = "Collez le lien que vous avez reçu dans le cadre ci-dessous pour vous connecter avec votre contact.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"peer-to-peer" = "pair-à-pair";
|
||||
@@ -2257,6 +2401,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending disappearing messages." = "Interdire l’envoi de messages éphémères.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending files and media." = "Interdire l'envoi de fichiers et de médias.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit sending voice messages." = "Interdire l'envoi de messages vocaux.";
|
||||
|
||||
@@ -2269,6 +2416,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Protocol timeout" = "Délai du protocole";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Protocol timeout per KB" = "Délai d'attente du protocole par KB";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Push notifications" = "Notifications push";
|
||||
|
||||
@@ -2276,7 +2426,7 @@
|
||||
"Rate the app" = "Évaluer l'app";
|
||||
|
||||
/* chat item menu */
|
||||
"React..." = "Réagir...";
|
||||
"React…" = "Réagissez…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read" = "Lire";
|
||||
@@ -2300,7 +2450,7 @@
|
||||
"received answer…" = "réponse reçu…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Received at" = "Reçu le";
|
||||
"Received at" = "Reçu à";
|
||||
|
||||
/* copied message info */
|
||||
"Received at: %@" = "Reçu le : %@";
|
||||
@@ -2314,6 +2464,9 @@
|
||||
/* message info title */
|
||||
"Received message" = "Message reçu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receiving address will be changed to a different server. Address change will complete after sender comes online." = "L'adresse de réception sera changée pour un autre serveur. Le changement d'adresse sera terminé lorsque l'expéditeur sera en ligne.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receiving file will be stopped." = "La réception du fichier sera interrompue.";
|
||||
|
||||
@@ -2321,7 +2474,13 @@
|
||||
"Receiving via" = "Réception via";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Recipients see updates as you type them." = "Les destinataires voient les mises à jour au fur et à mesure que vous les tapez.";
|
||||
"Recipients see updates as you type them." = "Les destinataires voient les mises à jour au fur et à mesure que vous leur écrivez.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reconnect all connected servers to force message delivery. It uses additional traffic." = "Reconnecter tous les serveurs connectés pour forcer la livraison des messages. Cette méthode utilise du trafic supplémentaire.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reconnect servers?" = "Reconnecter les serveurs?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Record updated at" = "Enregistrement mis à jour le";
|
||||
@@ -2336,7 +2495,7 @@
|
||||
"Reject" = "Rejeter";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reject contact (sender NOT notified)" = "Rejeter le contact (l'expéditeur N'en est PAS informé)";
|
||||
"Reject (sender NOT notified)" = "Rejeter le contact (l'expéditeur N'en est PAS informé)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reject contact request" = "Rejeter la demande de contact";
|
||||
@@ -2371,6 +2530,15 @@
|
||||
/* rcv group event chat item */
|
||||
"removed you" = "vous a retiré";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Renegotiate" = "Renégocier";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Renegotiate encryption" = "Renégocier le chiffrement";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Renegotiate encryption?" = "Renégocier le chiffrement?";
|
||||
|
||||
/* chat item action */
|
||||
"Reply" = "Répondre";
|
||||
|
||||
@@ -2489,7 +2657,7 @@
|
||||
"Scan server QR code" = "Scanner un code QR de serveur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Search" = "Chercher";
|
||||
"Search" = "Recherche";
|
||||
|
||||
/* network option */
|
||||
"sec" = "sec";
|
||||
@@ -2509,6 +2677,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Security code" = "Code de sécurité";
|
||||
|
||||
/* chat item text */
|
||||
"security code changed" = "code de sécurité modifié";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Select" = "Choisir";
|
||||
|
||||
@@ -2530,6 +2701,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send a live message - it will update for the recipient(s) as you type it" = "Envoyez un message dynamique - il sera mis à jour pour le⸱s destinataire⸱s au fur et à mesure que vous le tapez";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send delivery receipts to" = "Envoyer les accusés de réception à";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Envoi de message direct";
|
||||
|
||||
@@ -2551,6 +2725,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send questions and ideas" = "Envoyez vos questions et idées";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send receipts" = "Envoyer les justificatifs";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send them from gallery or custom keyboards." = "Envoyez-les depuis la phototèque ou des claviers personnalisés.";
|
||||
|
||||
@@ -2560,9 +2737,21 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Sender may have deleted the connection request." = "L'expéditeur a peut-être supprimé la demande de connexion.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "L'envoi d'accusés de réception sera activé pour tous les contacts dans tous les profils de chat visibles.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending delivery receipts will be enabled for all contacts." = "L'envoi d'accusés de réception sera activé pour tous les contacts.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending file will be stopped." = "L'envoi du fichier sera interrompu.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending receipts is disabled for %lld contacts" = "L'envoi d'accusés de réception est désactivé pour %lld contacts";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending receipts is enabled for %lld contacts" = "L'envoi d'accusés de réception est activé pour %lld contacts";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending via" = "Envoi via";
|
||||
|
||||
@@ -2768,7 +2957,7 @@
|
||||
"Tap button " = "Appuyez sur le bouton ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to activate profile." = "Appuyez pour activer le profil.";
|
||||
"Tap to activate profile." = "Appuyez pour activer un profil.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to join" = "Appuyez pour rejoindre";
|
||||
@@ -2830,6 +3019,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The created archive is available via app Settings / Database / Old database archive." = "L'archive créée est disponible via l'app Paramètres / Base de données / Ancienne archive de base de données.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Le chiffrement fonctionne et le nouvel accord de chiffrement n'est pas nécessaire. Cela peut provoquer des erreurs de connexion !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The group is fully decentralized – it is visible only to the members." = "Le groupe est entièrement décentralisé – il n'est visible que par ses membres.";
|
||||
|
||||
@@ -2854,6 +3046,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The profile is only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The second tick we missed! ✅" = "Le deuxième coche que nous avons manqué ! ✅";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The sender will NOT be notified" = "L'expéditeur N'en sera PAS informé";
|
||||
|
||||
@@ -2869,6 +3064,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"There should be at least one visible user profile." = "Il doit y avoir au moins un profil d'utilisateur visible.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"These settings are for your current profile **%@**." = "Ces paramètres s'appliquent à votre profil actuel **%@**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"They can be overridden in contact and group settings." = "Ils peuvent être remplacés dans les paramètres des contacts";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Cette action ne peut être annulée - tous les fichiers et médias reçus et envoyés seront supprimés. Les photos à faible résolution seront conservées.";
|
||||
|
||||
@@ -2881,9 +3082,6 @@
|
||||
/* notification title */
|
||||
"this contact" = "ce contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This error is permanent for this connection, please re-connect." = "Cette erreur est persistante pour cette connexion, veuillez vous reconnecter.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This group no longer exists." = "Ce groupe n'existe plus.";
|
||||
|
||||
@@ -2896,9 +3094,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"To connect, your contact can scan QR code or use the link in the app." = "Pour se connecter, votre contact peut scanner le code QR ou utiliser le lien dans l'application.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To find the profile used for an incognito connection, tap the contact or group name on top of the chat." = "Pour trouver le profil utilisé lors d'une connexion incognito, appuyez sur le nom du contact ou du groupe en haut du chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To make a new connection" = "Pour établir une nouvelle connexion";
|
||||
|
||||
@@ -2924,7 +3119,7 @@
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Pour vérifier le chiffrement de bout en bout avec votre contact, comparez (ou scannez) le code sur vos appareils.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Transport isolation" = "Isolement du transport";
|
||||
"Transport isolation" = "Transport isolé";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Trying to connect to the server used to receive messages from this contact (error: %@)." = "Tentative de connexion au serveur utilisé pour recevoir les messages de ce contact (erreur : %@).";
|
||||
@@ -2944,12 +3139,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unable to record voice message" = "Impossible d'enregistrer un message vocal";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* item status description */
|
||||
"Unexpected error: %@" = "Erreur inattendue : %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "État de la migration inattendu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unfav." = "Unfav.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unhide" = "Dévoiler";
|
||||
|
||||
@@ -3005,7 +3203,7 @@
|
||||
"Update network settings?" = "Mettre à jour les paramètres réseau ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update transport isolation mode?" = "Mettre à jour le mode d'isolation du transport ?";
|
||||
"Update transport isolation mode?" = "Mettre à jour le mode d'isolement du transport ?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "mise à jour du profil de groupe";
|
||||
@@ -3202,6 +3400,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can create it later" = "Vous pouvez la créer plus tard";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can enable later via Settings" = "Vous pouvez l'activer ultérieurement via Paramètres";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can enable them later via app Privacy & Security settings." = "Vous pouvez les activer ultérieurement via les paramètres de Confidentialité et Sécurité de l'application.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can hide or mute a user profile - swipe it to the right." = "Vous pouvez masquer ou mettre en sourdine un profil d'utilisateur - faites-le glisser vers la droite.";
|
||||
|
||||
@@ -3257,7 +3461,7 @@
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Vous devez saisir la phrase secrète à chaque fois que l'application démarre - elle n'est pas stockée sur l'appareil.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You invited your contact" = "Vous avez invité votre contact";
|
||||
"You invited a contact" = "Vous avez invité votre contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You joined this group" = "Vous avez rejoint ce groupe";
|
||||
@@ -3337,9 +3541,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profile will be sent to group members" = "Votre profil de chat sera envoyé aux membres du groupe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profile will be sent to your contact" = "Votre profil de chat sera envoyé à votre contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Vos profils de chat";
|
||||
|
||||
@@ -3376,9 +3577,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts.\nLes serveurs SimpleX ne peuvent pas voir votre profil.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile will be sent to the contact that you received this link from" = "Votre profil sera envoyé au contact qui vous a envoyé ce lien";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile, contacts and delivered messages are stored on your device." = "Votre profil, vos contacts et les messages reçus sont stockés sur votre appareil.";
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user