mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
181 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 746dd33728 | |||
| 2fceecab37 | |||
| e094ec5cf5 | |||
| fbd6012611 | |||
| 913ce4ebfd | |||
| 1e01a961fe | |||
| 27f4a71146 | |||
| 282bca4d81 | |||
| e5011ad150 | |||
| 0d4d08e52b | |||
| 7f8c7b2d99 | |||
| f5b666ae6d | |||
| 1cfe51af45 | |||
| 68af58bda1 | |||
| f51796b81c | |||
| 995618cf1c | |||
| 68baa18af8 | |||
| a4f1c9c915 | |||
| b8141c3555 | |||
| 47214e33ea | |||
| ff239d81e5 | |||
| 0a6e47dd61 | |||
| ee75219ed3 | |||
| 2e337a753f | |||
| 9918d91def | |||
| 82dd5751c1 | |||
| de637fab50 | |||
| cb49f6f01d | |||
| 6d0a83aa58 | |||
| 05f57e98ad | |||
| 6a66525927 | |||
| 9530a6055a | |||
| f5ed8debcc | |||
| 355d2449c5 | |||
| f7382cdd6f | |||
| dc8d10d068 | |||
| 951245d33f | |||
| 521b901cc9 | |||
| 6311ba451b | |||
| 754c76d6fd | |||
| 545f110fb8 | |||
| 4c6387c854 | |||
| aea7ff1c89 | |||
| 77ac972e09 | |||
| 256f85024f | |||
| 870f9e42dd | |||
| b3544cfbc3 | |||
| 0a4a3a24e1 | |||
| 8a66390a78 | |||
| 3d48eded3d | |||
| 6546426ec0 | |||
| 4a7ceb00fb | |||
| 53560378bb | |||
| bfa8717ed4 | |||
| cdb3b6aafd | |||
| 9f3d3e8ba4 | |||
| 047aad592e | |||
| 087acd9180 | |||
| 0b822e4a5c | |||
| f8a469488e | |||
| 3b5e806418 | |||
| 79e208193a | |||
| ef5c13b1c1 | |||
| 38533213d2 | |||
| 5f1aa6fa9d | |||
| b0002fe07d | |||
| ef21fd1d26 | |||
| 4a311b9578 | |||
| b8da5e225b | |||
| 8b3300e197 | |||
| f27de052cf | |||
| 5cc537f14c | |||
| dc8ca4cf89 | |||
| b62dd801f1 | |||
| 0c096e2c89 | |||
| cc127e56fe | |||
| 1781495ee3 | |||
| 831231d8e6 | |||
| 45102442f4 | |||
| f323c8e112 | |||
| 3bdc6b5e28 | |||
| d8373262bc | |||
| 3597d34716 | |||
| bd4259e89e | |||
| 55ead740cc | |||
| 5ef0eda2d7 | |||
| 49a9b0e7d6 | |||
| 45ada450a2 | |||
| 307a1b3c5e | |||
| ed6b3bbead | |||
| 901610eec5 | |||
| 7d4127c51d | |||
| 13215d91d7 | |||
| e1a8099474 | |||
| daa8d9bb21 | |||
| 5fcbade1bc | |||
| 3937ffa9a6 | |||
| 80ddb50e1c | |||
| f6e66f1c53 | |||
| 0c23ff9ae3 | |||
| 1570bc2b99 | |||
| 1e2104cabf | |||
| f3014f258d | |||
| f0991cc0ba | |||
| 74b78a8d7b | |||
| 82cd70a75c | |||
| fe4eb7b5af | |||
| c459e71d02 | |||
| 2516d5a393 | |||
| 477d98d75a | |||
| 4253cd7fb9 | |||
| ca78958667 | |||
| 1f5b80d560 | |||
| 2de111e76c | |||
| 8343285d93 | |||
| 5dbe2b2745 | |||
| fb9485190d | |||
| 6881600e06 | |||
| 9ed723bafa | |||
| 9ded1c9821 | |||
| bb374c68b1 | |||
| c3e82a6a4e | |||
| 7c12e82042 | |||
| e7e66ff873 | |||
| c4d7e5307c | |||
| d6b9a45a39 | |||
| 7fd3b4d6ba | |||
| 4004aafbc5 | |||
| 95008eeeaf | |||
| c7a8992043 | |||
| ea2b5f2ccf | |||
| ed9f277421 | |||
| 5c14c3b349 | |||
| d8fb31f167 | |||
| 02db38ffd3 | |||
| 7692195bfa | |||
| c435cbdc7b | |||
| effc281271 | |||
| 41eb2e5689 | |||
| 67d74a0a27 | |||
| c60af078d7 | |||
| f66405e79b | |||
| 74d186af16 | |||
| e4fbe66d95 | |||
| 187fef0c5a | |||
| 4782cab507 | |||
| cb52d75ff0 | |||
| bcbee67709 | |||
| 2501cbe55d | |||
| 2bd049db87 | |||
| 6b8b9ab4fd | |||
| 30db24265e | |||
| 316d605899 | |||
| b4257f7767 | |||
| a3f2d5c919 | |||
| cf46469cd5 | |||
| 0312fde818 | |||
| 9defa44f0c | |||
| 915b53054c | |||
| f81557b4fd | |||
| e273bd1239 | |||
| a63caf4640 | |||
| e7f0234134 | |||
| 340552321e | |||
| 98a3fc214d | |||
| 6a578cfe3c | |||
| dacc075fe8 | |||
| 55418e2bc0 | |||
| f2b5c0f3a8 | |||
| 5ebdf5dba9 | |||
| 8e045764df | |||
| 503d3d77e6 | |||
| 81bd7d97c5 | |||
| 8f57925067 | |||
| 9bf99db82e | |||
| 5615cdbf1a | |||
| d802ae0058 | |||
| 8f2278198c | |||
| 10937a5a4e | |||
| 6aff6e9804 | |||
| 95477cae7e |
@@ -55,22 +55,22 @@ jobs:
|
||||
ghc: "8.10.7"
|
||||
cache_path: ~/.cabal/store
|
||||
- os: ubuntu-20.04
|
||||
ghc: "9.6.4"
|
||||
ghc: "9.6.3"
|
||||
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
|
||||
ghc: "9.6.4"
|
||||
ghc: "9.6.3"
|
||||
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
|
||||
ghc: "9.6.4"
|
||||
ghc: "9.6.3"
|
||||
cache_path: ~/.cabal/store
|
||||
asset_name: simplex-chat-macos-x86-64
|
||||
desktop_asset_name: simplex-desktop-macos-x86_64.dmg
|
||||
- os: windows-latest
|
||||
ghc: "9.6.4"
|
||||
ghc: "9.6.3"
|
||||
cache_path: C:/cabal
|
||||
asset_name: simplex-chat-windows-x86-64
|
||||
desktop_asset_name: simplex-desktop-windows-x86_64.msi
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Haskell
|
||||
uses: haskell-actions/setup@v2
|
||||
uses: haskell/actions/setup@v2
|
||||
with:
|
||||
ghc-version: ${{ matrix.ghc }}
|
||||
cabal-version: "3.10.1.0"
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }}
|
||||
APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }}
|
||||
run: |
|
||||
scripts/ci/build-desktop-mac.sh
|
||||
scripts/build-desktop-mac.sh
|
||||
path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg)
|
||||
echo "package_path=$path" >> $GITHUB_OUTPUT
|
||||
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
|
||||
@@ -277,7 +277,9 @@ jobs:
|
||||
# Unix /
|
||||
|
||||
# / Windows
|
||||
# rm -rf dist-newstyle/src/direct-sq* is here because of the bug in cabal's dependency which prevents second build from finishing
|
||||
|
||||
# * In powershell multiline commands do not fail if individual commands fail - https://github.community/t/multiline-commands-on-windows-do-not-fail-if-individual-commands-fail/16753
|
||||
# * And GitHub Actions does not support parameterizing shell in a matrix job - https://github.community/t/using-matrix-to-specify-shell-is-it-possible/17065
|
||||
|
||||
- name: 'Setup MSYS2'
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ FROM ubuntu:${TAG} AS build
|
||||
RUN apt-get update && apt-get install -y curl git build-essential libgmp3-dev zlib1g-dev llvm-12 llvm-12-dev libnuma-dev libssl-dev
|
||||
|
||||
# Specify bootstrap Haskell versions
|
||||
ENV BOOTSTRAP_HASKELL_GHC_VERSION=9.6.4
|
||||
ENV BOOTSTRAP_HASKELL_GHC_VERSION=9.6.3
|
||||
ENV BOOTSTRAP_HASKELL_CABAL_VERSION=3.10.1.0
|
||||
|
||||
# Install ghcup
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[](https://www.reddit.com/r/SimpleXChat)
|
||||
<a rel="me" href="https://mastodon.social/@simplex"></a>
|
||||
|
||||
| 30/03/2023 | EN, [FR](/docs/lang/fr/README.md), [CZ](/docs/lang/cs/README.md), [PL](/docs/lang/pl/README.md) |
|
||||
| 30/03/2023 | EN, [FR](/docs/lang/fr/README.md), [CZ](/docs/lang/cs/README.md) |
|
||||
|
||||
<img src="images/simplex-chat-logo.svg" alt="SimpleX logo" width="100%">
|
||||
|
||||
@@ -234,8 +234,6 @@ You can use SimpleX with your own servers and still communicate with people usin
|
||||
|
||||
Recent and important updates:
|
||||
|
||||
[Mar 23, 2024. SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration.](./blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md)
|
||||
|
||||
[Mar 14, 2024. SimpleX Chat v5.6 beta: adding quantum resistance to Signal double ratchet algorithm.](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md)
|
||||
|
||||
[Jan 24, 2024. SimpleX Chat: free infrastructure from Linode, v5.5 released with private notes, group history and a simpler UX to connect.](./blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md)
|
||||
@@ -379,9 +377,8 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A
|
||||
- ✅ Using mobile profiles from the desktop app.
|
||||
- ✅ Private notes.
|
||||
- ✅ Improve sending videos (including encryption of locally stored videos).
|
||||
- ✅ Post-quantum resistant key exchange in double ratchet protocol.
|
||||
- 🏗 Improve stability and reduce battery usage.
|
||||
- 🏗 Improve experience for the new users.
|
||||
- 🏗 Post-quantum resistant key exchange in double ratchet protocol.
|
||||
- 🏗 Large groups, communities and public channels.
|
||||
- 🏗 Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic).
|
||||
- Privacy & security slider - a simple way to set all settings at once.
|
||||
|
||||
@@ -103,7 +103,6 @@ final class ChatModel: ObservableObject {
|
||||
// tracks keyboard height via subscription in AppDelegate
|
||||
@Published var keyboardHeight: CGFloat = 0
|
||||
@Published var pasteboardHasStrings: Bool = UIPasteboard.general.hasStrings
|
||||
@Published var networkInfo = UserNetworkInfo(networkType: .other, online: true)
|
||||
|
||||
var messageDelivery: Dictionary<Int64, () -> Void> = [:]
|
||||
|
||||
@@ -779,24 +778,6 @@ final class Chat: ObservableObject, Identifiable {
|
||||
|
||||
var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } }
|
||||
|
||||
func groupFeatureEnabled(_ feature: GroupFeature) -> Bool {
|
||||
if case let .group(groupInfo) = self.chatInfo {
|
||||
let p = groupInfo.fullGroupPreferences
|
||||
return switch feature {
|
||||
case .timedMessages: p.timedMessages.on
|
||||
case .directMessages: p.directMessages.on(for: groupInfo.membership)
|
||||
case .fullDelete: p.fullDelete.on
|
||||
case .reactions: p.reactions.on
|
||||
case .voice: p.voice.on(for: groupInfo.membership)
|
||||
case .files: p.files.on(for: groupInfo.membership)
|
||||
case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership)
|
||||
case .history: p.history.on
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
|
||||
}
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
//
|
||||
// NetworkObserver.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Avently on 05.04.2024.
|
||||
// Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
import SimpleXChat
|
||||
|
||||
class NetworkObserver {
|
||||
static let shared = NetworkObserver()
|
||||
private let queue: DispatchQueue = DispatchQueue(label: "chat.simplex.app.NetworkObserver")
|
||||
private var prevInfo: UserNetworkInfo? = nil
|
||||
private var monitor: NWPathMonitor?
|
||||
private let monitorLock: DispatchQueue = DispatchQueue(label: "chat.simplex.app.monitorLock")
|
||||
|
||||
func restartMonitor() {
|
||||
monitorLock.sync {
|
||||
monitor?.cancel()
|
||||
let mon = NWPathMonitor()
|
||||
mon.pathUpdateHandler = { [weak self] path in
|
||||
self?.networkPathChanged(path: path)
|
||||
}
|
||||
mon.start(queue: queue)
|
||||
monitor = mon
|
||||
}
|
||||
}
|
||||
|
||||
private func networkPathChanged(path: NWPath) {
|
||||
let info = UserNetworkInfo(
|
||||
networkType: networkTypeFromPath(path),
|
||||
online: path.status == .satisfied
|
||||
)
|
||||
if (prevInfo != info) {
|
||||
prevInfo = info
|
||||
setNetworkInfo(info)
|
||||
}
|
||||
}
|
||||
|
||||
private func networkTypeFromPath(_ path: NWPath) -> UserNetworkType {
|
||||
if path.usesInterfaceType(.wiredEthernet) {
|
||||
.ethernet
|
||||
} else if path.usesInterfaceType(.wifi) {
|
||||
.wifi
|
||||
} else if path.usesInterfaceType(.cellular) {
|
||||
.cellular
|
||||
} else if path.usesInterfaceType(.other) {
|
||||
.other
|
||||
} else {
|
||||
.none
|
||||
}
|
||||
}
|
||||
|
||||
private static var networkObserver: NetworkObserver? = nil
|
||||
|
||||
private func setNetworkInfo(_ info: UserNetworkInfo) {
|
||||
logger.debug("setNetworkInfo Network changed: \(String(describing: info))")
|
||||
DispatchQueue.main.sync {
|
||||
ChatModel.shared.networkInfo = info
|
||||
}
|
||||
if !hasChatCtrl() { return }
|
||||
self.monitorLock.sync {
|
||||
do {
|
||||
try apiSetNetworkInfo(info)
|
||||
} catch let err {
|
||||
logger.error("setNetworkInfo error: \(responseError(err))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,6 @@ public let CURRENT_CHAT_VERSION: Int = 2
|
||||
// version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core)
|
||||
public let CREATE_MEMBER_CONTACT_VRANGE = VersionRange(minVersion: 2, maxVersion: CURRENT_CHAT_VERSION)
|
||||
|
||||
private let networkStatusesLock = DispatchQueue(label: "chat.simplex.app.network-statuses.lock")
|
||||
|
||||
enum TerminalItem: Identifiable {
|
||||
case cmd(Date, ChatCommand)
|
||||
case resp(Date, ChatResponse)
|
||||
@@ -534,12 +532,6 @@ func setNetworkConfig(_ cfg: NetCfg, ctrl: chat_ctrl? = nil) throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetNetworkInfo(_ networkInfo: UserNetworkInfo) throws {
|
||||
let r = chatSendCmdSync(.apiSetNetworkInfo(networkInfo: networkInfo))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func reconnectAllServers() async throws {
|
||||
try await sendCommandOkResp(.reconnectAllServers)
|
||||
}
|
||||
@@ -1303,7 +1295,6 @@ func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = ni
|
||||
defer { m.ctrlInitInProgress = false }
|
||||
(m.chatDbEncrypted, m.chatDbStatus) = chatMigrateInit(dbKey, confirmMigrations: confirmMigrations)
|
||||
if m.chatDbStatus != .ok { return }
|
||||
NetworkObserver.shared.restartMonitor()
|
||||
// If we migrated successfully means previous re-encryption process on database level finished successfully too
|
||||
if encryptionStartedDefault.get() {
|
||||
encryptionStartedDefault.set(false)
|
||||
@@ -1469,8 +1460,6 @@ class ChatReceiver {
|
||||
private var receiveMessages = true
|
||||
private var _lastMsgTime = Date.now
|
||||
|
||||
var messagesChannel: ((ChatResponse) -> Void)? = nil
|
||||
|
||||
static let shared = ChatReceiver()
|
||||
|
||||
var lastMsgTime: Date { get { _lastMsgTime } }
|
||||
@@ -1488,9 +1477,6 @@ class ChatReceiver {
|
||||
if let msg = await chatRecvMsg() {
|
||||
self._lastMsgTime = .now
|
||||
await processReceivedMsg(msg)
|
||||
if let messagesChannel {
|
||||
messagesChannel(msg)
|
||||
}
|
||||
}
|
||||
_ = try? await Task.sleep(nanoseconds: 7_500_000)
|
||||
}
|
||||
@@ -1580,30 +1566,34 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
m.removeChat(mergedContact.id)
|
||||
}
|
||||
}
|
||||
case let .networkStatus(status, connections):
|
||||
// dispatch queue to synchronize access
|
||||
networkStatusesLock.sync {
|
||||
var ns = m.networkStatuses
|
||||
// slow loop is on the background thread
|
||||
for cId in connections {
|
||||
ns[cId] = status
|
||||
case let .contactsSubscribed(_, contactRefs):
|
||||
await updateContactsStatus(contactRefs, status: .connected)
|
||||
case let .contactsDisconnected(_, contactRefs):
|
||||
await updateContactsStatus(contactRefs, status: .disconnected)
|
||||
case let .contactSubSummary(_, contactSubscriptions):
|
||||
await MainActor.run {
|
||||
for sub in contactSubscriptions {
|
||||
// no need to update contact here, and it is slow
|
||||
// if active(user) {
|
||||
// m.updateContact(sub.contact)
|
||||
// }
|
||||
if let err = sub.contactError {
|
||||
processContactSubError(sub.contact, err)
|
||||
} else {
|
||||
m.setContactNetworkStatus(sub.contact, .connected)
|
||||
}
|
||||
}
|
||||
// fast model update is on the main thread
|
||||
DispatchQueue.main.sync {
|
||||
m.networkStatuses = ns
|
||||
}
|
||||
case let .networkStatus(status, connections):
|
||||
await MainActor.run {
|
||||
for cId in connections {
|
||||
m.networkStatuses[cId] = status
|
||||
}
|
||||
}
|
||||
case let .networkStatuses(_, statuses): ()
|
||||
// dispatch queue to synchronize access
|
||||
networkStatusesLock.sync {
|
||||
var ns = m.networkStatuses
|
||||
// slow loop is on the background thread
|
||||
await MainActor.run {
|
||||
for s in statuses {
|
||||
ns[s.agentConnId] = s.networkStatus
|
||||
}
|
||||
// fast model update is on the main thread
|
||||
DispatchQueue.main.sync {
|
||||
m.networkStatuses = ns
|
||||
m.networkStatuses[s.agentConnId] = s.networkStatus
|
||||
}
|
||||
}
|
||||
case let .newChatItem(user, aChatItem):
|
||||
@@ -1954,6 +1944,26 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async {
|
||||
}
|
||||
}
|
||||
|
||||
func updateContactsStatus(_ contactRefs: [ContactRef], status: NetworkStatus) async {
|
||||
let m = ChatModel.shared
|
||||
await MainActor.run {
|
||||
for c in contactRefs {
|
||||
m.networkStatuses[c.agentConnId] = status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processContactSubError(_ contact: Contact, _ chatError: ChatError) {
|
||||
let m = ChatModel.shared
|
||||
var err: String
|
||||
switch chatError {
|
||||
case .errorAgent(agentError: .BROKER(_, .NETWORK)): err = "network"
|
||||
case .errorAgent(agentError: .SMP(smpErr: .AUTH)): err = "contact deleted"
|
||||
default: err = String(describing: chatError)
|
||||
}
|
||||
m.setContactNetworkStatus(contact, .error(connectionError: err))
|
||||
}
|
||||
|
||||
func refreshCallInvitations() throws {
|
||||
let m = ChatModel.shared
|
||||
let callInvitations = try justRefreshCallInvitations()
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import SwiftUI
|
||||
import WebKit
|
||||
import SimpleXChat
|
||||
import AVFoundation
|
||||
|
||||
struct ActiveCallView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@@ -22,7 +21,6 @@ struct ActiveCallView: View {
|
||||
@Binding var canConnectCall: Bool
|
||||
@State var prevColorScheme: ColorScheme = .dark
|
||||
@State var pipShown = false
|
||||
@State var wasConnected = false
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
@@ -71,11 +69,6 @@ struct ActiveCallView: View {
|
||||
Task { await m.callCommand.setClient(nil) }
|
||||
AppDelegate.keepScreenOn(false)
|
||||
client?.endCall()
|
||||
CallSoundsPlayer.shared.stop()
|
||||
try? AVAudioSession.sharedInstance().setCategory(.soloAmbient)
|
||||
if (wasConnected) {
|
||||
CallSoundsPlayer.shared.vibrate(long: true)
|
||||
}
|
||||
}
|
||||
.background(m.activeCallViewIsCollapsed ? .clear : .black)
|
||||
// Quite a big delay when opening/closing the view when a scheme changes (globally) this way. It's not needed when CallKit is used since status bar is green with white text on it
|
||||
@@ -110,11 +103,6 @@ struct ActiveCallView: View {
|
||||
call.callState = .invitationSent
|
||||
call.localCapabilities = capabilities
|
||||
}
|
||||
if call.supportsVideo {
|
||||
try? AVAudioSession.sharedInstance().setCategory(.playback, options: .defaultToSpeaker)
|
||||
}
|
||||
CallSoundsPlayer.shared.startConnectingCallSound()
|
||||
activeCallWaitDeliveryReceipt()
|
||||
}
|
||||
case let .offer(offer, iceCandidates, capabilities):
|
||||
Task {
|
||||
@@ -138,8 +126,6 @@ struct ActiveCallView: View {
|
||||
}
|
||||
await MainActor.run {
|
||||
call.callState = .negotiated
|
||||
CallSoundsPlayer.shared.stop()
|
||||
try? AVAudioSession.sharedInstance().setCategory(.soloAmbient)
|
||||
}
|
||||
}
|
||||
case let .ice(iceCandidates):
|
||||
@@ -158,10 +144,6 @@ struct ActiveCallView: View {
|
||||
: CallController.shared.reportIncomingCall(call: call, connectedAt: nil)
|
||||
call.callState = .connected
|
||||
call.connectedAt = .now
|
||||
if !wasConnected {
|
||||
CallSoundsPlayer.shared.vibrate(long: false)
|
||||
wasConnected = true
|
||||
}
|
||||
}
|
||||
if state.connectionState == "closed" {
|
||||
closeCallView(client)
|
||||
@@ -179,10 +161,6 @@ struct ActiveCallView: View {
|
||||
call.callState = .connected
|
||||
call.connectionInfo = connectionInfo
|
||||
call.connectedAt = .now
|
||||
if !wasConnected {
|
||||
CallSoundsPlayer.shared.vibrate(long: false)
|
||||
wasConnected = true
|
||||
}
|
||||
case .ended:
|
||||
closeCallView(client)
|
||||
call.callState = .ended
|
||||
@@ -209,22 +187,6 @@ struct ActiveCallView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func activeCallWaitDeliveryReceipt() {
|
||||
ChatReceiver.shared.messagesChannel = { msg in
|
||||
guard let call = ChatModel.shared.activeCall, call.callState == .invitationSent else {
|
||||
ChatReceiver.shared.messagesChannel = nil
|
||||
return
|
||||
}
|
||||
if case let .chatItemStatusUpdated(_, msg) = msg,
|
||||
msg.chatInfo.id == call.contact.id,
|
||||
case .sndCall = msg.chatItem.content,
|
||||
case .sndRcvd = msg.chatItem.meta.itemStatus {
|
||||
CallSoundsPlayer.shared.startInCallSound()
|
||||
ChatReceiver.shared.messagesChannel = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func closeCallView(_ client: WebRTCClient) {
|
||||
if m.activeCall != nil {
|
||||
m.showCallView = false
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
import UIKit
|
||||
|
||||
class SoundPlayer {
|
||||
static let shared = SoundPlayer()
|
||||
@@ -44,63 +43,3 @@ class SoundPlayer {
|
||||
audioPlayer = nil
|
||||
}
|
||||
}
|
||||
|
||||
class CallSoundsPlayer {
|
||||
static let shared = CallSoundsPlayer()
|
||||
private var audioPlayer: AVAudioPlayer?
|
||||
private var playerTask: Task = Task {}
|
||||
|
||||
private func start(_ soundName: String, delayMs: Double) {
|
||||
audioPlayer?.stop()
|
||||
playerTask.cancel()
|
||||
logger.debug("start \(soundName)")
|
||||
guard let path = Bundle.main.path(forResource: soundName, ofType: "mp3", inDirectory: "sounds") else {
|
||||
logger.debug("start: file not found")
|
||||
return
|
||||
}
|
||||
do {
|
||||
let player = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
|
||||
if player.prepareToPlay() {
|
||||
audioPlayer = player
|
||||
}
|
||||
} catch {
|
||||
logger.debug("start: AVAudioPlayer error \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
playerTask = Task {
|
||||
while let player = audioPlayer {
|
||||
player.play()
|
||||
do {
|
||||
try await Task.sleep(nanoseconds: UInt64((player.duration * 1_000_000_000) + delayMs * 1_000_000))
|
||||
} catch {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func startConnectingCallSound() {
|
||||
start("connecting_call", delayMs: 0)
|
||||
}
|
||||
|
||||
func startInCallSound() {
|
||||
// Taken from https://github.com/TelegramOrg/Telegram-Android
|
||||
// https://github.com/TelegramOrg/Telegram-Android/blob/master/LICENSE
|
||||
start("in_call", delayMs: 1000)
|
||||
}
|
||||
|
||||
func stop() {
|
||||
playerTask.cancel()
|
||||
audioPlayer?.stop()
|
||||
audioPlayer = nil
|
||||
}
|
||||
|
||||
func vibrate(long: Bool) {
|
||||
// iOS just don't want to vibrate more than once after a short period of time, and all 'styles' feel the same
|
||||
if long {
|
||||
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
|
||||
} else {
|
||||
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,18 +431,17 @@ struct RTCIceServer: Codable, Equatable {
|
||||
}
|
||||
|
||||
// the servers are expected in this format:
|
||||
// stuns:stun.simplex.im:443?transport=tcp
|
||||
// turns:private2:Hxuq2QxUjnhj96Zq2r4HjqHRj@turn.simplex.im:443?transport=tcp
|
||||
// stun:stun.simplex.im:443?transport=tcp
|
||||
// turn:private:yleob6AVkiNI87hpR94Z@turn.simplex.im:443?transport=tcp
|
||||
func parseRTCIceServer(_ str: String) -> RTCIceServer? {
|
||||
var s = replaceScheme(str, "stun:")
|
||||
s = replaceScheme(s, "stuns:")
|
||||
s = replaceScheme(s, "turn:")
|
||||
s = replaceScheme(s, "turns:")
|
||||
if let u: URL = URL(string: s),
|
||||
let scheme = u.scheme,
|
||||
let host = u.host,
|
||||
let port = u.port,
|
||||
u.path == "" && (scheme == "stun" || scheme == "stuns" || scheme == "turn" || scheme == "turns") {
|
||||
u.path == "" && (scheme == "stun" || scheme == "turn" || scheme == "turns") {
|
||||
let query = u.query == nil || u.query == "" ? "" : "?" + (u.query ?? "")
|
||||
return RTCIceServer(
|
||||
urls: ["\(scheme):\(host):\(port)\(query)"],
|
||||
|
||||
@@ -49,7 +49,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
|
||||
}
|
||||
|
||||
private let rtcAudioSession = RTCAudioSession.sharedInstance()
|
||||
private let audioQueue = DispatchQueue(label: "chat.simplex.app.audio")
|
||||
private let audioQueue = DispatchQueue(label: "audio")
|
||||
private var sendCallResponse: (WVAPIMessage) async -> Void
|
||||
var activeCall: Binding<Call?>
|
||||
private var localRendererAspectRatio: Binding<CGFloat?>
|
||||
@@ -65,14 +65,14 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
|
||||
self.localRendererAspectRatio = localRendererAspectRatio
|
||||
rtcAudioSession.useManualAudio = CallController.useCallKit()
|
||||
rtcAudioSession.isAudioEnabled = !CallController.useCallKit()
|
||||
logger.debug("WebRTCClient: rtcAudioSession has manual audio \(self.rtcAudioSession.useManualAudio) and audio enabled \(self.rtcAudioSession.isAudioEnabled)")
|
||||
logger.debug("WebRTCClient: rtcAudioSession has manual audio \(self.rtcAudioSession.useManualAudio) and audio enabled \(self.rtcAudioSession.isAudioEnabled)}")
|
||||
super.init()
|
||||
}
|
||||
|
||||
let defaultIceServers: [WebRTC.RTCIceServer] = [
|
||||
WebRTC.RTCIceServer(urlStrings: ["stuns:stun.simplex.im:443"]),
|
||||
//WebRTC.RTCIceServer(urlStrings: ["turns:turn.simplex.im:443?transport=udp"], username: "private2", credential: "Hxuq2QxUjnhj96Zq2r4HjqHRj"),
|
||||
WebRTC.RTCIceServer(urlStrings: ["turns:turn.simplex.im:443?transport=tcp"], username: "private2", credential: "Hxuq2QxUjnhj96Zq2r4HjqHRj"),
|
||||
WebRTC.RTCIceServer(urlStrings: ["stun:stun.simplex.im:443"]),
|
||||
WebRTC.RTCIceServer(urlStrings: ["turn:turn.simplex.im:443?transport=udp"], username: "private", credential: "yleob6AVkiNI87hpR94Z"),
|
||||
WebRTC.RTCIceServer(urlStrings: ["turn:turn.simplex.im:443?transport=tcp"], username: "private", credential: "yleob6AVkiNI87hpR94Z"),
|
||||
]
|
||||
|
||||
func initializeCall(_ iceServers: [WebRTC.RTCIceServer]?, _ mediaType: CallMediaType, _ aesKey: String?, _ relay: Bool?) -> Call {
|
||||
|
||||
@@ -11,7 +11,6 @@ import SimpleXChat
|
||||
|
||||
struct CIChatFeatureView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
@Binding var revealed: Bool
|
||||
var feature: Feature
|
||||
@@ -19,7 +18,7 @@ struct CIChatFeatureView: View {
|
||||
var iconColor: Color
|
||||
|
||||
var body: some View {
|
||||
if !revealed, let fs = mergedFeatures() {
|
||||
if !revealed, let fs = mergedFeautures() {
|
||||
HStack {
|
||||
ForEach(fs, content: featureIconView)
|
||||
}
|
||||
@@ -48,7 +47,7 @@ struct CIChatFeatureView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func mergedFeatures() -> [FeatureInfo]? {
|
||||
private func mergedFeautures() -> [FeatureInfo]? {
|
||||
var fs: [FeatureInfo] = []
|
||||
var icons: Set<String> = []
|
||||
if var i = m.getChatItemIndex(chatItem) {
|
||||
@@ -68,8 +67,8 @@ struct CIChatFeatureView: View {
|
||||
switch ci.content {
|
||||
case let .rcvChatFeature(feature, enabled, param): FeatureInfo(feature, enabled.iconColor, param)
|
||||
case let .sndChatFeature(feature, enabled, param): FeatureInfo(feature, enabled.iconColor, param)
|
||||
case let .rcvGroupFeature(feature, preference, param, role): FeatureInfo(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor, param)
|
||||
case let .sndGroupFeature(feature, preference, param, role): FeatureInfo(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor, param)
|
||||
case let .rcvGroupFeature(feature, preference, param): FeatureInfo(feature, preference.enable.iconColor, param)
|
||||
case let .sndGroupFeature(feature, preference, param): FeatureInfo(feature, preference.enable.iconColor, param)
|
||||
default: nil
|
||||
}
|
||||
}
|
||||
@@ -104,6 +103,6 @@ struct CIChatFeatureView: View {
|
||||
struct CIChatFeatureView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let enabled = FeatureEnabled(forUser: false, forContact: false)
|
||||
CIChatFeatureView(chat: Chat.sampleData, chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), revealed: Binding.constant(true), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor)
|
||||
CIChatFeatureView(chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), revealed: Binding.constant(true), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,14 +70,14 @@ struct CIImageView: View {
|
||||
}
|
||||
|
||||
private func imageView(_ img: UIImage) -> some View {
|
||||
let w = img.size.width <= img.size.height ? maxWidth * 0.75 : maxWidth
|
||||
let w = img.size.width <= img.size.height ? maxWidth * 0.75 : img.imageData == nil ? .infinity : maxWidth
|
||||
DispatchQueue.main.async { imgWidth = w }
|
||||
return ZStack(alignment: .topTrailing) {
|
||||
if img.imageData == nil {
|
||||
Image(uiImage: img)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: w)
|
||||
.frame(maxWidth: w)
|
||||
} else {
|
||||
SwiftyGif(image: img)
|
||||
.frame(width: w, height: w * img.size.height / img.size.width)
|
||||
|
||||
@@ -126,7 +126,7 @@ struct CIVideoView: View {
|
||||
if !decryptionInProgress {
|
||||
Button {
|
||||
decrypt(file: file) {
|
||||
if urlDecrypted != nil {
|
||||
if let decrypted = urlDecrypted {
|
||||
videoPlaying = true
|
||||
player?.play()
|
||||
}
|
||||
@@ -243,13 +243,13 @@ struct CIVideoView: View {
|
||||
}
|
||||
|
||||
private func imageView(_ img: UIImage) -> some View {
|
||||
let w = img.size.width <= img.size.height ? maxWidth * 0.75 : maxWidth
|
||||
let w = img.size.width <= img.size.height ? maxWidth * 0.75 : .infinity
|
||||
DispatchQueue.main.async { videoWidth = w }
|
||||
return ZStack(alignment: .topTrailing) {
|
||||
Image(uiImage: img)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: w)
|
||||
.frame(maxWidth: w)
|
||||
loadingIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,6 @@ struct FramedItemView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if chatItem.meta.itemForwarded != nil {
|
||||
framedItemHeader(icon: "arrowshape.turn.up.forward", caption: Text("forwarded").italic())
|
||||
}
|
||||
|
||||
ChatItemContentView(chat: chat, chatItem: chatItem, revealed: $revealed, msgContentView: framedMsgContentView)
|
||||
@@ -355,9 +353,9 @@ private struct MetaColorPreferenceKey: PreferenceKey {
|
||||
|
||||
func onlyImageOrVideo(_ ci: ChatItem) -> Bool {
|
||||
if case let .image(text, _) = ci.content.msgContent {
|
||||
return ci.meta.itemDeleted == nil && !ci.meta.isLive && ci.quotedItem == nil && ci.meta.itemForwarded == nil && text == ""
|
||||
return ci.meta.itemDeleted == nil && !ci.meta.isLive && ci.quotedItem == nil && text == ""
|
||||
} else if case let .video(text, _, _) = ci.content.msgContent {
|
||||
return ci.meta.itemDeleted == nil && !ci.meta.isLive && ci.quotedItem == nil && ci.meta.itemForwarded == nil && text == ""
|
||||
return ci.meta.itemDeleted == nil && !ci.meta.isLive && ci.quotedItem == nil && text == ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ struct ChatItemView: View {
|
||||
let ci = chatItem
|
||||
if chatItem.meta.itemDeleted != nil && (!revealed || chatItem.isDeletedContent) {
|
||||
MarkedDeletedItemView(chat: chat, chatItem: chatItem, revealed: $revealed)
|
||||
} else if ci.quotedItem == nil && ci.meta.itemForwarded == nil && ci.meta.itemDeleted == nil && !ci.meta.isLive {
|
||||
} else if ci.quotedItem == nil && ci.meta.itemDeleted == nil && !ci.meta.isLive {
|
||||
if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) {
|
||||
EmojiItemView(chat: chat, chatItem: ci)
|
||||
} else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent {
|
||||
@@ -102,9 +102,9 @@ struct ChatItemContentView<Content: View>: View {
|
||||
case let .rcvChatPreference(feature, allowed, param):
|
||||
CIFeaturePreferenceView(chat: chat, chatItem: chatItem, feature: feature, allowed: allowed, param: param)
|
||||
case let .sndChatPreference(feature, _, _):
|
||||
CIChatFeatureView(chat: chat, chatItem: chatItem, revealed: $revealed, feature: feature, icon: feature.icon, iconColor: .secondary)
|
||||
case let .rcvGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor)
|
||||
case let .sndGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor)
|
||||
CIChatFeatureView(chatItem: chatItem, revealed: $revealed, feature: feature, icon: feature.icon, iconColor: .secondary)
|
||||
case let .rcvGroupFeature(feature, preference, _): chatFeatureView(feature, preference.enable.iconColor)
|
||||
case let .sndGroupFeature(feature, preference, _): chatFeatureView(feature, preference.enable.iconColor)
|
||||
case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red)
|
||||
case let .rcvGroupFeatureRejected(feature): chatFeatureView(feature, .red)
|
||||
case .sndModerated: deletedItemView()
|
||||
@@ -149,7 +149,7 @@ struct ChatItemContentView<Content: View>: View {
|
||||
}
|
||||
|
||||
private func chatFeatureView(_ feature: Feature, _ iconColor: Color) -> some View {
|
||||
CIChatFeatureView(chat: chat, chatItem: chatItem, revealed: $revealed, feature: feature, iconColor: iconColor)
|
||||
CIChatFeatureView(chatItem: chatItem, revealed: $revealed, feature: feature, iconColor: iconColor)
|
||||
}
|
||||
|
||||
private var mergedGroupEventText: Text? {
|
||||
|
||||
@@ -17,7 +17,6 @@ struct ChatView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
@State @ObservedObject var chat: Chat
|
||||
@State private var showChatInfoSheet: Bool = false
|
||||
@State private var showAddMembersSheet: Bool = false
|
||||
@@ -235,9 +234,7 @@ struct ChatView: View {
|
||||
|
||||
private func initChatView() {
|
||||
let cInfo = chat.chatInfo
|
||||
// This check prevents the call to apiContactInfo after the app is suspended, and the database is closed.
|
||||
if case .active = scenePhase,
|
||||
case let .direct(contact) = cInfo {
|
||||
if case let .direct(contact) = cInfo {
|
||||
Task {
|
||||
do {
|
||||
let (stats, _) = try await apiContactInfo(chat.chatInfo.apiId)
|
||||
@@ -296,7 +293,7 @@ struct ChatView: View {
|
||||
}
|
||||
|
||||
private func voiceWithoutFrame(_ ci: ChatItem) -> Bool {
|
||||
ci.content.msgContent?.isVoice == true && ci.content.text.count == 0 && ci.quotedItem == nil && ci.meta.itemForwarded == nil
|
||||
ci.content.msgContent?.isVoice == true && ci.content.text.count == 0 && ci.quotedItem == nil
|
||||
}
|
||||
|
||||
private func chatItemsList() -> some View {
|
||||
@@ -514,7 +511,6 @@ struct ChatView: View {
|
||||
chat: chat,
|
||||
chatItem: ci,
|
||||
maxWidth: maxWidth,
|
||||
itemWidth: maxWidth,
|
||||
composeState: $composeState,
|
||||
selectedMember: $selectedMember,
|
||||
chatView: self
|
||||
@@ -527,7 +523,6 @@ struct ChatView: View {
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var maxWidth: CGFloat
|
||||
@State var itemWidth: CGFloat
|
||||
@Binding var composeState: ComposeState
|
||||
@Binding var selectedMember: GMember?
|
||||
var chatView: ChatView
|
||||
@@ -656,7 +651,7 @@ struct ChatView: View {
|
||||
playbackState: $playbackState,
|
||||
playbackTime: $playbackTime
|
||||
)
|
||||
.uiKitContextMenu(hasImageOrVideo: ci.content.msgContent?.isImageOrVideo == true, maxWidth: maxWidth, itemWidth: $itemWidth, menu: uiMenu, allowMenu: $allowMenu)
|
||||
.uiKitContextMenu(maxWidth: maxWidth, menu: uiMenu, allowMenu: $allowMenu)
|
||||
.accessibilityLabel("")
|
||||
if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 {
|
||||
chatItemReactions(ci)
|
||||
@@ -667,7 +662,7 @@ struct ChatView: View {
|
||||
Button("Delete for me", role: .destructive) {
|
||||
deleteMessage(.cidmInternal)
|
||||
}
|
||||
if let di = deletingItem, di.meta.deletable && !di.localNote {
|
||||
if let di = deletingItem, di.meta.editable && !di.localNote {
|
||||
Button(broadcastDeleteButtonText, role: .destructive) {
|
||||
deleteMessage(.cidmBroadcast)
|
||||
}
|
||||
|
||||
@@ -161,16 +161,6 @@ struct ComposeState {
|
||||
}
|
||||
}
|
||||
|
||||
var attachmentPreview: Bool {
|
||||
switch preview {
|
||||
case .noPreview: false
|
||||
case .linkPreview: false
|
||||
case let .mediaPreviews(mediaPreviews): !mediaPreviews.isEmpty
|
||||
case .voicePreview: false
|
||||
case .filePreview: true
|
||||
}
|
||||
}
|
||||
|
||||
var empty: Bool {
|
||||
message == "" && noPreview
|
||||
}
|
||||
@@ -244,7 +234,6 @@ struct ComposeView: View {
|
||||
@Binding var keyboardVisible: Bool
|
||||
|
||||
@State var linkUrl: URL? = nil
|
||||
@State var hasSimplexLink: Bool = false
|
||||
@State var prevLinkUrl: URL? = nil
|
||||
@State var pendingLinkUrl: URL? = nil
|
||||
@State var cancelledLinks: Set<String> = []
|
||||
@@ -271,16 +260,6 @@ struct ComposeView: View {
|
||||
if chat.chatInfo.contact?.nextSendGrpInv ?? false {
|
||||
ContextInvitingContactMemberView()
|
||||
}
|
||||
let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
|
||||
let fileProhibited = composeState.attachmentPreview && !chat.groupFeatureEnabled(.files)
|
||||
let voiceProhibited = composeState.voicePreview && !chat.chatInfo.featureEnabled(.voice)
|
||||
if simplexLinkProhibited {
|
||||
msgNotAllowedView("SimpleX links not allowed", icon: "link")
|
||||
} else if fileProhibited {
|
||||
msgNotAllowedView("Files and media not allowed", icon: "doc")
|
||||
} else if voiceProhibited {
|
||||
msgNotAllowedView("Voice messages not allowed", icon: "mic")
|
||||
}
|
||||
contextItemView()
|
||||
switch (composeState.editing, composeState.preview) {
|
||||
case (true, .filePreview): EmptyView()
|
||||
@@ -299,7 +278,7 @@ struct ComposeView: View {
|
||||
.padding(.bottom, 12)
|
||||
.padding(.leading, 12)
|
||||
if case let .group(g) = chat.chatInfo,
|
||||
!g.fullGroupPreferences.files.on(for: g.membership) {
|
||||
!g.fullGroupPreferences.files.on {
|
||||
b.disabled(true).onTapGesture {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title: "Files and media prohibited!",
|
||||
@@ -324,7 +303,6 @@ struct ComposeView: View {
|
||||
},
|
||||
nextSendGrpInv: chat.chatInfo.contact?.nextSendGrpInv ?? false,
|
||||
voiceMessageAllowed: chat.chatInfo.featureEnabled(.voice),
|
||||
disableSendButton: simplexLinkProhibited || fileProhibited || voiceProhibited,
|
||||
showEnableVoiceMessagesAlert: chat.chatInfo.showEnableVoiceMessagesAlert,
|
||||
startVoiceMessageRecording: {
|
||||
Task {
|
||||
@@ -359,18 +337,13 @@ struct ComposeView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: composeState.message) { msg in
|
||||
.onChange(of: composeState.message) { _ in
|
||||
if composeState.linkPreviewAllowed {
|
||||
if msg.count > 0 {
|
||||
showLinkPreview(msg)
|
||||
if composeState.message.count > 0 {
|
||||
showLinkPreview(composeState.message)
|
||||
} else {
|
||||
resetLinkPreview()
|
||||
hasSimplexLink = false
|
||||
}
|
||||
} else if msg.count > 0 && !chat.groupFeatureEnabled(.simplexLinks) {
|
||||
(_, hasSimplexLink) = parseMessage(msg)
|
||||
} else {
|
||||
hasSimplexLink = false
|
||||
}
|
||||
}
|
||||
.onChange(of: chat.userCanSend) { canSend in
|
||||
@@ -637,18 +610,6 @@ struct ComposeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func msgNotAllowedView(_ reason: LocalizedStringKey, icon: String) -> some View {
|
||||
HStack {
|
||||
Image(systemName: icon).foregroundColor(.secondary)
|
||||
Text(reason).italic()
|
||||
}
|
||||
.padding(12)
|
||||
.frame(minHeight: 50)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Color(uiColor: .tertiarySystemGroupedBackground))
|
||||
.padding(.top, 8)
|
||||
}
|
||||
|
||||
@ViewBuilder private func contextItemView() -> some View {
|
||||
switch composeState.contextItem {
|
||||
case .noContextItem:
|
||||
@@ -857,7 +818,7 @@ struct ComposeView: View {
|
||||
func checkLinkPreview() -> MsgContent {
|
||||
switch (composeState.preview) {
|
||||
case let .linkPreview(linkPreview: linkPreview):
|
||||
if let url = parseMessage(msgText).url,
|
||||
if let url = parseMessage(msgText),
|
||||
let linkPreview = linkPreview,
|
||||
url == linkPreview.uri {
|
||||
return .link(text: msgText, preview: linkPreview)
|
||||
@@ -986,7 +947,7 @@ struct ComposeView: View {
|
||||
|
||||
private func showLinkPreview(_ s: String) {
|
||||
prevLinkUrl = linkUrl
|
||||
(linkUrl, hasSimplexLink) = parseMessage(s)
|
||||
linkUrl = parseMessage(s)
|
||||
if let url = linkUrl {
|
||||
if url != composeState.linkPreview?.uri && url != pendingLinkUrl {
|
||||
pendingLinkUrl = url
|
||||
@@ -1003,17 +964,13 @@ struct ComposeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func parseMessage(_ msg: String) -> (url: URL?, hasSimplexLink: Bool) {
|
||||
guard let parsedMsg = parseSimpleXMarkdown(msg) else { return (nil, false) }
|
||||
let url: URL? = if let uri = parsedMsg.first(where: { ft in
|
||||
private func parseMessage(_ msg: String) -> URL? {
|
||||
let parsedMsg = parseSimpleXMarkdown(msg)
|
||||
let uri = parsedMsg?.first(where: { ft in
|
||||
ft.format == .uri && !cancelledLinks.contains(ft.text) && !isSimplexLink(ft.text)
|
||||
}) {
|
||||
URL(string: uri.text)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
let simplexLink = parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false })
|
||||
return (url, simplexLink)
|
||||
})
|
||||
if let uri = uri { return URL(string: uri.text) }
|
||||
else { return nil }
|
||||
}
|
||||
|
||||
private func isSimplexLink(_ link: String) -> Bool {
|
||||
|
||||
@@ -20,7 +20,6 @@ struct SendMessageView: View {
|
||||
var nextSendGrpInv: Bool = false
|
||||
var showVoiceMessageButton: Bool = true
|
||||
var voiceMessageAllowed: Bool = true
|
||||
var disableSendButton = false
|
||||
var showEnableVoiceMessagesAlert: ChatInfo.ShowEnableVoiceMessagesAlert = .other
|
||||
var startVoiceMessageRecording: (() -> Void)? = nil
|
||||
var finishVoiceMessageRecording: (() -> Void)? = nil
|
||||
@@ -185,8 +184,7 @@ struct SendMessageView: View {
|
||||
!composeState.sendEnabled ||
|
||||
composeState.inProgress ||
|
||||
(!voiceMessageAllowed && composeState.voicePreview) ||
|
||||
composeState.endLiveDisabled ||
|
||||
disableSendButton
|
||||
composeState.endLiveDisabled
|
||||
)
|
||||
.frame(width: 29, height: 29)
|
||||
.contextMenu{
|
||||
|
||||
@@ -83,7 +83,7 @@ struct GroupMemberInfoView: View {
|
||||
Section {
|
||||
if let contactId = member.memberContactId, let chat = knownDirectChat(contactId) {
|
||||
knownDirectChatButton(chat)
|
||||
} else if groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) {
|
||||
} else if groupInfo.fullGroupPreferences.directMessages.on {
|
||||
if let contactId = member.memberContactId {
|
||||
newDirectChatButton(contactId)
|
||||
} else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false {
|
||||
@@ -110,7 +110,7 @@ struct GroupMemberInfoView: View {
|
||||
Label("Share address", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
if let contactId = member.memberContactId {
|
||||
if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) {
|
||||
if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on {
|
||||
connectViaAddressButton(contactLink)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -9,12 +9,6 @@
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
private let featureRoles: [(role: GroupMemberRole?, text: LocalizedStringKey)] = [
|
||||
(nil, "all members"),
|
||||
(.admin, "admins"),
|
||||
(.owner, "owners")
|
||||
]
|
||||
|
||||
struct GroupPreferencesView: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@@ -30,12 +24,10 @@ struct GroupPreferencesView: View {
|
||||
List {
|
||||
featureSection(.timedMessages, $preferences.timedMessages.enable)
|
||||
featureSection(.fullDelete, $preferences.fullDelete.enable)
|
||||
featureSection(.directMessages, $preferences.directMessages.enable, $preferences.directMessages.role)
|
||||
featureSection(.directMessages, $preferences.directMessages.enable)
|
||||
featureSection(.reactions, $preferences.reactions.enable)
|
||||
featureSection(.voice, $preferences.voice.enable, $preferences.voice.role)
|
||||
featureSection(.files, $preferences.files.enable, $preferences.files.role)
|
||||
// TODO enable simplexLinks preference in 5.8
|
||||
// featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role)
|
||||
featureSection(.voice, $preferences.voice.enable)
|
||||
featureSection(.files, $preferences.files.enable)
|
||||
featureSection(.history, $preferences.history.enable)
|
||||
|
||||
if groupInfo.canEdit {
|
||||
@@ -72,7 +64,7 @@ struct GroupPreferencesView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func featureSection(_ feature: GroupFeature, _ enableFeature: Binding<GroupFeatureEnabled>, _ enableForRole: Binding<GroupMemberRole?>? = nil) -> some View {
|
||||
private func featureSection(_ feature: GroupFeature, _ enableFeature: Binding<GroupFeatureEnabled>) -> some View {
|
||||
Section {
|
||||
let color: Color = enableFeature.wrappedValue == .on ? .green : .secondary
|
||||
let icon = enableFeature.wrappedValue == .on ? feature.iconFilled : feature.icon
|
||||
@@ -95,16 +87,6 @@ struct GroupPreferencesView: View {
|
||||
)
|
||||
.frame(height: 36)
|
||||
}
|
||||
if enableFeature.wrappedValue == .on, let enableForRole {
|
||||
Picker("Enabled for", selection: enableForRole) {
|
||||
ForEach(featureRoles, id: \.role) { fr in
|
||||
Text(fr.text)
|
||||
}
|
||||
}
|
||||
.frame(height: 36)
|
||||
// remove in v5.8
|
||||
.disabled(true)
|
||||
}
|
||||
} else {
|
||||
settingsRow(icon, color: color) {
|
||||
infoRow(Text(feature.text), enableFeature.wrappedValue.text)
|
||||
@@ -112,26 +94,10 @@ struct GroupPreferencesView: View {
|
||||
if timedOn {
|
||||
infoRow("Delete after", timeText(preferences.timedMessages.ttl))
|
||||
}
|
||||
if enableFeature.wrappedValue == .on, let enableForRole {
|
||||
HStack {
|
||||
Text("Enabled for").foregroundColor(.secondary)
|
||||
Spacer()
|
||||
Text(
|
||||
featureRoles.first(where: { fr in fr.role == enableForRole.wrappedValue })?.text
|
||||
?? "all members"
|
||||
)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.canEdit))
|
||||
}
|
||||
.onChange(of: enableFeature.wrappedValue) { enabled in
|
||||
if case .off = enabled {
|
||||
enableForRole?.wrappedValue = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func savePreferences() {
|
||||
|
||||
@@ -12,7 +12,6 @@ private let fillColorLight = Color(uiColor: UIColor(red: 0.99, green: 0.99, blue
|
||||
struct UserPicker: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
@Binding var showSettings: Bool
|
||||
@Binding var showConnectDesktop: Bool
|
||||
@Binding var userPickerVisible: Bool
|
||||
@@ -92,10 +91,7 @@ struct UserPicker: View {
|
||||
.opacity(userPickerVisible ? 1.0 : 0.0)
|
||||
.onAppear {
|
||||
do {
|
||||
// This check prevents the call of listUsers after the app is suspended, and the database is closed.
|
||||
if case .active = scenePhase {
|
||||
m.users = try listUsers()
|
||||
}
|
||||
m.users = try listUsers()
|
||||
} catch let error {
|
||||
logger.error("Error loading users \(responseError(error))")
|
||||
}
|
||||
|
||||
@@ -11,20 +11,11 @@ import UIKit
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
func uiKitContextMenu(hasImageOrVideo: Bool, maxWidth: CGFloat, itemWidth: Binding<CGFloat>, menu: Binding<UIMenu>, allowMenu: Binding<Bool>) -> some View {
|
||||
func uiKitContextMenu(maxWidth: CGFloat, menu: Binding<UIMenu>, allowMenu: Binding<Bool>) -> some View {
|
||||
Group {
|
||||
if allowMenu.wrappedValue {
|
||||
if hasImageOrVideo {
|
||||
InteractionView(content:
|
||||
self.environmentObject(ChatModel.shared)
|
||||
.overlay(DetermineWidthImageVideoItem())
|
||||
.onPreferenceChange(DetermineWidthImageVideoItem.Key.self) { itemWidth.wrappedValue = $0 == 0 ? maxWidth : $0 }
|
||||
, maxWidth: maxWidth, itemWidth: itemWidth, menu: menu)
|
||||
.frame(maxWidth: itemWidth.wrappedValue)
|
||||
} else {
|
||||
InteractionView(content: self.environmentObject(ChatModel.shared), maxWidth: maxWidth, itemWidth: itemWidth, menu: menu)
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
}
|
||||
InteractionView(content: self, maxWidth: maxWidth, menu: menu)
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
@@ -40,14 +31,13 @@ private class HostingViewHolder: UIView {
|
||||
struct InteractionView<Content: View>: UIViewRepresentable {
|
||||
let content: Content
|
||||
var maxWidth: CGFloat
|
||||
var itemWidth: Binding<CGFloat>
|
||||
@Binding var menu: UIMenu
|
||||
|
||||
func makeUIView(context: Context) -> UIView {
|
||||
let view = HostingViewHolder()
|
||||
view.contentSize = CGSizeMake(maxWidth, .infinity)
|
||||
view.backgroundColor = .clear
|
||||
let hostView = UIHostingController(rootView: content)
|
||||
view.contentSize = hostView.view.intrinsicContentSize
|
||||
hostView.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
let constraints = [
|
||||
hostView.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
@@ -67,11 +57,7 @@ struct InteractionView<Content: View>: UIViewRepresentable {
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIView, context: Context) {
|
||||
let was = (uiView as! HostingViewHolder).contentSize
|
||||
(uiView as! HostingViewHolder).contentSize = uiView.subviews[0].sizeThatFits(CGSizeMake(itemWidth.wrappedValue, .infinity))
|
||||
if was != (uiView as! HostingViewHolder).contentSize {
|
||||
uiView.invalidateIntrinsicContentSize()
|
||||
}
|
||||
(uiView as! HostingViewHolder).contentSize = uiView.subviews[0].sizeThatFits(CGSizeMake(maxWidth, .infinity))
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
|
||||
@@ -21,19 +21,6 @@ struct DetermineWidth: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct DetermineWidthImageVideoItem: View {
|
||||
typealias Key = MaximumWidthImageVideoPreferenceKey
|
||||
var body: some View {
|
||||
GeometryReader { proxy in
|
||||
Color.clear
|
||||
.preference(
|
||||
key: MaximumWidthImageVideoPreferenceKey.self,
|
||||
value: proxy.size.width
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MaximumWidthPreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGFloat = 0
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
@@ -41,13 +28,6 @@ struct MaximumWidthPreferenceKey: PreferenceKey {
|
||||
}
|
||||
}
|
||||
|
||||
struct MaximumWidthImageVideoPreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGFloat = 0
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
value = max(value, nextValue())
|
||||
}
|
||||
}
|
||||
|
||||
struct DetermineWidth_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DetermineWidth()
|
||||
|
||||
@@ -448,9 +448,6 @@ struct MigrateToDevice: View {
|
||||
case .rcvFileError:
|
||||
alert = .error(title: "Download failed", error: "File was deleted or link is invalid")
|
||||
migrationState = .downloadFailed(totalBytes: totalBytes, link: link, archivePath: archivePath)
|
||||
case .chatError(_, .error(.noRcvFileUser)):
|
||||
alert = .error(title: "Download failed", error: "File was deleted or link is invalid")
|
||||
migrationState = .downloadFailed(totalBytes: totalBytes, link: link, archivePath: archivePath)
|
||||
default:
|
||||
logger.debug("unsupported event: \(msg.responseType)")
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ struct AdvancedNetworkSettings: View {
|
||||
|
||||
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [7_500000, 10_000000, 15_000000, 20_000000, 30_000000, 45_000000], label: secondsLabel)
|
||||
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel)
|
||||
timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [2_500, 5_000, 10_000, 15_000, 20_000, 30_000], label: secondsLabel)
|
||||
timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [15_000, 30_000, 45_000, 60_000, 90_000, 120_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)
|
||||
|
||||
@@ -24,7 +24,6 @@ private enum NetworkAlert: Identifiable {
|
||||
}
|
||||
|
||||
struct NetworkAndServers: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@State private var cfgLoaded = false
|
||||
@State private var currentNetCfg = NetCfg.defaults
|
||||
@@ -83,14 +82,6 @@ struct NetworkAndServers: View {
|
||||
Text("WebRTC ICE servers")
|
||||
}
|
||||
}
|
||||
|
||||
Section("Network connection") {
|
||||
HStack {
|
||||
Text(m.networkInfo.networkType.text)
|
||||
Spacer()
|
||||
Image(systemName: "circle.fill").foregroundColor(m.networkInfo.online ? .green : .red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
||||
@@ -631,7 +631,6 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Admins can block a member for all." xml:space="preserve">
|
||||
<source>Admins can block a member for all.</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">
|
||||
@@ -691,7 +690,6 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." xml:space="preserve">
|
||||
<source>All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays.</source>
|
||||
<target>Всички ваши контакти, разговори и файлове ще бъдат сигурно криптирани и качени на парчета в конфигурираните XFTP релета.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow" xml:space="preserve">
|
||||
@@ -821,7 +819,6 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="App data migration" xml:space="preserve">
|
||||
<source>App data migration</source>
|
||||
<target>Миграция на данните от приложението</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve">
|
||||
@@ -861,17 +858,14 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Apply" xml:space="preserve">
|
||||
<source>Apply</source>
|
||||
<target>Приложи</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Archive and upload" xml:space="preserve">
|
||||
<source>Archive and upload</source>
|
||||
<target>Архивиране и качване</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Archiving database" xml:space="preserve">
|
||||
<source>Archiving database</source>
|
||||
<target>Архивиране на база данни</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Attach" xml:space="preserve">
|
||||
@@ -1066,7 +1060,6 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Cancel migration" xml:space="preserve">
|
||||
<source>Cancel migration</source>
|
||||
<target>Отмени миграцията</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
|
||||
@@ -1172,7 +1165,6 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat migrated!" xml:space="preserve">
|
||||
<source>Chat migrated!</source>
|
||||
<target>Чатът е мигриран!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat preferences" xml:space="preserve">
|
||||
@@ -1271,7 +1263,6 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Confirm network settings" xml:space="preserve">
|
||||
<source>Confirm network settings</source>
|
||||
<target>Потвърди мрежовите настройки</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Confirm new passphrase…" xml:space="preserve">
|
||||
@@ -1286,12 +1277,10 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Confirm that you remember database passphrase to migrate it." xml:space="preserve">
|
||||
<source>Confirm that you remember database passphrase to migrate it.</source>
|
||||
<target>Потвърдете, че помните паролата на базата данни, преди да я мигрирате.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Confirm upload" xml:space="preserve">
|
||||
<source>Confirm upload</source>
|
||||
<target>Потвърди качването</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect" xml:space="preserve">
|
||||
@@ -1555,7 +1544,6 @@ This is your own one-time link!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Creating archive link" xml:space="preserve">
|
||||
<source>Creating archive link</source>
|
||||
<target>Създаване на архивен линк</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Creating link…" xml:space="preserve">
|
||||
@@ -1780,7 +1768,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete database from this device" xml:space="preserve">
|
||||
<source>Delete database from this device</source>
|
||||
<target>Изтриване на базата данни от това устройство</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete file" xml:space="preserve">
|
||||
@@ -2075,7 +2062,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Download failed" xml:space="preserve">
|
||||
<source>Download failed</source>
|
||||
<target>Неуспешно изтегляне</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Download file" xml:space="preserve">
|
||||
@@ -2085,12 +2071,10 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Downloading archive" xml:space="preserve">
|
||||
<source>Downloading archive</source>
|
||||
<target>Архива се изтегля</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Downloading link details" xml:space="preserve">
|
||||
<source>Downloading link details</source>
|
||||
<target>Подробности за линка се изтеглят</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Duplicate display name!" xml:space="preserve">
|
||||
@@ -2150,7 +2134,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable in direct chats (BETA)!" xml:space="preserve">
|
||||
<source>Enable in direct chats (BETA)!</source>
|
||||
<target>Активиране в личните чатове (БЕТА)!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable instant notifications?" xml:space="preserve">
|
||||
@@ -2270,7 +2253,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter passphrase" xml:space="preserve">
|
||||
<source>Enter passphrase</source>
|
||||
<target>Въведи парола</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter passphrase…" xml:space="preserve">
|
||||
@@ -2335,7 +2317,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error allowing contact PQ encryption" xml:space="preserve">
|
||||
<source>Error allowing contact PQ encryption</source>
|
||||
<target>Грешка при разрешаване на PQ криптиране за контакт</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error changing address" xml:space="preserve">
|
||||
@@ -2430,7 +2411,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error downloading the archive" xml:space="preserve">
|
||||
<source>Error downloading the archive</source>
|
||||
<target>Грешка при изтеглянето на архива</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error enabling delivery receipts!" xml:space="preserve">
|
||||
@@ -2510,7 +2490,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error saving settings" xml:space="preserve">
|
||||
<source>Error saving settings</source>
|
||||
<target>Грешка при запазване на настройките</target>
|
||||
<note>when migrating</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error saving user password" xml:space="preserve">
|
||||
@@ -2585,12 +2564,10 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error uploading the archive" xml:space="preserve">
|
||||
<source>Error uploading the archive</source>
|
||||
<target>Грешка при качването на архива</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error verifying passphrase:" xml:space="preserve">
|
||||
<source>Error verifying passphrase:</source>
|
||||
<target>Грешка при проверката на паролата:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error: " xml:space="preserve">
|
||||
@@ -2645,7 +2622,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Exported file doesn't exist" xml:space="preserve">
|
||||
<source>Exported file doesn't exist</source>
|
||||
<target>Експортираният файл не съществува</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Exporting database archive…" xml:space="preserve">
|
||||
@@ -2720,12 +2696,10 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Finalize migration" xml:space="preserve">
|
||||
<source>Finalize migration</source>
|
||||
<target>Завърши миграцията</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Finalize migration on another device." xml:space="preserve">
|
||||
<source>Finalize migration on another device.</source>
|
||||
<target>Завършете миграцията на другото устройство.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Finally, we have them! 🚀" xml:space="preserve">
|
||||
@@ -3020,7 +2994,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hungarian interface" xml:space="preserve">
|
||||
<source>Hungarian interface</source>
|
||||
<target>Унгарски интерфейс</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ICE servers (one per line)" xml:space="preserve">
|
||||
@@ -3090,12 +3063,10 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Import failed" xml:space="preserve">
|
||||
<source>Import failed</source>
|
||||
<target>Неуспешно импортиране</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Importing archive" xml:space="preserve">
|
||||
<source>Importing archive</source>
|
||||
<target>Импортиране на архив</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
@@ -3115,7 +3086,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="In order to continue, chat should be stopped." xml:space="preserve">
|
||||
<source>In order to continue, chat should be stopped.</source>
|
||||
<target>За да продължите, чатът трябва да бъде спрян.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="In reply to" xml:space="preserve">
|
||||
@@ -3232,7 +3202,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
|
||||
<source>Invalid migration confirmation</source>
|
||||
<target>Невалидно потвърждение за мигриране</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid name!" xml:space="preserve">
|
||||
@@ -3605,7 +3574,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message too large" xml:space="preserve">
|
||||
<source>Message too large</source>
|
||||
<target>Съобщението е твърде голямо</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages" xml:space="preserve">
|
||||
@@ -3633,32 +3601,26 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate device" xml:space="preserve">
|
||||
<source>Migrate device</source>
|
||||
<target>Мигрирай устройството</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate from another device" xml:space="preserve">
|
||||
<source>Migrate from another device</source>
|
||||
<target>Мигриране от друго устройство</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate here" xml:space="preserve">
|
||||
<source>Migrate here</source>
|
||||
<target>Мигрирай тук</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate to another device" xml:space="preserve">
|
||||
<source>Migrate to another device</source>
|
||||
<target>Миграция към друго устройство</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate to another device via QR code." xml:space="preserve">
|
||||
<source>Migrate to another device via QR code.</source>
|
||||
<target>Мигрирайте към друго устройство чрез QR код.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating" xml:space="preserve">
|
||||
<source>Migrating</source>
|
||||
<target>Мигриране</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating database archive…" xml:space="preserve">
|
||||
@@ -3668,7 +3630,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migration complete" xml:space="preserve">
|
||||
<source>Migration complete</source>
|
||||
<target>Миграцията е завършена</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migration error:" xml:space="preserve">
|
||||
@@ -4032,7 +3993,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open migration to another device" xml:space="preserve">
|
||||
<source>Open migration to another device</source>
|
||||
<target>Отвори миграцията към друго устройство</target>
|
||||
<note>authentication reason</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open user profiles" xml:space="preserve">
|
||||
@@ -4052,7 +4012,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or paste archive link" xml:space="preserve">
|
||||
<source>Or paste archive link</source>
|
||||
<target>Или постави архивен линк</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or scan QR code" xml:space="preserve">
|
||||
@@ -4062,7 +4021,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or securely share this file link" xml:space="preserve">
|
||||
<source>Or securely share this file link</source>
|
||||
<target>Или сигурно споделете този линк към файла</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or show this code" xml:space="preserve">
|
||||
@@ -4152,7 +4110,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Picture-in-picture calls" xml:space="preserve">
|
||||
<source>Picture-in-picture calls</source>
|
||||
<target>Обаждания "картина в картина"</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please ask your contact to enable sending voice messages." xml:space="preserve">
|
||||
@@ -4177,7 +4134,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please confirm that network settings are correct for this device." xml:space="preserve">
|
||||
<source>Please confirm that network settings are correct for this device.</source>
|
||||
<target>Моля, потвърдете, че мрежовите настройки са правилни за това устройство.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact developers. Error: %@" xml:space="preserve">
|
||||
@@ -4239,7 +4195,6 @@ Error: %@</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Post-quantum E2EE" xml:space="preserve">
|
||||
<source>Post-quantum E2EE</source>
|
||||
<target>Постквантово E2EE</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Preserve the last message draft, with attachments." xml:space="preserve">
|
||||
@@ -4379,12 +4334,10 @@ Error: %@</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Push server" xml:space="preserve">
|
||||
<source>Push server</source>
|
||||
<target>Push сървър</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Quantum resistant encryption" xml:space="preserve">
|
||||
<source>Quantum resistant encryption</source>
|
||||
<target>Квантово устойчиво криптиране</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Rate the app" xml:space="preserve">
|
||||
@@ -4574,12 +4527,10 @@ Error: %@</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat download" xml:space="preserve">
|
||||
<source>Repeat download</source>
|
||||
<target>Повтори изтеглянето</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat import" xml:space="preserve">
|
||||
<source>Repeat import</source>
|
||||
<target>Повтори импортирането</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat join request?" xml:space="preserve">
|
||||
@@ -4589,7 +4540,6 @@ Error: %@</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat upload" xml:space="preserve">
|
||||
<source>Repeat upload</source>
|
||||
<target>Повтори качването</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
@@ -4694,7 +4644,6 @@ Error: %@</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safer groups" xml:space="preserve">
|
||||
<source>Safer groups</source>
|
||||
<target>По-безопасни групи</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save" xml:space="preserve">
|
||||
@@ -5064,7 +5013,6 @@ Error: %@</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set passphrase" xml:space="preserve">
|
||||
<source>Set passphrase</source>
|
||||
<target>Задаване на парола</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set passphrase to export" xml:space="preserve">
|
||||
@@ -5124,7 +5072,6 @@ Error: %@</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show QR code" xml:space="preserve">
|
||||
<source>Show QR code</source>
|
||||
<target>Покажи QR код</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show calls in phone history" xml:space="preserve">
|
||||
@@ -5269,7 +5216,6 @@ Error: %@</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Stop chat" xml:space="preserve">
|
||||
<source>Stop chat</source>
|
||||
<target>Спри чата</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Stop chat to enable database actions" xml:space="preserve">
|
||||
@@ -5314,7 +5260,6 @@ Error: %@</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Stopping chat" xml:space="preserve">
|
||||
<source>Stopping chat</source>
|
||||
<target>Спиране на чата</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Submit" xml:space="preserve">
|
||||
@@ -5566,12 +5511,10 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
</trans-unit>
|
||||
<trans-unit id="This chat is protected by end-to-end encryption." xml:space="preserve">
|
||||
<source>This chat is protected by end-to-end encryption.</source>
|
||||
<target>Този чат е защитен чрез криптиране от край до край.</target>
|
||||
<note>E2EE info chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This chat is protected by quantum resistant end-to-end encryption." xml:space="preserve">
|
||||
<source>This chat is protected by quantum resistant end-to-end encryption.</source>
|
||||
<target>Този чат е защитен от квантово устойчиво криптиране от край до край.</target>
|
||||
<note>E2EE info chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This device name" xml:space="preserve">
|
||||
@@ -5870,7 +5813,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
</trans-unit>
|
||||
<trans-unit id="Upload failed" xml:space="preserve">
|
||||
<source>Upload failed</source>
|
||||
<target>Неуспешно качване</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Upload file" xml:space="preserve">
|
||||
@@ -5880,7 +5822,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
</trans-unit>
|
||||
<trans-unit id="Uploading archive" xml:space="preserve">
|
||||
<source>Uploading archive</source>
|
||||
<target>Архивът се качва</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use .onion hosts" xml:space="preserve">
|
||||
@@ -5935,7 +5876,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
</trans-unit>
|
||||
<trans-unit id="Use the app while in the call." xml:space="preserve">
|
||||
<source>Use the app while in the call.</source>
|
||||
<target>Използвайте приложението по време на разговора.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="User profile" xml:space="preserve">
|
||||
@@ -5975,12 +5915,10 @@ To connect, please ask your contact to create another connection link and check
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify database passphrase" xml:space="preserve">
|
||||
<source>Verify database passphrase</source>
|
||||
<target>Проверете паролата на базата данни</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify passphrase" xml:space="preserve">
|
||||
<source>Verify passphrase</source>
|
||||
<target>Провери паролата</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify security code" xml:space="preserve">
|
||||
@@ -6075,7 +6013,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
</trans-unit>
|
||||
<trans-unit id="Warning: starting chat on multiple devices is not supported and will cause message delivery failures" xml:space="preserve">
|
||||
<source>Warning: starting chat on multiple devices is not supported and will cause message delivery failures</source>
|
||||
<target>Внимание: стартирането на чата на множество устройства не се поддържа и ще доведе до неуспешно изпращане на съобщения</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Warning: you may lose some data!" xml:space="preserve">
|
||||
@@ -6100,7 +6037,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
</trans-unit>
|
||||
<trans-unit id="Welcome message is too long" xml:space="preserve">
|
||||
<source>Welcome message is too long</source>
|
||||
<target>Съобщението при посрещане е твърде дълго</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="What's new" xml:space="preserve">
|
||||
@@ -6251,7 +6187,6 @@ Repeat join request?</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can give another try." xml:space="preserve">
|
||||
<source>You can give another try.</source>
|
||||
<target>Можете да опитате още веднъж.</target>
|
||||
<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">
|
||||
@@ -7159,7 +7094,6 @@ SimpleX сървърите не могат да видят вашия профи
|
||||
</trans-unit>
|
||||
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
|
||||
<source>quantum resistant e2e encryption</source>
|
||||
<target>квантово устойчиво e2e криптиране</target>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="received answer…" xml:space="preserve">
|
||||
@@ -7239,7 +7173,6 @@ SimpleX сървърите не могат да видят вашия профи
|
||||
</trans-unit>
|
||||
<trans-unit id="standard end-to-end encryption" xml:space="preserve">
|
||||
<source>standard end-to-end encryption</source>
|
||||
<target>стандартно криптиране от край до край</target>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="starting…" xml:space="preserve">
|
||||
|
||||
@@ -635,7 +635,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Admins can block a member for all." xml:space="preserve">
|
||||
<source>Admins can block a member for all.</source>
|
||||
<target>Administratoren können ein Gruppenmitglied für Alle blockieren.</target>
|
||||
<target>Administratoren können für ein Mitglied alle Funktionen blockieren.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Admins can create the links to join groups." xml:space="preserve">
|
||||
@@ -1291,7 +1291,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Confirm that you remember database passphrase to migrate it." xml:space="preserve">
|
||||
<source>Confirm that you remember database passphrase to migrate it.</source>
|
||||
<target>Bitte bestätigen Sie für die Migration, dass Sie sich an Ihr Datenbank-Passwort erinnern.</target>
|
||||
<target>Für die Migration bestätigen Sie bitte, dass Sie sich an das Datenbank-Passwort erinnern.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Confirm upload" xml:space="preserve">
|
||||
@@ -3660,7 +3660,7 @@ Das ist Ihr Link für die Gruppe %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate to another device via QR code." xml:space="preserve">
|
||||
<source>Migrate to another device via QR code.</source>
|
||||
<target>Daten können über einen QR-Code auf ein anderes Gerät migriert werden.</target>
|
||||
<target>Über einen QR-Code auf ein anderes Gerät migrieren.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating" xml:space="preserve">
|
||||
@@ -5618,7 +5618,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro
|
||||
</trans-unit>
|
||||
<trans-unit id="To ask any questions and to receive updates:" xml:space="preserve">
|
||||
<source>To ask any questions and to receive updates:</source>
|
||||
<target>Um Fragen zu stellen und aktuelle Informationen zu erhalten:</target>
|
||||
<target>Um Fragen zu stellen und Aktualisierungen zu erhalten:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To connect, your contact can scan QR code or use the link in the app." xml:space="preserve">
|
||||
@@ -6508,7 +6508,7 @@ Sie können diese Verbindung abbrechen und den Kontakt entfernen (und es später
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts will remain connected." xml:space="preserve">
|
||||
<source>Your contacts will remain connected.</source>
|
||||
<target>Ihre Kontakte bleiben weiterhin verbunden.</target>
|
||||
<target>Ihre Kontakte bleiben verbunden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your current chat database will be DELETED and REPLACED with the imported one." xml:space="preserve">
|
||||
|
||||
@@ -134,7 +134,6 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ uploaded" xml:space="preserve">
|
||||
<source>%@ uploaded</source>
|
||||
<target>%@ cargado</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ wants to connect!" xml:space="preserve">
|
||||
@@ -1296,7 +1295,6 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Confirm upload" xml:space="preserve">
|
||||
<source>Confirm upload</source>
|
||||
<target>Confirmar subida</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect" xml:space="preserve">
|
||||
@@ -1560,7 +1558,6 @@ This is your own one-time link!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Creating archive link" xml:space="preserve">
|
||||
<source>Creating archive link</source>
|
||||
<target>Creando enlace de archivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Creating link…" xml:space="preserve">
|
||||
@@ -1785,7 +1782,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete database from this device" xml:space="preserve">
|
||||
<source>Delete database from this device</source>
|
||||
<target>Eliminar base de datos de este dispositivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete file" xml:space="preserve">
|
||||
@@ -2080,7 +2076,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Download failed" xml:space="preserve">
|
||||
<source>Download failed</source>
|
||||
<target>Error en la descarga</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Download file" xml:space="preserve">
|
||||
@@ -2090,12 +2085,10 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Downloading archive" xml:space="preserve">
|
||||
<source>Downloading archive</source>
|
||||
<target>Descargando archivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Downloading link details" xml:space="preserve">
|
||||
<source>Downloading link details</source>
|
||||
<target>Descargando detalles del enlace</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Duplicate display name!" xml:space="preserve">
|
||||
@@ -2155,7 +2148,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable in direct chats (BETA)!" xml:space="preserve">
|
||||
<source>Enable in direct chats (BETA)!</source>
|
||||
<target>Activar en chats directos (BETA)!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable instant notifications?" xml:space="preserve">
|
||||
@@ -2275,7 +2267,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter passphrase" xml:space="preserve">
|
||||
<source>Enter passphrase</source>
|
||||
<target>Introducir frase de contraseña</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter passphrase…" xml:space="preserve">
|
||||
@@ -2340,7 +2331,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error allowing contact PQ encryption" xml:space="preserve">
|
||||
<source>Error allowing contact PQ encryption</source>
|
||||
<target>Error al permitir cifrado PQ al contacto</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error changing address" xml:space="preserve">
|
||||
@@ -2435,7 +2425,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error downloading the archive" xml:space="preserve">
|
||||
<source>Error downloading the archive</source>
|
||||
<target>Error al descargar el archivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error enabling delivery receipts!" xml:space="preserve">
|
||||
@@ -2515,7 +2504,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error saving settings" xml:space="preserve">
|
||||
<source>Error saving settings</source>
|
||||
<target>Error al guardar ajustes</target>
|
||||
<note>when migrating</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error saving user password" xml:space="preserve">
|
||||
@@ -2590,12 +2578,10 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error uploading the archive" xml:space="preserve">
|
||||
<source>Error uploading the archive</source>
|
||||
<target>Error al subir el archivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error verifying passphrase:" xml:space="preserve">
|
||||
<source>Error verifying passphrase:</source>
|
||||
<target>Error al verificar la frase de contraseña:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error: " xml:space="preserve">
|
||||
@@ -2650,7 +2636,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Exported file doesn't exist" xml:space="preserve">
|
||||
<source>Exported file doesn't exist</source>
|
||||
<target>El archivo exportado no existe</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Exporting database archive…" xml:space="preserve">
|
||||
@@ -2725,12 +2710,10 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Finalize migration" xml:space="preserve">
|
||||
<source>Finalize migration</source>
|
||||
<target>Finalizar la migración</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Finalize migration on another device." xml:space="preserve">
|
||||
<source>Finalize migration on another device.</source>
|
||||
<target>Finalizar la migración en otro dispositivo.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Finally, we have them! 🚀" xml:space="preserve">
|
||||
@@ -3025,7 +3008,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hungarian interface" xml:space="preserve">
|
||||
<source>Hungarian interface</source>
|
||||
<target>Interfaz húngara</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ICE servers (one per line)" xml:space="preserve">
|
||||
@@ -3095,12 +3077,10 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Import failed" xml:space="preserve">
|
||||
<source>Import failed</source>
|
||||
<target>Error de importación</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Importing archive" xml:space="preserve">
|
||||
<source>Importing archive</source>
|
||||
<target>Importando archivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
@@ -3120,7 +3100,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="In order to continue, chat should be stopped." xml:space="preserve">
|
||||
<source>In order to continue, chat should be stopped.</source>
|
||||
<target>Para continuar, el chat debe ser interrumpido.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="In reply to" xml:space="preserve">
|
||||
@@ -3237,7 +3216,6 @@ This cannot be undone!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
|
||||
<source>Invalid migration confirmation</source>
|
||||
<target>Confirmación de migración inválida</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid name!" xml:space="preserve">
|
||||
@@ -3610,7 +3588,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message too large" xml:space="preserve">
|
||||
<source>Message too large</source>
|
||||
<target>Mensaje demasiado grande</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages" xml:space="preserve">
|
||||
@@ -3630,42 +3607,34 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
|
||||
<source>Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
|
||||
<target>Los mensajes, archivos y llamadas están protegidos por **cifrado de extremo a extremo** con perfecta confidencialidad, repudio y recuperación tras ataques.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
|
||||
<source>Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
|
||||
<target>Los mensajes, archivos y llamadas están protegidos por **cifrado de extremo a extremo resistente a la computación cuántica** con perfecta confidencialidad, repudio y recuperación tras ataques.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate device" xml:space="preserve">
|
||||
<source>Migrate device</source>
|
||||
<target>Migrar dispositivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate from another device" xml:space="preserve">
|
||||
<source>Migrate from another device</source>
|
||||
<target>Migrar desde otro dispositivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate here" xml:space="preserve">
|
||||
<source>Migrate here</source>
|
||||
<target>Migrar aquí</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate to another device" xml:space="preserve">
|
||||
<source>Migrate to another device</source>
|
||||
<target>Migrar hacia otro dispositivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate to another device via QR code." xml:space="preserve">
|
||||
<source>Migrate to another device via QR code.</source>
|
||||
<target>Migrar hacia otro dispositivo mediante código QR.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating" xml:space="preserve">
|
||||
<source>Migrating</source>
|
||||
<target>Migrando</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating database archive…" xml:space="preserve">
|
||||
@@ -3675,7 +3644,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migration complete" xml:space="preserve">
|
||||
<source>Migration complete</source>
|
||||
<target>Migración completada</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migration error:" xml:space="preserve">
|
||||
@@ -4039,7 +4007,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open migration to another device" xml:space="preserve">
|
||||
<source>Open migration to another device</source>
|
||||
<target>Abrir la migración hacia otro dispositivo</target>
|
||||
<note>authentication reason</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open user profiles" xml:space="preserve">
|
||||
@@ -4059,7 +4026,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or paste archive link" xml:space="preserve">
|
||||
<source>Or paste archive link</source>
|
||||
<target>O pegar enlace del archivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or scan QR code" xml:space="preserve">
|
||||
@@ -4069,7 +4035,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or securely share this file link" xml:space="preserve">
|
||||
<source>Or securely share this file link</source>
|
||||
<target>O comparta de forma segura el enlace de este archivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or show this code" xml:space="preserve">
|
||||
@@ -4159,7 +4124,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Picture-in-picture calls" xml:space="preserve">
|
||||
<source>Picture-in-picture calls</source>
|
||||
<target>Llamadas picture-in-picture</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please ask your contact to enable sending voice messages." xml:space="preserve">
|
||||
@@ -4184,7 +4148,6 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please confirm that network settings are correct for this device." xml:space="preserve">
|
||||
<source>Please confirm that network settings are correct for this device.</source>
|
||||
<target>Por favor confirme que la configuración de red es correcta para este dispositivo.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact developers. Error: %@" xml:space="preserve">
|
||||
@@ -4246,7 +4209,6 @@ Error: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Post-quantum E2EE" xml:space="preserve">
|
||||
<source>Post-quantum E2EE</source>
|
||||
<target>E2EE postcuántica</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Preserve the last message draft, with attachments." xml:space="preserve">
|
||||
@@ -4386,12 +4348,10 @@ Error: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Push server" xml:space="preserve">
|
||||
<source>Push server</source>
|
||||
<target>Servidor push</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Quantum resistant encryption" xml:space="preserve">
|
||||
<source>Quantum resistant encryption</source>
|
||||
<target>Cifrado resistente a la tecnología cuántica</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Rate the app" xml:space="preserve">
|
||||
@@ -4581,12 +4541,10 @@ Error: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat download" xml:space="preserve">
|
||||
<source>Repeat download</source>
|
||||
<target>Repetir descarga</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat import" xml:space="preserve">
|
||||
<source>Repeat import</source>
|
||||
<target>Repetir importación</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat join request?" xml:space="preserve">
|
||||
@@ -4596,7 +4554,6 @@ Error: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat upload" xml:space="preserve">
|
||||
<source>Repeat upload</source>
|
||||
<target>Repetir la carga</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
@@ -4701,7 +4658,6 @@ Error: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safer groups" xml:space="preserve">
|
||||
<source>Safer groups</source>
|
||||
<target>Grupos más seguros</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save" xml:space="preserve">
|
||||
@@ -5071,7 +5027,6 @@ Error: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set passphrase" xml:space="preserve">
|
||||
<source>Set passphrase</source>
|
||||
<target>Definir frase de contraseña</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set passphrase to export" xml:space="preserve">
|
||||
@@ -5131,7 +5086,6 @@ Error: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show QR code" xml:space="preserve">
|
||||
<source>Show QR code</source>
|
||||
<target>Mostrar código QR</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show calls in phone history" xml:space="preserve">
|
||||
@@ -5276,7 +5230,6 @@ Error: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Stop chat" xml:space="preserve">
|
||||
<source>Stop chat</source>
|
||||
<target>Detener el chat</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Stop chat to enable database actions" xml:space="preserve">
|
||||
@@ -5321,7 +5274,6 @@ Error: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Stopping chat" xml:space="preserve">
|
||||
<source>Stopping chat</source>
|
||||
<target>Detención del chat</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Submit" xml:space="preserve">
|
||||
@@ -5573,12 +5525,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="This chat is protected by end-to-end encryption." xml:space="preserve">
|
||||
<source>This chat is protected by end-to-end encryption.</source>
|
||||
<target>Este chat está protegido por cifrado de extremo a extremo.</target>
|
||||
<note>E2EE info chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This chat is protected by quantum resistant end-to-end encryption." xml:space="preserve">
|
||||
<source>This chat is protected by quantum resistant end-to-end encryption.</source>
|
||||
<target>Este chat está protegido por un cifrado de extremo a extremo resistente a tecnologías cuánticas.</target>
|
||||
<note>E2EE info chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This device name" xml:space="preserve">
|
||||
@@ -5878,7 +5828,6 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
|
||||
</trans-unit>
|
||||
<trans-unit id="Upload failed" xml:space="preserve">
|
||||
<source>Upload failed</source>
|
||||
<target>Error de carga</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Upload file" xml:space="preserve">
|
||||
@@ -5888,7 +5837,6 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
|
||||
</trans-unit>
|
||||
<trans-unit id="Uploading archive" xml:space="preserve">
|
||||
<source>Uploading archive</source>
|
||||
<target>Subiendo el archivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use .onion hosts" xml:space="preserve">
|
||||
@@ -5943,7 +5891,6 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
|
||||
</trans-unit>
|
||||
<trans-unit id="Use the app while in the call." xml:space="preserve">
|
||||
<source>Use the app while in the call.</source>
|
||||
<target>Usar la app durante la llamada.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="User profile" xml:space="preserve">
|
||||
@@ -5983,12 +5930,10 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify database passphrase" xml:space="preserve">
|
||||
<source>Verify database passphrase</source>
|
||||
<target>Verificar la contraseña de la base de datos</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify passphrase" xml:space="preserve">
|
||||
<source>Verify passphrase</source>
|
||||
<target>Verificar frase de contraseña</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify security code" xml:space="preserve">
|
||||
@@ -6083,7 +6028,6 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
|
||||
</trans-unit>
|
||||
<trans-unit id="Warning: starting chat on multiple devices is not supported and will cause message delivery failures" xml:space="preserve">
|
||||
<source>Warning: starting chat on multiple devices is not supported and will cause message delivery failures</source>
|
||||
<target>Advertencia: el inicio del chat en varios dispositivos no es compatible y provocará fallos en la entrega de mensajes</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Warning: you may lose some data!" xml:space="preserve">
|
||||
@@ -6108,7 +6052,6 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
|
||||
</trans-unit>
|
||||
<trans-unit id="Welcome message is too long" xml:space="preserve">
|
||||
<source>Welcome message is too long</source>
|
||||
<target>El mensaje de bienvenida es demasiado largo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="What's new" xml:space="preserve">
|
||||
@@ -6168,7 +6111,6 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb
|
||||
</trans-unit>
|
||||
<trans-unit id="You **must not** use the same database on two devices." xml:space="preserve">
|
||||
<source>You **must not** use the same database on two devices.</source>
|
||||
<target>**No debe** usar la misma base de datos en dos dispositivos.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You accepted connection" xml:space="preserve">
|
||||
@@ -6260,7 +6202,6 @@ Repeat join request?</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can give another try." xml:space="preserve">
|
||||
<source>You can give another try.</source>
|
||||
<target>Puede intentarlo de nuevo.</target>
|
||||
<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">
|
||||
@@ -7168,7 +7109,6 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
|
||||
<source>quantum resistant e2e encryption</source>
|
||||
<target>cifrado e2e resistente a la cuántica</target>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="received answer…" xml:space="preserve">
|
||||
@@ -7248,7 +7188,6 @@ Los servidores de SimpleX no pueden ver tu perfil.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="standard end-to-end encryption" xml:space="preserve">
|
||||
<source>standard end-to-end encryption</source>
|
||||
<target>cifrado estándar de extremo a extremo</target>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="starting…" xml:space="preserve">
|
||||
|
||||
@@ -2155,7 +2155,7 @@ Cette opération ne peut être annulée !</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable in direct chats (BETA)!" xml:space="preserve">
|
||||
<source>Enable in direct chats (BETA)!</source>
|
||||
<target>Activer dans les conversations directes (BETA) !</target>
|
||||
<target>Activé dans les conversations directes (BETA) !</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable instant notifications?" xml:space="preserve">
|
||||
@@ -3635,7 +3635,7 @@ Voici votre lien pour le groupe %@ !</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
|
||||
<source>Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
|
||||
<target>Les messages, fichiers et appels sont protégés par un chiffrement **e2e résistant post-quantique** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction.</target>
|
||||
<target>Les messages, fichiers et appels sont protégés par un chiffrement **2e2 résistant post-quantique** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate device" xml:space="preserve">
|
||||
@@ -5578,7 +5578,7 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise.
|
||||
</trans-unit>
|
||||
<trans-unit id="This chat is protected by quantum resistant end-to-end encryption." xml:space="preserve">
|
||||
<source>This chat is protected by quantum resistant end-to-end encryption.</source>
|
||||
<target>Cette discussion est protégée par un chiffrement de bout en bout résistant aux technologies quantiques.</target>
|
||||
<target>Cette discussion est protégée par un chiffrement de bout en bout résistant post-quantique.</target>
|
||||
<note>E2EE info chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This device name" xml:space="preserve">
|
||||
@@ -6082,7 +6082,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
|
||||
</trans-unit>
|
||||
<trans-unit id="Warning: starting chat on multiple devices is not supported and will cause message delivery failures" xml:space="preserve">
|
||||
<source>Warning: starting chat on multiple devices is not supported and will cause message delivery failures</source>
|
||||
<target>Attention: démarrer une session de chat sur plusieurs appareils n'est pas pris en charge et entraînera des dysfonctionnements au niveau de la transmission des messages</target>
|
||||
<target>Attention : démarrer une session de chat sur plusieurs appareils n'est pas pris en charge et entraînera des dysfonctionnements au niveau de la transmission des messages</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Warning: you may lose some data!" xml:space="preserve">
|
||||
|
||||
@@ -5311,11 +5311,6 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target state="translated">ללא היסטוריה</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@" xml:space="preserve" approved="no">
|
||||
<source>%@ and %@</source>
|
||||
<target state="translated">%@ ו-%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="he" datatype="plaintext">
|
||||
|
||||
@@ -3635,7 +3635,7 @@ Questo è il tuo link per il gruppo %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
|
||||
<source>Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
|
||||
<target>I messaggi, i file e le chiamate sono protetti da **crittografia e2e resistente alla quantistica** con perfect forward secrecy, ripudio e recupero da intrusione.</target>
|
||||
<target>I messaggi, i file e le chiamate sono protetti da **crittografia e2e resistente al quantistico** con perfect forward secrecy, ripudio e recupero da intrusione.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate device" xml:space="preserve">
|
||||
@@ -4391,7 +4391,7 @@ Errore: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Quantum resistant encryption" xml:space="preserve">
|
||||
<source>Quantum resistant encryption</source>
|
||||
<target>Crittografia resistente alla quantistica</target>
|
||||
<target>Crittografia resistente al quantistico</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Rate the app" xml:space="preserve">
|
||||
@@ -5578,7 +5578,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.</ta
|
||||
</trans-unit>
|
||||
<trans-unit id="This chat is protected by quantum resistant end-to-end encryption." xml:space="preserve">
|
||||
<source>This chat is protected by quantum resistant end-to-end encryption.</source>
|
||||
<target>Questa chat è protetta da crittografia end-to-end resistente alla quantistica.</target>
|
||||
<target>Questa chat è protetta da crittografia end-to-end resistente al quantistico.</target>
|
||||
<note>E2EE info chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This device name" xml:space="preserve">
|
||||
@@ -6002,7 +6002,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
|
||||
</trans-unit>
|
||||
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
|
||||
<source>Via secure quantum resistant protocol.</source>
|
||||
<target>Tramite protocollo sicuro resistente alla quantistica.</target>
|
||||
<target>Tramite protocollo sicuro resistente al quantistico.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Video call" xml:space="preserve">
|
||||
@@ -7167,7 +7167,7 @@ I server di SimpleX non possono vedere il tuo profilo.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
|
||||
<source>quantum resistant e2e encryption</source>
|
||||
<target>crittografia e2e resistente alla quantistica</target>
|
||||
<target>crittografia e2e resistente al quantistico</target>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="received answer…" xml:space="preserve">
|
||||
|
||||
@@ -4024,7 +4024,7 @@ Dit is jouw link voor groep %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open chat" xml:space="preserve">
|
||||
<source>Open chat</source>
|
||||
<target>Chat openen</target>
|
||||
<target>Gesprekken openen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open chat console" xml:space="preserve">
|
||||
|
||||
@@ -15,39 +15,32 @@
|
||||
Available in v5.1</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" " xml:space="preserve" approved="no">
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target state="translated"> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" " xml:space="preserve" approved="no">
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target state="translated"> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" " xml:space="preserve" approved="no">
|
||||
<trans-unit id=" " xml:space="preserve">
|
||||
<source> </source>
|
||||
<target state="translated"> </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" (" xml:space="preserve" approved="no">
|
||||
<trans-unit id=" (" xml:space="preserve">
|
||||
<source> (</source>
|
||||
<target state="translated"> (</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" (can be copied)" xml:space="preserve" approved="no">
|
||||
<trans-unit id=" (can be copied)" xml:space="preserve">
|
||||
<source> (can be copied)</source>
|
||||
<target state="translated"> .(pode ser copiado)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="!1 colored!" xml:space="preserve" approved="no">
|
||||
<trans-unit id="!1 colored!" xml:space="preserve">
|
||||
<source>!1 colored!</source>
|
||||
<target state="translated">!1 colorido!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="#secret#" xml:space="preserve" approved="no">
|
||||
<trans-unit id="#secret#" xml:space="preserve">
|
||||
<source>#secret#</source>
|
||||
<target state="translated">#secreto#</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
@@ -62,59 +55,48 @@ Available in v5.1</source>
|
||||
<source>%@ / %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is connected!" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%@ is connected!" xml:space="preserve">
|
||||
<source>%@ is connected!</source>
|
||||
<target state="translated">%@ está conectado!</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is not verified" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%@ is not verified" xml:space="preserve">
|
||||
<source>%@ is not verified</source>
|
||||
<target state="translated">%@ não foi verificado</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is verified" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%@ is verified" xml:space="preserve">
|
||||
<source>%@ is verified</source>
|
||||
<target state="translated">%@ foi verificado</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ servers" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%@ servers" xml:space="preserve">
|
||||
<source>%@ servers</source>
|
||||
<target state="translated">%@ servidores</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ wants to connect!" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%@ wants to connect!" xml:space="preserve">
|
||||
<source>%@ wants to connect!</source>
|
||||
<target state="translated">%@ quer se conectar!</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d days" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%d days" xml:space="preserve">
|
||||
<source>%d days</source>
|
||||
<target state="translated">%d dias</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d hours" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%d hours" xml:space="preserve">
|
||||
<source>%d hours</source>
|
||||
<target state="translated">%d horas</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d min" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%d min" xml:space="preserve">
|
||||
<source>%d min</source>
|
||||
<target state="translated">%d minuto</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d months" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%d months" xml:space="preserve">
|
||||
<source>%d months</source>
|
||||
<target state="translated">%d meses</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d sec" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%d sec" xml:space="preserve">
|
||||
<source>%d sec</source>
|
||||
<target state="translated">%d segundo</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d skipped message(s)" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%d skipped message(s)" xml:space="preserve">
|
||||
<source>%d skipped message(s)</source>
|
||||
<target state="translated">%d mensagem(s) ignorada(s)</target>
|
||||
<note>integrity error chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld" xml:space="preserve">
|
||||
@@ -125,34 +107,28 @@ Available in v5.1</source>
|
||||
<source>%lld %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld contact(s) selected" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%lld contact(s) selected" xml:space="preserve">
|
||||
<source>%lld contact(s) selected</source>
|
||||
<target state="translated">%lld contato(s) selecionado(s)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld file(s) with total size of %@" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%lld file(s) with total size of %@" xml:space="preserve">
|
||||
<source>%lld file(s) with total size of %@</source>
|
||||
<target state="translated">%lld arquivo(s) com tamanho total de %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld members" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%lld members" xml:space="preserve">
|
||||
<source>%lld members</source>
|
||||
<target state="translated">%lld membros</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld minutes" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%lld minutes" xml:space="preserve">
|
||||
<source>%lld minutes</source>
|
||||
<target state="translated">%lld minutos</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld second(s)" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%lld second(s)" xml:space="preserve">
|
||||
<source>%lld second(s)</source>
|
||||
<target state="translated">%lld segundo(s)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld seconds" xml:space="preserve" approved="no">
|
||||
<trans-unit id="%lld seconds" xml:space="preserve">
|
||||
<source>%lld seconds</source>
|
||||
<target state="translated">%lld segundos</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldd" xml:space="preserve">
|
||||
@@ -183,14 +159,12 @@ Available in v5.1</source>
|
||||
<source>%lldw</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%u messages failed to decrypt." xml:space="preserve" approved="no">
|
||||
<trans-unit id="%u messages failed to decrypt." xml:space="preserve">
|
||||
<source>%u messages failed to decrypt.</source>
|
||||
<target state="translated">%u mensagens não foram descriptografadas.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%u messages skipped." xml:space="preserve" approved="no">
|
||||
<trans-unit id="%u messages skipped." xml:space="preserve">
|
||||
<source>%u messages skipped.</source>
|
||||
<target state="translated">%u mensagens ignoradas.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(" xml:space="preserve">
|
||||
@@ -201,56 +175,48 @@ Available in v5.1</source>
|
||||
<source>)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve" approved="no">
|
||||
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve">
|
||||
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
|
||||
<target state="translated">**Adicionar novo contato**: para criar seu QR Code único ou link para seu contato.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve">
|
||||
<source>**Create link / QR code** for your contact to use.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve" approved="no">
|
||||
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve">
|
||||
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
|
||||
<target state="translated">**Mais privado**: verifique novas mensagens a cada 20 minutos. O token do dispositivo é compartilhado com o servidor SimpleX Chat, mas não com quantos contatos ou mensagens você possui.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve" approved="no">
|
||||
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve">
|
||||
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
|
||||
<target state="translated">**Totalmente privado**: não use o servidor de notificações do SimpleX Chat, verifique as mensagens periodicamente em segundo plano (depende da frequência com que você usa o aplicativo).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Paste received link** or open it in the browser and tap **Open in mobile app**." xml:space="preserve">
|
||||
<source>**Paste received link** or open it in the browser and tap **Open in mobile app**.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Please note**: you will NOT be able to recover or change passphrase if you lose it." xml:space="preserve" approved="no">
|
||||
<trans-unit id="**Please note**: you will NOT be able to recover or change passphrase if you lose it." xml:space="preserve">
|
||||
<source>**Please note**: you will NOT be able to recover or change passphrase if you lose it.</source>
|
||||
<target state="translated">**Atenção**: Você NÃO poderá recuperar ou alterar a senha caso a perca.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve" approved="no">
|
||||
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve">
|
||||
<source>**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.</source>
|
||||
<target state="translated">**Recomendado**: O token do dispositivo e as notificações são enviados ao servidor de notificação do SimpleX Chat, mas não o conteúdo, o tamanho da mensagem ou de quem ela é.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Scan QR code**: to connect to your contact in person or via video call." xml:space="preserve">
|
||||
<source>**Scan QR code**: to connect to your contact in person or via video call.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Warning**: Instant push notifications require passphrase saved in Keychain." xml:space="preserve" approved="no">
|
||||
<trans-unit id="**Warning**: Instant push notifications require passphrase saved in Keychain." xml:space="preserve">
|
||||
<source>**Warning**: Instant push notifications require passphrase saved in Keychain.</source>
|
||||
<target state="translated">**Aviso**: As notificações push instantâneas exigem uma senha salva nas Chaves.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**e2e encrypted** audio call" xml:space="preserve" approved="no">
|
||||
<trans-unit id="**e2e encrypted** audio call" xml:space="preserve">
|
||||
<source>**e2e encrypted** audio call</source>
|
||||
<target state="translated">** Criptografado e2e** chamada de áudio</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**e2e encrypted** video call" xml:space="preserve" approved="no">
|
||||
<trans-unit id="**e2e encrypted** video call" xml:space="preserve">
|
||||
<source>**e2e encrypted** video call</source>
|
||||
<target state="translated">**Criptografado e2e** chamada de vídeo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="*bold*" xml:space="preserve">
|
||||
@@ -387,175 +353,144 @@ Available in v5.1</source>
|
||||
<source>All group members will remain connected.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." xml:space="preserve" approved="no">
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." xml:space="preserve">
|
||||
<source>All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you.</source>
|
||||
<target state="translated">Todas as mensagens serão apagadas – isso não pode ser desfeito! As mensagens serão apagadas SOMENTE para você.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts will remain connected" xml:space="preserve">
|
||||
<source>All your contacts will remain connected</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Allow" xml:space="preserve">
|
||||
<source>Allow</source>
|
||||
<target state="translated">Permitir</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow calls only if your contact allows them." xml:space="preserve" approved="no">
|
||||
<trans-unit id="Allow calls only if your contact allows them." xml:space="preserve">
|
||||
<source>Allow calls only if your contact allows them.</source>
|
||||
<target state="translated">Permita chamadas somente se seu contato permitir.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow disappearing messages only if your contact allows it to you." xml:space="preserve" approved="no">
|
||||
<trans-unit id="Allow disappearing messages only if your contact allows it to you." xml:space="preserve">
|
||||
<source>Allow disappearing messages only if your contact allows it to you.</source>
|
||||
<target state="translated">Permita o desaparecimento de mensagens somente se o seu contato permitir.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you." xml:space="preserve">
|
||||
<source>Allow irreversible message deletion only if your contact allows it to you.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow sending direct messages to members." xml:space="preserve" approved="no">
|
||||
<trans-unit id="Allow sending direct messages to members." xml:space="preserve">
|
||||
<source>Allow sending direct messages to members.</source>
|
||||
<target state="translated">Permitir o envio de mensagens diretas aos membros.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow sending disappearing messages." xml:space="preserve" approved="no">
|
||||
<trans-unit id="Allow sending disappearing messages." xml:space="preserve">
|
||||
<source>Allow sending disappearing messages.</source>
|
||||
<target state="translated">Permitir o envio de mensagens que desaparecem.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to send voice messages." xml:space="preserve" approved="no">
|
||||
<trans-unit id="Allow to send voice messages." xml:space="preserve">
|
||||
<source>Allow to send voice messages.</source>
|
||||
<target state="translated">Permitir enviar mensagens de voz.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow voice messages only if your contact allows them." xml:space="preserve" approved="no">
|
||||
<trans-unit id="Allow voice messages only if your contact allows them." xml:space="preserve">
|
||||
<source>Allow voice messages only if your contact allows them.</source>
|
||||
<target state="translated">Permita mensagens de voz apenas se o seu contato permitir.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow voice messages?" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Allow voice messages?" xml:space="preserve">
|
||||
<source>Allow voice messages?</source>
|
||||
<target state="translated">Permitir mensagens de voz?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to call you." xml:space="preserve" approved="no">
|
||||
<trans-unit id="Allow your contacts to call you." xml:space="preserve">
|
||||
<source>Allow your contacts to call you.</source>
|
||||
<target state="translated">Permita que seus contatos liguem para você.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Allow your contacts to irreversibly delete sent messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to send disappearing messages." xml:space="preserve" approved="no">
|
||||
<trans-unit id="Allow your contacts to send disappearing messages." xml:space="preserve">
|
||||
<source>Allow your contacts to send disappearing messages.</source>
|
||||
<target state="translated">Permita que seus contatos enviem mensagens que desaparecem.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to send voice messages." xml:space="preserve" approved="no">
|
||||
<trans-unit id="Allow your contacts to send voice messages." xml:space="preserve">
|
||||
<source>Allow your contacts to send voice messages.</source>
|
||||
<target state="translated">Permita que seus contatos enviem mensagens de voz.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already connected?" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Already connected?" xml:space="preserve">
|
||||
<source>Already connected?</source>
|
||||
<target state="translated">Já conectado?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Always use relay" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Always use relay" xml:space="preserve">
|
||||
<source>Always use relay</source>
|
||||
<target state="translated">Sempre usar retransmissão</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Answer call" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Answer call" xml:space="preserve">
|
||||
<source>Answer call</source>
|
||||
<target state="translated">Atender chamada</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App build: %@" xml:space="preserve" approved="no">
|
||||
<trans-unit id="App build: %@" xml:space="preserve">
|
||||
<source>App build: %@</source>
|
||||
<target state="translated">Versão do Aplicativo: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App icon" xml:space="preserve" approved="no">
|
||||
<trans-unit id="App icon" xml:space="preserve">
|
||||
<source>App icon</source>
|
||||
<target state="translated">Ícone do Aplicativo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App passcode" xml:space="preserve" approved="no">
|
||||
<trans-unit id="App passcode" xml:space="preserve">
|
||||
<source>App passcode</source>
|
||||
<target state="translated">Senha do Aplicativo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App version" xml:space="preserve" approved="no">
|
||||
<trans-unit id="App version" xml:space="preserve">
|
||||
<source>App version</source>
|
||||
<target state="translated">Versão do Aplicativo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App version: v%@" xml:space="preserve" approved="no">
|
||||
<trans-unit id="App version: v%@" xml:space="preserve">
|
||||
<source>App version: v%@</source>
|
||||
<target state="translated">Versão do Aplicativo: v%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Appearance" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Appearance" xml:space="preserve">
|
||||
<source>Appearance</source>
|
||||
<target state="translated">Aparência</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Attach" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Attach" xml:space="preserve">
|
||||
<source>Attach</source>
|
||||
<target state="translated">Anexar</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio & video calls" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Audio & video calls" xml:space="preserve">
|
||||
<source>Audio & video calls</source>
|
||||
<target state="translated">Chamadas de áudio e vídeo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio and video calls" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Audio and video calls" xml:space="preserve">
|
||||
<source>Audio and video calls</source>
|
||||
<target state="translated">Chamadas de áudio e vídeo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio/video calls" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Audio/video calls" xml:space="preserve">
|
||||
<source>Audio/video calls</source>
|
||||
<target state="translated">Chamadas de áudio/vídeo</target>
|
||||
<note>chat feature</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio/video calls are prohibited." xml:space="preserve" approved="no">
|
||||
<trans-unit id="Audio/video calls are prohibited." xml:space="preserve">
|
||||
<source>Audio/video calls are prohibited.</source>
|
||||
<target state="translated">Chamadas de áudio/vídeo são proibidas.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Authentication cancelled" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Authentication cancelled" xml:space="preserve">
|
||||
<source>Authentication cancelled</source>
|
||||
<target state="translated">Autenticação cancelada</target>
|
||||
<note>PIN entry</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Authentication failed" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Authentication failed" xml:space="preserve">
|
||||
<source>Authentication failed</source>
|
||||
<target state="translated">Falha na autenticação</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Authentication is required before the call is connected, but you may miss calls." xml:space="preserve" approved="no">
|
||||
<trans-unit id="Authentication is required before the call is connected, but you may miss calls." xml:space="preserve">
|
||||
<source>Authentication is required before the call is connected, but you may miss calls.</source>
|
||||
<target state="translated">A autenticação é necessária antes que a chamada seja conectada, mas você pode perder chamadas.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Authentication unavailable" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Authentication unavailable" xml:space="preserve">
|
||||
<source>Authentication unavailable</source>
|
||||
<target state="translated">Autenticação indisponível</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Auto-accept contact requests" xml:space="preserve" approved="no">
|
||||
<trans-unit id="Auto-accept contact requests" xml:space="preserve">
|
||||
<source>Auto-accept contact requests</source>
|
||||
<target state="translated">Aceitar solicitações de contato automaticamente</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Auto-accept images" xml:space="preserve">
|
||||
@@ -4227,226 +4162,6 @@ SimpleX servers cannot see your profile.</source>
|
||||
<source>\~strike~</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="## In reply to" xml:space="preserve" approved="no">
|
||||
<source>## In reply to</source>
|
||||
<target state="translated">## Em resposta a</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ uploaded" xml:space="preserve" approved="no">
|
||||
<source>%@ uploaded</source>
|
||||
<target state="translated">%@ enviado</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d weeks" xml:space="preserve" approved="no">
|
||||
<source>%d weeks</source>
|
||||
<target state="translated">%d semanas</target>
|
||||
<note>time interval</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages blocked by admin" xml:space="preserve" approved="no">
|
||||
<source>%lld messages blocked by admin</source>
|
||||
<target state="translated">%lld mensagens bloqueadas pelo administrador</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld new interface languages" xml:space="preserve" approved="no">
|
||||
<source>%lld new interface languages</source>
|
||||
<target state="translated">%lld novas interface de idiomas</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Add contact**: to create a new invitation link, or connect via a link you received." xml:space="preserve" approved="no">
|
||||
<source>**Add contact**: to create a new invitation link, or connect via a link you received.</source>
|
||||
<target state="translated">**Adicionar contato**: para criar um novo link de convite ou conectar-se por meio de um link que você recebeu.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Create group**: to create a new group." xml:space="preserve" approved="no">
|
||||
<source>**Create group**: to create a new group.</source>
|
||||
<target state="translated">**Criar grupo**: para criar um novo grupo.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." xml:space="preserve" approved="no">
|
||||
<source>**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection.</source>
|
||||
<target state="translated">**Observação**: usar o mesmo banco de dados em dois dispositivos interromperá a descriptografia de mensagens de suas conexões, como proteção de segurança.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="## History" xml:space="preserve" approved="no">
|
||||
<source>## History</source>
|
||||
<target state="translated">## Histórico</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Warning**: the archive will be removed." xml:space="preserve" approved="no">
|
||||
<source>**Warning**: the archive will be removed.</source>
|
||||
<target state="translated">**Atenção**: O arquivo será removido.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@" xml:space="preserve" approved="no">
|
||||
<source>%@ and %@</source>
|
||||
<target state="translated">%@ e %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@ connected" xml:space="preserve" approved="no">
|
||||
<source>%@ and %@ connected</source>
|
||||
<target state="translated">%@ e %@ conectado</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ at %@:" xml:space="preserve" approved="no">
|
||||
<source>%1$@ at %2$@:</source>
|
||||
<target state="translated">%1$@ em %2$@:</target>
|
||||
<note>copied message info, <sender> at <time></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ connected" xml:space="preserve" approved="no">
|
||||
<source>%@ connected</source>
|
||||
<target state="translated">%@ conectado</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld members" xml:space="preserve" approved="no">
|
||||
<source>%@, %@ and %lld members</source>
|
||||
<target state="translated">%@, %@ e %lld membros</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve" approved="no">
|
||||
<source>%@, %@ and %lld other members connected</source>
|
||||
<target state="translated">%@, %@ e %lld outros membros conectados</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld group events" xml:space="preserve" approved="no">
|
||||
<source>%lld group events</source>
|
||||
<target state="translated">%lld eventos de grupo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages marked deleted" xml:space="preserve" approved="no">
|
||||
<source>%lld messages marked deleted</source>
|
||||
<target state="translated">%lld mensagens marcadas como excluídas</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages blocked" xml:space="preserve" approved="no">
|
||||
<source>%lld messages blocked</source>
|
||||
<target state="translated">%lld mensagens bloqueadas</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages moderated by %@" xml:space="preserve" approved="no">
|
||||
<source>%lld messages moderated by %@</source>
|
||||
<target state="translated">%lld mensagens moderadas por %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(this device v%@)" xml:space="preserve" approved="no">
|
||||
<source>(this device v%@)</source>
|
||||
<target state="translated">(este dispositivo v%@)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All data is erased when it is entered." xml:space="preserve" approved="no">
|
||||
<source>All data is erased when it is entered.</source>
|
||||
<target state="translated">Todos os dados são apagados quando são inseridos.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you. (24 hours)" xml:space="preserve" approved="no">
|
||||
<source>Allow irreversible message deletion only if your contact allows it to you. (24 hours)</source>
|
||||
<target state="translated">Permita a exclusão irreversível de mensagens somente se o seu contato permitir. (24 horas)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Apply" xml:space="preserve" approved="no">
|
||||
<source>Apply</source>
|
||||
<target state="translated">Aplicar</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Archive and upload" xml:space="preserve" approved="no">
|
||||
<source>Archive and upload</source>
|
||||
<target state="translated">Arquivar e fazer envio</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve" approved="no">
|
||||
<source>All new messages from %@ will be hidden!</source>
|
||||
<target state="translated">Todas as novas mensagens de %@ ficarão ocultas!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already connecting!" xml:space="preserve" approved="no">
|
||||
<source>Already connecting!</source>
|
||||
<target state="translated">Já conectando!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve" approved="no">
|
||||
<source>App encrypts new local files (except videos).</source>
|
||||
<target state="translated">O aplicativo criptografa novos arquivos locais (exceto vídeos).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App passcode is replaced with self-destruct passcode." xml:space="preserve" approved="no">
|
||||
<source>App passcode is replaced with self-destruct passcode.</source>
|
||||
<target state="translated">A senha do Aplicativo é substituída pela senha de autodestruição.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts will remain connected." xml:space="preserve" approved="no">
|
||||
<source>All your contacts will remain connected.</source>
|
||||
<target state="translated">Todos os seus contatos permanecerão conectados.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts will remain connected. Profile update will be sent to your contacts." xml:space="preserve" approved="no">
|
||||
<source>All your contacts will remain connected. Profile update will be sent to your contacts.</source>
|
||||
<target state="translated">Todos os seus contatos permanecerão conectados. A atualização do perfil será enviada para seus contatos.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to send files and media." xml:space="preserve" approved="no">
|
||||
<source>Allow to send files and media.</source>
|
||||
<target state="translated">Permitir o envio de arquivos e mídia.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App data migration" xml:space="preserve" approved="no">
|
||||
<source>App data migration</source>
|
||||
<target state="translated">Migração de dados de aplicativos</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Archiving database" xml:space="preserve" approved="no">
|
||||
<source>Archiving database</source>
|
||||
<target state="translated">Arquivando banco de dados</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone!" xml:space="preserve" approved="no">
|
||||
<source>All messages will be deleted - this cannot be undone!</source>
|
||||
<target state="translated">Todas as mensagens serão apagadas – isso não pode ser desfeito!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." xml:space="preserve" approved="no">
|
||||
<source>All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays.</source>
|
||||
<target state="translated">Todos os seus contatos, conversas e arquivos serão criptografados com segurança e enviados em partes para retransmissões XFTP configuradas.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow message reactions only if your contact allows them." xml:space="preserve" approved="no">
|
||||
<source>Allow message reactions only if your contact allows them.</source>
|
||||
<target state="translated">Permita reações às mensagens somente se o seu contato permitir.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow message reactions." xml:space="preserve" approved="no">
|
||||
<source>Allow message reactions.</source>
|
||||
<target state="translated">Permitir reações às mensagens.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to irreversibly delete sent messages. (24 hours)" xml:space="preserve" approved="no">
|
||||
<source>Allow your contacts to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target state="translated">Permita que seus contatos apaguem irreversivelmente as mensagens enviadas. (24 horas)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already joining the group!" xml:space="preserve" approved="no">
|
||||
<source>Already joining the group!</source>
|
||||
<target state="translated">Entrando no grupo!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="An empty chat profile with the provided name is created, and the app opens as usual." xml:space="preserve" approved="no">
|
||||
<source>An empty chat profile with the provided name is created, and the app opens as usual.</source>
|
||||
<target state="translated">Um perfil de conversa vazio com o nome fornecido é criado e o aplicativo abre normalmente.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Auto-accept" xml:space="preserve" approved="no">
|
||||
<source>Auto-accept</source>
|
||||
<target state="translated">Aceitar automaticamente</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts adding message reactions." xml:space="preserve" approved="no">
|
||||
<source>Allow your contacts adding message reactions.</source>
|
||||
<target state="translated">Permita que seus contatos adicionem reações às mensagens.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve" approved="no">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target state="translated">Permitir apagar irreversivelmente as mensagens enviadas. (24 horas)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="pt" datatype="plaintext">
|
||||
|
||||
@@ -6082,7 +6082,7 @@ To connect, please ask your contact to create another connection link and check
|
||||
</trans-unit>
|
||||
<trans-unit id="Warning: starting chat on multiple devices is not supported and will cause message delivery failures" xml:space="preserve">
|
||||
<source>Warning: starting chat on multiple devices is not supported and will cause message delivery failures</source>
|
||||
<target>Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений</target>
|
||||
<target>Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Warning: you may lose some data!" xml:space="preserve">
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ at %@:" xml:space="preserve">
|
||||
<source>%1$@ at %2$@:</source>
|
||||
<target>1$@, %2$@'de:</target>
|
||||
<target>%1$@, %2$@ de</target>
|
||||
<note>copied message info, <sender> at <time></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ connected" xml:space="preserve">
|
||||
@@ -2340,7 +2340,6 @@ Bu geri alınamaz!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error allowing contact PQ encryption" xml:space="preserve">
|
||||
<source>Error allowing contact PQ encryption</source>
|
||||
<target>İletişim PQ şifrelemesine izin verirken hata oluştu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error changing address" xml:space="preserve">
|
||||
@@ -3025,7 +3024,6 @@ Bu geri alınamaz!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hungarian interface" xml:space="preserve">
|
||||
<source>Hungarian interface</source>
|
||||
<target>Macarca arayüz</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ICE servers (one per line)" xml:space="preserve">
|
||||
@@ -3630,42 +3628,34 @@ Bu senin grup için bağlantın %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
|
||||
<source>Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
|
||||
<target>Mesajlar, dosyalar ve aramalar **uçtan uca şifreleme** ile mükemmel ileri gizlilik, inkar ve izinsiz giriş kurtarma ile korunur.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
|
||||
<source>Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
|
||||
<target>Mesajlar, dosyalar ve aramalar **kuantum dirençli e2e şifreleme** ile mükemmel ileri gizlilik, inkar ve zorla girme kurtarma ile korunur.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate device" xml:space="preserve">
|
||||
<source>Migrate device</source>
|
||||
<target>Cihazı taşıma</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate from another device" xml:space="preserve">
|
||||
<source>Migrate from another device</source>
|
||||
<target>Başka bir cihazdan geçiş yapın</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate here" xml:space="preserve">
|
||||
<source>Migrate here</source>
|
||||
<target>Buraya göç edin</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate to another device" xml:space="preserve">
|
||||
<source>Migrate to another device</source>
|
||||
<target>Başka bir cihaza taşıma</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate to another device via QR code." xml:space="preserve">
|
||||
<source>Migrate to another device via QR code.</source>
|
||||
<target>QR kodu aracılığıyla başka bir cihaza geçiş yapın.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating" xml:space="preserve">
|
||||
<source>Migrating</source>
|
||||
<target>Göçmenlik</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating database archive…" xml:space="preserve">
|
||||
@@ -3675,7 +3665,6 @@ Bu senin grup için bağlantın %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migration complete" xml:space="preserve">
|
||||
<source>Migration complete</source>
|
||||
<target>Geçiş tamamlandı</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migration error:" xml:space="preserve">
|
||||
@@ -4039,7 +4028,6 @@ Bu senin grup için bağlantın %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open migration to another device" xml:space="preserve">
|
||||
<source>Open migration to another device</source>
|
||||
<target>Başka bir cihaza açık geçiş</target>
|
||||
<note>authentication reason</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open user profiles" xml:space="preserve">
|
||||
@@ -4059,7 +4047,6 @@ Bu senin grup için bağlantın %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or paste archive link" xml:space="preserve">
|
||||
<source>Or paste archive link</source>
|
||||
<target>Veya arşiv bağlantısını yapıştırın</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or scan QR code" xml:space="preserve">
|
||||
@@ -4069,7 +4056,6 @@ Bu senin grup için bağlantın %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or securely share this file link" xml:space="preserve">
|
||||
<source>Or securely share this file link</source>
|
||||
<target>Veya bu dosya bağlantısını güvenli bir şekilde paylaşın</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or show this code" xml:space="preserve">
|
||||
@@ -4159,7 +4145,6 @@ Bu senin grup için bağlantın %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Picture-in-picture calls" xml:space="preserve">
|
||||
<source>Picture-in-picture calls</source>
|
||||
<target>Resim içinde resim aramaları</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please ask your contact to enable sending voice messages." xml:space="preserve">
|
||||
@@ -4184,7 +4169,6 @@ Bu senin grup için bağlantın %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please confirm that network settings are correct for this device." xml:space="preserve">
|
||||
<source>Please confirm that network settings are correct for this device.</source>
|
||||
<target>Lütfen bu cihaz için ağ ayarlarının doğru olduğunu onaylayın.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact developers. Error: %@" xml:space="preserve">
|
||||
@@ -4246,7 +4230,6 @@ Hata: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Post-quantum E2EE" xml:space="preserve">
|
||||
<source>Post-quantum E2EE</source>
|
||||
<target>Kuantum sonrası E2EE</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Preserve the last message draft, with attachments." xml:space="preserve">
|
||||
@@ -4386,12 +4369,10 @@ Hata: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Push server" xml:space="preserve">
|
||||
<source>Push server</source>
|
||||
<target>Push sunucu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Quantum resistant encryption" xml:space="preserve">
|
||||
<source>Quantum resistant encryption</source>
|
||||
<target>Kuantum dirençli şifreleme</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Rate the app" xml:space="preserve">
|
||||
@@ -4441,7 +4422,7 @@ Hata: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Receipts are disabled" xml:space="preserve">
|
||||
<source>Receipts are disabled</source>
|
||||
<target>Makbuzlar devre dışı bırakıldı</target>
|
||||
<target>Görüldü devre dışı bırakıldı</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Received at" xml:space="preserve">
|
||||
@@ -4581,12 +4562,10 @@ Hata: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat download" xml:space="preserve">
|
||||
<source>Repeat download</source>
|
||||
<target>Tekrar indir</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat import" xml:space="preserve">
|
||||
<source>Repeat import</source>
|
||||
<target>İthalatı tekrarla</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat join request?" xml:space="preserve">
|
||||
@@ -4596,7 +4575,6 @@ Hata: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat upload" xml:space="preserve">
|
||||
<source>Repeat upload</source>
|
||||
<target>Yüklemeyi tekrarla</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
@@ -4701,7 +4679,6 @@ Hata: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safer groups" xml:space="preserve">
|
||||
<source>Safer groups</source>
|
||||
<target>Daha güvenli gruplar</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save" xml:space="preserve">
|
||||
@@ -4961,7 +4938,7 @@ Hata: %@</target>
|
||||
</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>
|
||||
<target>Teslimat makbuzlarının gönderilmesi tüm kişiler için etkinleştirilecektir.</target>
|
||||
<target>Görüldü bilgisi bütün kişileri için etkinleştirilecektir.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending file will be stopped." xml:space="preserve">
|
||||
@@ -5071,7 +5048,6 @@ Hata: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set passphrase" xml:space="preserve">
|
||||
<source>Set passphrase</source>
|
||||
<target>Parolayı ayarla</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set passphrase to export" xml:space="preserve">
|
||||
@@ -5131,7 +5107,6 @@ Hata: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show QR code" xml:space="preserve">
|
||||
<source>Show QR code</source>
|
||||
<target>QR kodunu göster</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show calls in phone history" xml:space="preserve">
|
||||
@@ -5276,7 +5251,6 @@ Hata: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Stop chat" xml:space="preserve">
|
||||
<source>Stop chat</source>
|
||||
<target>Sohbeti kes</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Stop chat to enable database actions" xml:space="preserve">
|
||||
@@ -5321,7 +5295,6 @@ Hata: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Stopping chat" xml:space="preserve">
|
||||
<source>Stopping chat</source>
|
||||
<target>Sohbeti durdurma</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Submit" xml:space="preserve">
|
||||
@@ -5351,7 +5324,7 @@ Hata: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP_KEEPCNT" xml:space="preserve">
|
||||
<source>TCP_KEEPCNT</source>
|
||||
<target>TCP_KEEPCNT</target>
|
||||
<target>TCP_CNTYİTUT</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP_KEEPIDLE" xml:space="preserve">
|
||||
@@ -5573,12 +5546,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir.
|
||||
</trans-unit>
|
||||
<trans-unit id="This chat is protected by end-to-end encryption." xml:space="preserve">
|
||||
<source>This chat is protected by end-to-end encryption.</source>
|
||||
<target>Bu sohbet uçtan uca şifreleme ile korunmaktadır.</target>
|
||||
<note>E2EE info chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This chat is protected by quantum resistant end-to-end encryption." xml:space="preserve">
|
||||
<source>This chat is protected by quantum resistant end-to-end encryption.</source>
|
||||
<target>Bu sohbet kuantum dirençli uçtan uca şifreleme ile korunmaktadır.</target>
|
||||
<note>E2EE info chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This device name" xml:space="preserve">
|
||||
@@ -5877,7 +5848,6 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste
|
||||
</trans-unit>
|
||||
<trans-unit id="Upload failed" xml:space="preserve">
|
||||
<source>Upload failed</source>
|
||||
<target>Yükleme başarısız</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Upload file" xml:space="preserve">
|
||||
@@ -5887,7 +5857,6 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste
|
||||
</trans-unit>
|
||||
<trans-unit id="Uploading archive" xml:space="preserve">
|
||||
<source>Uploading archive</source>
|
||||
<target>Arşiv yükleme</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use .onion hosts" xml:space="preserve">
|
||||
@@ -5942,7 +5911,6 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste
|
||||
</trans-unit>
|
||||
<trans-unit id="Use the app while in the call." xml:space="preserve">
|
||||
<source>Use the app while in the call.</source>
|
||||
<target>Görüşme sırasında uygulamayı kullanın.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="User profile" xml:space="preserve">
|
||||
@@ -5982,12 +5950,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify database passphrase" xml:space="preserve">
|
||||
<source>Verify database passphrase</source>
|
||||
<target>Veritabanı parolasını doğrulayın</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify passphrase" xml:space="preserve">
|
||||
<source>Verify passphrase</source>
|
||||
<target>Parolayı doğrula</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify security code" xml:space="preserve">
|
||||
@@ -6082,7 +6048,6 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste
|
||||
</trans-unit>
|
||||
<trans-unit id="Warning: starting chat on multiple devices is not supported and will cause message delivery failures" xml:space="preserve">
|
||||
<source>Warning: starting chat on multiple devices is not supported and will cause message delivery failures</source>
|
||||
<target>Uyarı: birden fazla cihazda sohbet başlatmak desteklenmez ve mesaj teslim hatalarına neden olur</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Warning: you may lose some data!" xml:space="preserve">
|
||||
@@ -6107,7 +6072,6 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste
|
||||
</trans-unit>
|
||||
<trans-unit id="Welcome message is too long" xml:space="preserve">
|
||||
<source>Welcome message is too long</source>
|
||||
<target>Hoş geldiniz mesajı çok uzun</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="What's new" xml:space="preserve">
|
||||
@@ -6167,7 +6131,6 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste
|
||||
</trans-unit>
|
||||
<trans-unit id="You **must not** use the same database on two devices." xml:space="preserve">
|
||||
<source>You **must not** use the same database on two devices.</source>
|
||||
<target>Aynı veritabanını iki cihazda **kullanmamalısınız**.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You accepted connection" xml:space="preserve">
|
||||
@@ -6259,7 +6222,6 @@ Katılma isteği tekrarlansın mı?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can give another try." xml:space="preserve">
|
||||
<source>You can give another try.</source>
|
||||
<target>Bir kez daha deneyebilirsiniz.</target>
|
||||
<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">
|
||||
@@ -7167,7 +7129,6 @@ SimpleX sunucuları profilinizi göremez.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="quantum resistant e2e encryption" xml:space="preserve">
|
||||
<source>quantum resistant e2e encryption</source>
|
||||
<target>kuantuma dayanıklı e2e şifreleme</target>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="received answer…" xml:space="preserve">
|
||||
@@ -7247,7 +7208,6 @@ SimpleX sunucuları profilinizi göremez.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="standard end-to-end encryption" xml:space="preserve">
|
||||
<source>standard end-to-end encryption</source>
|
||||
<target>standart uçtan uca şifreleme</target>
|
||||
<note>chat item text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="starting…" xml:space="preserve">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// ConcurrentQueue.swift
|
||||
// SimpleX NSE
|
||||
//
|
||||
// Created by Evgeny on 08/12/2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct DequeueElement<T> {
|
||||
var elementId: UUID?
|
||||
var task: Task<T?, Never>
|
||||
}
|
||||
|
||||
class ConcurrentQueue<T> {
|
||||
private var queue: [T] = []
|
||||
private var queueLock = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.concurrent-queue.lock.\(UUID())")
|
||||
private var continuations = [(elementId: UUID, continuation: CheckedContinuation<T?, Never>)]()
|
||||
|
||||
func enqueue(_ el: T) {
|
||||
resumeContinuation(el) { self.queue.append(el) }
|
||||
}
|
||||
|
||||
func frontEnqueue(_ el: T) {
|
||||
resumeContinuation(el) { self.queue.insert(el, at: 0) }
|
||||
}
|
||||
|
||||
private func resumeContinuation(_ el: T, add: @escaping () -> Void) {
|
||||
queueLock.sync {
|
||||
if let (_, cont) = continuations.first {
|
||||
continuations.remove(at: 0)
|
||||
cont.resume(returning: el)
|
||||
} else {
|
||||
add()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dequeue() -> DequeueElement<T> {
|
||||
queueLock.sync {
|
||||
if queue.isEmpty {
|
||||
let elementId = UUID()
|
||||
let task = Task {
|
||||
await withCheckedContinuation { cont in
|
||||
continuations.append((elementId, cont))
|
||||
}
|
||||
}
|
||||
return DequeueElement(elementId: elementId, task: task)
|
||||
} else {
|
||||
let el = queue.remove(at: 0)
|
||||
return DequeueElement(task: Task { el })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cancelDequeue(_ elementId: UUID) {
|
||||
queueLock.sync {
|
||||
let cancelled = continuations.filter { $0.elementId == elementId }
|
||||
continuations.removeAll { $0.elementId == elementId }
|
||||
cancelled.forEach { $0.continuation.resume(returning: nil) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,110 @@ let nseSuspendSchedule: SuspendSchedule = (2, 4)
|
||||
|
||||
let fastNSESuspendSchedule: SuspendSchedule = (1, 1)
|
||||
|
||||
typealias NtfStream = ConcurrentQueue<NSENotification>
|
||||
|
||||
// Notifications are delivered via concurrent queues, as they are all received from chat controller in a single loop that
|
||||
// writes to ConcurrentQueue and when notification is processed, the instance of Notification service extension reads from the queue.
|
||||
// One queue per connection (entity) is used.
|
||||
// The concurrent queues allow read cancellation, to ensure that notifications are not lost in case the current thread completes
|
||||
// before expected notification is read (multiple notifications can be expected, because one notification can be delivered for several messages).
|
||||
actor PendingNtfs {
|
||||
static let shared = PendingNtfs()
|
||||
private var ntfStreams: [String: NtfStream] = [:]
|
||||
|
||||
func createStream(_ id: String) async {
|
||||
logger.debug("NotificationService PendingNtfs.createStream: \(id)")
|
||||
if ntfStreams[id] == nil {
|
||||
ntfStreams[id] = ConcurrentQueue()
|
||||
logger.debug("NotificationService PendingNtfs.createStream: created ConcurrentQueue")
|
||||
}
|
||||
}
|
||||
|
||||
func readStream(_ id: String, for nse: NotificationService, ntfInfo: NtfMessages) async {
|
||||
logger.debug("NotificationService PendingNtfs.readStream: \(id) \(ntfInfo.ntfMessages.count)")
|
||||
if !ntfInfo.user.showNotifications {
|
||||
nse.setBestAttemptNtf(.empty)
|
||||
}
|
||||
if let s = ntfStreams[id] {
|
||||
logger.debug("NotificationService PendingNtfs.readStream: has stream")
|
||||
var expected = Set(ntfInfo.ntfMessages.map { $0.msgId })
|
||||
logger.debug("NotificationService PendingNtfs.readStream: expecting: \(expected)")
|
||||
var readCancelled = false
|
||||
var dequeued: DequeueElement<NSENotification>?
|
||||
nse.cancelRead = {
|
||||
readCancelled = true
|
||||
if let elementId = dequeued?.elementId {
|
||||
s.cancelDequeue(elementId)
|
||||
}
|
||||
}
|
||||
while !readCancelled {
|
||||
dequeued = s.dequeue()
|
||||
if let ntf = await dequeued?.task.value {
|
||||
if readCancelled {
|
||||
logger.debug("NotificationService PendingNtfs.readStream: read cancelled, put ntf to queue front")
|
||||
s.frontEnqueue(ntf)
|
||||
break
|
||||
} else if case let .msgInfo(info) = ntf {
|
||||
let found = expected.remove(info.msgId)
|
||||
if found != nil {
|
||||
logger.debug("NotificationService PendingNtfs.readStream: msgInfo, last: \(expected.isEmpty)")
|
||||
if expected.isEmpty { break }
|
||||
} else if let msgTs = ntfInfo.msgTs, info.msgTs > msgTs {
|
||||
logger.debug("NotificationService PendingNtfs.readStream: unexpected msgInfo")
|
||||
s.frontEnqueue(ntf)
|
||||
break
|
||||
}
|
||||
} else if ntfInfo.user.showNotifications {
|
||||
logger.debug("NotificationService PendingNtfs.readStream: setting best attempt")
|
||||
nse.setBestAttemptNtf(ntf)
|
||||
if ntf.isCallInvitation { break }
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
nse.cancelRead = nil
|
||||
logger.debug("NotificationService PendingNtfs.readStream: exiting")
|
||||
}
|
||||
}
|
||||
|
||||
func writeStream(_ id: String, _ ntf: NSENotification) async {
|
||||
logger.debug("NotificationService PendingNtfs.writeStream: \(id)")
|
||||
if let s = ntfStreams[id] {
|
||||
logger.debug("NotificationService PendingNtfs.writeStream: writing ntf")
|
||||
s.enqueue(ntf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The current implementation assumes concurrent notification delivery and uses semaphores
|
||||
// to process only one notification per connection (entity) at a time.
|
||||
class NtfStreamSemaphores {
|
||||
static let shared = NtfStreamSemaphores()
|
||||
private static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-semaphores.lock")
|
||||
private var semaphores: [String: DispatchSemaphore] = [:]
|
||||
|
||||
func waitForStream(_ id: String) {
|
||||
streamSemaphore(id, value: 0)?.wait()
|
||||
}
|
||||
|
||||
func signalStreamReady(_ id: String) {
|
||||
streamSemaphore(id, value: 1)?.signal()
|
||||
}
|
||||
|
||||
// this function returns nil if semaphore is just created, so passed value shoud be coordinated with the desired end value of the semaphore
|
||||
private func streamSemaphore(_ id: String, value: Int) -> DispatchSemaphore? {
|
||||
NtfStreamSemaphores.queue.sync {
|
||||
if let s = semaphores[id] {
|
||||
return s
|
||||
} else {
|
||||
semaphores[id] = DispatchSemaphore(value: value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum NSENotification {
|
||||
case nse(UNMutableNotificationContent)
|
||||
case callkit(RcvCallInvitation)
|
||||
@@ -45,7 +149,7 @@ class NSEThreads {
|
||||
static let shared = NSEThreads()
|
||||
private static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock")
|
||||
private var allThreads: Set<UUID> = []
|
||||
private var activeThreads: [(UUID, NotificationService)] = []
|
||||
private var activeThreads: Set<UUID> = []
|
||||
|
||||
func newThread() -> UUID {
|
||||
NSEThreads.queue.sync {
|
||||
@@ -54,42 +158,19 @@ class NSEThreads {
|
||||
}
|
||||
}
|
||||
|
||||
func startThread(_ t: UUID, _ service: NotificationService) {
|
||||
func startThread(_ t: UUID) {
|
||||
NSEThreads.queue.sync {
|
||||
if allThreads.contains(t) {
|
||||
activeThreads.append((t, service))
|
||||
_ = activeThreads.insert(t)
|
||||
} else {
|
||||
logger.warning("NotificationService startThread: thread \(t) was removed before it started")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processNotification(_ id: ChatId, _ ntf: NSENotification) async -> Void {
|
||||
var waitTime: Int64 = 5_000_000000
|
||||
while waitTime > 0 {
|
||||
if let (_, nse) = rcvEntityThread(id),
|
||||
nse.shouldProcessNtf && nse.processReceivedNtf(ntf) {
|
||||
break
|
||||
} else {
|
||||
try? await Task.sleep(nanoseconds: 10_000000)
|
||||
waitTime -= 10_000000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func rcvEntityThread(_ id: ChatId) -> (UUID, NotificationService)? {
|
||||
NSEThreads.queue.sync {
|
||||
activeThreads.first(where: { (_, nse) in nse.receiveEntityId == id })
|
||||
}
|
||||
}
|
||||
|
||||
func endThread(_ t: UUID) -> Bool {
|
||||
NSEThreads.queue.sync {
|
||||
let tActive: UUID? = if let index = activeThreads.firstIndex(where: { $0.0 == t }) {
|
||||
activeThreads.remove(at: index).0
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
let tActive = activeThreads.remove(t)
|
||||
let t = allThreads.remove(t)
|
||||
if tActive != nil && activeThreads.isEmpty {
|
||||
return true
|
||||
@@ -117,11 +198,8 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
// thread is added to allThreads here - if thread did not start chat,
|
||||
// chat does not need to be suspended but NSE state still needs to be set to "suspended".
|
||||
var threadId: UUID? = NSEThreads.shared.newThread()
|
||||
var notificationInfo: NtfMessages?
|
||||
var receiveEntityId: String?
|
||||
var expectedMessages: Set<String> = []
|
||||
// return true if the message is taken - it prevents sending it to another NotificationService instance for processing
|
||||
var shouldProcessNtf = false
|
||||
var cancelRead: (() -> Void)?
|
||||
var appSubscriber: AppSubscriber?
|
||||
var returnedSuspension = false
|
||||
|
||||
@@ -187,7 +265,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
// check it here again
|
||||
appStateGroupDefault.get().inactive {
|
||||
// thread is added to activeThreads tracking set here - if thread started chat it needs to be suspended
|
||||
if let t = threadId { NSEThreads.shared.startThread(t, self) }
|
||||
if let t = threadId { NSEThreads.shared.startThread(t) }
|
||||
let dbStatus = startChat()
|
||||
if case .ok = dbStatus,
|
||||
let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) {
|
||||
@@ -198,11 +276,17 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
? .nse(createConnectionEventNtf(ntfInfo.user, connEntity))
|
||||
: .empty
|
||||
)
|
||||
if let id = connEntity.id, ntfInfo.msgTs != nil {
|
||||
notificationInfo = ntfInfo
|
||||
if let id = connEntity.id {
|
||||
receiveEntityId = id
|
||||
expectedMessages = Set(ntfInfo.ntfMessages.map { $0.msgId })
|
||||
shouldProcessNtf = true
|
||||
NtfStreamSemaphores.shared.waitForStream(id)
|
||||
if receiveEntityId != nil {
|
||||
Task {
|
||||
logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id)")
|
||||
await PendingNtfs.shared.createStream(id)
|
||||
await PendingNtfs.shared.readStream(id, for: self, ntfInfo: ntfInfo)
|
||||
deliverBestAttemptNtf()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -218,38 +302,6 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
deliverBestAttemptNtf(urgent: true)
|
||||
}
|
||||
|
||||
func processReceivedNtf(_ ntf: NSENotification) -> Bool {
|
||||
guard let ntfInfo = notificationInfo, let msgTs = ntfInfo.msgTs else { return false }
|
||||
if !ntfInfo.user.showNotifications {
|
||||
self.setBestAttemptNtf(.empty)
|
||||
}
|
||||
if case let .msgInfo(info) = ntf {
|
||||
let found = expectedMessages.remove(info.msgId)
|
||||
if found != nil {
|
||||
logger.debug("NotificationService processNtf: msgInfo, last: \(self.expectedMessages.isEmpty)")
|
||||
if expectedMessages.isEmpty {
|
||||
self.deliverBestAttemptNtf()
|
||||
}
|
||||
return true
|
||||
} else if info.msgTs > msgTs {
|
||||
logger.debug("NotificationService processNtf: unexpected msgInfo, let other instance to process it, stopping this one")
|
||||
self.deliverBestAttemptNtf()
|
||||
return false
|
||||
} else {
|
||||
logger.debug("NotificationService processNtf: unknown message, let other instance to process it")
|
||||
return false
|
||||
}
|
||||
} else if ntfInfo.user.showNotifications {
|
||||
logger.debug("NotificationService processNtf: setting best attempt")
|
||||
self.setBestAttemptNtf(ntf)
|
||||
if ntf.isCallInvitation {
|
||||
self.deliverBestAttemptNtf()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setBadgeCount() {
|
||||
badgeCount = ntfBadgeCountGroupDefault.get() + 1
|
||||
ntfBadgeCountGroupDefault.set(badgeCount)
|
||||
@@ -271,9 +323,14 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
private func deliverBestAttemptNtf(urgent: Bool = false) {
|
||||
logger.debug("NotificationService.deliverBestAttemptNtf")
|
||||
// stop processing other messages
|
||||
shouldProcessNtf = false
|
||||
|
||||
if let cancel = cancelRead {
|
||||
cancelRead = nil
|
||||
cancel()
|
||||
}
|
||||
if let id = receiveEntityId {
|
||||
receiveEntityId = nil
|
||||
NtfStreamSemaphores.shared.signalStreamReady(id)
|
||||
}
|
||||
let suspend: Bool
|
||||
if let t = threadId {
|
||||
threadId = nil
|
||||
@@ -515,7 +572,7 @@ func chatSuspended() {
|
||||
}
|
||||
|
||||
// A single loop is used per Notification service extension process to receive and process all messages depending on the NSE state
|
||||
// If the extension is not active yet, or suspended/suspending, or the app is running, the notifications will not be received.
|
||||
// If the extension is not active yet, or suspended/suspending, or the app is running, the notifications will no be received.
|
||||
func receiveMessages() async {
|
||||
logger.debug("NotificationService receiveMessages")
|
||||
while true {
|
||||
@@ -534,7 +591,8 @@ func receiveMessages() async {
|
||||
logger.debug("NotificationService receiveMsg: message")
|
||||
if let (id, ntf) = await receivedMsgNtf(msg) {
|
||||
logger.debug("NotificationService receiveMsg: notification")
|
||||
await NSEThreads.shared.processNotification(id, ntf)
|
||||
await PendingNtfs.shared.createStream(id)
|
||||
await PendingNtfs.shared.writeStream(id, ntf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFC727B2782E00FB6C6D /* BGManager.swift */; };
|
||||
5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; };
|
||||
5C36027327F47AD5009F19D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C36027227F47AD5009F19D9 /* AppDelegate.swift */; };
|
||||
5C371E742BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E6F2BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a */; };
|
||||
5C371E752BACC5D600100AD3 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E702BACC5D600100AD3 /* libgmpxx.a */; };
|
||||
5C371E762BACC5D600100AD3 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E712BACC5D600100AD3 /* libffi.a */; };
|
||||
5C371E772BACC5D600100AD3 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E722BACC5D600100AD3 /* libgmp.a */; };
|
||||
5C371E782BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E732BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a */; };
|
||||
5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */; };
|
||||
5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */; };
|
||||
5C3CCFCC2AE6BD3100C3F0C3 /* ConnectDesktopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3CCFCB2AE6BD3100C3F0C3 /* ConnectDesktopView.swift */; };
|
||||
@@ -110,11 +115,6 @@
|
||||
5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */; };
|
||||
5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */; };
|
||||
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
|
||||
5CC83D1A2BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC83D152BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a */; };
|
||||
5CC83D1B2BCC504B00A0C558 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC83D162BCC504B00A0C558 /* libgmpxx.a */; };
|
||||
5CC83D1C2BCC504B00A0C558 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC83D172BCC504B00A0C558 /* libgmp.a */; };
|
||||
5CC83D1D2BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC83D182BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a */; };
|
||||
5CC83D1E2BCC504B00A0C558 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC83D192BCC504B00A0C558 /* libffi.a */; };
|
||||
5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; };
|
||||
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; };
|
||||
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CD67B8D2B0E858A00C510B1 /* hs_init.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@@ -144,6 +144,7 @@
|
||||
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 */; };
|
||||
5CF9371E2B23429500E1D781 /* ConcurrentQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371D2B23429500E1D781 /* ConcurrentQueue.swift */; };
|
||||
5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; };
|
||||
5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; };
|
||||
5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; };
|
||||
@@ -187,7 +188,6 @@
|
||||
8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */; };
|
||||
8C7D949A2B88952700B7B9E1 /* MigrateToDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7D94992B88952700B7B9E1 /* MigrateToDevice.swift */; };
|
||||
8C7DF3202B7CDB0A00C886D0 /* MigrateFromDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7DF31F2B7CDB0A00C886D0 /* MigrateFromDevice.swift */; };
|
||||
8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; };
|
||||
D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; };
|
||||
D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; };
|
||||
D741547829AF89AF0022400A /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547729AF89AF0022400A /* StoreKit.framework */; };
|
||||
@@ -291,6 +291,11 @@
|
||||
5C371E4E2BA9AAA200100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5C371E4F2BA9AB6400100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = "hu.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5C371E502BA9AB6400100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
5C371E6F2BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5C371E702BACC5D600100AD3 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C371E712BACC5D600100AD3 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C371E722BACC5D600100AD3 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C371E732BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a"; sourceTree = "<group>"; };
|
||||
5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetermineWidth.swift; sourceTree = "<group>"; };
|
||||
5C3A88D027DF57800060F1C2 /* FramedItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FramedItemView.swift; sourceTree = "<group>"; };
|
||||
5C3CCFCB2AE6BD3100C3F0C3 /* ConnectDesktopView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectDesktopView.swift; sourceTree = "<group>"; };
|
||||
@@ -402,11 +407,6 @@
|
||||
5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = "<group>"; };
|
||||
5CC2C0FB2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5CC83D152BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a"; sourceTree = "<group>"; };
|
||||
5CC83D162BCC504B00A0C558 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CC83D172BCC504B00A0C558 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CC83D182BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a"; sourceTree = "<group>"; };
|
||||
5CC83D192BCC504B00A0C558 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = "<group>"; };
|
||||
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = "<group>"; };
|
||||
5CD67B8D2B0E858A00C510B1 /* hs_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hs_init.h; sourceTree = "<group>"; };
|
||||
@@ -437,6 +437,7 @@
|
||||
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>"; };
|
||||
5CF9371D2B23429500E1D781 /* ConcurrentQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrentQueue.swift; sourceTree = "<group>"; };
|
||||
5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = "<group>"; };
|
||||
5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = "<group>"; };
|
||||
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
|
||||
@@ -481,7 +482,6 @@
|
||||
8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
||||
8C7D94992B88952700B7B9E1 /* MigrateToDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToDevice.swift; sourceTree = "<group>"; };
|
||||
8C7DF31F2B7CDB0A00C886D0 /* MigrateFromDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateFromDevice.swift; sourceTree = "<group>"; };
|
||||
8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = "<group>"; };
|
||||
D72A9087294BD7A70047C86D /* NativeTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTextEditor.swift; sourceTree = "<group>"; };
|
||||
D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
@@ -523,12 +523,12 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CC83D1D2BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a in Frameworks */,
|
||||
5CC83D1B2BCC504B00A0C558 /* libgmpxx.a in Frameworks */,
|
||||
5CC83D1C2BCC504B00A0C558 /* libgmp.a in Frameworks */,
|
||||
5CC83D1A2BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a in Frameworks */,
|
||||
5CC83D1E2BCC504B00A0C558 /* libffi.a in Frameworks */,
|
||||
5C371E752BACC5D600100AD3 /* libgmpxx.a in Frameworks */,
|
||||
5C371E742BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5C371E782BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a in Frameworks */,
|
||||
5C371E762BACC5D600100AD3 /* libffi.a in Frameworks */,
|
||||
5C371E772BACC5D600100AD3 /* libgmp.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -592,11 +592,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CC83D192BCC504B00A0C558 /* libffi.a */,
|
||||
5CC83D172BCC504B00A0C558 /* libgmp.a */,
|
||||
5CC83D162BCC504B00A0C558 /* libgmpxx.a */,
|
||||
5CC83D182BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a */,
|
||||
5CC83D152BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a */,
|
||||
5C371E712BACC5D600100AD3 /* libffi.a */,
|
||||
5C371E722BACC5D600100AD3 /* libgmp.a */,
|
||||
5C371E702BACC5D600100AD3 /* libgmpxx.a */,
|
||||
5C371E6F2BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a */,
|
||||
5C371E732BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -625,7 +625,6 @@
|
||||
5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */,
|
||||
5C93293E2928E0FD0090FFF9 /* AudioRecPlay.swift */,
|
||||
5CBD2859295711D700EC2CF4 /* ImageUtils.swift */,
|
||||
8CC956ED2BC0041000412A11 /* NetworkObserver.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@@ -801,6 +800,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CDCAD5128186DE400503DA2 /* SimpleX NSE.entitlements */,
|
||||
5CF9371D2B23429500E1D781 /* ConcurrentQueue.swift */,
|
||||
5CDCAD472818589900503DA2 /* NotificationService.swift */,
|
||||
5CDCAD492818589900503DA2 /* Info.plist */,
|
||||
5CB0BA862826CB3A00B3292C /* InfoPlist.strings */,
|
||||
@@ -1179,7 +1179,6 @@
|
||||
5C9A5BDB2871E05400A5B906 /* SetNotificationsMode.swift in Sources */,
|
||||
5CB0BA8E2827126500B3292C /* OnboardingView.swift in Sources */,
|
||||
6442E0BE2880182D00CEC0F9 /* GroupChatInfoView.swift in Sources */,
|
||||
8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */,
|
||||
5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */,
|
||||
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */,
|
||||
5CA7DFC329302AF000F7FDDE /* AppSheet.swift in Sources */,
|
||||
@@ -1286,6 +1285,7 @@
|
||||
files = (
|
||||
5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */,
|
||||
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */,
|
||||
5CF9371E2B23429500E1D781 /* ConcurrentQueue.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1445,7 +1445,6 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@@ -1507,7 +1506,6 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -1536,16 +1534,12 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "SimpleX--iOS--Info.plist";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
|
||||
@@ -1564,13 +1558,11 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES_THIN;
|
||||
MARKETING_VERSION = 5.7;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
@@ -1585,16 +1577,12 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
ENABLE_PREVIEWS = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "SimpleX--iOS--Info.plist";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
|
||||
@@ -1613,8 +1601,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.7;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1670,15 +1657,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "SimpleX NSE/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "SimpleX NSE";
|
||||
@@ -1689,15 +1673,13 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.7;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Osize";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
@@ -1707,15 +1689,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "SimpleX NSE/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "SimpleX NSE";
|
||||
@@ -1726,15 +1705,13 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.7;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Osize";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
@@ -1746,17 +1723,14 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
@@ -1774,8 +1748,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.7;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1784,7 +1757,6 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_INCLUDE_PATHS = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = ./SimpleXChat/SimpleX.h;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@@ -1797,17 +1769,14 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
@@ -1825,8 +1794,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.7;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1835,7 +1803,6 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_INCLUDE_PATHS = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = ./SimpleXChat/SimpleX.h;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
|
||||
@@ -77,7 +77,6 @@ public enum ChatCommand {
|
||||
case apiGetChatItemTTL(userId: Int64)
|
||||
case apiSetNetworkConfig(networkConfig: NetCfg)
|
||||
case apiGetNetworkConfig
|
||||
case apiSetNetworkInfo(networkInfo: UserNetworkInfo)
|
||||
case reconnectAllServers
|
||||
case apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings)
|
||||
case apiSetMemberSettings(groupId: Int64, groupMemberId: Int64, memberSettings: GroupMemberSettings)
|
||||
@@ -222,7 +221,6 @@ public enum ChatCommand {
|
||||
case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)"
|
||||
case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))"
|
||||
case .apiGetNetworkConfig: return "/network"
|
||||
case let .apiSetNetworkInfo(networkInfo): return "/_network info \(encodeJSON(networkInfo))"
|
||||
case .reconnectAllServers: return "/reconnect"
|
||||
case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id)) \(encodeJSON(chatSettings))"
|
||||
case let .apiSetMemberSettings(groupId, groupMemberId, memberSettings): return "/_member settings #\(groupId) \(groupMemberId) \(encodeJSON(memberSettings))"
|
||||
@@ -370,7 +368,6 @@ public enum ChatCommand {
|
||||
case .apiGetChatItemTTL: return "apiGetChatItemTTL"
|
||||
case .apiSetNetworkConfig: return "apiSetNetworkConfig"
|
||||
case .apiGetNetworkConfig: return "apiGetNetworkConfig"
|
||||
case .apiSetNetworkInfo: return "apiSetNetworkInfo"
|
||||
case .reconnectAllServers: return "reconnectAllServers"
|
||||
case .apiSetChatSettings: return "apiSetChatSettings"
|
||||
case .apiSetMemberSettings: return "apiSetMemberSettings"
|
||||
@@ -560,6 +557,11 @@ public enum ChatResponse: Decodable, Error {
|
||||
case contactRequestRejected(user: UserRef)
|
||||
case contactUpdated(user: UserRef, toContact: Contact)
|
||||
case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember)
|
||||
// TODO remove events below
|
||||
case contactsSubscribed(server: String, contactRefs: [ContactRef])
|
||||
case contactsDisconnected(server: String, contactRefs: [ContactRef])
|
||||
case contactSubSummary(user: UserRef, contactSubscriptions: [ContactSubStatus])
|
||||
// TODO remove events above
|
||||
case networkStatus(networkStatus: NetworkStatus, connections: [String])
|
||||
case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus])
|
||||
case groupSubscribed(user: UserRef, groupInfo: GroupRef)
|
||||
@@ -722,6 +724,9 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .contactRequestRejected: return "contactRequestRejected"
|
||||
case .contactUpdated: return "contactUpdated"
|
||||
case .groupMemberUpdated: return "groupMemberUpdated"
|
||||
case .contactsSubscribed: return "contactsSubscribed"
|
||||
case .contactsDisconnected: return "contactsDisconnected"
|
||||
case .contactSubSummary: return "contactSubSummary"
|
||||
case .networkStatus: return "networkStatus"
|
||||
case .networkStatuses: return "networkStatuses"
|
||||
case .groupSubscribed: return "groupSubscribed"
|
||||
@@ -880,6 +885,9 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .contactRequestRejected: return noDetails
|
||||
case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact))
|
||||
case let .groupMemberUpdated(u, groupInfo, fromMember, toMember): return withUser(u, "groupInfo: \(groupInfo)\nfromMember: \(fromMember)\ntoMember: \(toMember)")
|
||||
case let .contactsSubscribed(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
||||
case let .contactsDisconnected(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
||||
case let .contactSubSummary(u, contactSubscriptions): return withUser(u, String(describing: contactSubscriptions))
|
||||
case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))"
|
||||
case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses))
|
||||
case let .groupSubscribed(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
@@ -1264,7 +1272,7 @@ public struct NetCfg: Codable, Equatable {
|
||||
sessionMode: TransportSessionMode.user,
|
||||
tcpConnectTimeout: 20_000_000,
|
||||
tcpTimeout: 15_000_000,
|
||||
tcpTimeoutPerKb: 10_000,
|
||||
tcpTimeoutPerKb: 45_000,
|
||||
tcpKeepAlive: KeepAliveOpts.defaults,
|
||||
smpPingInterval: 1200_000_000,
|
||||
smpPingCount: 3,
|
||||
@@ -1276,7 +1284,7 @@ public struct NetCfg: Codable, Equatable {
|
||||
sessionMode: TransportSessionMode.user,
|
||||
tcpConnectTimeout: 30_000_000,
|
||||
tcpTimeout: 20_000_000,
|
||||
tcpTimeoutPerKb: 15_000,
|
||||
tcpTimeoutPerKb: 60_000,
|
||||
tcpKeepAlive: KeepAliveOpts.defaults,
|
||||
smpPingInterval: 1200_000_000,
|
||||
smpPingCount: 3,
|
||||
@@ -1717,7 +1725,6 @@ public enum ChatErrorType: Decodable {
|
||||
case fallbackToSMPProhibited(fileId: Int64)
|
||||
case inlineFileProhibited(fileId: Int64)
|
||||
case invalidQuote
|
||||
case invalidForward
|
||||
case invalidChatItemUpdate
|
||||
case invalidChatItemDelete
|
||||
case hasCurrentCall
|
||||
@@ -1820,7 +1827,6 @@ public enum AgentErrorType: Decodable {
|
||||
case BROKER(brokerAddress: String, brokerErr: BrokerErrorType)
|
||||
case AGENT(agentErr: SMPAgentError)
|
||||
case INTERNAL(internalErr: String)
|
||||
case CRITICAL(offerRestart: Bool, criticalErr: String)
|
||||
case INACTIVE
|
||||
}
|
||||
|
||||
@@ -1872,8 +1878,6 @@ public enum XFTPErrorType: Decodable {
|
||||
case NO_FILE
|
||||
case HAS_FILE
|
||||
case FILE_IO
|
||||
case TIMEOUT
|
||||
case REDIRECT(redirectError: String)
|
||||
case INTERNAL
|
||||
}
|
||||
|
||||
@@ -1881,8 +1885,6 @@ public enum RCErrorType: Decodable {
|
||||
case `internal`(internalErr: String)
|
||||
case identity
|
||||
case noLocalAddress
|
||||
case newController
|
||||
case notDiscovered
|
||||
case tlsStartFailed
|
||||
case exception(exception: String)
|
||||
case ctrlAuth
|
||||
@@ -1908,7 +1910,6 @@ public enum ProtocolTransportError: Decodable {
|
||||
case badBlock
|
||||
case largeMsg
|
||||
case badSession
|
||||
case noServerAuth
|
||||
case handshake(handshakeErr: SMPHandshakeError)
|
||||
}
|
||||
|
||||
@@ -1916,7 +1917,6 @@ public enum SMPHandshakeError: Decodable {
|
||||
case PARSE
|
||||
case VERSION
|
||||
case IDENTITY
|
||||
case BAD_AUTH
|
||||
}
|
||||
|
||||
public enum SMPAgentError: Decodable {
|
||||
@@ -1938,13 +1938,10 @@ public enum RemoteCtrlError: Decodable {
|
||||
case badState
|
||||
case busy
|
||||
case timeout
|
||||
case noKnownControllers
|
||||
case badController
|
||||
case disconnected(remoteCtrlId: Int64, reason: String)
|
||||
case badInvitation
|
||||
case badVersion(appVersion: String)
|
||||
case hTTP2Error(http2Error: String)
|
||||
case protocolError
|
||||
// case protocolError(protocolError: RemoteProtocolError)
|
||||
}
|
||||
|
||||
public struct MigrationFileLinkData: Codable {
|
||||
@@ -2090,31 +2087,3 @@ public enum AppSettingsLockScreenCalls: String, Codable {
|
||||
case show
|
||||
case accept
|
||||
}
|
||||
|
||||
public struct UserNetworkInfo: Codable, Equatable {
|
||||
public let networkType: UserNetworkType
|
||||
public let online: Bool
|
||||
|
||||
public init(networkType: UserNetworkType, online: Bool) {
|
||||
self.networkType = networkType
|
||||
self.online = online
|
||||
}
|
||||
}
|
||||
|
||||
public enum UserNetworkType: String, Codable {
|
||||
case none
|
||||
case cellular
|
||||
case wifi
|
||||
case ethernet
|
||||
case other
|
||||
|
||||
public var text: LocalizedStringKey {
|
||||
switch self {
|
||||
case .none: "No network connection"
|
||||
case .cellular: "Cellular"
|
||||
case .wifi: "WiFi"
|
||||
case .ethernet: "Wired ethernet"
|
||||
case .other: "Other"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ public let confirmDBUpgradesGroupDefault = BoolDefault(defaults: groupDefaults,
|
||||
|
||||
public let callKitEnabledGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_CALL_KIT_ENABLED)
|
||||
|
||||
public let pqExperimentalEnabledDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED)
|
||||
public let pqExperimentalEnabledDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES)
|
||||
|
||||
public class DateDefault {
|
||||
var defaults: UserDefaults
|
||||
|
||||
@@ -543,7 +543,6 @@ public protocol Feature {
|
||||
var iconFilled: String { get }
|
||||
var iconScale: CGFloat { get }
|
||||
var hasParam: Bool { get }
|
||||
var hasRole: Bool { get }
|
||||
var text: String { get }
|
||||
}
|
||||
|
||||
@@ -570,8 +569,6 @@ public enum ChatFeature: String, Decodable, Feature {
|
||||
}
|
||||
}
|
||||
|
||||
public var hasRole: Bool { false }
|
||||
|
||||
public var text: String {
|
||||
switch self {
|
||||
case .timedMessages: return NSLocalizedString("Disappearing messages", comment: "chat feature")
|
||||
@@ -697,7 +694,6 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case reactions
|
||||
case voice
|
||||
case files
|
||||
case simplexLinks
|
||||
case history
|
||||
|
||||
public var id: Self { self }
|
||||
@@ -709,19 +705,6 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
}
|
||||
}
|
||||
|
||||
public var hasRole: Bool {
|
||||
switch self {
|
||||
case .timedMessages: false
|
||||
case .directMessages: true
|
||||
case .fullDelete: false
|
||||
case .reactions: false
|
||||
case .voice: true
|
||||
case .files: true
|
||||
case .simplexLinks: true
|
||||
case .history: false
|
||||
}
|
||||
}
|
||||
|
||||
public var text: String {
|
||||
switch self {
|
||||
case .timedMessages: return NSLocalizedString("Disappearing messages", comment: "chat feature")
|
||||
@@ -730,7 +713,6 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case .reactions: return NSLocalizedString("Message reactions", comment: "chat feature")
|
||||
case .voice: return NSLocalizedString("Voice messages", comment: "chat feature")
|
||||
case .files: return NSLocalizedString("Files and media", comment: "chat feature")
|
||||
case .simplexLinks: return NSLocalizedString("SimpleX links", comment: "chat feature")
|
||||
case .history: return NSLocalizedString("Visible history", comment: "chat feature")
|
||||
}
|
||||
}
|
||||
@@ -743,7 +725,6 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case .reactions: return "face.smiling"
|
||||
case .voice: return "mic"
|
||||
case .files: return "doc"
|
||||
case .simplexLinks: return "link.circle"
|
||||
case .history: return "clock"
|
||||
}
|
||||
}
|
||||
@@ -756,7 +737,6 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case .reactions: return "face.smiling.fill"
|
||||
case .voice: return "mic.fill"
|
||||
case .files: return "doc.fill"
|
||||
case .simplexLinks: return "link.circle.fill"
|
||||
case .history: return "clock.fill"
|
||||
}
|
||||
}
|
||||
@@ -801,11 +781,6 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case .on: return "Allow to send files and media."
|
||||
case .off: return "Prohibit sending files and media."
|
||||
}
|
||||
case .simplexLinks:
|
||||
switch enabled {
|
||||
case .on: return "Allow to send SimpleX links."
|
||||
case .off: return "Prohibit sending SimpleX links."
|
||||
}
|
||||
case .history:
|
||||
switch enabled {
|
||||
case .on: return "Send up to 100 last messages to new members."
|
||||
@@ -844,11 +819,6 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case .on: return "Group members can send files and media."
|
||||
case .off: return "Files and media are prohibited in this group."
|
||||
}
|
||||
case .simplexLinks:
|
||||
switch enabled {
|
||||
case .on: return "Group members can send SimpleX links."
|
||||
case .off: return "SimpleX links are prohibited in this group."
|
||||
}
|
||||
case .history:
|
||||
switch enabled {
|
||||
case .on: return "Up to 100 last messages are sent to new members."
|
||||
@@ -988,22 +958,20 @@ public enum FeatureAllowed: String, Codable, Identifiable {
|
||||
|
||||
public struct FullGroupPreferences: Decodable, Equatable {
|
||||
public var timedMessages: TimedMessagesGroupPreference
|
||||
public var directMessages: RoleGroupPreference
|
||||
public var directMessages: GroupPreference
|
||||
public var fullDelete: GroupPreference
|
||||
public var reactions: GroupPreference
|
||||
public var voice: RoleGroupPreference
|
||||
public var files: RoleGroupPreference
|
||||
public var simplexLinks: RoleGroupPreference
|
||||
public var voice: GroupPreference
|
||||
public var files: GroupPreference
|
||||
public var history: GroupPreference
|
||||
|
||||
public init(
|
||||
timedMessages: TimedMessagesGroupPreference,
|
||||
directMessages: RoleGroupPreference,
|
||||
directMessages: GroupPreference,
|
||||
fullDelete: GroupPreference,
|
||||
reactions: GroupPreference,
|
||||
voice: RoleGroupPreference,
|
||||
files: RoleGroupPreference,
|
||||
simplexLinks: RoleGroupPreference,
|
||||
voice: GroupPreference,
|
||||
files: GroupPreference,
|
||||
history: GroupPreference
|
||||
) {
|
||||
self.timedMessages = timedMessages
|
||||
@@ -1012,40 +980,36 @@ public struct FullGroupPreferences: Decodable, Equatable {
|
||||
self.reactions = reactions
|
||||
self.voice = voice
|
||||
self.files = files
|
||||
self.simplexLinks = simplexLinks
|
||||
self.history = history
|
||||
}
|
||||
|
||||
public static let sampleData = FullGroupPreferences(
|
||||
timedMessages: TimedMessagesGroupPreference(enable: .off),
|
||||
directMessages: RoleGroupPreference(enable: .off, role: nil),
|
||||
directMessages: GroupPreference(enable: .off),
|
||||
fullDelete: GroupPreference(enable: .off),
|
||||
reactions: GroupPreference(enable: .on),
|
||||
voice: RoleGroupPreference(enable: .on, role: nil),
|
||||
files: RoleGroupPreference(enable: .on, role: nil),
|
||||
simplexLinks: RoleGroupPreference(enable: .on, role: nil),
|
||||
voice: GroupPreference(enable: .on),
|
||||
files: GroupPreference(enable: .on),
|
||||
history: GroupPreference(enable: .on)
|
||||
)
|
||||
}
|
||||
|
||||
public struct GroupPreferences: Codable {
|
||||
public var timedMessages: TimedMessagesGroupPreference?
|
||||
public var directMessages: RoleGroupPreference?
|
||||
public var directMessages: GroupPreference?
|
||||
public var fullDelete: GroupPreference?
|
||||
public var reactions: GroupPreference?
|
||||
public var voice: RoleGroupPreference?
|
||||
public var files: RoleGroupPreference?
|
||||
public var simplexLinks: RoleGroupPreference?
|
||||
public var voice: GroupPreference?
|
||||
public var files: GroupPreference?
|
||||
public var history: GroupPreference?
|
||||
|
||||
public init(
|
||||
timedMessages: TimedMessagesGroupPreference? = nil,
|
||||
directMessages: RoleGroupPreference? = nil,
|
||||
directMessages: GroupPreference? = nil,
|
||||
fullDelete: GroupPreference? = nil,
|
||||
reactions: GroupPreference? = nil,
|
||||
voice: RoleGroupPreference? = nil,
|
||||
files: RoleGroupPreference? = nil,
|
||||
simplexLinks: RoleGroupPreference? = nil,
|
||||
voice: GroupPreference? = nil,
|
||||
files: GroupPreference? = nil,
|
||||
history: GroupPreference? = nil
|
||||
) {
|
||||
self.timedMessages = timedMessages
|
||||
@@ -1054,18 +1018,16 @@ public struct GroupPreferences: Codable {
|
||||
self.reactions = reactions
|
||||
self.voice = voice
|
||||
self.files = files
|
||||
self.simplexLinks = simplexLinks
|
||||
self.history = history
|
||||
}
|
||||
|
||||
public static let sampleData = GroupPreferences(
|
||||
timedMessages: TimedMessagesGroupPreference(enable: .off),
|
||||
directMessages: RoleGroupPreference(enable: .off, role: nil),
|
||||
directMessages: GroupPreference(enable: .off),
|
||||
fullDelete: GroupPreference(enable: .off),
|
||||
reactions: GroupPreference(enable: .on),
|
||||
voice: RoleGroupPreference(enable: .on, role: nil),
|
||||
files: RoleGroupPreference(enable: .on, role: nil),
|
||||
simplexLinks: RoleGroupPreference(enable: .on, role: nil),
|
||||
voice: GroupPreference(enable: .on),
|
||||
files: GroupPreference(enable: .on),
|
||||
history: GroupPreference(enable: .on)
|
||||
)
|
||||
}
|
||||
@@ -1078,7 +1040,6 @@ public func toGroupPreferences(_ fullPreferences: FullGroupPreferences) -> Group
|
||||
reactions: fullPreferences.reactions,
|
||||
voice: fullPreferences.voice,
|
||||
files: fullPreferences.files,
|
||||
simplexLinks: fullPreferences.simplexLinks,
|
||||
history: fullPreferences.history
|
||||
)
|
||||
}
|
||||
@@ -1090,37 +1051,11 @@ public struct GroupPreference: Codable, Equatable {
|
||||
enable == .on
|
||||
}
|
||||
|
||||
public func enabled(_ role: GroupMemberRole?, for m: GroupMember?) -> GroupFeatureEnabled {
|
||||
switch enable {
|
||||
case .off: .off
|
||||
case .on:
|
||||
if let role, let m {
|
||||
m.memberRole >= role ? .on : .off
|
||||
} else {
|
||||
.on
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public init(enable: GroupFeatureEnabled) {
|
||||
self.enable = enable
|
||||
}
|
||||
}
|
||||
|
||||
public struct RoleGroupPreference: Codable, Equatable {
|
||||
public var enable: GroupFeatureEnabled
|
||||
public var role: GroupMemberRole?
|
||||
|
||||
public func on(for m: GroupMember) -> Bool {
|
||||
enable == .on && m.memberRole >= (role ?? .observer)
|
||||
}
|
||||
|
||||
public init(enable: GroupFeatureEnabled, role: GroupMemberRole?) {
|
||||
self.enable = enable
|
||||
self.role = role
|
||||
}
|
||||
}
|
||||
|
||||
public struct TimedMessagesGroupPreference: Codable, Equatable {
|
||||
public var enable: GroupFeatureEnabled
|
||||
public var ttl: Int?
|
||||
@@ -1345,7 +1280,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case .timedMessages: return prefs.timedMessages.on
|
||||
case .fullDelete: return prefs.fullDelete.on
|
||||
case .reactions: return prefs.reactions.on
|
||||
case .voice: return prefs.voice.on(for: groupInfo.membership)
|
||||
case .voice: return prefs.voice.on
|
||||
case .calls: return false
|
||||
}
|
||||
case .local:
|
||||
@@ -1388,7 +1323,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
return .other
|
||||
}
|
||||
case let .group(groupInfo):
|
||||
if !groupInfo.fullGroupPreferences.voice.on(for: groupInfo.membership) {
|
||||
if !groupInfo.fullGroupPreferences.voice.on {
|
||||
return .groupOwnerCan
|
||||
} else {
|
||||
return .other
|
||||
@@ -2040,7 +1975,7 @@ public struct GroupMemberIds: Decodable {
|
||||
var groupId: Int64
|
||||
}
|
||||
|
||||
public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Codable {
|
||||
public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Decodable {
|
||||
case observer = "observer"
|
||||
case author = "author"
|
||||
case member = "member"
|
||||
@@ -2185,7 +2120,7 @@ public enum ConnectionEntity: Decodable {
|
||||
public var id: String? {
|
||||
switch self {
|
||||
case let .rcvDirectMsgConnection(contact):
|
||||
return contact?.id
|
||||
return contact?.id ?? nil
|
||||
case let .rcvGroupMsgConnection(_, groupMember):
|
||||
return groupMember.id
|
||||
case let .userContactConnection(userContact):
|
||||
@@ -2437,10 +2372,10 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
public static func getSample (_ id: Int64, _ dir: CIDirection, _ ts: Date, _ text: String, _ status: CIStatus = .sndNew, quotedItem: CIQuote? = nil, file: CIFile? = nil, itemDeleted: CIDeleted? = nil, itemEdited: Bool = false, itemLive: Bool = false, deletable: Bool = true, editable: Bool = true) -> ChatItem {
|
||||
public static func getSample (_ id: Int64, _ dir: CIDirection, _ ts: Date, _ text: String, _ status: CIStatus = .sndNew, quotedItem: CIQuote? = nil, file: CIFile? = nil, itemDeleted: CIDeleted? = nil, itemEdited: Bool = false, itemLive: Bool = false, editable: Bool = true) -> ChatItem {
|
||||
ChatItem(
|
||||
chatDir: dir,
|
||||
meta: CIMeta.getSample(id, ts, text, status, itemDeleted: itemDeleted, itemEdited: itemEdited, itemLive: itemLive, deletable: deletable, editable: editable),
|
||||
meta: CIMeta.getSample(id, ts, text, status, itemDeleted: itemDeleted, itemEdited: itemEdited, itemLive: itemLive, editable: editable),
|
||||
content: .sndMsgContent(msgContent: .text(text)),
|
||||
quotedItem: quotedItem,
|
||||
file: file
|
||||
@@ -2531,7 +2466,6 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
itemDeleted: nil,
|
||||
itemEdited: false,
|
||||
itemLive: false,
|
||||
deletable: false,
|
||||
editable: false
|
||||
),
|
||||
content: .rcvDeleted(deleteMode: .cidmBroadcast),
|
||||
@@ -2553,7 +2487,6 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
itemDeleted: nil,
|
||||
itemEdited: false,
|
||||
itemLive: true,
|
||||
deletable: false,
|
||||
editable: false
|
||||
),
|
||||
content: .sndMsgContent(msgContent: .text("")),
|
||||
@@ -2613,12 +2546,10 @@ public struct CIMeta: Decodable {
|
||||
public var itemStatus: CIStatus
|
||||
public var createdAt: Date
|
||||
public var updatedAt: Date
|
||||
public var itemForwarded: CIForwardedFrom?
|
||||
public var itemDeleted: CIDeleted?
|
||||
public var itemEdited: Bool
|
||||
public var itemTimed: CITimed?
|
||||
public var itemLive: Bool?
|
||||
public var deletable: Bool
|
||||
public var editable: Bool
|
||||
|
||||
public var timestampText: Text { get { formatTimestampText(itemTs) } }
|
||||
@@ -2635,7 +2566,7 @@ public struct CIMeta: Decodable {
|
||||
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, deletable: Bool = true, editable: Bool = true) -> CIMeta {
|
||||
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 {
|
||||
CIMeta(
|
||||
itemId: id,
|
||||
itemTs: ts,
|
||||
@@ -2646,7 +2577,6 @@ public struct CIMeta: Decodable {
|
||||
itemDeleted: itemDeleted,
|
||||
itemEdited: itemEdited,
|
||||
itemLive: itemLive,
|
||||
deletable: deletable,
|
||||
editable: editable
|
||||
)
|
||||
}
|
||||
@@ -2662,7 +2592,6 @@ public struct CIMeta: Decodable {
|
||||
itemDeleted: nil,
|
||||
itemEdited: false,
|
||||
itemLive: false,
|
||||
deletable: false,
|
||||
editable: false
|
||||
)
|
||||
}
|
||||
@@ -2784,17 +2713,6 @@ public enum CIDeleted: Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum MsgDirection: Decodable {
|
||||
case rcv
|
||||
case snd
|
||||
}
|
||||
|
||||
public enum CIForwardedFrom: Decodable {
|
||||
case unknown
|
||||
case contact(chatName: String, msgDir: MsgDirection, contactId: Int64?, chatItemId: Int64?)
|
||||
case group(chatName: String, msgDir: MsgDirection, groupId: Int64?, chatItemId: Int64?)
|
||||
}
|
||||
|
||||
public enum CIDeleteMode: String, Decodable {
|
||||
case cidmBroadcast = "broadcast"
|
||||
case cidmInternal = "internal"
|
||||
@@ -2824,8 +2742,8 @@ public enum CIContent: Decodable, ItemContent {
|
||||
case sndChatFeature(feature: ChatFeature, enabled: FeatureEnabled, param: Int?)
|
||||
case rcvChatPreference(feature: ChatFeature, allowed: FeatureAllowed, param: Int?)
|
||||
case sndChatPreference(feature: ChatFeature, allowed: FeatureAllowed, param: Int?)
|
||||
case rcvGroupFeature(groupFeature: GroupFeature, preference: GroupPreference, param: Int?, memberRole_: GroupMemberRole?)
|
||||
case sndGroupFeature(groupFeature: GroupFeature, preference: GroupPreference, param: Int?, memberRole_: GroupMemberRole?)
|
||||
case rcvGroupFeature(groupFeature: GroupFeature, preference: GroupPreference, param: Int?)
|
||||
case sndGroupFeature(groupFeature: GroupFeature, preference: GroupPreference, param: Int?)
|
||||
case rcvChatFeatureRejected(feature: ChatFeature)
|
||||
case rcvGroupFeatureRejected(groupFeature: GroupFeature)
|
||||
case sndModerated
|
||||
@@ -2859,8 +2777,8 @@ public enum CIContent: Decodable, ItemContent {
|
||||
case let .sndChatFeature(feature, enabled, param): return CIContent.featureText(feature, enabled.text, param)
|
||||
case let .rcvChatPreference(feature, allowed, param): return CIContent.preferenceText(feature, allowed, param)
|
||||
case let .sndChatPreference(feature, allowed, param): return CIContent.preferenceText(feature, allowed, param)
|
||||
case let .rcvGroupFeature(feature, preference, param, role): return CIContent.featureText(feature, preference.enable.text, param, role)
|
||||
case let .sndGroupFeature(feature, preference, param, role): return CIContent.featureText(feature, preference.enable.text, param, role)
|
||||
case let .rcvGroupFeature(feature, preference, param): return CIContent.featureText(feature, preference.enable.text, param)
|
||||
case let .sndGroupFeature(feature, preference, param): return CIContent.featureText(feature, preference.enable.text, param)
|
||||
case let .rcvChatFeatureRejected(feature): return String.localizedStringWithFormat("%@: received, prohibited", feature.text)
|
||||
case let .rcvGroupFeatureRejected(groupFeature): return String.localizedStringWithFormat("%@: received, prohibited", groupFeature.text)
|
||||
case .sndModerated: return NSLocalizedString("moderated", comment: "moderated chat item")
|
||||
@@ -2885,25 +2803,10 @@ public enum CIContent: Decodable, ItemContent {
|
||||
NSLocalizedString("This chat is protected by end-to-end encryption.", comment: "E2EE info chat item")
|
||||
}
|
||||
|
||||
static func featureText(_ feature: Feature, _ enabled: String, _ param: Int?, _ role: GroupMemberRole? = nil) -> String {
|
||||
(
|
||||
feature.hasParam
|
||||
? "\(feature.text): \(timeText(param))"
|
||||
: "\(feature.text): \(enabled)"
|
||||
) +
|
||||
(
|
||||
feature.hasRole && role != nil
|
||||
? " (\(roleText(role)))"
|
||||
: ""
|
||||
)
|
||||
}
|
||||
|
||||
private static func roleText(_ role: GroupMemberRole?) -> String {
|
||||
switch role {
|
||||
case .owner: NSLocalizedString("owners", comment: "feature role")
|
||||
case .admin: NSLocalizedString("admins", comment: "feature role")
|
||||
default: NSLocalizedString("all members", comment: "feature role")
|
||||
}
|
||||
static func featureText(_ feature: Feature, _ enabled: String, _ param: Int?) -> String {
|
||||
feature.hasParam
|
||||
? "\(feature.text): \(timeText(param))"
|
||||
: "\(feature.text): \(enabled)"
|
||||
}
|
||||
|
||||
public static func preferenceText(_ feature: Feature, _ allowed: FeatureAllowed, _ param: Int?) -> String {
|
||||
@@ -3306,14 +3209,6 @@ public enum MsgContent: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public var isImageOrVideo: Bool {
|
||||
switch self {
|
||||
case .image: true
|
||||
case .video: true
|
||||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
var cmdString: String {
|
||||
"json \(encodeJSON(self))"
|
||||
}
|
||||
@@ -3848,7 +3743,6 @@ public enum ChatItemTTL: Hashable, Identifiable, Comparable {
|
||||
public struct ChatItemInfo: Decodable {
|
||||
public var itemVersions: [ChatItemVersion]
|
||||
public var memberDeliveryStatuses: [MemberDeliveryStatus]?
|
||||
public var forwardedFromChatItem: AChatItem?
|
||||
}
|
||||
|
||||
public struct ChatItemVersion: Decodable {
|
||||
|
||||
@@ -377,9 +377,6 @@
|
||||
/* member role */
|
||||
"admin" = "админ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Admins can block a member for all." = "Администраторите могат да блокират член за всички.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Admins can create the links to join groups." = "Админите могат да създадат линкове за присъединяване към групи.";
|
||||
|
||||
@@ -419,9 +416,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"All your contacts will remain connected. Profile update will be sent to your contacts." = "Всички ваши контакти ще останат свързани. Актуализацията на профила ще бъде изпратена до вашите контакти.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Всички ваши контакти, разговори и файлове ще бъдат сигурно криптирани и качени на парчета в конфигурираните XFTP релета.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow" = "Позволи";
|
||||
|
||||
@@ -503,9 +497,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "Компилация на приложението: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App data migration" = "Миграция на данните от приложението";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App encrypts new local files (except videos)." = "Приложението криптира нови локални файлове (с изключение на видеоклипове).";
|
||||
|
||||
@@ -527,15 +518,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Appearance" = "Изглед";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Apply" = "Приложи";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Archive and upload" = "Архивиране и качване";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Archiving database" = "Архивиране на база данни";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Attach" = "Прикачи";
|
||||
|
||||
@@ -683,9 +665,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Отказ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel migration" = "Отмени миграцията";
|
||||
|
||||
/* feature offered item */
|
||||
"cancelled %@" = "отменен %@";
|
||||
|
||||
@@ -765,9 +744,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Чатът е спрян. Ако вече сте използвали тази база данни на друго устройство, трябва да я прехвърлите обратно, преди да стартирате чата отново.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat migrated!" = "Чатът е мигриран!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat preferences" = "Чат настройки";
|
||||
|
||||
@@ -825,9 +801,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm database upgrades" = "Потвърди актуализаациите на базата данни";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm network settings" = "Потвърди мрежовите настройки";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm new passphrase…" = "Потвърди новата парола…";
|
||||
|
||||
@@ -837,12 +810,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm password" = "Потвърди парола";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm that you remember database passphrase to migrate it." = "Потвърдете, че помните паролата на базата данни, преди да я мигрирате.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm upload" = "Потвърди качването";
|
||||
|
||||
/* server test step */
|
||||
"Connect" = "Свързване";
|
||||
|
||||
@@ -1041,9 +1008,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Created on %@" = "Създаден на %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Creating archive link" = "Създаване на архивен линк";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Creating link…" = "Линкът се създава…";
|
||||
|
||||
@@ -1191,9 +1155,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Изтрий базата данни";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database from this device" = "Изтриване на базата данни от това устройство";
|
||||
|
||||
/* server test step */
|
||||
"Delete file" = "Изтрий файл";
|
||||
|
||||
@@ -1386,18 +1347,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Downgrade and open chat" = "Понижи версията и отвори чата";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Download failed" = "Неуспешно изтегляне";
|
||||
|
||||
/* server test step */
|
||||
"Download file" = "Свали файл";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Downloading archive" = "Архива се изтегля";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Downloading link details" = "Подробности за линка се изтеглят";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Duplicate display name!" = "Дублирано име!";
|
||||
|
||||
@@ -1431,9 +1383,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Enable for all" = "Активиране за всички";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable in direct chats (BETA)!" = "Активиране в личните чатове (БЕТА)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable instant notifications?" = "Активирай незабавни известия?";
|
||||
|
||||
@@ -1548,9 +1497,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Enter Passcode" = "Въведете kодa за достъп";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enter passphrase" = "Въведи парола";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enter passphrase…" = "Въведи парола…";
|
||||
|
||||
@@ -1590,9 +1536,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error adding member(s)" = "Грешка при добавяне на член(ове)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error allowing contact PQ encryption" = "Грешка при разрешаване на PQ криптиране за контакт";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error changing address" = "Грешка при промяна на адреса";
|
||||
|
||||
@@ -1647,9 +1590,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting user profile" = "Грешка при изтриване на потребителския профил";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error downloading the archive" = "Грешка при изтеглянето на архива";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error enabling delivery receipts!" = "Грешка при активирането на потвърждениeто за доставка!";
|
||||
|
||||
@@ -1695,9 +1635,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error saving passphrase to keychain" = "Грешка при запазване на парола в Кeychain";
|
||||
|
||||
/* when migrating */
|
||||
"Error saving settings" = "Грешка при запазване на настройките";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error saving user password" = "Грешка при запазване на потребителска парола";
|
||||
|
||||
@@ -1740,12 +1677,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error updating user privacy" = "Грешка при актуализиране на поверителността на потребителя";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error uploading the archive" = "Грешка при качването на архива";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error verifying passphrase:" = "Грешка при проверката на паролата:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error: " = "Грешка: ";
|
||||
|
||||
@@ -1779,9 +1710,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Exported database archive." = "Експортиран архив на базата данни.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Exported file doesn't exist" = "Експортираният файл не съществува";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Exporting database archive…" = "Експортиране на архив на базата данни…";
|
||||
|
||||
@@ -1824,12 +1752,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Filter unread and favorite chats." = "Филтрирайте непрочетените и любимите чатове.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Finalize migration" = "Завърши миграцията";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Finalize migration on another device." = "Завършете миграцията на другото устройство.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Finally, we have them! 🚀" = "Най-накрая ги имаме! 🚀";
|
||||
|
||||
@@ -2013,9 +1935,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"How to use your servers" = "Как да използвате вашите сървъри";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Hungarian interface" = "Унгарски интерфейс";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"ICE servers (one per line)" = "ICE сървъри (по един на ред)";
|
||||
|
||||
@@ -2055,12 +1974,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Import database" = "Импортиране на база данни";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import failed" = "Неуспешно импортиране";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Importing archive" = "Импортиране на архив";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Improved message delivery" = "Подобрена доставка на съобщения";
|
||||
|
||||
@@ -2070,9 +1983,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Improved server configuration" = "Подобрена конфигурация на сървъра";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"In order to continue, chat should be stopped." = "За да продължите, чатът трябва да бъде спрян.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"In reply to" = "В отговор на";
|
||||
|
||||
@@ -2157,9 +2067,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid link" = "Невалиден линк";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid migration confirmation" = "Невалидно потвърждение за мигриране";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid name!" = "Невалидно име!";
|
||||
|
||||
@@ -2424,9 +2331,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Message text" = "Текст на съобщението";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message too large" = "Съобщението е твърде голямо";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Messages" = "Съобщения";
|
||||
|
||||
@@ -2436,30 +2340,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Messages from %@ will be shown!" = "Съобщенията от %@ ще бъдат показани!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "Мигрирай устройството";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate from another device" = "Мигриране от друго устройство";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate here" = "Мигрирай тук";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate to another device" = "Миграция към друго устройство";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate to another device via QR code." = "Мигрирайте към друго устройство чрез QR код.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating" = "Мигриране";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating database archive…" = "Архивът на базата данни се мигрира…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migration complete" = "Миграцията е завършена";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migration error:" = "Грешка при мигриране:";
|
||||
|
||||
@@ -2717,9 +2600,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Open group" = "Отвори група";
|
||||
|
||||
/* authentication reason */
|
||||
"Open migration to another device" = "Отвори миграцията към друго устройство";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open Settings" = "Отвори настройки";
|
||||
|
||||
@@ -2732,15 +2612,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Opening app…" = "Приложението се отваря…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or paste archive link" = "Или постави архивен линк";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or scan QR code" = "Или сканирай QR код";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or securely share this file link" = "Или сигурно споделете този линк към файла";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or show this code" = "Или покажи този код";
|
||||
|
||||
@@ -2792,9 +2666,6 @@
|
||||
/* message decrypt error item */
|
||||
"Permanent decryption error" = "Постоянна грешка при декриптиране";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Picture-in-picture calls" = "Обаждания \"картина в картина\"";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"PING count" = "PING бройка";
|
||||
|
||||
@@ -2813,9 +2684,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Please check yours and your contact preferences." = "Моля, проверете вашите настройки и тези вашия за контакт.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please confirm that network settings are correct for this device." = "Моля, потвърдете, че мрежовите настройки са правилни за това устройство.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please contact developers.\nError: %@" = "Моля, свържете се с разработчиците.\nГрешка: %@";
|
||||
|
||||
@@ -2849,9 +2717,6 @@
|
||||
/* server test error */
|
||||
"Possibly, certificate fingerprint in server address is incorrect" = "Въжможно е пръстовият отпечатък на сертификата в адреса на сървъра да е неправилен";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Post-quantum E2EE" = "Постквантово E2EE";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Preserve the last message draft, with attachments." = "Запазете последната чернова на съобщението с прикачени файлове.";
|
||||
|
||||
@@ -2933,15 +2798,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Push notifications" = "Push известия";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Push server" = "Push сървър";
|
||||
|
||||
/* chat item text */
|
||||
"quantum resistant e2e encryption" = "квантово устойчиво e2e криптиране";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Quantum resistant encryption" = "Квантово устойчиво криптиране";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Rate the app" = "Оценете приложението";
|
||||
|
||||
@@ -3077,18 +2933,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat connection request?" = "Изпрати отново заявката за свързване?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat download" = "Повтори изтеглянето";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat import" = "Повтори импортирането";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat join request?" = "Изпрати отново заявката за присъединяване?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat upload" = "Повтори качването";
|
||||
|
||||
/* chat item action */
|
||||
"Reply" = "Отговори";
|
||||
|
||||
@@ -3146,9 +2993,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Run chat" = "Стартиране на чат";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Safer groups" = "По-безопасни групи";
|
||||
|
||||
/* chat item action */
|
||||
"Save" = "Запази";
|
||||
|
||||
@@ -3389,9 +3233,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Set passcode" = "Задай kод за достъп";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set passphrase" = "Задаване на парола";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set passphrase to export" = "Задай парола за експортиране";
|
||||
|
||||
@@ -3437,9 +3278,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Show preview" = "Показване на визуализация";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show QR code" = "Покажи QR код";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show:" = "Покажи:";
|
||||
|
||||
@@ -3500,9 +3338,6 @@
|
||||
/* notification title */
|
||||
"Somebody" = "Някой";
|
||||
|
||||
/* chat item text */
|
||||
"standard end-to-end encryption" = "стандартно криптиране от край до край";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Започни чат";
|
||||
|
||||
@@ -3518,9 +3353,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Stop" = "Спри";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stop chat" = "Спри чата";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stop chat to enable database actions" = "Спрете чата, за да активирате действията с базата данни";
|
||||
|
||||
@@ -3548,9 +3380,6 @@
|
||||
/* authentication reason */
|
||||
"Stop SimpleX" = "Спри SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stopping chat" = "Спиране на чата";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"strike" = "зачеркнат";
|
||||
|
||||
@@ -3701,12 +3530,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Това действие не може да бъде отменено - вашият профил, контакти, съобщения и файлове ще бъдат безвъзвратно загубени.";
|
||||
|
||||
/* E2EE info chat item */
|
||||
"This chat is protected by end-to-end encryption." = "Този чат е защитен чрез криптиране от край до край.";
|
||||
|
||||
/* E2EE info chat item */
|
||||
"This chat is protected by quantum resistant end-to-end encryption." = "Този чат е защитен от квантово устойчиво криптиране от край до край.";
|
||||
|
||||
/* notification title */
|
||||
"this contact" = "този контакт";
|
||||
|
||||
@@ -3899,15 +3722,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "Актуализирай и отвори чата";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upload failed" = "Неуспешно качване";
|
||||
|
||||
/* server test step */
|
||||
"Upload file" = "Качи файл";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Uploading archive" = "Архивът се качва";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use .onion hosts" = "Използвай .onion хостове";
|
||||
|
||||
@@ -3938,9 +3755,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Use SimpleX Chat servers?" = "Използвай сървърите на SimpleX Chat?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use the app while in the call." = "Използвайте приложението по време на разговора.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"User profile" = "Потребителски профил";
|
||||
|
||||
@@ -3968,12 +3782,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Verify connections" = "Потвърждение за свързване";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify database passphrase" = "Проверете паролата на базата данни";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify passphrase" = "Провери паролата";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify security code" = "Потвърди кода за сигурност";
|
||||
|
||||
@@ -4052,9 +3860,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"wants to connect to you!" = "иска да се свърже с вас!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Внимание: стартирането на чата на множество устройства не се поддържа и ще доведе до неуспешно изпращане на съобщения";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Warning: you may lose some data!" = "Предупреждение: Може да загубите някои данни!";
|
||||
|
||||
@@ -4070,9 +3875,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Welcome message" = "Съобщение при посрещане";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Welcome message is too long" = "Съобщението при посрещане е твърде дълго";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"What's new" = "Какво е новото";
|
||||
|
||||
@@ -4169,9 +3971,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can enable them later via app Privacy & Security settings." = "Можете да ги активирате по-късно през настройките за \"Поверителност и сигурност\" на приложението.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can give another try." = "Можете да опитате още веднъж.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can hide or mute a user profile - swipe it to the right." = "Можете да скриете или заглушите известията за потребителски профил - плъзнете надясно.";
|
||||
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
"admin" = "Admin";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Admins can block a member for all." = "Administratoren können ein Gruppenmitglied für Alle blockieren.";
|
||||
"Admins can block a member for all." = "Administratoren können für ein Mitglied alle Funktionen blockieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Admins can create the links to join groups." = "Administratoren können Links für den Beitritt zu Gruppen erzeugen.";
|
||||
@@ -853,7 +853,7 @@
|
||||
"Confirm password" = "Passwort bestätigen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm that you remember database passphrase to migrate it." = "Bitte bestätigen Sie für die Migration, dass Sie sich an Ihr Datenbank-Passwort erinnern.";
|
||||
"Confirm that you remember database passphrase to migrate it." = "Für die Migration bestätigen Sie bitte, dass Sie sich an das Datenbank-Passwort erinnern.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm upload" = "Hochladen bestätigen";
|
||||
@@ -2470,7 +2470,7 @@
|
||||
"Migrate to another device" = "Auf ein anderes Gerät migrieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate to another device via QR code." = "Daten können über einen QR-Code auf ein anderes Gerät migriert werden.";
|
||||
"Migrate to another device via QR code." = "Über einen QR-Code auf ein anderes Gerät migrieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating" = "Migrieren";
|
||||
@@ -3753,7 +3753,7 @@
|
||||
"This setting applies to messages in your current chat profile **%@**." = "Diese Einstellung gilt für Nachrichten in Ihrem aktuellen Chat-Profil **%@**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To ask any questions and to receive updates:" = "Um Fragen zu stellen und aktuelle Informationen zu erhalten:";
|
||||
"To ask any questions and to receive updates:" = "Um Fragen zu stellen und Aktualisierungen zu erhalten:";
|
||||
|
||||
/* 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.";
|
||||
@@ -4359,7 +4359,7 @@
|
||||
"Your contacts can allow full message deletion." = "Ihre Kontakte können die unwiederbringliche Löschung von Nachrichten erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts will remain connected." = "Ihre Kontakte bleiben weiterhin verbunden.";
|
||||
"Your contacts will remain connected." = "Ihre Kontakte bleiben verbunden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your current chat database will be DELETED and REPLACED with the imported one." = "Ihre aktuelle Chat-Datenbank wird GELÖSCHT und durch die Importierte ERSETZT.";
|
||||
|
||||
@@ -157,9 +157,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"%@ servers" = "Servidores %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ uploaded" = "%@ cargado";
|
||||
|
||||
/* notification title */
|
||||
"%@ wants to connect!" = "¡ %@ quiere contactar!";
|
||||
|
||||
@@ -855,9 +852,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm that you remember database passphrase to migrate it." = "Confirme que recuerda la frase secreta de la base de datos para migrarla.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm upload" = "Confirmar subida";
|
||||
|
||||
/* server test step */
|
||||
"Connect" = "Conectar";
|
||||
|
||||
@@ -1056,9 +1050,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Created on %@" = "Creado en %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Creating archive link" = "Creando enlace de archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Creating link…" = "Creando enlace…";
|
||||
|
||||
@@ -1206,9 +1197,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Eliminar base de datos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database from this device" = "Eliminar base de datos de este dispositivo";
|
||||
|
||||
/* server test step */
|
||||
"Delete file" = "Eliminar archivo";
|
||||
|
||||
@@ -1401,18 +1389,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Downgrade and open chat" = "Degradar y abrir Chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Download failed" = "Error en la descarga";
|
||||
|
||||
/* server test step */
|
||||
"Download file" = "Descargar archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Downloading archive" = "Descargando archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Downloading link details" = "Descargando detalles del enlace";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Duplicate display name!" = "¡Nombre mostrado duplicado!";
|
||||
|
||||
@@ -1446,9 +1425,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Enable for all" = "Activar para todos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable in direct chats (BETA)!" = "Activar en chats directos (BETA)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable instant notifications?" = "¿Activar notificación instantánea?";
|
||||
|
||||
@@ -1563,9 +1539,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Enter Passcode" = "Introduce Código";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enter passphrase" = "Introducir frase de contraseña";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enter passphrase…" = "Introduce la contraseña…";
|
||||
|
||||
@@ -1605,9 +1578,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error adding member(s)" = "Error al añadir miembro(s)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error allowing contact PQ encryption" = "Error al permitir cifrado PQ al contacto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error changing address" = "Error al cambiar servidor";
|
||||
|
||||
@@ -1662,9 +1632,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting user profile" = "Error al eliminar perfil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error downloading the archive" = "Error al descargar el archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error enabling delivery receipts!" = "¡Error al activar confirmaciones de entrega!";
|
||||
|
||||
@@ -1710,9 +1677,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error saving passphrase to keychain" = "Error al guardar contraseña en Keychain";
|
||||
|
||||
/* when migrating */
|
||||
"Error saving settings" = "Error al guardar ajustes";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error saving user password" = "Error al guardar contraseña de usuario";
|
||||
|
||||
@@ -1755,12 +1719,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error updating user privacy" = "Error al actualizar privacidad de usuario";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error uploading the archive" = "Error al subir el archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error verifying passphrase:" = "Error al verificar la frase de contraseña:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error: " = "Error: ";
|
||||
|
||||
@@ -1794,9 +1752,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Exported database archive." = "Archivo de base de datos exportado.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Exported file doesn't exist" = "El archivo exportado no existe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Exporting database archive…" = "Exportando base de datos…";
|
||||
|
||||
@@ -1839,12 +1794,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Filter unread and favorite chats." = "Filtra chats no leídos y favoritos.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Finalize migration" = "Finalizar la migración";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Finalize migration on another device." = "Finalizar la migración en otro dispositivo.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Finally, we have them! 🚀" = "¡Por fin los tenemos! 🚀";
|
||||
|
||||
@@ -2028,9 +1977,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"How to use your servers" = "Cómo usar los servidores";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Hungarian interface" = "Interfaz húngara";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"ICE servers (one per line)" = "Servidores ICE (uno por línea)";
|
||||
|
||||
@@ -2070,12 +2016,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Import database" = "Importar base de datos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import failed" = "Error de importación";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Importing archive" = "Importando archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Improved message delivery" = "Entrega de mensajes mejorada";
|
||||
|
||||
@@ -2085,9 +2025,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Improved server configuration" = "Configuración del servidor mejorada";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"In order to continue, chat should be stopped." = "Para continuar, el chat debe ser interrumpido.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"In reply to" = "En respuesta a";
|
||||
|
||||
@@ -2172,9 +2109,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid link" = "Enlace no válido";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid migration confirmation" = "Confirmación de migración inválida";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid name!" = "¡Nombre no válido!";
|
||||
|
||||
@@ -2439,9 +2373,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Message text" = "Contacto y texto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message too large" = "Mensaje demasiado grande";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Messages" = "Mensajes";
|
||||
|
||||
@@ -2451,36 +2382,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Messages from %@ will be shown!" = "¡Los mensajes de %@ serán mostrados!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Los mensajes, archivos y llamadas están protegidos por **cifrado de extremo a extremo** con perfecta confidencialidad, repudio y recuperación tras ataques.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Los mensajes, archivos y llamadas están protegidos por **cifrado de extremo a extremo resistente a la computación cuántica** con perfecta confidencialidad, repudio y recuperación tras ataques.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "Migrar dispositivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate from another device" = "Migrar desde otro dispositivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate here" = "Migrar aquí";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate to another device" = "Migrar hacia otro dispositivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate to another device via QR code." = "Migrar hacia otro dispositivo mediante código QR.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating" = "Migrando";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating database archive…" = "Migrando base de datos…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migration complete" = "Migración completada";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migration error:" = "Error de migración:";
|
||||
|
||||
@@ -2738,9 +2642,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Open group" = "Grupo abierto";
|
||||
|
||||
/* authentication reason */
|
||||
"Open migration to another device" = "Abrir la migración hacia otro dispositivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open Settings" = "Abrir Configuración";
|
||||
|
||||
@@ -2753,15 +2654,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Opening app…" = "Iniciando aplicación…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or paste archive link" = "O pegar enlace del archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or scan QR code" = "O escanear código QR";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or securely share this file link" = "O comparta de forma segura el enlace de este archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or show this code" = "O mostrar este código";
|
||||
|
||||
@@ -2813,9 +2708,6 @@
|
||||
/* message decrypt error item */
|
||||
"Permanent decryption error" = "Error permanente descifrado";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Picture-in-picture calls" = "Llamadas picture-in-picture";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"PING count" = "Contador PING";
|
||||
|
||||
@@ -2834,9 +2726,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Please check yours and your contact preferences." = "Comprueba tus preferencias y las de tu contacto.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please confirm that network settings are correct for this device." = "Por favor confirme que la configuración de red es correcta para este dispositivo.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please contact developers.\nError: %@" = "Por favor, contacta con los desarrolladores.\nError: %@";
|
||||
|
||||
@@ -2870,9 +2759,6 @@
|
||||
/* server test error */
|
||||
"Possibly, certificate fingerprint in server address is incorrect" = "Posiblemente la huella digital del certificado en la dirección del servidor es incorrecta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Post-quantum E2EE" = "E2EE postcuántica";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Preserve the last message draft, with attachments." = "Conserva el último borrador del mensaje con los datos adjuntos.";
|
||||
|
||||
@@ -2954,15 +2840,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Push notifications" = "Notificaciones automáticas";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Push server" = "Servidor push";
|
||||
|
||||
/* chat item text */
|
||||
"quantum resistant e2e encryption" = "cifrado e2e resistente a la cuántica";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Quantum resistant encryption" = "Cifrado resistente a la tecnología cuántica";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Rate the app" = "Valora la aplicación";
|
||||
|
||||
@@ -3098,18 +2975,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat connection request?" = "¿Repetir solicitud de conexión?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat download" = "Repetir descarga";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat import" = "Repetir importación";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat join request?" = "¿Repetir solicitud de admisión?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat upload" = "Repetir la carga";
|
||||
|
||||
/* chat item action */
|
||||
"Reply" = "Responder";
|
||||
|
||||
@@ -3167,9 +3035,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Run chat" = "Ejecutar chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Safer groups" = "Grupos más seguros";
|
||||
|
||||
/* chat item action */
|
||||
"Save" = "Guardar";
|
||||
|
||||
@@ -3410,9 +3275,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Set passcode" = "Código autodestrucción";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set passphrase" = "Definir frase de contraseña";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set passphrase to export" = "Escribe la contraseña para exportar";
|
||||
|
||||
@@ -3458,9 +3320,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Show preview" = "Mostrar vista previa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show QR code" = "Mostrar código QR";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show:" = "Mostrar:";
|
||||
|
||||
@@ -3521,9 +3380,6 @@
|
||||
/* notification title */
|
||||
"Somebody" = "Alguien";
|
||||
|
||||
/* chat item text */
|
||||
"standard end-to-end encryption" = "cifrado estándar de extremo a extremo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Iniciar chat";
|
||||
|
||||
@@ -3539,9 +3395,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Stop" = "Detener";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stop chat" = "Detener el chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stop chat to enable database actions" = "Detén SimpleX para habilitar las acciones sobre la base de datos";
|
||||
|
||||
@@ -3569,9 +3422,6 @@
|
||||
/* authentication reason */
|
||||
"Stop SimpleX" = "Detener SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stopping chat" = "Detención del chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"strike" = "tachado";
|
||||
|
||||
@@ -3722,12 +3572,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Esta acción es irreversible. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente.";
|
||||
|
||||
/* E2EE info chat item */
|
||||
"This chat is protected by end-to-end encryption." = "Este chat está protegido por cifrado de extremo a extremo.";
|
||||
|
||||
/* E2EE info chat item */
|
||||
"This chat is protected by quantum resistant end-to-end encryption." = "Este chat está protegido por un cifrado de extremo a extremo resistente a tecnologías cuánticas.";
|
||||
|
||||
/* notification title */
|
||||
"this contact" = "este contacto";
|
||||
|
||||
@@ -3920,15 +3764,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "Actualizar y abrir Chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upload failed" = "Error de carga";
|
||||
|
||||
/* server test step */
|
||||
"Upload file" = "Subir archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Uploading archive" = "Subiendo el archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use .onion hosts" = "Usar hosts .onion";
|
||||
|
||||
@@ -3959,9 +3797,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Use SimpleX Chat servers?" = "¿Usar servidores SimpleX Chat?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use the app while in the call." = "Usar la app durante la llamada.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"User profile" = "Perfil de usuario";
|
||||
|
||||
@@ -3989,12 +3824,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Verify connections" = "Verificar conexiones";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify database passphrase" = "Verificar la contraseña de la base de datos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify passphrase" = "Verificar frase de contraseña";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify security code" = "Comprobar código de seguridad";
|
||||
|
||||
@@ -4073,9 +3902,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"wants to connect to you!" = "¡quiere contactar contigo!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Advertencia: el inicio del chat en varios dispositivos no es compatible y provocará fallos en la entrega de mensajes";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Warning: you may lose some data!" = "Atención: ¡puedes perder algunos datos!";
|
||||
|
||||
@@ -4091,9 +3917,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Welcome message" = "Mensaje de bienvenida";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Welcome message is too long" = "El mensaje de bienvenida es demasiado largo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"What's new" = "Novedades";
|
||||
|
||||
@@ -4130,9 +3953,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You" = "Tú";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You **must not** use the same database on two devices." = "**No debe** usar la misma base de datos en dos dispositivos.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You accepted connection" = "Has aceptado la conexión";
|
||||
|
||||
@@ -4193,9 +4013,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can enable them later via app Privacy & Security settings." = "Puedes activarlos más tarde en la configuración de Privacidad y Seguridad.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can give another try." = "Puede intentarlo de nuevo.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can hide or mute a user profile - swipe it to the right." = "Puedes ocultar o silenciar un perfil deslizándolo a la derecha.";
|
||||
|
||||
|
||||
@@ -1447,7 +1447,7 @@
|
||||
"Enable for all" = "Activer pour tous";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable in direct chats (BETA)!" = "Activer dans les conversations directes (BETA) !";
|
||||
"Enable in direct chats (BETA)!" = "Activé dans les conversations directes (BETA) !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable instant notifications?" = "Activer les notifications instantanées ?";
|
||||
@@ -2455,7 +2455,7 @@
|
||||
"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Les messages, fichiers et appels sont protégés par un chiffrement **de bout en bout** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Les messages, fichiers et appels sont protégés par un chiffrement **e2e résistant post-quantique** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction.";
|
||||
"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Les messages, fichiers et appels sont protégés par un chiffrement **2e2 résistant post-quantique** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "Transférer l'appareil";
|
||||
@@ -3726,7 +3726,7 @@
|
||||
"This chat is protected by end-to-end encryption." = "Cette discussion est protégée par un chiffrement de bout en bout.";
|
||||
|
||||
/* E2EE info chat item */
|
||||
"This chat is protected by quantum resistant end-to-end encryption." = "Cette discussion est protégée par un chiffrement de bout en bout résistant aux technologies quantiques.";
|
||||
"This chat is protected by quantum resistant end-to-end encryption." = "Cette discussion est protégée par un chiffrement de bout en bout résistant post-quantique.";
|
||||
|
||||
/* notification title */
|
||||
"this contact" = "ce contact";
|
||||
@@ -4074,7 +4074,7 @@
|
||||
"wants to connect to you!" = "veut établir une connexion !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Attention: démarrer une session de chat sur plusieurs appareils n'est pas pris en charge et entraînera des dysfonctionnements au niveau de la transmission des messages";
|
||||
"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Attention : démarrer une session de chat sur plusieurs appareils n'est pas pris en charge et entraînera des dysfonctionnements au niveau de la transmission des messages";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Warning: you may lose some data!" = "Attention : vous risquez de perdre des données !";
|
||||
|
||||
@@ -2455,7 +2455,7 @@
|
||||
"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "I messaggi, i file e le chiamate sono protetti da **crittografia end-to-end** con perfect forward secrecy, ripudio e recupero da intrusione.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "I messaggi, i file e le chiamate sono protetti da **crittografia e2e resistente alla quantistica** con perfect forward secrecy, ripudio e recupero da intrusione.";
|
||||
"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "I messaggi, i file e le chiamate sono protetti da **crittografia e2e resistente al quantistico** con perfect forward secrecy, ripudio e recupero da intrusione.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "Migra dispositivo";
|
||||
@@ -2958,10 +2958,10 @@
|
||||
"Push server" = "Server push";
|
||||
|
||||
/* chat item text */
|
||||
"quantum resistant e2e encryption" = "crittografia e2e resistente alla quantistica";
|
||||
"quantum resistant e2e encryption" = "crittografia e2e resistente al quantistico";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Quantum resistant encryption" = "Crittografia resistente alla quantistica";
|
||||
"Quantum resistant encryption" = "Crittografia resistente al quantistico";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Rate the app" = "Valuta l'app";
|
||||
@@ -3726,7 +3726,7 @@
|
||||
"This chat is protected by end-to-end encryption." = "Questa chat è protetta da crittografia end-to-end.";
|
||||
|
||||
/* E2EE info chat item */
|
||||
"This chat is protected by quantum resistant end-to-end encryption." = "Questa chat è protetta da crittografia end-to-end resistente alla quantistica.";
|
||||
"This chat is protected by quantum resistant end-to-end encryption." = "Questa chat è protetta da crittografia end-to-end resistente al quantistico.";
|
||||
|
||||
/* notification title */
|
||||
"this contact" = "questo contatto";
|
||||
@@ -4014,7 +4014,7 @@
|
||||
"via relay" = "via relay";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Via secure quantum resistant protocol." = "Tramite protocollo sicuro resistente alla quantistica.";
|
||||
"Via secure quantum resistant protocol." = "Tramite protocollo sicuro resistente al quantistico.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Video call" = "Videochiamata";
|
||||
|
||||
@@ -2730,7 +2730,7 @@
|
||||
"Open" = "Open";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open chat" = "Chat openen";
|
||||
"Open chat" = "Gesprekken openen";
|
||||
|
||||
/* authentication reason */
|
||||
"Open chat console" = "Chat console openen";
|
||||
|
||||
@@ -4074,7 +4074,7 @@
|
||||
"wants to connect to you!" = "хочет соединиться с Вами!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений";
|
||||
"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Warning: you may lose some data!" = "Предупреждение: Вы можете потерять какие то данные!";
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -137,7 +137,7 @@
|
||||
"%@ and %@ connected" = "%@ ve %@ bağlandı";
|
||||
|
||||
/* copied message info, <sender> at <time> */
|
||||
"%@ at %@:" = "1$@, %2$@'de:";
|
||||
"%@ at %@:" = "%1$@, %2$@ de";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ connected" = "%@ bağlandı";
|
||||
@@ -1605,9 +1605,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error adding member(s)" = "Üye(ler) eklenirken hata oluştu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error allowing contact PQ encryption" = "İletişim PQ şifrelemesine izin verirken hata oluştu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error changing address" = "Adres değiştirilirken hata oluştu";
|
||||
|
||||
@@ -2028,9 +2025,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"How to use your servers" = "Sunucularını nasıl kullanabilirsin";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Hungarian interface" = "Macarca arayüz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"ICE servers (one per line)" = "ICE sunucuları (her satıra bir tane)";
|
||||
|
||||
@@ -2451,36 +2445,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Messages from %@ will be shown!" = "%@ den gelen mesajlar gösterilecektir!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Mesajlar, dosyalar ve aramalar **uçtan uca şifreleme** ile mükemmel ileri gizlilik, inkar ve izinsiz giriş kurtarma ile korunur.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Mesajlar, dosyalar ve aramalar **kuantum dirençli e2e şifreleme** ile mükemmel ileri gizlilik, inkar ve zorla girme kurtarma ile korunur.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "Cihazı taşıma";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate from another device" = "Başka bir cihazdan geçiş yapın";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate here" = "Buraya göç edin";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate to another device" = "Başka bir cihaza taşıma";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate to another device via QR code." = "QR kodu aracılığıyla başka bir cihaza geçiş yapın.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating" = "Göçmenlik";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating database archive…" = "Veritabanı arşivine geçiliyor…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migration complete" = "Geçiş tamamlandı";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migration error:" = "Geçiş hatası:";
|
||||
|
||||
@@ -2738,9 +2705,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Open group" = "Grubu aç";
|
||||
|
||||
/* authentication reason */
|
||||
"Open migration to another device" = "Başka bir cihaza açık geçiş";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open Settings" = "Ayarları aç";
|
||||
|
||||
@@ -2753,15 +2717,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Opening app…" = "Uygulama açılıyor…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or paste archive link" = "Veya arşiv bağlantısını yapıştırın";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or scan QR code" = "Veya QR kodu okut";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or securely share this file link" = "Veya bu dosya bağlantısını güvenli bir şekilde paylaşın";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or show this code" = "Veya bu kodu göster";
|
||||
|
||||
@@ -2813,9 +2771,6 @@
|
||||
/* message decrypt error item */
|
||||
"Permanent decryption error" = "Kalıcı şifre çözümü hatası";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Picture-in-picture calls" = "Resim içinde resim aramaları";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"PING count" = "PING sayısı";
|
||||
|
||||
@@ -2834,9 +2789,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Please check yours and your contact preferences." = "Lütfen sizinkini ve iletişim tercihlerinizi kontrol edin.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please confirm that network settings are correct for this device." = "Lütfen bu cihaz için ağ ayarlarının doğru olduğunu onaylayın.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please contact developers.\nError: %@" = "Lütfen geliştiricilerle irtibata geçin.\nHata: %@";
|
||||
|
||||
@@ -2870,9 +2822,6 @@
|
||||
/* server test error */
|
||||
"Possibly, certificate fingerprint in server address is incorrect" = "Muhtemelen, sunucu adresindeki parmakizi sertifikası doğru değil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Post-quantum E2EE" = "Kuantum sonrası E2EE";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Preserve the last message draft, with attachments." = "Son mesaj taslağını ekleriyle birlikte koru.";
|
||||
|
||||
@@ -2954,15 +2903,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Push notifications" = "Anında bildirimler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Push server" = "Push sunucu";
|
||||
|
||||
/* chat item text */
|
||||
"quantum resistant e2e encryption" = "kuantuma dayanıklı e2e şifreleme";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Quantum resistant encryption" = "Kuantum dirençli şifreleme";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Rate the app" = "Uygulamayı değerlendir";
|
||||
|
||||
@@ -2991,7 +2931,7 @@
|
||||
"Read more in our GitHub repository." = "Daha fazlasını GitHub depomuzdan oku.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receipts are disabled" = "Makbuzlar devre dışı bırakıldı";
|
||||
"Receipts are disabled" = "Görüldü devre dışı bırakıldı";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"received answer…" = "alınan cevap…";
|
||||
@@ -3098,18 +3038,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat connection request?" = "Bağlantı isteği tekrarlansın mı?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat download" = "Tekrar indir";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat import" = "İthalatı tekrarla";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat join request?" = "Katılma isteği tekrarlansın mı?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat upload" = "Yüklemeyi tekrarla";
|
||||
|
||||
/* chat item action */
|
||||
"Reply" = "Yanıtla";
|
||||
|
||||
@@ -3167,9 +3098,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Run chat" = "Sohbeti çalıştır";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Safer groups" = "Daha güvenli gruplar";
|
||||
|
||||
/* chat item action */
|
||||
"Save" = "Kaydet";
|
||||
|
||||
@@ -3339,7 +3267,7 @@
|
||||
"Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "Görüldü bilgisi, tüm görünür sohbet profillerindeki tüm kişiler için etkinleştirilecektir.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending delivery receipts will be enabled for all contacts." = "Teslimat makbuzlarının gönderilmesi tüm kişiler için etkinleştirilecektir.";
|
||||
"Sending delivery receipts will be enabled for all contacts." = "Görüldü bilgisi bütün kişileri için etkinleştirilecektir.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending file will be stopped." = "Dosya gönderimi durdurulacaktır.";
|
||||
@@ -3410,9 +3338,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Set passcode" = "Şifre ayarla";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set passphrase" = "Parolayı ayarla";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set passphrase to export" = "Dışa aktarmak için parola ayarla";
|
||||
|
||||
@@ -3458,9 +3383,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Show preview" = "Ön gösterimi göser";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show QR code" = "QR kodunu göster";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show:" = "Göster:";
|
||||
|
||||
@@ -3521,9 +3443,6 @@
|
||||
/* notification title */
|
||||
"Somebody" = "Biri";
|
||||
|
||||
/* chat item text */
|
||||
"standard end-to-end encryption" = "standart uçtan uca şifreleme";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Sohbeti başlat";
|
||||
|
||||
@@ -3539,9 +3458,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Stop" = "Dur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stop chat" = "Sohbeti kes";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stop chat to enable database actions" = "Veritabanı eylemlerini etkinleştirmek için sohbeti durdur";
|
||||
|
||||
@@ -3569,9 +3485,6 @@
|
||||
/* authentication reason */
|
||||
"Stop SimpleX" = "SimpleX'i durdur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stopping chat" = "Sohbeti durdurma";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"strike" = "çizik";
|
||||
|
||||
@@ -3618,7 +3531,7 @@
|
||||
"TCP connection timeout" = "TCP bağlantı zaman aşımı";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP_KEEPCNT" = "TCP_KEEPCNT";
|
||||
"TCP_KEEPCNT" = "TCP_CNTYİTUT";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP_KEEPIDLE" = "TCP_BOŞTAKAL";
|
||||
@@ -3722,12 +3635,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Bu işlem geri alınamaz - profiliniz, kişileriniz, mesajlarınız ve dosyalarınız geri döndürülemez şekilde kaybolacaktır.";
|
||||
|
||||
/* E2EE info chat item */
|
||||
"This chat is protected by end-to-end encryption." = "Bu sohbet uçtan uca şifreleme ile korunmaktadır.";
|
||||
|
||||
/* E2EE info chat item */
|
||||
"This chat is protected by quantum resistant end-to-end encryption." = "Bu sohbet kuantum dirençli uçtan uca şifreleme ile korunmaktadır.";
|
||||
|
||||
/* notification title */
|
||||
"this contact" = "Bu kişi";
|
||||
|
||||
@@ -3920,15 +3827,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "Yükselt ve sohbeti aç";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upload failed" = "Yükleme başarısız";
|
||||
|
||||
/* server test step */
|
||||
"Upload file" = "Dosya yükle";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Uploading archive" = "Arşiv yükleme";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use .onion hosts" = ".onion ana bilgisayarlarını kullan";
|
||||
|
||||
@@ -3959,9 +3860,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Use SimpleX Chat servers?" = "SimpleX Chat sunucuları kullanılsın mı?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use the app while in the call." = "Görüşme sırasında uygulamayı kullanın.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"User profile" = "Kullanıcı profili";
|
||||
|
||||
@@ -3989,12 +3887,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Verify connections" = "Bağlantıları doğrula";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify database passphrase" = "Veritabanı parolasını doğrulayın";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify passphrase" = "Parolayı doğrula";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify security code" = "Güvenlik kodunu doğrula";
|
||||
|
||||
@@ -4073,9 +3965,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"wants to connect to you!" = "bağlanmak istiyor!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Uyarı: birden fazla cihazda sohbet başlatmak desteklenmez ve mesaj teslim hatalarına neden olur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Warning: you may lose some data!" = "Uyarı: Bazı verileri kaybedebilirsin!";
|
||||
|
||||
@@ -4091,9 +3980,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Welcome message" = "Karşılama mesajı";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Welcome message is too long" = "Hoş geldiniz mesajı çok uzun";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"What's new" = "Neler yeni";
|
||||
|
||||
@@ -4130,9 +4016,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You" = "Sen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You **must not** use the same database on two devices." = "Aynı veritabanını iki cihazda **kullanmamalısınız**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You accepted connection" = "Bağlantıyı onayladın";
|
||||
|
||||
@@ -4193,9 +4076,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can enable them later via app Privacy & Security settings." = "Daha sonra uygulamanın Gizlilik ve Güvenlik ayarlarından etkinleştirebilirsiniz.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can give another try." = "Bir kez daha deneyebilirsiniz.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can hide or mute a user profile - swipe it to the right." = "Bir kullanıcı profilini gizleyebilir veya sessize alabilirsiniz - sağa kaydırın.";
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,6 @@
|
||||
/* Privacy - Face ID Usage Description */
|
||||
"NSFaceIDUsageDescription" = "SimpleX використовує Face ID для локальної автентифікації";
|
||||
|
||||
/* Privacy - Local Network Usage Description */
|
||||
"NSLocalNetworkUsageDescription" = "SimpleX використовує доступ до локальної мережі, щоб дозволити користувачеві користуватися профілем чату через десктопну програму в тій же мережі.";
|
||||
|
||||
/* Privacy - Microphone Usage Description */
|
||||
"NSMicrophoneUsageDescription" = "SimpleX потребує доступу до мікрофона для аудіо та відео дзвінків, а також для запису голосових повідомлень.";
|
||||
|
||||
|
||||
@@ -8,14 +8,12 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 34
|
||||
compileSdkVersion(34)
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "chat.simplex.app"
|
||||
namespace = "chat.simplex.app"
|
||||
minSdk = 28
|
||||
//noinspection OldTargetApi
|
||||
targetSdk = 33
|
||||
minSdkVersion(28)
|
||||
targetSdkVersion(33)
|
||||
// !!!
|
||||
// skip version code after release to F-Droid, as it uses two version codes
|
||||
versionCode = (extra["android.version_code"] as String).toInt()
|
||||
@@ -48,7 +46,12 @@ android {
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs += "-opt-in=kotlinx.coroutines.DelicateCoroutinesApi"
|
||||
freeCompilerArgs += "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi"
|
||||
freeCompilerArgs += "-opt-in=androidx.compose.ui.text.ExperimentalTextApi"
|
||||
@@ -68,20 +71,18 @@ android {
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
packaging {
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
jniLibs.useLegacyPackaging = rootProject.extra["compression.level"] as Int != 0
|
||||
}
|
||||
android.sourceSets["main"].assets.setSrcDirs(listOf("../common/src/commonMain/resources/assets"))
|
||||
val isRelease = gradle.startParameter.taskNames.find { it.lowercase().contains("release") } != null
|
||||
val isBundle = gradle.startParameter.taskNames.find { it.lowercase().contains("bundle") } != null
|
||||
val isRelease = gradle.startParameter.taskNames.find { it.toLowerCase().contains("release") } != null
|
||||
val isBundle = gradle.startParameter.taskNames.find { it.toLowerCase().contains("bundle") } != null
|
||||
// if (isRelease) {
|
||||
// Comma separated list of languages that will be included in the apk
|
||||
android.defaultConfig.resourceConfigurations += listOf(
|
||||
android.defaultConfig.resConfigs(
|
||||
"en",
|
||||
"ar",
|
||||
"bg",
|
||||
@@ -103,7 +104,7 @@ android {
|
||||
"uk",
|
||||
"zh-rCN"
|
||||
)
|
||||
ndkVersion = "23.1.7779620"
|
||||
// }
|
||||
if (isBundle) {
|
||||
defaultConfig.ndk.abiFilters("arm64-v8a", "armeabi-v7a")
|
||||
} else {
|
||||
@@ -124,44 +125,41 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
implementation("androidx.core:core-ktx:1.7.0")
|
||||
//implementation("androidx.compose.ui:ui:${rootProject.extra["compose.version"] as String}")
|
||||
//implementation("androidx.compose.material:material:$compose_version")
|
||||
//implementation("androidx.compose.ui:ui-tooling-preview:$compose_version")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
|
||||
implementation("androidx.lifecycle:lifecycle-process:2.7.0")
|
||||
implementation("androidx.activity:activity-compose:1.8.2")
|
||||
val workVersion = "2.9.0"
|
||||
implementation("androidx.work:work-runtime-ktx:$workVersion")
|
||||
implementation("androidx.work:work-multiprocess:$workVersion")
|
||||
implementation("androidx.appcompat:appcompat:1.5.1")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.1")
|
||||
implementation("androidx.lifecycle:lifecycle-process:2.4.1")
|
||||
implementation("androidx.activity:activity-compose:1.5.0")
|
||||
val work_version = "2.7.1"
|
||||
implementation("androidx.work:work-runtime-ktx:$work_version")
|
||||
implementation("androidx.work:work-multiprocess:$work_version")
|
||||
|
||||
implementation("com.jakewharton:process-phoenix:2.2.0")
|
||||
|
||||
//Camera Permission
|
||||
implementation("com.google.accompanist:accompanist-permissions:0.23.0")
|
||||
implementation("com.jakewharton:process-phoenix:2.1.2")
|
||||
|
||||
//implementation("androidx.compose.material:material-icons-extended:$compose_version")
|
||||
//implementation("androidx.compose.ui:ui-util:$compose_version")
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
|
||||
//androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling:1.6.4")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling:1.4.3")
|
||||
}
|
||||
|
||||
tasks {
|
||||
val compressApk by creating {
|
||||
doLast {
|
||||
val isRelease = gradle.startParameter.taskNames.find { it.lowercase().contains("release") } != null
|
||||
val isRelease = gradle.startParameter.taskNames.find { it.toLowerCase().contains("release") } != null
|
||||
val buildType: String = if (isRelease) "release" else "debug"
|
||||
val javaHome = System.getProperties()["java.home"] ?: org.gradle.internal.jvm.Jvm.current().javaHome
|
||||
val sdkDir = android.sdkDirectory.absolutePath
|
||||
val keyAlias: String
|
||||
val keyPassword: String
|
||||
val storeFile: String
|
||||
val storePassword: String
|
||||
var keyAlias = ""
|
||||
var keyPassword = ""
|
||||
var storeFile = ""
|
||||
var storePassword = ""
|
||||
if (project.properties["android.injected.signing.key.alias"] != null) {
|
||||
keyAlias = project.properties["android.injected.signing.key.alias"] as String
|
||||
keyPassword = project.properties["android.injected.signing.key.password"] as String
|
||||
@@ -186,16 +184,16 @@ tasks {
|
||||
}
|
||||
exec {
|
||||
workingDir("../../../scripts/android")
|
||||
environment = mapOf("JAVA_HOME" to "$javaHome")
|
||||
setEnvironment(mapOf("JAVA_HOME" to "$javaHome"))
|
||||
commandLine = listOf(
|
||||
"./compress-and-sign-apk.sh",
|
||||
"${rootProject.extra["compression.level"]}",
|
||||
"$outputDir",
|
||||
sdkDir,
|
||||
storeFile,
|
||||
storePassword,
|
||||
keyAlias,
|
||||
keyPassword
|
||||
"$sdkDir",
|
||||
"$storeFile",
|
||||
"$storePassword",
|
||||
"$keyAlias",
|
||||
"$keyPassword"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ import androidx.work.*
|
||||
import chat.simplex.app.model.NtfManager
|
||||
import chat.simplex.app.model.NtfManager.AcceptCallAction
|
||||
import chat.simplex.app.views.call.CallActivity
|
||||
import chat.simplex.common.helpers.*
|
||||
import chat.simplex.common.helpers.APPLICATION_ID
|
||||
import chat.simplex.common.helpers.requiresIgnoringBattery
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.model.ChatModel.updatingChatsMutex
|
||||
@@ -290,10 +291,6 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
activeCallDestroyWebView()
|
||||
}
|
||||
|
||||
override fun androidRestartNetworkObserver() {
|
||||
NetworkObserver.shared.restartNetworkObserver()
|
||||
}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Composable
|
||||
override fun androidLockPortraitOrientation() {
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.*
|
||||
import android.os.SystemClock
|
||||
import android.provider.Settings
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
|
||||
+1
-2
@@ -4,7 +4,6 @@ import android.app.*
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.media.AudioAttributes
|
||||
@@ -219,7 +218,7 @@ object NtfManager {
|
||||
.setGroup(MessageGroup)
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
||||
.setSmallIcon(R.drawable.ntf_icon)
|
||||
.setLargeIcon(null as Bitmap?)
|
||||
.setLargeIcon(null)
|
||||
.setColor(0x88FFFF)
|
||||
.setAutoCancel(true)
|
||||
.setVibrate(null)
|
||||
|
||||
+1
-33
@@ -1,9 +1,7 @@
|
||||
package chat.simplex.app.views.call
|
||||
|
||||
import android.Manifest
|
||||
import android.app.*
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.os.*
|
||||
@@ -30,7 +28,6 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import chat.simplex.app.*
|
||||
import chat.simplex.app.R
|
||||
@@ -39,12 +36,10 @@ import chat.simplex.app.model.NtfManager
|
||||
import chat.simplex.app.model.NtfManager.AcceptCallAction
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.platform.chatModel
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.call.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.Clock
|
||||
@@ -114,20 +109,9 @@ class CallActivity: ComponentActivity(), ServiceConnection {
|
||||
m.callCommand.add(WCallCommand.Layout(layoutType))
|
||||
}
|
||||
|
||||
private fun hasGrantedPermissions(): Boolean {
|
||||
val grantedAudio = ContextCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
|
||||
val grantedCamera = !callSupportsVideo() || ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
|
||||
return grantedAudio && grantedCamera
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (isOnLockScreenNow()) {
|
||||
super.onBackPressed()
|
||||
} else if (!hasGrantedPermissions() && !callSupportsVideo()) {
|
||||
val call = m.activeCall.value
|
||||
if (call != null) {
|
||||
withBGApi { chatModel.callManager.endCall(call) }
|
||||
}
|
||||
} else {
|
||||
m.activeCallViewIsCollapsed.value = true
|
||||
}
|
||||
@@ -239,21 +223,8 @@ fun CallActivityView() {
|
||||
}
|
||||
Box(Modifier.background(Color.Black)) {
|
||||
if (call != null) {
|
||||
val permissionsState = rememberMultiplePermissionsState(
|
||||
permissions = if (callSupportsVideo()) {
|
||||
listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
|
||||
} else {
|
||||
listOf(Manifest.permission.RECORD_AUDIO)
|
||||
}
|
||||
)
|
||||
if (permissionsState.allPermissionsGranted) {
|
||||
ActiveCallView()
|
||||
} else {
|
||||
CallPermissionsView(remember { m.activeCallViewIsCollapsed }.value, callSupportsVideo()) {
|
||||
withBGApi { chatModel.callManager.endCall(call) }
|
||||
}
|
||||
}
|
||||
val view = LocalView.current
|
||||
ActiveCallView()
|
||||
if (callSupportsVideo()) {
|
||||
val scope = rememberCoroutineScope()
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -281,9 +252,6 @@ fun CallActivityView() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!m.activeCallViewIsCollapsed.value) {
|
||||
AlertManager.shared.showInView()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(call == null) {
|
||||
if (call != null) {
|
||||
|
||||
@@ -47,9 +47,6 @@ buildscript {
|
||||
classpath(kotlin("gradle-plugin", version = rootProject.extra["kotlin.version"] as String))
|
||||
classpath("dev.icerock.moko:resources-generator:0.23.0")
|
||||
|
||||
// Workaround gradle sync issue: https://github.com/gmazzo/gradle-buildconfig-plugin/issues/137#issuecomment-1935739759
|
||||
classpath("com.squareup:kotlinpoet:1.16.0")
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
@@ -83,30 +80,6 @@ plugins {
|
||||
id("org.jetbrains.kotlin.plugin.serialization") apply false
|
||||
}
|
||||
|
||||
// https://raymondctc.medium.com/configuring-your-sourcecompatibility-targetcompatibility-and-kotlinoptions-jvmtarget-all-at-once-66bf2198145f
|
||||
val jvmVersion: Provider<String> = providers.gradleProperty("kotlin.jvm.target")
|
||||
|
||||
configure(subprojects) {
|
||||
// Apply compileOptions to subprojects
|
||||
plugins.withType<com.android.build.gradle.BasePlugin>().configureEach {
|
||||
extensions.findByType<com.android.build.gradle.BaseExtension>()?.apply {
|
||||
jvmVersion.map { JavaVersion.toVersion(it) }.orNull?.let {
|
||||
compileOptions {
|
||||
sourceCompatibility = it
|
||||
targetCompatibility = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply kotlinOptions.jvmTarget to subprojects
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
if (jvmVersion.isPresent) jvmTarget = jvmVersion.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete::class) {
|
||||
delete(rootProject.buildDir)
|
||||
}
|
||||
|
||||
@@ -4,16 +4,17 @@ plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
id("dev.icerock.mobile.multiplatform-resources")
|
||||
id("com.github.gmazzo.buildconfig") version "5.3.5"
|
||||
id("com.github.gmazzo.buildconfig") version "4.0.4"
|
||||
}
|
||||
|
||||
group = "chat.simplex"
|
||||
version = extra["android.version_name"] as String
|
||||
|
||||
kotlin {
|
||||
androidTarget()
|
||||
jvm("desktop")
|
||||
applyDefaultHierarchyTemplate()
|
||||
android()
|
||||
jvm("desktop") {
|
||||
jvmToolchain(11)
|
||||
}
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings {
|
||||
@@ -34,20 +35,20 @@ kotlin {
|
||||
api(compose.runtime)
|
||||
api(compose.foundation)
|
||||
api(compose.material)
|
||||
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
|
||||
api("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
|
||||
api("com.russhwolf:multiplatform-settings:1.1.1")
|
||||
api("com.charleskorn.kaml:kaml:0.58.0")
|
||||
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
|
||||
api("org.jetbrains.kotlinx:kotlinx-datetime:0.3.2")
|
||||
api("com.russhwolf:multiplatform-settings:1.0.0")
|
||||
api("com.charleskorn.kaml:kaml:0.43.0")
|
||||
api("dev.icerock.moko:resources-compose:0.23.0")
|
||||
api("org.jetbrains.compose.ui:ui-text:${rootProject.extra["compose.version"] as String}")
|
||||
implementation("org.jetbrains.compose.components:components-animatedimage:${rootProject.extra["compose.version"] as String}")
|
||||
//Barcode
|
||||
api("org.boofcv:boofcv-core:1.1.3")
|
||||
api("org.boofcv:boofcv-core:0.40.1")
|
||||
implementation("com.godaddy.android.colorpicker:compose-color-picker-jvm:0.7.0")
|
||||
// Link Previews
|
||||
implementation("org.jsoup:jsoup:1.17.2")
|
||||
implementation("org.jsoup:jsoup:1.13.1")
|
||||
// Resources
|
||||
api("dev.icerock.moko:resources:0.23.0")
|
||||
api("dev.icerock.moko:resources-compose:0.23.0")
|
||||
implementation("dev.icerock.moko:resources:0.23.0")
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
@@ -56,50 +57,47 @@ kotlin {
|
||||
}
|
||||
}
|
||||
val androidMain by getting {
|
||||
kotlin.srcDir("build/generated/moko/androidMain/src")
|
||||
dependencies {
|
||||
implementation("androidx.activity:activity-compose:1.8.2")
|
||||
val workVersion = "2.9.0"
|
||||
implementation("androidx.work:work-runtime-ktx:$workVersion")
|
||||
implementation("com.google.accompanist:accompanist-insets:0.30.1")
|
||||
implementation("androidx.activity:activity-compose:1.5.0")
|
||||
val work_version = "2.7.1"
|
||||
implementation("androidx.work:work-runtime-ktx:$work_version")
|
||||
implementation("com.google.accompanist:accompanist-insets:0.23.0")
|
||||
implementation("dev.icerock.moko:resources:0.23.0")
|
||||
|
||||
// Video support
|
||||
implementation("com.google.android.exoplayer:exoplayer:2.19.1")
|
||||
implementation("com.google.android.exoplayer:exoplayer:2.17.1")
|
||||
|
||||
// Biometric authentication
|
||||
implementation("androidx.biometric:biometric:1.2.0-alpha05")
|
||||
implementation("androidx.biometric:biometric:1.2.0-alpha04")
|
||||
|
||||
//Barcode
|
||||
implementation("org.boofcv:boofcv-android:1.1.3")
|
||||
implementation("org.boofcv:boofcv-android:0.40.1")
|
||||
|
||||
//Camera Permission
|
||||
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
|
||||
implementation("com.google.accompanist:accompanist-permissions:0.23.0")
|
||||
|
||||
implementation("androidx.webkit:webkit:1.10.0")
|
||||
implementation("androidx.webkit:webkit:1.4.0")
|
||||
|
||||
// GIFs support
|
||||
implementation("io.coil-kt:coil-compose:2.6.0")
|
||||
implementation("io.coil-kt:coil-gif:2.6.0")
|
||||
implementation("io.coil-kt:coil-compose:2.1.0")
|
||||
implementation("io.coil-kt:coil-gif:2.1.0")
|
||||
|
||||
implementation("com.jakewharton:process-phoenix:2.2.0")
|
||||
implementation("com.jakewharton:process-phoenix:2.1.2")
|
||||
|
||||
val cameraXVersion = "1.3.2"
|
||||
implementation("androidx.camera:camera-core:${cameraXVersion}")
|
||||
implementation("androidx.camera:camera-camera2:${cameraXVersion}")
|
||||
implementation("androidx.camera:camera-lifecycle:${cameraXVersion}")
|
||||
implementation("androidx.camera:camera-view:${cameraXVersion}")
|
||||
|
||||
// Calls lifecycle listener
|
||||
implementation("androidx.lifecycle:lifecycle-process:2.4.1")
|
||||
val camerax_version = "1.1.0-beta01"
|
||||
implementation("androidx.camera:camera-core:${camerax_version}")
|
||||
implementation("androidx.camera:camera-camera2:${camerax_version}")
|
||||
implementation("androidx.camera:camera-lifecycle:${camerax_version}")
|
||||
implementation("androidx.camera:camera-view:${camerax_version}")
|
||||
}
|
||||
}
|
||||
val desktopMain by getting {
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.0")
|
||||
implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.7.1")
|
||||
implementation("com.github.Dansoftowner:jSystemThemeDetector:3.6")
|
||||
implementation("com.sshtools:two-slices:0.9.0-SNAPSHOT")
|
||||
implementation("org.slf4j:slf4j-simple:2.0.12")
|
||||
implementation("uk.co.caprica:vlcj:4.8.2")
|
||||
implementation("org.slf4j:slf4j-simple:2.0.7")
|
||||
implementation("uk.co.caprica:vlcj:4.7.3")
|
||||
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a")
|
||||
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a")
|
||||
}
|
||||
@@ -109,21 +107,23 @@ kotlin {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "chat.simplex.common"
|
||||
compileSdk = 34
|
||||
compileSdkVersion(34)
|
||||
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
|
||||
defaultConfig {
|
||||
minSdk = 28
|
||||
minSdkVersion(28)
|
||||
targetSdkVersion(33)
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
testOptions.targetSdk = 33
|
||||
lint.targetSdk = 33
|
||||
val isAndroid = gradle.startParameter.taskNames.find {
|
||||
val lower = it.lowercase()
|
||||
val lower = it.toLowerCase()
|
||||
lower.contains("release") || lower.startsWith("assemble") || lower.startsWith("install")
|
||||
} != null
|
||||
if (isAndroid) {
|
||||
// This is not needed on Android but can't be moved to desktopMain because MR lib don't support this.
|
||||
// No other ways to exclude a file work, but it's large and should be excluded
|
||||
// No other ways to exclude a file work but it's large and should be excluded
|
||||
kotlin.sourceSets["commonMain"].resources.exclude("/MR/fonts/NotoColorEmoji-Regular.ttf")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="chat.simplex.common">
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
</manifest>
|
||||
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
package chat.simplex.common.helpers
|
||||
|
||||
import android.net.*
|
||||
import android.util.Log
|
||||
import androidx.core.content.getSystemService
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.model.UserNetworkInfo
|
||||
import chat.simplex.common.model.UserNetworkType
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.helpers.withBGApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class NetworkObserver {
|
||||
private var prevInfo: UserNetworkInfo? = null
|
||||
|
||||
// When having both mobile and Wi-Fi networks enabled with Wi-Fi being active, then disabling Wi-Fi, network reports its offline (which is true)
|
||||
// but since it will be online after switching to mobile, there is no need to inform backend about such temporary change.
|
||||
// But if it will not be online after some seconds, report it and apply required measures
|
||||
private var noNetworkJob = Job() as Job
|
||||
private val networkCallback = object: ConnectivityManager.NetworkCallback() {
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) = networkCapabilitiesChanged(networkCapabilities)
|
||||
override fun onLost(network: Network) = networkLost()
|
||||
}
|
||||
private val connectivityManager: ConnectivityManager? = androidAppContext.getSystemService()
|
||||
|
||||
fun restartNetworkObserver() {
|
||||
if (connectivityManager == null) {
|
||||
Log.e(TAG, "Connectivity manager is unavailable, network observer is disabled")
|
||||
val info = UserNetworkInfo(
|
||||
networkType = UserNetworkType.OTHER,
|
||||
online = true,
|
||||
)
|
||||
prevInfo = info
|
||||
setNetworkInfo(info)
|
||||
return
|
||||
}
|
||||
try {
|
||||
connectivityManager.unregisterNetworkCallback(networkCallback)
|
||||
} catch (e: Exception) {
|
||||
// do nothing
|
||||
}
|
||||
val initialCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||
if (initialCapabilities != null) {
|
||||
networkCapabilitiesChanged(initialCapabilities)
|
||||
} else {
|
||||
networkLost()
|
||||
}
|
||||
try {
|
||||
connectivityManager.registerDefaultNetworkCallback(networkCallback)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error registering network callback: ${e.stackTraceToString()}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun networkCapabilitiesChanged(capabilities: NetworkCapabilities) {
|
||||
connectivityManager ?: return
|
||||
val info = UserNetworkInfo(
|
||||
networkType = networkTypeFromCapabilities(capabilities),
|
||||
online = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED),
|
||||
)
|
||||
if (prevInfo != info) {
|
||||
prevInfo = info
|
||||
setNetworkInfo(info)
|
||||
}
|
||||
}
|
||||
|
||||
private fun networkLost() {
|
||||
Log.d(TAG, "Network changed: lost")
|
||||
val none = UserNetworkInfo(networkType = UserNetworkType.NONE, false)
|
||||
prevInfo = none
|
||||
setNetworkInfo(none)
|
||||
}
|
||||
|
||||
private fun setNetworkInfo(info: UserNetworkInfo) {
|
||||
Log.d(TAG, "Network changed: $info")
|
||||
noNetworkJob.cancel()
|
||||
if (info.online) {
|
||||
withBGApi {
|
||||
if (controller.hasChatCtrl() && controller.apiSetNetworkInfo(info)) {
|
||||
chatModel.networkInfo.value = info
|
||||
}
|
||||
}
|
||||
} else {
|
||||
noNetworkJob = withBGApi {
|
||||
delay(3000)
|
||||
if (controller.hasChatCtrl() && controller.apiSetNetworkInfo(info)) {
|
||||
chatModel.networkInfo.value = info
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun networkTypeFromCapabilities(capabilities: NetworkCapabilities): UserNetworkType = when {
|
||||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> UserNetworkType.ETHERNET
|
||||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> UserNetworkType.WIFI
|
||||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> UserNetworkType.CELLULAR
|
||||
else -> UserNetworkType.OTHER
|
||||
}
|
||||
|
||||
companion object {
|
||||
val shared = NetworkObserver()
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package chat.simplex.common.helpers
|
||||
|
||||
import android.content.*
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.helpers.AlertManager
|
||||
import chat.simplex.common.views.helpers.generalGetString
|
||||
import chat.simplex.res.MR
|
||||
|
||||
fun Context.openAppSettingsInSystem() {
|
||||
Intent().apply {
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
data = Uri.parse("package:${androidAppContext.packageName}")
|
||||
try {
|
||||
startActivity(this)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e(TAG, e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.showAllowPermissionInSettingsAlert(action: () -> Unit = ::openAppSettingsInSystem) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.permissions_grant_in_settings),
|
||||
text = generalGetString(MR.strings.permissions_find_in_settings_and_grant),
|
||||
confirmText = generalGetString(MR.strings.permissions_open_settings),
|
||||
onConfirm = action,
|
||||
)
|
||||
}
|
||||
+6
-84
@@ -2,16 +2,17 @@ package chat.simplex.common.helpers
|
||||
|
||||
import android.media.*
|
||||
import android.net.Uri
|
||||
import android.os.*
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import androidx.core.content.ContextCompat
|
||||
import chat.simplex.common.R
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.helpers.withApi
|
||||
import chat.simplex.common.platform.SoundPlayerInterface
|
||||
import chat.simplex.common.platform.androidAppContext
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
object SoundPlayer: SoundPlayerInterface {
|
||||
private var player: MediaPlayer? = null
|
||||
private var playing = false
|
||||
var playing = false
|
||||
|
||||
override fun start(scope: CoroutineScope, sound: Boolean) {
|
||||
player?.reset()
|
||||
@@ -31,7 +32,7 @@ object SoundPlayer: SoundPlayerInterface {
|
||||
scope.launch {
|
||||
while (playing) {
|
||||
if (sound) player?.start()
|
||||
vibrator?.vibrateApiVersionAware(effect)
|
||||
vibrator?.vibrate(effect)
|
||||
delay(3500)
|
||||
}
|
||||
}
|
||||
@@ -42,82 +43,3 @@ object SoundPlayer: SoundPlayerInterface {
|
||||
player?.stop()
|
||||
}
|
||||
}
|
||||
|
||||
object CallSoundsPlayer: CallSoundsPlayerInterface {
|
||||
private var player: MediaPlayer? = null
|
||||
private var playingJob: Job? = null
|
||||
|
||||
private fun start(soundPath: String, delay: Long, scope: CoroutineScope) {
|
||||
playingJob?.cancel()
|
||||
player?.reset()
|
||||
player = MediaPlayer().apply {
|
||||
setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
|
||||
.build()
|
||||
)
|
||||
setDataSource(androidAppContext, Uri.parse(soundPath))
|
||||
prepare()
|
||||
}
|
||||
if (delay < 1000) {
|
||||
player?.isLooping = true
|
||||
player?.start()
|
||||
return
|
||||
}
|
||||
playingJob = scope.launch {
|
||||
while (isActive) {
|
||||
player?.start()
|
||||
delay(delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun startConnectingCallSound(scope: CoroutineScope) {
|
||||
// Taken from https://github.com/TelegramOrg/Telegram-Android
|
||||
// https://github.com/TelegramOrg/Telegram-Android/blob/master/LICENSE
|
||||
start("android.resource://" + androidAppContext.packageName + "/" + R.raw.connecting_call, 0, scope)
|
||||
}
|
||||
|
||||
override fun startInCallSound(scope: CoroutineScope) {
|
||||
start("android.resource://" + androidAppContext.packageName + "/" + R.raw.in_call, 2000, scope)
|
||||
}
|
||||
|
||||
override fun vibrate(times: Int) {
|
||||
val vibrator = ContextCompat.getSystemService(androidAppContext, Vibrator::class.java)
|
||||
val effect = VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)
|
||||
vibrator?.vibrateApiVersionAware(effect)
|
||||
repeat(times - 1) {
|
||||
withApi {
|
||||
delay(50)
|
||||
vibrator?.vibrateApiVersionAware(effect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
playingJob?.cancel()
|
||||
player?.stop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Vibrator.vibrateApiVersionAware(effect: VibrationEffect) {
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
vibrate(
|
||||
effect,
|
||||
VibrationAttributes.Builder()
|
||||
.setUsage(VibrationAttributes.USAGE_ALARM)
|
||||
.build()
|
||||
)
|
||||
} else if (Build.VERSION.SDK_INT >= 29) {
|
||||
vibrate(
|
||||
effect,
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
.setUsage(AudioAttributes.USAGE_ALARM)
|
||||
.build()
|
||||
)
|
||||
} else {
|
||||
vibrate(effect)
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -296,7 +296,6 @@ actual object AudioPlayer: AudioPlayerInterface {
|
||||
}
|
||||
|
||||
actual typealias SoundPlayer = chat.simplex.common.helpers.SoundPlayer
|
||||
actual typealias CallSoundsPlayer = chat.simplex.common.helpers.CallSoundsPlayer
|
||||
|
||||
class CryptoMediaSource(val data: ByteArray) : MediaDataSource() {
|
||||
override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int {
|
||||
|
||||
+62
-180
@@ -1,7 +1,5 @@
|
||||
package chat.simplex.common.views.call
|
||||
|
||||
import SectionSpacer
|
||||
import SectionView
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
@@ -26,18 +24,17 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.*
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.webkit.WebViewAssetLoader
|
||||
import androidx.webkit.WebViewClientCompat
|
||||
import chat.simplex.common.helpers.showAllowPermissionInSettingsAlert
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.model.ChatModel
|
||||
@@ -45,12 +42,13 @@ import chat.simplex.common.model.Contact
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import com.google.accompanist.permissions.*
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
|
||||
// Should be destroy()'ed and set as null when call is ended. Otherwise, it will be a leak
|
||||
@@ -69,8 +67,6 @@ fun activeCallDestroyWebView() = withApi {
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Composable
|
||||
actual fun ActiveCallView() {
|
||||
val call = remember { chatModel.activeCall }.value
|
||||
val scope = rememberCoroutineScope()
|
||||
val audioViaBluetooth = rememberSaveable { mutableStateOf(false) }
|
||||
val proximityLock = remember {
|
||||
val pm = (androidAppContext.getSystemService(Context.POWER_SERVICE) as PowerManager)
|
||||
@@ -80,13 +76,6 @@ actual fun ActiveCallView() {
|
||||
null
|
||||
}
|
||||
}
|
||||
val wasConnected = rememberSaveable { mutableStateOf(false) }
|
||||
LaunchedEffect(call) {
|
||||
if (call?.callState == CallState.Connected && !wasConnected.value) {
|
||||
CallSoundsPlayer.vibrate(2)
|
||||
wasConnected.value = true
|
||||
}
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
var btDeviceCount = 0
|
||||
@@ -116,10 +105,6 @@ actual fun ActiveCallView() {
|
||||
}
|
||||
am.registerAudioDeviceCallback(audioCallback, null)
|
||||
onDispose {
|
||||
CallSoundsPlayer.stop()
|
||||
if (wasConnected.value) {
|
||||
CallSoundsPlayer.vibrate()
|
||||
}
|
||||
dropAudioManagerOverrides()
|
||||
am.unregisterAudioDeviceCallback(audioCallback)
|
||||
if (proximityLock?.isHeld == true) {
|
||||
@@ -135,6 +120,8 @@ actual fun ActiveCallView() {
|
||||
if (proximityLock?.isHeld == false) proximityLock.acquire()
|
||||
}
|
||||
}
|
||||
val scope = rememberCoroutineScope()
|
||||
val call = chatModel.activeCall.value
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
WebRTCView(chatModel.callCommand) { apiMsg ->
|
||||
Log.d(TAG, "received from WebRTCView: $apiMsg")
|
||||
@@ -147,9 +134,6 @@ actual fun ActiveCallView() {
|
||||
val callType = CallType(call.localMedia, r.capabilities)
|
||||
chatModel.controller.apiSendCallInvitation(callRh, call.contact, callType)
|
||||
updateActiveCall(call) { it.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities) }
|
||||
setCallSound(call.soundSpeaker, audioViaBluetooth)
|
||||
CallSoundsPlayer.startConnectingCallSound(scope)
|
||||
activeCallWaitDeliveryReceipt(scope)
|
||||
}
|
||||
is WCallResponse.Offer -> withBGApi {
|
||||
chatModel.controller.apiSendCallOffer(callRh, call.contact, r.offer, r.iceCandidates, call.localMedia, r.capabilities)
|
||||
@@ -158,7 +142,6 @@ actual fun ActiveCallView() {
|
||||
is WCallResponse.Answer -> withBGApi {
|
||||
chatModel.controller.apiSendCallAnswer(callRh, call.contact, r.answer, r.iceCandidates)
|
||||
updateActiveCall(call) { it.copy(callState = CallState.Negotiated) }
|
||||
CallSoundsPlayer.stop()
|
||||
}
|
||||
is WCallResponse.Ice -> withBGApi {
|
||||
chatModel.controller.apiSendCallExtraInfo(callRh, call.contact, r.iceCandidates)
|
||||
@@ -226,6 +209,7 @@ actual fun ActiveCallView() {
|
||||
ActiveCallOverlay(call, chatModel, audioViaBluetooth)
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
DisposableEffect(Unit) {
|
||||
val activity = context as? Activity ?: return@DisposableEffect onDispose {}
|
||||
@@ -526,138 +510,34 @@ private fun DisabledBackgroundCallsButton() {
|
||||
// }
|
||||
//}
|
||||
|
||||
@Composable
|
||||
fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Unit) {
|
||||
val audioPermission = rememberPermissionState(Manifest.permission.RECORD_AUDIO)
|
||||
val cameraPermission = rememberPermissionState(Manifest.permission.CAMERA)
|
||||
val permissionsState = rememberMultiplePermissionsState(
|
||||
permissions = if (hasVideo) {
|
||||
listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
|
||||
} else {
|
||||
listOf(Manifest.permission.RECORD_AUDIO)
|
||||
}
|
||||
)
|
||||
val context = LocalContext.current
|
||||
val buttonEnabled = remember { mutableStateOf(true) }
|
||||
LaunchedEffect(Unit) {
|
||||
if (!pipActive) {
|
||||
permissionsState.launchMultiplePermissionRequestWithFallback(buttonEnabled, context::showAllowPermissionInSettingsAlert)
|
||||
}
|
||||
}
|
||||
|
||||
if (pipActive) {
|
||||
Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.SpaceEvenly) {
|
||||
if (audioPermission.status is PermissionStatus.Denied) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_call_500),
|
||||
stringResource(MR.strings.permissions_record_audio),
|
||||
Modifier.size(24.dp),
|
||||
tint = Color(0xFFFFFFD8)
|
||||
)
|
||||
}
|
||||
if (hasVideo && cameraPermission.status is PermissionStatus.Denied) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_videocam),
|
||||
stringResource(MR.strings.permissions_camera),
|
||||
Modifier.size(24.dp),
|
||||
tint = Color(0xFFFFFFD8)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ColumnWithScrollBar(Modifier.fillMaxSize()) {
|
||||
Spacer(Modifier.height(AppBarHeight))
|
||||
|
||||
AppBarTitle(stringResource(MR.strings.permissions_required))
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
val onClick = {
|
||||
if (permissionsState.shouldShowRationale) {
|
||||
context.showAllowPermissionInSettingsAlert()
|
||||
} else {
|
||||
permissionsState.launchMultiplePermissionRequestWithFallback(buttonEnabled, context::showAllowPermissionInSettingsAlert)
|
||||
}
|
||||
}
|
||||
Text(stringResource(MR.strings.permissions_grant), Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), textAlign = TextAlign.Center, color = Color(0xFFFFFFD8))
|
||||
SectionSpacer()
|
||||
SectionView {
|
||||
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
val text = if (hasVideo && audioPermission.status is PermissionStatus.Denied && cameraPermission.status is PermissionStatus.Denied) {
|
||||
stringResource(MR.strings.permissions_camera_and_record_audio)
|
||||
} else if (audioPermission.status is PermissionStatus.Denied) {
|
||||
stringResource(MR.strings.permissions_record_audio)
|
||||
} else if (hasVideo && cameraPermission.status is PermissionStatus.Denied) {
|
||||
stringResource(MR.strings.permissions_camera)
|
||||
} else ""
|
||||
GrantPermissionButton(text, buttonEnabled.value, onClick)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
Box(Modifier.fillMaxWidth().padding(bottom = if (hasVideo) 0.dp else DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.Center) {
|
||||
SimpleButtonFrame(cancel, Modifier.height(64.dp)) {
|
||||
Text(stringResource(MR.strings.call_service_notification_end_call), fontSize = 20.sp, color = Color(0xFFFFFFD8))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GrantPermissionButton(text: String, enabled: Boolean, onClick: () -> Unit) {
|
||||
Row(
|
||||
Modifier
|
||||
.clickable(enabled = enabled, onClick = onClick)
|
||||
.heightIn(min = 30.dp)
|
||||
.background(WarningOrange.copy(0.3f), RoundedCornerShape(50)),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(text, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), fontSize = 20.sp, color = WarningOrange)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The idea of this function is to ask system to show permission dialog and to see if it's really doing it.
|
||||
* Otherwise, show alert with a button that opens settings for manual permission granting
|
||||
* */
|
||||
private fun MultiplePermissionsState.launchMultiplePermissionRequestWithFallback(buttonEnabled: MutableState<Boolean>, fallback: () -> Unit) {
|
||||
buttonEnabled.value = false
|
||||
val lifecycleOwner = ProcessLifecycleOwner.get().lifecycle
|
||||
var useFallback = true
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_PAUSE) {
|
||||
useFallback = false
|
||||
buttonEnabled.value = true
|
||||
}
|
||||
}
|
||||
lifecycleOwner.addObserver(observer)
|
||||
withBGApi {
|
||||
delay(2000)
|
||||
if (useFallback && chatModel.activeCall.value != null) {
|
||||
fallback()
|
||||
}
|
||||
buttonEnabled.value = true
|
||||
}.invokeOnCompletion {
|
||||
// Main thread only
|
||||
withApi {
|
||||
lifecycleOwner.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
launchMultiplePermissionRequest()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIMessage) -> Unit) {
|
||||
val webView = remember { mutableStateOf<WebView?>(null) }
|
||||
val permissionsState = rememberMultiplePermissionsState(
|
||||
permissions = listOf(
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.MODIFY_AUDIO_SETTINGS,
|
||||
Manifest.permission.INTERNET
|
||||
)
|
||||
)
|
||||
fun processCommand(wv: WebView, cmd: WCallCommand) {
|
||||
val apiCall = WVAPICall(command = cmd)
|
||||
wv.evaluateJavascript("processCommand(${json.encodeToString(apiCall)})", null)
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
DisposableEffect(lifecycleOwner) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_RESUME || event == Lifecycle.Event.ON_START) {
|
||||
permissionsState.launchMultiplePermissionRequest()
|
||||
}
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose {
|
||||
// val wv = webView.value
|
||||
// if (wv != null) processCommand(wv, WCallCommand.End)
|
||||
// webView.value?.destroy()
|
||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
||||
// val wv = webView.value
|
||||
// if (wv != null) processCommand(wv, WCallCommand.End)
|
||||
// webView.value?.destroy()
|
||||
webView.value = null
|
||||
}
|
||||
}
|
||||
@@ -680,42 +560,44 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIM
|
||||
.addPathHandler("/assets/www/", WebViewAssetLoader.AssetsPathHandler(LocalContext.current))
|
||||
.build()
|
||||
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
AndroidView(
|
||||
factory = { AndroidViewContext ->
|
||||
(staticWebView ?: WebView(androidAppContext)).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
)
|
||||
this.webChromeClient = object: WebChromeClient() {
|
||||
override fun onPermissionRequest(request: PermissionRequest) {
|
||||
if (request.origin.toString().startsWith("file:/")) {
|
||||
request.grant(request.resources)
|
||||
} else {
|
||||
Log.d(TAG, "Permission request from webview denied.")
|
||||
request.deny()
|
||||
if (permissionsState.allPermissionsGranted) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
AndroidView(
|
||||
factory = { AndroidViewContext ->
|
||||
(staticWebView ?: WebView(androidAppContext)).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
)
|
||||
this.webChromeClient = object: WebChromeClient() {
|
||||
override fun onPermissionRequest(request: PermissionRequest) {
|
||||
if (request.origin.toString().startsWith("file:/")) {
|
||||
request.grant(request.resources)
|
||||
} else {
|
||||
Log.d(TAG, "Permission request from webview denied.")
|
||||
request.deny()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.webViewClient = LocalContentWebViewClient(webView, assetLoader)
|
||||
this.clearHistory()
|
||||
this.clearCache(true)
|
||||
this.addJavascriptInterface(WebRTCInterface(onResponse), "WebRTCInterface")
|
||||
val webViewSettings = this.settings
|
||||
webViewSettings.allowFileAccess = true
|
||||
webViewSettings.allowContentAccess = true
|
||||
webViewSettings.javaScriptEnabled = true
|
||||
webViewSettings.mediaPlaybackRequiresUserGesture = false
|
||||
webViewSettings.cacheMode = WebSettings.LOAD_NO_CACHE
|
||||
if (staticWebView == null) {
|
||||
this.loadUrl("file:android_asset/www/android/call.html")
|
||||
} else {
|
||||
webView.value = this
|
||||
this.webViewClient = LocalContentWebViewClient(webView, assetLoader)
|
||||
this.clearHistory()
|
||||
this.clearCache(true)
|
||||
this.addJavascriptInterface(WebRTCInterface(onResponse), "WebRTCInterface")
|
||||
val webViewSettings = this.settings
|
||||
webViewSettings.allowFileAccess = true
|
||||
webViewSettings.allowContentAccess = true
|
||||
webViewSettings.javaScriptEnabled = true
|
||||
webViewSettings.mediaPlaybackRequiresUserGesture = false
|
||||
webViewSettings.cacheMode = WebSettings.LOAD_NO_CACHE
|
||||
if (staticWebView == null) {
|
||||
this.loadUrl("file:android_asset/www/android/call.html")
|
||||
} else {
|
||||
webView.value = this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) { /* WebView */ }
|
||||
) { /* WebView */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import chat.simplex.common.helpers.toUri
|
||||
import chat.simplex.common.model.CIFile
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.helpers.ModalManager
|
||||
@@ -14,6 +15,7 @@ import coil.compose.rememberAsyncImagePainter
|
||||
import coil.decode.GifDecoder
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.request.ImageRequest
|
||||
import java.net.URI
|
||||
|
||||
@Composable
|
||||
actual fun SimpleAndAnimatedImageView(
|
||||
@@ -41,7 +43,6 @@ actual fun SimpleAndAnimatedImageView(
|
||||
}
|
||||
|
||||
private val imageLoader = ImageLoader.Builder(androidAppContext)
|
||||
.networkObserverEnabled(false)
|
||||
.components {
|
||||
if (SDK_INT >= 28) {
|
||||
add(ImageDecoderDecoder.Factory())
|
||||
|
||||
+1
-2
@@ -15,7 +15,6 @@ import chat.simplex.common.platform.FileChooserLauncher
|
||||
import chat.simplex.common.platform.saveImage
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import com.google.accompanist.permissions.PermissionStatus
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
@@ -31,7 +30,7 @@ actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserL
|
||||
ItemAction(stringResource(MR.strings.save_verb), painterResource(if (cItem.file?.fileSource?.cryptoArgs == null) MR.images.ic_download else MR.images.ic_lock_open_right), onClick = {
|
||||
when (cItem.content.msgContent) {
|
||||
is MsgContent.MCImage -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || writePermissionState.status == PermissionStatus.Granted) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || writePermissionState.hasPermission) {
|
||||
saveImage(cItem.file)
|
||||
} else {
|
||||
writePermissionState.launchPermissionRequest()
|
||||
|
||||
+13
-14
@@ -3,7 +3,7 @@ package chat.simplex.common.views.chat.item
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
@@ -11,8 +11,8 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.isVisible
|
||||
import chat.simplex.common.helpers.toUri
|
||||
import chat.simplex.common.platform.VideoPlayer
|
||||
import chat.simplex.common.platform.androidAppContext
|
||||
import chat.simplex.res.MR
|
||||
import coil.ImageLoader
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
@@ -23,11 +23,21 @@ import coil.size.Size
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import java.net.URI
|
||||
|
||||
@Composable
|
||||
actual fun FullScreenImageView(modifier: Modifier, data: ByteArray, imageBitmap: ImageBitmap) {
|
||||
// I'm using a new private instance of imageLoader here because if I use one instance in multiple places
|
||||
// I'm making a new instance of imageLoader here because if I use one instance in multiple places
|
||||
// after end of composition here a GIF from the first instance will be paused automatically which isn't what I want
|
||||
val imageLoader = ImageLoader.Builder(LocalContext.current)
|
||||
.components {
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
add(ImageDecoderDecoder.Factory())
|
||||
} else {
|
||||
add(GifDecoder.Factory())
|
||||
}
|
||||
}
|
||||
.build()
|
||||
Image(
|
||||
rememberAsyncImagePainter(
|
||||
ImageRequest.Builder(LocalContext.current).data(data = data).size(Size.ORIGINAL).build(),
|
||||
@@ -63,14 +73,3 @@ actual fun FullScreenVideoView(player: VideoPlayer, modifier: Modifier, close: (
|
||||
modifier
|
||||
)
|
||||
}
|
||||
|
||||
private val imageLoader = ImageLoader.Builder(androidAppContext)
|
||||
.networkObserverEnabled(false)
|
||||
.components {
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
add(ImageDecoderDecoder.Factory())
|
||||
} else {
|
||||
add(GifDecoder.Factory())
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
+4
-8
@@ -11,7 +11,6 @@ import androidx.camera.view.PreviewView
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
@@ -28,7 +27,6 @@ import chat.simplex.common.platform.TAG
|
||||
import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import com.google.accompanist.permissions.PermissionStatus
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
@@ -67,7 +65,7 @@ actual fun QRCodeScanner(
|
||||
.widthIn(max = 400.dp)
|
||||
.aspectRatio(1f)
|
||||
val showScanner = remember { showQRCodeScanner }
|
||||
if (showScanner.value && cameraPermissionState.status == PermissionStatus.Granted) {
|
||||
if (showScanner.value && cameraPermissionState.hasPermission) {
|
||||
AndroidView(
|
||||
factory = { AndroidViewContext ->
|
||||
PreviewView(AndroidViewContext).apply {
|
||||
@@ -128,22 +126,20 @@ actual fun QRCodeScanner(
|
||||
disabledBackgroundColor = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.9f),
|
||||
disabledContentColor = MaterialTheme.colors.primary,
|
||||
)
|
||||
var permissionRequested by rememberSaveable { mutableStateOf(false) }
|
||||
when {
|
||||
cameraPermissionState.status is PermissionStatus.Denied && !permissionRequested && showScanner.value -> {
|
||||
!cameraPermissionState.hasPermission && !cameraPermissionState.permissionRequested && showScanner.value -> {
|
||||
LaunchedEffect(Unit) {
|
||||
permissionRequested = true
|
||||
cameraPermissionState.launchPermissionRequest()
|
||||
}
|
||||
}
|
||||
cameraPermissionState.status is PermissionStatus.Denied -> {
|
||||
!cameraPermissionState.hasPermission -> {
|
||||
Button({ cameraPermissionState.launchPermissionRequest() }, modifier = modifier, colors = buttonColors) {
|
||||
Icon(painterResource(MR.images.ic_camera_enhance), null)
|
||||
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
|
||||
Text(stringResource(MR.strings.enable_camera_access))
|
||||
}
|
||||
}
|
||||
cameraPermissionState.status == PermissionStatus.Granted -> {
|
||||
cameraPermissionState.hasPermission -> {
|
||||
Button({ showQRCodeScanner.value = true }, modifier = modifier, colors = buttonColors) {
|
||||
Icon(painterResource(MR.images.ic_qr_code), null)
|
||||
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
|
||||
|
||||
+2
-3
@@ -5,15 +5,14 @@ import android.os.Build
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import chat.simplex.common.platform.ntfManager
|
||||
import com.google.accompanist.permissions.PermissionStatus
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
|
||||
@Composable
|
||||
actual fun SetNotificationsModeAdditions() {
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
val notificationsPermissionState = rememberPermissionState(Manifest.permission.POST_NOTIFICATIONS)
|
||||
LaunchedEffect(notificationsPermissionState.status == PermissionStatus.Granted) {
|
||||
if (notificationsPermissionState.status == PermissionStatus.Granted) {
|
||||
LaunchedEffect(notificationsPermissionState.hasPermission) {
|
||||
if (notificationsPermissionState.hasPermission) {
|
||||
ntfManager.androidCreateNtfChannelsMaybeShowAlert()
|
||||
} else {
|
||||
notificationsPermissionState.launchPermissionRequest()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -71,7 +71,7 @@ if(NOT APPLE)
|
||||
else()
|
||||
# Without direct linking it can't find hs_init in linking step
|
||||
add_library( rts SHARED IMPORTED )
|
||||
FILE(GLOB RTSLIB ${CMAKE_SOURCE_DIR}/libs/${OS_LIB_PATH}-${OS_LIB_ARCH}/libHSrts*_thr-*.${OS_LIB_EXT})
|
||||
FILE(GLOB RTSLIB ${CMAKE_SOURCE_DIR}/libs/${OS_LIB_PATH}-${OS_LIB_ARCH}/deps/libHSrts*_thr-*.${OS_LIB_EXT})
|
||||
set_target_properties( rts PROPERTIES IMPORTED_LOCATION ${RTSLIB})
|
||||
|
||||
target_link_libraries(app-lib rts simplex)
|
||||
|
||||
+10
-68
@@ -118,7 +118,6 @@ object ChatModel {
|
||||
val simplexLinkMode by lazy { mutableStateOf(ChatController.appPrefs.simplexLinkMode.get()) }
|
||||
|
||||
val clipboardHasText = mutableStateOf(false)
|
||||
val networkInfo = mutableStateOf(UserNetworkInfo(networkType = UserNetworkType.OTHER, online = true))
|
||||
|
||||
val updatingChatsMutex: Mutex = Mutex()
|
||||
val changingActiveUserMutex: Mutex = Mutex()
|
||||
@@ -805,24 +804,6 @@ data class Chat(
|
||||
|
||||
val id: String get() = chatInfo.id
|
||||
|
||||
fun groupFeatureEnabled(feature: GroupFeature): Boolean =
|
||||
if (chatInfo is ChatInfo.Group) {
|
||||
val groupInfo = chatInfo.groupInfo
|
||||
val p = groupInfo.fullGroupPreferences
|
||||
when (feature) {
|
||||
GroupFeature.TimedMessages -> p.timedMessages.on
|
||||
GroupFeature.DirectMessages -> p.directMessages.on(groupInfo.membership)
|
||||
GroupFeature.FullDelete -> p.fullDelete.on
|
||||
GroupFeature.Reactions -> p.reactions.on
|
||||
GroupFeature.Voice -> p.voice.on(groupInfo.membership)
|
||||
GroupFeature.Files -> p.files.on(groupInfo.membership)
|
||||
GroupFeature.SimplexLinks -> p.simplexLinks.on(groupInfo.membership)
|
||||
GroupFeature.History -> p.history.on
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ChatStats(val unreadCount: Int = 0, val minUnreadItemId: Long = 0, val unreadChat: Boolean = false)
|
||||
|
||||
@@ -1258,7 +1239,7 @@ data class GroupInfo (
|
||||
ChatFeature.TimedMessages -> fullGroupPreferences.timedMessages.on
|
||||
ChatFeature.FullDelete -> fullGroupPreferences.fullDelete.on
|
||||
ChatFeature.Reactions -> fullGroupPreferences.reactions.on
|
||||
ChatFeature.Voice -> fullGroupPreferences.voice.on(membership)
|
||||
ChatFeature.Voice -> fullGroupPreferences.voice.on
|
||||
ChatFeature.Calls -> false
|
||||
}
|
||||
override val timedMessagesTTL: Int? get() = with(fullGroupPreferences.timedMessages) { if (on) ttl else null }
|
||||
@@ -1902,16 +1883,14 @@ data class ChatItem (
|
||||
status: CIStatus = CIStatus.SndNew(),
|
||||
quotedItem: CIQuote? = null,
|
||||
file: CIFile? = null,
|
||||
itemForwarded: CIForwardedFrom? = null,
|
||||
itemDeleted: CIDeleted? = null,
|
||||
itemEdited: Boolean = false,
|
||||
itemTimed: CITimed? = null,
|
||||
deletable: Boolean = true,
|
||||
editable: Boolean = true
|
||||
) =
|
||||
ChatItem(
|
||||
chatDir = dir,
|
||||
meta = CIMeta.getSample(id, ts, text, status, itemForwarded, itemDeleted, itemEdited, itemTimed, deletable, editable),
|
||||
meta = CIMeta.getSample(id, ts, text, status, itemDeleted, itemEdited, itemTimed, editable),
|
||||
content = CIContent.SndMsgContent(msgContent = MsgContent.MCText(text)),
|
||||
quotedItem = quotedItem,
|
||||
reactions = listOf(),
|
||||
@@ -1995,12 +1974,10 @@ data class ChatItem (
|
||||
itemStatus = CIStatus.RcvRead(),
|
||||
createdAt = Clock.System.now(),
|
||||
updatedAt = Clock.System.now(),
|
||||
itemForwarded = null,
|
||||
itemDeleted = null,
|
||||
itemEdited = false,
|
||||
itemTimed = null,
|
||||
itemLive = false,
|
||||
deletable = false,
|
||||
editable = false
|
||||
),
|
||||
content = CIContent.RcvDeleted(deleteMode = CIDeleteMode.cidmBroadcast),
|
||||
@@ -2018,12 +1995,10 @@ data class ChatItem (
|
||||
itemStatus = CIStatus.RcvRead(),
|
||||
createdAt = Clock.System.now(),
|
||||
updatedAt = Clock.System.now(),
|
||||
itemForwarded = null,
|
||||
itemDeleted = null,
|
||||
itemEdited = false,
|
||||
itemTimed = null,
|
||||
itemLive = true,
|
||||
deletable = false,
|
||||
editable = false
|
||||
),
|
||||
content = CIContent.SndMsgContent(MsgContent.MCText("")),
|
||||
@@ -2120,12 +2095,10 @@ data class CIMeta (
|
||||
val itemStatus: CIStatus,
|
||||
val createdAt: Instant,
|
||||
val updatedAt: Instant,
|
||||
val itemForwarded: CIForwardedFrom?,
|
||||
val itemDeleted: CIDeleted?,
|
||||
val itemEdited: Boolean,
|
||||
val itemTimed: CITimed?,
|
||||
val itemLive: Boolean?,
|
||||
val deletable: Boolean,
|
||||
val editable: Boolean
|
||||
) {
|
||||
val timestampText: String get() = getTimestampText(itemTs)
|
||||
@@ -2145,8 +2118,7 @@ data class CIMeta (
|
||||
companion object {
|
||||
fun getSample(
|
||||
id: Long, ts: Instant, text: String, status: CIStatus = CIStatus.SndNew(),
|
||||
itemForwarded: CIForwardedFrom? = null, itemDeleted: CIDeleted? = null, itemEdited: Boolean = false,
|
||||
itemTimed: CITimed? = null, itemLive: Boolean = false, deletable: Boolean = true, editable: Boolean = true
|
||||
itemDeleted: CIDeleted? = null, itemEdited: Boolean = false, itemTimed: CITimed? = null, itemLive: Boolean = false, editable: Boolean = true
|
||||
): CIMeta =
|
||||
CIMeta(
|
||||
itemId = id,
|
||||
@@ -2155,12 +2127,10 @@ data class CIMeta (
|
||||
itemStatus = status,
|
||||
createdAt = ts,
|
||||
updatedAt = ts,
|
||||
itemForwarded = itemForwarded,
|
||||
itemDeleted = itemDeleted,
|
||||
itemEdited = itemEdited,
|
||||
itemTimed = itemTimed,
|
||||
itemLive = itemLive,
|
||||
deletable = deletable,
|
||||
editable = editable
|
||||
)
|
||||
|
||||
@@ -2173,12 +2143,10 @@ data class CIMeta (
|
||||
itemStatus = CIStatus.SndNew(),
|
||||
createdAt = Clock.System.now(),
|
||||
updatedAt = Clock.System.now(),
|
||||
itemForwarded = null,
|
||||
itemDeleted = null,
|
||||
itemEdited = false,
|
||||
itemTimed = null,
|
||||
itemLive = false,
|
||||
deletable = false,
|
||||
editable = false
|
||||
)
|
||||
}
|
||||
@@ -2289,19 +2257,6 @@ sealed class CIDeleted {
|
||||
@Serializable @SerialName("moderated") class Moderated(val deletedTs: Instant?, val byGroupMember: GroupMember): CIDeleted()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class MsgDirection {
|
||||
@SerialName("rcv") Rcv,
|
||||
@SerialName("snd") Snd;
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class CIForwardedFrom {
|
||||
@Serializable @SerialName("unknown") object Unknown: CIForwardedFrom()
|
||||
@Serializable @SerialName("contact") class Contact(val chatName: String, val msgDir: MsgDirection, val contactId: Long? = null, val chatItemId: Long? = null): CIForwardedFrom()
|
||||
@Serializable @SerialName("group") class Group(val chatName: String, val msgDir: MsgDirection, val groupId: Long? = null, val chatItemId: Long? = null): CIForwardedFrom()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class CIDeleteMode(val deleteMode: String) {
|
||||
@SerialName("internal") cidmInternal("internal"),
|
||||
@@ -2337,8 +2292,8 @@ sealed class CIContent: ItemContent {
|
||||
@Serializable @SerialName("sndChatFeature") class SndChatFeature(val feature: ChatFeature, val enabled: FeatureEnabled, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("rcvChatPreference") class RcvChatPreference(val feature: ChatFeature, val allowed: FeatureAllowed, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("sndChatPreference") class SndChatPreference(val feature: ChatFeature, val allowed: FeatureAllowed, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("rcvGroupFeature") class RcvGroupFeature(val groupFeature: GroupFeature, val preference: GroupPreference, val param: Int? = null, val memberRole_: GroupMemberRole?): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("sndGroupFeature") class SndGroupFeature(val groupFeature: GroupFeature, val preference: GroupPreference, val param: Int? = null, val memberRole_: GroupMemberRole?): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("rcvGroupFeature") class RcvGroupFeature(val groupFeature: GroupFeature, val preference: GroupPreference, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("sndGroupFeature") class SndGroupFeature(val groupFeature: GroupFeature, val preference: GroupPreference, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("rcvChatFeatureRejected") class RcvChatFeatureRejected(val feature: ChatFeature): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("rcvGroupFeatureRejected") class RcvGroupFeatureRejected(val groupFeature: GroupFeature): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("sndModerated") object SndModerated: CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@@ -2370,8 +2325,8 @@ sealed class CIContent: ItemContent {
|
||||
is SndChatFeature -> featureText(feature, enabled.text, param)
|
||||
is RcvChatPreference -> preferenceText(feature, allowed, param)
|
||||
is SndChatPreference -> preferenceText(feature, allowed, param)
|
||||
is RcvGroupFeature -> featureText(groupFeature, preference.enable.text, param, memberRole_)
|
||||
is SndGroupFeature -> featureText(groupFeature, preference.enable.text, param, memberRole_)
|
||||
is RcvGroupFeature -> featureText(groupFeature, preference.enable.text, param)
|
||||
is SndGroupFeature -> featureText(groupFeature, preference.enable.text, param)
|
||||
is RcvChatFeatureRejected -> "${feature.text}: ${generalGetString(MR.strings.feature_received_prohibited)}"
|
||||
is RcvGroupFeatureRejected -> "${groupFeature.text}: ${generalGetString(MR.strings.feature_received_prohibited)}"
|
||||
is SndModerated -> generalGetString(MR.strings.moderated_description)
|
||||
@@ -2408,23 +2363,11 @@ sealed class CIContent: ItemContent {
|
||||
|
||||
private val e2eeInfoNoPQStr: String = generalGetString(MR.strings.e2ee_info_no_pq_short)
|
||||
|
||||
fun featureText(feature: Feature, enabled: String, param: Int?, role: GroupMemberRole? = null): String =
|
||||
(if (feature.hasParam) {
|
||||
fun featureText(feature: Feature, enabled: String, param: Int?): String =
|
||||
if (feature.hasParam) {
|
||||
"${feature.text}: ${timeText(param)}"
|
||||
} else {
|
||||
"${feature.text}: $enabled"
|
||||
}) + (
|
||||
if (feature.hasRole && role != null)
|
||||
" (${roleText(role)})"
|
||||
else
|
||||
""
|
||||
)
|
||||
|
||||
private fun roleText(role: GroupMemberRole?): String =
|
||||
when (role) {
|
||||
GroupMemberRole.Owner -> generalGetString(MR.strings.feature_roles_owners)
|
||||
GroupMemberRole.Admin -> generalGetString(MR.strings.feature_roles_admins)
|
||||
else -> generalGetString(MR.strings.feature_roles_all_members)
|
||||
}
|
||||
|
||||
fun preferenceText(feature: Feature, allowed: FeatureAllowed, param: Int?): String = when {
|
||||
@@ -3308,8 +3251,7 @@ sealed class ChatItemTTL: Comparable<ChatItemTTL?> {
|
||||
@Serializable
|
||||
class ChatItemInfo(
|
||||
val itemVersions: List<ChatItemVersion>,
|
||||
val memberDeliveryStatuses: List<MemberDeliveryStatus>?,
|
||||
val forwardedFromChatItem: AChatItem?
|
||||
val memberDeliveryStatuses: List<MemberDeliveryStatus>?
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
+18
-131
@@ -20,7 +20,6 @@ import com.charleskorn.kaml.YamlConfiguration
|
||||
import chat.simplex.res.MR
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
@@ -351,15 +350,11 @@ object ChatController {
|
||||
var ctrl: ChatCtrl? = -1
|
||||
val appPrefs: AppPreferences by lazy { AppPreferences() }
|
||||
|
||||
val messagesChannel: Channel<APIResponse> = Channel()
|
||||
|
||||
val chatModel = ChatModel
|
||||
private var receiverStarted = false
|
||||
var lastMsgReceivedTimestamp: Long = System.currentTimeMillis()
|
||||
private set
|
||||
|
||||
fun hasChatCtrl() = ctrl != -1L && ctrl != null
|
||||
|
||||
private suspend fun currentUserId(funcName: String): Long = changingActiveUserMutex.withLock {
|
||||
val userId = chatModel.currentUser.value?.userId
|
||||
if (userId == null) {
|
||||
@@ -486,7 +481,6 @@ object ChatController {
|
||||
if (msg != null) {
|
||||
val finishedWithoutTimeout = withTimeoutOrNull(60_000L) {
|
||||
processReceivedMsg(msg)
|
||||
messagesChannel.trySend(msg)
|
||||
}
|
||||
if (finishedWithoutTimeout == null) {
|
||||
Log.e(TAG, "Timeout reached while processing received message: " + msg.resp.responseType)
|
||||
@@ -881,9 +875,6 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apiSetNetworkInfo(networkInfo: UserNetworkInfo): Boolean =
|
||||
sendCommandOkResp(null, CC.APISetNetworkInfo(networkInfo))
|
||||
|
||||
suspend fun apiSetMemberSettings(rh: Long?, groupId: Long, groupMemberId: Long, memberSettings: GroupMemberSettings): Boolean =
|
||||
sendCommandOkResp(rh, CC.ApiSetMemberSettings(groupId, groupMemberId, memberSettings))
|
||||
|
||||
@@ -1762,8 +1753,6 @@ object ChatController {
|
||||
chatModel.removeChat(rhId, r.mergedContact.id)
|
||||
}
|
||||
}
|
||||
// ContactsSubscribed, ContactsDisconnected and ContactSubSummary are only used in CLI,
|
||||
// They have to be used here for remote desktop to process these status updates.
|
||||
is CR.ContactsSubscribed -> updateContactsStatus(r.contactRefs, NetworkStatus.Connected())
|
||||
is CR.ContactsDisconnected -> updateContactsStatus(r.contactRefs, NetworkStatus.Disconnected())
|
||||
is CR.ContactSubSummary -> {
|
||||
@@ -2128,7 +2117,7 @@ object ChatController {
|
||||
if (active(r.user)) {
|
||||
chatModel.updateContact(rhId, r.contact)
|
||||
}
|
||||
is CR.ChatRespError -> when {
|
||||
is CR.ChatCmdError -> when {
|
||||
r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.CRITICAL -> {
|
||||
chatModel.processedCriticalError.newError(r.chatError.agentError, r.chatError.agentError.offerRestart)
|
||||
}
|
||||
@@ -2418,7 +2407,6 @@ sealed class CC {
|
||||
class APIGetChatItemTTL(val userId: Long): CC()
|
||||
class APISetNetworkConfig(val networkConfig: NetCfg): CC()
|
||||
class APIGetNetworkConfig: CC()
|
||||
class APISetNetworkInfo(val networkInfo: UserNetworkInfo): CC()
|
||||
class APISetChatSettings(val type: ChatType, val id: Long, val chatSettings: ChatSettings): CC()
|
||||
class ApiSetMemberSettings(val groupId: Long, val groupMemberId: Long, val memberSettings: GroupMemberSettings): CC()
|
||||
class APIContactInfo(val contactId: Long): CC()
|
||||
@@ -2561,7 +2549,6 @@ sealed class CC {
|
||||
is APIGetChatItemTTL -> "/_ttl $userId"
|
||||
is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}"
|
||||
is APIGetNetworkConfig -> "/network"
|
||||
is APISetNetworkInfo -> "/_network info ${json.encodeToString(networkInfo)}"
|
||||
is APISetChatSettings -> "/_settings ${chatRef(type, id)} ${json.encodeToString(chatSettings)}"
|
||||
is ApiSetMemberSettings -> "/_member settings #$groupId $groupMemberId ${json.encodeToString(memberSettings)}"
|
||||
is APIContactInfo -> "/_info @$contactId"
|
||||
@@ -2699,7 +2686,6 @@ sealed class CC {
|
||||
is APIGetChatItemTTL -> "apiGetChatItemTTL"
|
||||
is APISetNetworkConfig -> "apiSetNetworkConfig"
|
||||
is APIGetNetworkConfig -> "apiGetNetworkConfig"
|
||||
is APISetNetworkInfo -> "apiSetNetworkInfo"
|
||||
is APISetChatSettings -> "apiSetChatSettings"
|
||||
is ApiSetMemberSettings -> "apiSetMemberSettings"
|
||||
is APIContactInfo -> "apiContactInfo"
|
||||
@@ -3047,7 +3033,7 @@ data class NetCfg(
|
||||
sessionMode = TransportSessionMode.User,
|
||||
tcpConnectTimeout = 20_000_000,
|
||||
tcpTimeout = 15_000_000,
|
||||
tcpTimeoutPerKb = 10_000,
|
||||
tcpTimeoutPerKb = 45_000,
|
||||
tcpKeepAlive = KeepAliveOpts.defaults,
|
||||
smpPingInterval = 1200_000_000,
|
||||
smpPingCount = 3
|
||||
@@ -3061,7 +3047,7 @@ data class NetCfg(
|
||||
sessionMode = TransportSessionMode.User,
|
||||
tcpConnectTimeout = 30_000_000,
|
||||
tcpTimeout = 20_000_000,
|
||||
tcpTimeoutPerKb = 15_000,
|
||||
tcpTimeoutPerKb = 60_000,
|
||||
tcpKeepAlive = KeepAliveOpts.defaults,
|
||||
smpPingInterval = 1200_000_000,
|
||||
smpPingCount = 3
|
||||
@@ -3430,7 +3416,6 @@ interface Feature {
|
||||
@Composable
|
||||
fun iconFilled(): Painter
|
||||
val hasParam: Boolean
|
||||
val hasRole: Boolean
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -3450,7 +3435,6 @@ enum class ChatFeature: Feature {
|
||||
TimedMessages -> true
|
||||
else -> false
|
||||
}
|
||||
override val hasRole: Boolean = false
|
||||
|
||||
override val text: String
|
||||
get() = when(this) {
|
||||
@@ -3551,7 +3535,6 @@ enum class GroupFeature: Feature {
|
||||
@SerialName("reactions") Reactions,
|
||||
@SerialName("voice") Voice,
|
||||
@SerialName("files") Files,
|
||||
@SerialName("simplexLinks") SimplexLinks,
|
||||
@SerialName("history") History;
|
||||
|
||||
override val hasParam: Boolean get() = when(this) {
|
||||
@@ -3559,18 +3542,6 @@ enum class GroupFeature: Feature {
|
||||
else -> false
|
||||
}
|
||||
|
||||
override val hasRole: Boolean
|
||||
get() = when (this) {
|
||||
TimedMessages -> false
|
||||
DirectMessages -> true
|
||||
FullDelete -> false
|
||||
Reactions -> false
|
||||
Voice -> true
|
||||
Files -> true
|
||||
SimplexLinks -> true
|
||||
History -> false
|
||||
}
|
||||
|
||||
override val text: String
|
||||
get() = when(this) {
|
||||
TimedMessages -> generalGetString(MR.strings.timed_messages)
|
||||
@@ -3579,7 +3550,6 @@ enum class GroupFeature: Feature {
|
||||
Reactions -> generalGetString(MR.strings.message_reactions)
|
||||
Voice -> generalGetString(MR.strings.voice_messages)
|
||||
Files -> generalGetString(MR.strings.files_and_media)
|
||||
SimplexLinks -> generalGetString(MR.strings.simplex_links)
|
||||
History -> generalGetString(MR.strings.recent_history)
|
||||
}
|
||||
|
||||
@@ -3591,7 +3561,6 @@ enum class GroupFeature: Feature {
|
||||
Reactions -> painterResource(MR.images.ic_add_reaction)
|
||||
Voice -> painterResource(MR.images.ic_keyboard_voice)
|
||||
Files -> painterResource(MR.images.ic_draft)
|
||||
SimplexLinks -> painterResource(MR.images.ic_link)
|
||||
History -> painterResource(MR.images.ic_schedule)
|
||||
}
|
||||
|
||||
@@ -3603,7 +3572,6 @@ enum class GroupFeature: Feature {
|
||||
Reactions -> painterResource(MR.images.ic_add_reaction_filled)
|
||||
Voice -> painterResource(MR.images.ic_keyboard_voice_filled)
|
||||
Files -> painterResource(MR.images.ic_draft_filled)
|
||||
SimplexLinks -> painterResource(MR.images.ic_link)
|
||||
History -> painterResource(MR.images.ic_schedule_filled)
|
||||
}
|
||||
|
||||
@@ -3634,10 +3602,6 @@ enum class GroupFeature: Feature {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_to_send_files)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_sending_files)
|
||||
}
|
||||
SimplexLinks -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_to_send_simplex_links)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_sending_simplex_links)
|
||||
}
|
||||
History -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.enable_sending_recent_history)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_recent_history)
|
||||
@@ -3669,10 +3633,6 @@ enum class GroupFeature: Feature {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_files)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.files_are_prohibited_in_group)
|
||||
}
|
||||
SimplexLinks -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_simplex_links)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.simplex_links_are_prohibited_in_group)
|
||||
}
|
||||
History -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.recent_history_is_sent_to_new_members)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.recent_history_is_not_sent_to_new_members)
|
||||
@@ -3786,12 +3746,11 @@ enum class FeatureAllowed {
|
||||
@Serializable
|
||||
data class FullGroupPreferences(
|
||||
val timedMessages: TimedMessagesGroupPreference,
|
||||
val directMessages: RoleGroupPreference,
|
||||
val directMessages: GroupPreference,
|
||||
val fullDelete: GroupPreference,
|
||||
val reactions: GroupPreference,
|
||||
val voice: RoleGroupPreference,
|
||||
val files: RoleGroupPreference,
|
||||
val simplexLinks: RoleGroupPreference,
|
||||
val voice: GroupPreference,
|
||||
val files: GroupPreference,
|
||||
val history: GroupPreference,
|
||||
) {
|
||||
fun toGroupPreferences(): GroupPreferences =
|
||||
@@ -3802,19 +3761,17 @@ data class FullGroupPreferences(
|
||||
reactions = reactions,
|
||||
voice = voice,
|
||||
files = files,
|
||||
simplexLinks = simplexLinks,
|
||||
history = history
|
||||
)
|
||||
|
||||
companion object {
|
||||
val sampleData = FullGroupPreferences(
|
||||
timedMessages = TimedMessagesGroupPreference(GroupFeatureEnabled.OFF),
|
||||
directMessages = RoleGroupPreference(GroupFeatureEnabled.OFF, role = null),
|
||||
directMessages = GroupPreference(GroupFeatureEnabled.OFF),
|
||||
fullDelete = GroupPreference(GroupFeatureEnabled.OFF),
|
||||
reactions = GroupPreference(GroupFeatureEnabled.ON),
|
||||
voice = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
|
||||
files = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
|
||||
simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
|
||||
voice = GroupPreference(GroupFeatureEnabled.ON),
|
||||
files = GroupPreference(GroupFeatureEnabled.ON),
|
||||
history = GroupPreference(GroupFeatureEnabled.ON),
|
||||
)
|
||||
}
|
||||
@@ -3823,23 +3780,21 @@ data class FullGroupPreferences(
|
||||
@Serializable
|
||||
data class GroupPreferences(
|
||||
val timedMessages: TimedMessagesGroupPreference? = null,
|
||||
val directMessages: RoleGroupPreference? = null,
|
||||
val directMessages: GroupPreference? = null,
|
||||
val fullDelete: GroupPreference? = null,
|
||||
val reactions: GroupPreference? = null,
|
||||
val voice: RoleGroupPreference? = null,
|
||||
val files: RoleGroupPreference? = null,
|
||||
val simplexLinks: RoleGroupPreference? = null,
|
||||
val voice: GroupPreference? = null,
|
||||
val files: GroupPreference? = null,
|
||||
val history: GroupPreference? = null,
|
||||
) {
|
||||
companion object {
|
||||
val sampleData = GroupPreferences(
|
||||
timedMessages = TimedMessagesGroupPreference(GroupFeatureEnabled.OFF),
|
||||
directMessages = RoleGroupPreference(GroupFeatureEnabled.OFF, role = null),
|
||||
directMessages = GroupPreference(GroupFeatureEnabled.OFF),
|
||||
fullDelete = GroupPreference(GroupFeatureEnabled.OFF),
|
||||
reactions = GroupPreference(GroupFeatureEnabled.ON),
|
||||
voice = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
|
||||
files = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
|
||||
simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
|
||||
voice = GroupPreference(GroupFeatureEnabled.ON),
|
||||
files = GroupPreference(GroupFeatureEnabled.ON),
|
||||
history = GroupPreference(GroupFeatureEnabled.ON),
|
||||
)
|
||||
}
|
||||
@@ -3850,26 +3805,6 @@ data class GroupPreference(
|
||||
val enable: GroupFeatureEnabled
|
||||
) {
|
||||
val on: Boolean get() = enable == GroupFeatureEnabled.ON
|
||||
|
||||
fun enabled(role: GroupMemberRole?, m: GroupMember?): GroupFeatureEnabled =
|
||||
when (enable) {
|
||||
GroupFeatureEnabled.OFF -> GroupFeatureEnabled.OFF
|
||||
GroupFeatureEnabled.ON ->
|
||||
if (role != null && m != null) {
|
||||
if (m.memberRole >= role) GroupFeatureEnabled.ON else GroupFeatureEnabled.OFF
|
||||
} else {
|
||||
GroupFeatureEnabled.ON
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class RoleGroupPreference(
|
||||
val enable: GroupFeatureEnabled,
|
||||
val role: GroupMemberRole? = null,
|
||||
) {
|
||||
fun on(m: GroupMember): Boolean =
|
||||
enable == GroupFeatureEnabled.ON && m.memberRole >= (role ?: GroupMemberRole.Observer)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -4823,7 +4758,6 @@ sealed class ChatErrorType {
|
||||
is FallbackToSMPProhibited -> "fallbackToSMPProhibited"
|
||||
is InlineFileProhibited -> "inlineFileProhibited"
|
||||
is InvalidQuote -> "invalidQuote"
|
||||
is InvalidForward -> "invalidForward"
|
||||
is InvalidChatItemUpdate -> "invalidChatItemUpdate"
|
||||
is InvalidChatItemDelete -> "invalidChatItemDelete"
|
||||
is HasCurrentCall -> "hasCurrentCall"
|
||||
@@ -4902,7 +4836,6 @@ sealed class ChatErrorType {
|
||||
@Serializable @SerialName("fallbackToSMPProhibited") class FallbackToSMPProhibited(val fileId: Long): ChatErrorType()
|
||||
@Serializable @SerialName("inlineFileProhibited") class InlineFileProhibited(val fileId: Long): ChatErrorType()
|
||||
@Serializable @SerialName("invalidQuote") object InvalidQuote: ChatErrorType()
|
||||
@Serializable @SerialName("invalidForward") object InvalidForward: ChatErrorType()
|
||||
@Serializable @SerialName("invalidChatItemUpdate") object InvalidChatItemUpdate: ChatErrorType()
|
||||
@Serializable @SerialName("invalidChatItemDelete") object InvalidChatItemDelete: ChatErrorType()
|
||||
@Serializable @SerialName("hasCurrentCall") object HasCurrentCall: ChatErrorType()
|
||||
@@ -5078,8 +5011,8 @@ sealed class AgentErrorType {
|
||||
is BROKER -> "BROKER ${brokerErr.string}"
|
||||
is AGENT -> "AGENT ${agentErr.string}"
|
||||
is INTERNAL -> "INTERNAL $internalErr"
|
||||
is CRITICAL -> "CRITICAL $offerRestart $criticalErr"
|
||||
is INACTIVE -> "INACTIVE"
|
||||
is CRITICAL -> "CRITICAL $offerRestart $criticalErr"
|
||||
}
|
||||
@Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType()
|
||||
@@ -5090,8 +5023,8 @@ sealed class AgentErrorType {
|
||||
@Serializable @SerialName("BROKER") class BROKER(val brokerAddress: String, val brokerErr: BrokerErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("AGENT") class AGENT(val agentErr: SMPAgentError): AgentErrorType()
|
||||
@Serializable @SerialName("INTERNAL") class INTERNAL(val internalErr: String): AgentErrorType()
|
||||
@Serializable @SerialName("CRITICAL") data class CRITICAL(val offerRestart: Boolean, val criticalErr: String): AgentErrorType()
|
||||
@Serializable @SerialName("INACTIVE") object INACTIVE: AgentErrorType()
|
||||
@Serializable @SerialName("CRITICAL") data class CRITICAL(val offerRestart: Boolean, val criticalErr: String): AgentErrorType()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -5190,13 +5123,11 @@ sealed class SMPTransportError {
|
||||
is BadBlock -> "badBlock"
|
||||
is LargeMsg -> "largeMsg"
|
||||
is BadSession -> "badSession"
|
||||
is NoServerAuth -> "noServerAuth"
|
||||
is Handshake -> "handshake ${handshakeErr.string}"
|
||||
}
|
||||
@Serializable @SerialName("badBlock") class BadBlock: SMPTransportError()
|
||||
@Serializable @SerialName("largeMsg") class LargeMsg: SMPTransportError()
|
||||
@Serializable @SerialName("badSession") class BadSession: SMPTransportError()
|
||||
@Serializable @SerialName("noServerAuth") class NoServerAuth: SMPTransportError()
|
||||
@Serializable @SerialName("handshake") class Handshake(val handshakeErr: SMPHandshakeError): SMPTransportError()
|
||||
}
|
||||
|
||||
@@ -5206,12 +5137,10 @@ sealed class SMPHandshakeError {
|
||||
is PARSE -> "PARSE"
|
||||
is VERSION -> "VERSION"
|
||||
is IDENTITY -> "IDENTITY"
|
||||
is BAD_AUTH -> "BAD_AUTH"
|
||||
}
|
||||
@Serializable @SerialName("PARSE") class PARSE: SMPHandshakeError()
|
||||
@Serializable @SerialName("VERSION") class VERSION: SMPHandshakeError()
|
||||
@Serializable @SerialName("IDENTITY") class IDENTITY: SMPHandshakeError()
|
||||
@Serializable @SerialName("BAD_AUTH") class BAD_AUTH: SMPHandshakeError()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -5246,8 +5175,6 @@ sealed class XFTPErrorType {
|
||||
is NO_FILE -> "NO_FILE"
|
||||
is HAS_FILE -> "HAS_FILE"
|
||||
is FILE_IO -> "FILE_IO"
|
||||
is TIMEOUT -> "TIMEOUT"
|
||||
is REDIRECT -> "REDIRECT"
|
||||
is INTERNAL -> "INTERNAL"
|
||||
}
|
||||
@Serializable @SerialName("BLOCK") object BLOCK: XFTPErrorType()
|
||||
@@ -5261,8 +5188,6 @@ sealed class XFTPErrorType {
|
||||
@Serializable @SerialName("NO_FILE") object NO_FILE: XFTPErrorType()
|
||||
@Serializable @SerialName("HAS_FILE") object HAS_FILE: XFTPErrorType()
|
||||
@Serializable @SerialName("FILE_IO") object FILE_IO: XFTPErrorType()
|
||||
@Serializable @SerialName("TIMEOUT") object TIMEOUT: XFTPErrorType()
|
||||
@Serializable @SerialName("REDIRECT") class REDIRECT(val redirectError: String): XFTPErrorType()
|
||||
@Serializable @SerialName("INTERNAL") object INTERNAL: XFTPErrorType()
|
||||
}
|
||||
|
||||
@@ -5272,8 +5197,6 @@ sealed class RCErrorType {
|
||||
is INTERNAL -> "INTERNAL $internalErr"
|
||||
is IDENTITY -> "IDENTITY"
|
||||
is NO_LOCAL_ADDRESS -> "NO_LOCAL_ADDRESS"
|
||||
is NEW_CONTROLLER -> "NEW_CONTROLLER"
|
||||
is NOT_DISCOVERED -> "NOT_DISCOVERED"
|
||||
is TLS_START_FAILED -> "TLS_START_FAILED"
|
||||
is EXCEPTION -> "EXCEPTION $EXCEPTION"
|
||||
is CTRL_AUTH -> "CTRL_AUTH"
|
||||
@@ -5288,8 +5211,6 @@ sealed class RCErrorType {
|
||||
@Serializable @SerialName("internal") data class INTERNAL(val internalErr: String): RCErrorType()
|
||||
@Serializable @SerialName("identity") object IDENTITY: RCErrorType()
|
||||
@Serializable @SerialName("noLocalAddress") object NO_LOCAL_ADDRESS: RCErrorType()
|
||||
@Serializable @SerialName("newController") object NEW_CONTROLLER: RCErrorType()
|
||||
@Serializable @SerialName("notDiscovered") object NOT_DISCOVERED: RCErrorType()
|
||||
@Serializable @SerialName("tlsStartFailed") object TLS_START_FAILED: RCErrorType()
|
||||
@Serializable @SerialName("exception") data class EXCEPTION(val exception: String): RCErrorType()
|
||||
@Serializable @SerialName("ctrlAuth") object CTRL_AUTH: RCErrorType()
|
||||
@@ -5348,39 +5269,28 @@ sealed class RemoteCtrlError {
|
||||
is BadState -> "badState"
|
||||
is Busy -> "busy"
|
||||
is Timeout -> "timeout"
|
||||
is NoKnownControllers -> "noKnownControllers"
|
||||
is BadController -> "badController"
|
||||
is Disconnected -> "disconnected"
|
||||
is BadInvitation -> "badInvitation"
|
||||
is BadVersion -> "badVersion"
|
||||
is HTTP2Error -> "http2Error"
|
||||
is ProtocolError -> "protocolError"
|
||||
}
|
||||
val localizedString: String get() = when (this) {
|
||||
is Inactive -> generalGetString(MR.strings.remote_ctrl_error_inactive)
|
||||
is BadState -> generalGetString(MR.strings.remote_ctrl_error_bad_state)
|
||||
is Busy -> generalGetString(MR.strings.remote_ctrl_error_busy)
|
||||
is Timeout -> generalGetString(MR.strings.remote_ctrl_error_timeout)
|
||||
is NoKnownControllers -> "no known controllers"
|
||||
is BadController -> "bad controller"
|
||||
is Disconnected -> generalGetString(MR.strings.remote_ctrl_error_disconnected)
|
||||
is BadInvitation -> generalGetString(MR.strings.remote_ctrl_error_bad_invitation)
|
||||
is BadVersion -> generalGetString(MR.strings.remote_ctrl_error_bad_version)
|
||||
is HTTP2Error -> "HTTP2 error"
|
||||
is ProtocolError -> "protocol error"
|
||||
}
|
||||
|
||||
@Serializable @SerialName("inactive") object Inactive: RemoteCtrlError()
|
||||
@Serializable @SerialName("badState") object BadState: RemoteCtrlError()
|
||||
@Serializable @SerialName("busy") object Busy: RemoteCtrlError()
|
||||
@Serializable @SerialName("timeout") object Timeout: RemoteCtrlError()
|
||||
@Serializable @SerialName("noKnownControllers") object NoKnownControllers: RemoteCtrlError()
|
||||
@Serializable @SerialName("badController") object BadController: RemoteCtrlError()
|
||||
@Serializable @SerialName("disconnected") class Disconnected(val remoteCtrlId: Long, val reason: String): RemoteCtrlError()
|
||||
@Serializable @SerialName("badInvitation") object BadInvitation: RemoteCtrlError()
|
||||
@Serializable @SerialName("badVersion") data class BadVersion(val appVersion: String): RemoteCtrlError()
|
||||
@Serializable @SerialName("hTTP2Error") data class HTTP2Error(val http2Error: String): RemoteCtrlError()
|
||||
@Serializable @SerialName("protocolError") object ProtocolError: RemoteCtrlError()
|
||||
//@Serializable @SerialName("protocolError") data class ProtocolError(val protocolError: RemoteProtocolError): RemoteCtrlError()
|
||||
}
|
||||
|
||||
enum class NotificationsMode() {
|
||||
@@ -5592,26 +5502,3 @@ enum class AppSettingsLockScreenCalls {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class UserNetworkInfo(
|
||||
val networkType: UserNetworkType,
|
||||
val online: Boolean,
|
||||
)
|
||||
|
||||
enum class UserNetworkType {
|
||||
@SerialName("none") NONE,
|
||||
@SerialName("cellular") CELLULAR,
|
||||
@SerialName("wifi") WIFI,
|
||||
@SerialName("ethernet") ETHERNET,
|
||||
@SerialName("other") OTHER;
|
||||
|
||||
val text: String
|
||||
get() = when (this) {
|
||||
NONE -> generalGetString(MR.strings.network_type_no_network_connection)
|
||||
CELLULAR -> generalGetString(MR.strings.network_type_cellular)
|
||||
WIFI -> generalGetString(MR.strings.network_type_network_wifi)
|
||||
ETHERNET -> generalGetString(MR.strings.network_type_ethernet)
|
||||
OTHER -> generalGetString(MR.strings.network_type_other)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,6 +9,7 @@ import chat.simplex.common.views.helpers.DatabaseUtils.randomDatabasePassword
|
||||
import chat.simplex.common.views.onboarding.OnboardingStage
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
@@ -87,7 +88,6 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
|
||||
Log.d(TAG, "Unable to migrate successfully: $res")
|
||||
return
|
||||
}
|
||||
platform.androidRestartNetworkObserver()
|
||||
controller.apiSetTempFolder(coreTmpDir.absolutePath)
|
||||
controller.apiSetFilesFolder(appFilesDir.absolutePath)
|
||||
if (appPlatform.isDesktop) {
|
||||
|
||||
@@ -109,7 +109,6 @@ private fun fileReady(file: CIFile, filePath: String) =
|
||||
@Composable
|
||||
expect fun rememberFileChooserLauncher(getContent: Boolean, rememberedValue: Any? = null, onResult: (URI?) -> Unit): FileChooserLauncher
|
||||
|
||||
@Composable
|
||||
expect fun rememberFileChooserMultipleLauncher(onResult: (List<URI>) -> Unit): FileChooserMultipleLauncher
|
||||
|
||||
expect class FileChooserLauncher() {
|
||||
|
||||
-1
@@ -23,7 +23,6 @@ interface PlatformInterface {
|
||||
fun androidStartCallActivity(acceptCall: Boolean, remoteHostId: Long? = null, chatId: ChatId? = null) {}
|
||||
fun androidPictureInPictureAllowed(): Boolean = true
|
||||
fun androidCallEnded() {}
|
||||
fun androidRestartNetworkObserver() {}
|
||||
@Composable fun androidLockPortraitOrientation() {}
|
||||
suspend fun androidAskToAllowBackgroundCalls(): Boolean = true
|
||||
@Composable fun desktopScrollBarComponents(): Triple<Animatable<Float, AnimationVector1D>, Modifier, MutableState<Job>> = remember { Triple(Animatable(0f), Modifier, mutableStateOf(Job())) }
|
||||
|
||||
-9
@@ -39,13 +39,4 @@ interface SoundPlayerInterface {
|
||||
fun stop()
|
||||
}
|
||||
|
||||
interface CallSoundsPlayerInterface {
|
||||
fun startConnectingCallSound(scope: CoroutineScope)
|
||||
fun startInCallSound(scope: CoroutineScope)
|
||||
fun stop()
|
||||
fun vibrate(times: Int = 1)
|
||||
}
|
||||
|
||||
expect object SoundPlayer: SoundPlayerInterface
|
||||
|
||||
expect object CallSoundsPlayer: CallSoundsPlayerInterface
|
||||
|
||||
@@ -260,7 +260,6 @@ val CurrentColors: MutableStateFlow<ThemeManager.ActiveTheme> = MutableStateFlow
|
||||
@Composable
|
||||
fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight
|
||||
|
||||
@Composable
|
||||
expect fun isSystemInDarkTheme(): Boolean
|
||||
|
||||
fun reactOnDarkThemeChanges(isDark: Boolean) {
|
||||
|
||||
-1
@@ -85,7 +85,6 @@ fun TerminalLayout(
|
||||
isDirectChat = false,
|
||||
liveMessageAlertShown = SharedPreference(get = { false }, set = {}),
|
||||
sendMsgEnabled = true,
|
||||
sendButtonEnabled = true,
|
||||
nextSendGrpInv = false,
|
||||
needToAllowVoiceToContact = false,
|
||||
allowedVoiceByPrefs = false,
|
||||
|
||||
-20
@@ -1,26 +1,6 @@
|
||||
package chat.simplex.common.views.call
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.platform.*
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
@Composable
|
||||
expect fun ActiveCallView()
|
||||
|
||||
fun activeCallWaitDeliveryReceipt(scope: CoroutineScope) = scope.launch(Dispatchers.Default) {
|
||||
for (apiResp in controller.messagesChannel) {
|
||||
val call = chatModel.activeCall.value
|
||||
if (call == null || call.callState > CallState.InvitationSent) break
|
||||
val msg = apiResp.resp
|
||||
if (apiResp.remoteHostId == call.remoteHostId &&
|
||||
msg is CR.ChatItemStatusUpdated &&
|
||||
msg.chatItem.chatInfo.id == call.contact.id &&
|
||||
msg.chatItem.chatItem.content is CIContent.SndCall &&
|
||||
msg.chatItem.chatItem.meta.itemStatus is CIStatus.SndRcvd) {
|
||||
CallSoundsPlayer.startInCallSound(scope)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -187,11 +187,10 @@ data class ConnectionState(
|
||||
)
|
||||
|
||||
// the servers are expected in this format:
|
||||
// stuns:stun.simplex.im:443?transport=tcp
|
||||
// turns:private2:Hxuq2QxUjnhj96Zq2r4HjqHRj@turn.simplex.im:443?transport=tcp
|
||||
// stun:stun.simplex.im:443?transport=tcp
|
||||
// turn:private:yleob6AVkiNI87hpR94Z@turn.simplex.im:443?transport=tcp
|
||||
fun parseRTCIceServer(str: String): RTCIceServer? {
|
||||
var s = replaceScheme(str, "stun:")
|
||||
s = replaceScheme(s, "stuns:")
|
||||
s = replaceScheme(s, "turn:")
|
||||
s = replaceScheme(s, "turns:")
|
||||
val u = runCatching { URI(s) }.getOrNull()
|
||||
@@ -199,7 +198,7 @@ fun parseRTCIceServer(str: String): RTCIceServer? {
|
||||
val scheme = u.scheme
|
||||
val host = u.host
|
||||
val port = u.port
|
||||
if (u.path == "" && (scheme == "stun" || scheme == "stuns" || scheme == "turn" || scheme == "turns")) {
|
||||
if (u.path == "" && (scheme == "stun" || scheme == "turn" || scheme == "turns")) {
|
||||
val userInfo = u.userInfo?.split(":")
|
||||
val query = if (u.query == null || u.query == "") "" else "?${u.query}"
|
||||
return RTCIceServer(
|
||||
|
||||
+3
-4
@@ -962,7 +962,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
||||
|
||||
@Composable
|
||||
fun ChatItemView(cItem: ChatItem, range: IntRange?, prevItem: ChatItem?) {
|
||||
val voiceWithTransparentBack = cItem.content.msgContent is MsgContent.MCVoice && cItem.content.text.isEmpty() && cItem.quotedItem == null && cItem.meta.itemForwarded == null
|
||||
val voiceWithTransparentBack = cItem.content.msgContent is MsgContent.MCVoice && cItem.content.text.isEmpty() && cItem.quotedItem == null
|
||||
if (chat.chatInfo is ChatInfo.Group) {
|
||||
if (cItem.chatDir is CIDirection.GroupRcv) {
|
||||
val member = cItem.chatDir.groupMember
|
||||
@@ -1392,12 +1392,11 @@ private fun providerForGallery(
|
||||
return null
|
||||
}
|
||||
|
||||
// Pager has a bug with overflowing when total pages is around Int.MAX_VALUE. Using smaller value
|
||||
var initialIndex = 10000 / 2
|
||||
var initialIndex = Int.MAX_VALUE / 2
|
||||
var initialChatId = cItemId
|
||||
return object: ImageGalleryProvider {
|
||||
override val initialIndex: Int = initialIndex
|
||||
override val totalMediaSize = mutableStateOf(10000)
|
||||
override val totalMediaSize = mutableStateOf(Int.MAX_VALUE)
|
||||
override fun getMedia(index: Int): ProviderMedia? {
|
||||
val internalIndex = initialIndex - index
|
||||
val item = item(internalIndex, initialChatId)?.second ?: return null
|
||||
|
||||
+17
-63
@@ -1,7 +1,6 @@
|
||||
@file:UseSerializers(UriSerializer::class)
|
||||
package chat.simplex.common.views.chat
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.*
|
||||
@@ -12,8 +11,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -21,7 +18,8 @@ import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.model.ChatModel.filesToDelete
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.ui.theme.Indigo
|
||||
import chat.simplex.common.ui.theme.isSystemInDarkTheme
|
||||
import chat.simplex.common.views.chat.item.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
@@ -117,15 +115,6 @@ data class ComposeState(
|
||||
}
|
||||
}
|
||||
|
||||
val attachmentPreview: Boolean
|
||||
get() = when (preview) {
|
||||
ComposePreview.NoPreview -> false
|
||||
is ComposePreview.CLinkPreview -> false
|
||||
is ComposePreview.MediaPreview -> preview.content.isNotEmpty()
|
||||
is ComposePreview.VoicePreview -> false
|
||||
is ComposePreview.FilePreview -> true
|
||||
}
|
||||
|
||||
val empty: Boolean
|
||||
get() = message.isEmpty() && preview is ComposePreview.NoPreview && contextItem is ComposeContextItem.NoContextItem
|
||||
|
||||
@@ -254,23 +243,10 @@ fun ComposeView(
|
||||
attachmentOption: MutableState<AttachmentOption?>,
|
||||
showChooseAttachment: () -> Unit
|
||||
) {
|
||||
val cancelledLinks = rememberSaveable { mutableSetOf<String>() }
|
||||
fun isSimplexLink(link: String): Boolean =
|
||||
link.startsWith("https://simplex.chat", true) || link.startsWith("http://simplex.chat", true)
|
||||
|
||||
fun parseMessage(msg: String): Pair<String?, Boolean> {
|
||||
if (msg.isBlank()) return null to false
|
||||
val parsedMsg = parseToMarkdown(msg) ?: return null to false
|
||||
val link = parsedMsg.firstOrNull { ft -> ft.format is Format.Uri && !cancelledLinks.contains(ft.text) && !isSimplexLink(ft.text) }
|
||||
val simplexLink = parsedMsg.any { ft -> ft.format is Format.SimplexLink }
|
||||
return link?.text to simplexLink
|
||||
}
|
||||
|
||||
val linkUrl = rememberSaveable { mutableStateOf<String?>(null) }
|
||||
// default value parsed because of draft
|
||||
val hasSimplexLink = rememberSaveable { mutableStateOf(parseMessage(composeState.value.message).second) }
|
||||
val prevLinkUrl = rememberSaveable { mutableStateOf<String?>(null) }
|
||||
val pendingLinkUrl = rememberSaveable { mutableStateOf<String?>(null) }
|
||||
val cancelledLinks = rememberSaveable { mutableSetOf<String>() }
|
||||
val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get()
|
||||
val saveLastDraft = chatModel.controller.appPrefs.privacySaveLastDraft.get()
|
||||
val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground)
|
||||
@@ -279,6 +255,15 @@ fun ComposeView(
|
||||
|
||||
AttachmentSelection(composeState, attachmentOption, composeState::processPickedFile) { uris, text -> CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(uris, text) } }
|
||||
|
||||
fun isSimplexLink(link: String): Boolean =
|
||||
link.startsWith("https://simplex.chat", true) || link.startsWith("http://simplex.chat", true)
|
||||
|
||||
fun parseMessage(msg: String): String? {
|
||||
val parsedMsg = parseToMarkdown(msg)
|
||||
val link = parsedMsg?.firstOrNull { ft -> ft.format is Format.Uri && !cancelledLinks.contains(ft.text) && !isSimplexLink(ft.text) }
|
||||
return link?.text
|
||||
}
|
||||
|
||||
fun loadLinkPreview(url: String, wait: Long? = null) {
|
||||
if (pendingLinkUrl.value == url) {
|
||||
composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(null))
|
||||
@@ -298,9 +283,7 @@ fun ComposeView(
|
||||
|
||||
fun showLinkPreview(s: String) {
|
||||
prevLinkUrl.value = linkUrl.value
|
||||
val parsed = parseMessage(s)
|
||||
linkUrl.value = parsed.first
|
||||
hasSimplexLink.value = parsed.second
|
||||
linkUrl.value = parseMessage(s)
|
||||
val url = linkUrl.value
|
||||
if (url != null) {
|
||||
if (url != composeState.value.linkPreview?.uri && url != pendingLinkUrl.value) {
|
||||
@@ -404,7 +387,7 @@ fun ComposeView(
|
||||
fun checkLinkPreview(): MsgContent {
|
||||
return when (val composePreview = cs.preview) {
|
||||
is ComposePreview.CLinkPreview -> {
|
||||
val url = parseMessage(msgText).first
|
||||
val url = parseMessage(msgText)
|
||||
val lp = composePreview.linkPreview
|
||||
if (lp != null && url == lp.uri) {
|
||||
MsgContent.MCLink(msgText, preview = lp)
|
||||
@@ -580,16 +563,8 @@ fun ComposeView(
|
||||
} else {
|
||||
textStyle.value = smallFont
|
||||
if (composeState.value.linkPreviewAllowed) {
|
||||
if (s.isNotEmpty()) {
|
||||
showLinkPreview(s)
|
||||
} else {
|
||||
resetLinkPreview()
|
||||
hasSimplexLink.value = false
|
||||
}
|
||||
} else if (s.isNotEmpty() && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks)) {
|
||||
hasSimplexLink.value = parseMessage(s).second
|
||||
} else {
|
||||
hasSimplexLink.value = false
|
||||
if (s.isNotEmpty()) showLinkPreview(s)
|
||||
else resetLinkPreview()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -725,16 +700,6 @@ fun ComposeView(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MsgNotAllowedView(reason: String, icon: Painter) {
|
||||
val color = CurrentColors.collectAsState().value.appColors.receivedMessage
|
||||
Row(Modifier.padding(top = 5.dp).fillMaxWidth().background(color).padding(horizontal = DEFAULT_PADDING_HALF, vertical = DEFAULT_PADDING_HALF * 1.5f), verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(icon, null, tint = MaterialTheme.colors.secondary)
|
||||
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
|
||||
Text(reason, fontStyle = FontStyle.Italic)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun contextItemView() {
|
||||
when (val contextItem = composeState.value.contextItem) {
|
||||
@@ -778,17 +743,7 @@ fun ComposeView(
|
||||
if (nextSendGrpInv.value) {
|
||||
ComposeContextInvitingContactMemberView()
|
||||
}
|
||||
val simplexLinkProhibited = hasSimplexLink.value && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks)
|
||||
val fileProhibited = composeState.value.attachmentPreview && !chat.groupFeatureEnabled(GroupFeature.Files)
|
||||
val voiceProhibited = composeState.value.preview is ComposePreview.VoicePreview && !chat.chatInfo.featureEnabled(ChatFeature.Voice)
|
||||
if (composeState.value.preview !is ComposePreview.VoicePreview || composeState.value.editing) {
|
||||
if (simplexLinkProhibited) {
|
||||
MsgNotAllowedView(generalGetString(MR.strings.simplex_links_not_allowed), icon = painterResource(MR.images.ic_link))
|
||||
} else if (fileProhibited) {
|
||||
MsgNotAllowedView(generalGetString(MR.strings.files_and_media_not_allowed), icon = painterResource(MR.images.ic_draft))
|
||||
} else if (voiceProhibited) {
|
||||
MsgNotAllowedView(generalGetString(MR.strings.voice_messages_not_allowed), icon = painterResource(MR.images.ic_mic))
|
||||
}
|
||||
contextItemView()
|
||||
when {
|
||||
composeState.value.editing && composeState.value.preview is ComposePreview.VoicePreview -> {}
|
||||
@@ -809,7 +764,7 @@ fun ComposeView(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership)
|
||||
val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on
|
||||
val attachmentClicked = if (isGroupAndProhibitedFiles) {
|
||||
{
|
||||
AlertManager.shared.showAlertMsg(
|
||||
@@ -916,7 +871,6 @@ fun ComposeView(
|
||||
chat.chatInfo is ChatInfo.Direct,
|
||||
liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown,
|
||||
sendMsgEnabled = sendMsgEnabled.value,
|
||||
sendButtonEnabled = sendMsgEnabled.value && !(simplexLinkProhibited || fileProhibited || voiceProhibited),
|
||||
nextSendGrpInv = nextSendGrpInv.value,
|
||||
needToAllowVoiceToContact,
|
||||
allowedVoiceByPrefs,
|
||||
|
||||
+2
-7
@@ -39,7 +39,6 @@ fun SendMsgView(
|
||||
isDirectChat: Boolean,
|
||||
liveMessageAlertShown: SharedPreference<Boolean>,
|
||||
sendMsgEnabled: Boolean,
|
||||
sendButtonEnabled: Boolean,
|
||||
nextSendGrpInv: Boolean,
|
||||
needToAllowVoiceToContact: Boolean,
|
||||
allowedVoiceByPrefs: Boolean,
|
||||
@@ -76,8 +75,7 @@ fun SendMsgView(
|
||||
val showDeleteTextButton = rememberSaveable { mutableStateOf(false) }
|
||||
val sendMsgButtonDisabled = !sendMsgEnabled || !cs.sendEnabled() ||
|
||||
(!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) ||
|
||||
cs.endLiveDisabled ||
|
||||
!sendButtonEnabled
|
||||
cs.endLiveDisabled
|
||||
PlatformTextField(composeState, sendMsgEnabled, sendMsgButtonDisabled, textStyle, showDeleteTextButton, userIsObserver, onMessageChange, editPrevMessage, onFilesPasted) {
|
||||
if (!cs.inProgress) {
|
||||
sendMessage(null)
|
||||
@@ -432,7 +430,7 @@ private fun SendMsgButton(
|
||||
.padding(4.dp)
|
||||
.alpha(alpha.value)
|
||||
.clip(CircleShape)
|
||||
.background(if (enabled) sendButtonColor else MaterialTheme.colors.secondary.copy(alpha = 0.75f))
|
||||
.background(if (enabled) sendButtonColor else MaterialTheme.colors.secondary)
|
||||
.padding(3.dp)
|
||||
)
|
||||
}
|
||||
@@ -554,7 +552,6 @@ fun PreviewSendMsgView() {
|
||||
isDirectChat = true,
|
||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||
sendMsgEnabled = true,
|
||||
sendButtonEnabled = true,
|
||||
nextSendGrpInv = false,
|
||||
needToAllowVoiceToContact = false,
|
||||
allowedVoiceByPrefs = true,
|
||||
@@ -589,7 +586,6 @@ fun PreviewSendMsgViewEditing() {
|
||||
isDirectChat = true,
|
||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||
sendMsgEnabled = true,
|
||||
sendButtonEnabled = true,
|
||||
nextSendGrpInv = false,
|
||||
needToAllowVoiceToContact = false,
|
||||
allowedVoiceByPrefs = true,
|
||||
@@ -624,7 +620,6 @@ fun PreviewSendMsgViewInProgress() {
|
||||
isDirectChat = true,
|
||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||
sendMsgEnabled = true,
|
||||
sendButtonEnabled = true,
|
||||
nextSendGrpInv = false,
|
||||
needToAllowVoiceToContact = false,
|
||||
allowedVoiceByPrefs = true,
|
||||
|
||||
+2
-2
@@ -309,7 +309,7 @@ fun GroupMemberInfoLayout(
|
||||
SectionView {
|
||||
if (contactId != null && knownDirectChat(contactId) != null) {
|
||||
OpenChatButton(onClick = { openDirectChat(contactId) })
|
||||
} else if (groupInfo.fullGroupPreferences.directMessages.on(groupInfo.membership)) {
|
||||
} else if (groupInfo.fullGroupPreferences.directMessages.on) {
|
||||
if (contactId != null) {
|
||||
OpenChatButton(onClick = { openDirectChat(contactId) })
|
||||
} else if (member.activeConn?.peerChatVRange?.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) == true) {
|
||||
@@ -335,7 +335,7 @@ fun GroupMemberInfoLayout(
|
||||
val clipboard = LocalClipboardManager.current
|
||||
ShareAddressButton { clipboard.shareText(simplexChatLink(member.contactLink)) }
|
||||
if (contactId != null) {
|
||||
if (knownDirectChat(contactId) == null && !groupInfo.fullGroupPreferences.directMessages.on(groupInfo.membership)) {
|
||||
if (knownDirectChat(contactId) == null && !groupInfo.fullGroupPreferences.directMessages.on) {
|
||||
ConnectViaAddressButton(onClick = { connectViaAddress(member.contactLink) })
|
||||
}
|
||||
} else {
|
||||
|
||||
+18
-57
@@ -6,6 +6,7 @@ import SectionDividerSpaced
|
||||
import SectionItemView
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
@@ -19,20 +20,13 @@ import chat.simplex.common.views.usersettings.PreferenceToggleWithIcon
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.ColumnWithScrollBar
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
|
||||
private val featureRoles: List<Pair<GroupMemberRole?, String>> = listOf(
|
||||
null to generalGetString(MR.strings.feature_roles_all_members),
|
||||
GroupMemberRole.Admin to generalGetString(MR.strings.feature_roles_admins),
|
||||
GroupMemberRole.Owner to generalGetString(MR.strings.feature_roles_owners)
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () -> Unit) {
|
||||
fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () -> Unit,) {
|
||||
val groupInfo = remember { derivedStateOf {
|
||||
val ch = m.getChat(chatId)
|
||||
val g = (ch?.chatInfo as? ChatInfo.Group)?.groupInfo
|
||||
if (g == null || ch.remoteHostId != rhId) null else g
|
||||
if (g == null || ch?.remoteHostId != rhId) null else g
|
||||
}}
|
||||
val gInfo = groupInfo.value ?: return
|
||||
var preferences by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(gInfo.fullGroupPreferences) }
|
||||
@@ -87,7 +81,7 @@ private fun GroupPreferencesLayout(
|
||||
val onTTLUpdated = { ttl: Int? ->
|
||||
applyPrefs(preferences.copy(timedMessages = preferences.timedMessages.copy(ttl = ttl)))
|
||||
}
|
||||
FeatureSection(GroupFeature.TimedMessages, timedMessages, null, groupInfo, preferences, onTTLUpdated) { enable, _ ->
|
||||
FeatureSection(GroupFeature.TimedMessages, timedMessages, groupInfo, preferences, onTTLUpdated) { enable ->
|
||||
if (enable == GroupFeatureEnabled.ON) {
|
||||
applyPrefs(preferences.copy(timedMessages = TimedMessagesGroupPreference(enable = enable, ttl = preferences.timedMessages.ttl ?: 86400)))
|
||||
} else {
|
||||
@@ -96,45 +90,33 @@ private fun GroupPreferencesLayout(
|
||||
}
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
val allowDirectMessages = remember(preferences) { mutableStateOf(preferences.directMessages.enable) }
|
||||
val directMessagesRole = remember(preferences) { mutableStateOf(preferences.directMessages.role) }
|
||||
FeatureSection(GroupFeature.DirectMessages, allowDirectMessages, directMessagesRole, groupInfo, preferences, onTTLUpdated) { enable, role ->
|
||||
applyPrefs(preferences.copy(directMessages = RoleGroupPreference(enable = enable, role)))
|
||||
FeatureSection(GroupFeature.DirectMessages, allowDirectMessages, groupInfo, preferences, onTTLUpdated) {
|
||||
applyPrefs(preferences.copy(directMessages = GroupPreference(enable = it)))
|
||||
}
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
val allowFullDeletion = remember(preferences) { mutableStateOf(preferences.fullDelete.enable) }
|
||||
FeatureSection(GroupFeature.FullDelete, allowFullDeletion, null, groupInfo, preferences, onTTLUpdated) { enable, _ ->
|
||||
applyPrefs(preferences.copy(fullDelete = GroupPreference(enable = enable)))
|
||||
FeatureSection(GroupFeature.FullDelete, allowFullDeletion, groupInfo, preferences, onTTLUpdated) {
|
||||
applyPrefs(preferences.copy(fullDelete = GroupPreference(enable = it)))
|
||||
}
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
val allowReactions = remember(preferences) { mutableStateOf(preferences.reactions.enable) }
|
||||
FeatureSection(GroupFeature.Reactions, allowReactions, null, groupInfo, preferences, onTTLUpdated) { enable, _ ->
|
||||
applyPrefs(preferences.copy(reactions = GroupPreference(enable = enable)))
|
||||
FeatureSection(GroupFeature.Reactions, allowReactions, groupInfo, preferences, onTTLUpdated) {
|
||||
applyPrefs(preferences.copy(reactions = GroupPreference(enable = it)))
|
||||
}
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
val allowVoice = remember(preferences) { mutableStateOf(preferences.voice.enable) }
|
||||
val voiceRole = remember(preferences) { mutableStateOf(preferences.voice.role) }
|
||||
FeatureSection(GroupFeature.Voice, allowVoice, voiceRole, groupInfo, preferences, onTTLUpdated) { enable, role ->
|
||||
applyPrefs(preferences.copy(voice = RoleGroupPreference(enable = enable, role)))
|
||||
FeatureSection(GroupFeature.Voice, allowVoice, groupInfo, preferences, onTTLUpdated) {
|
||||
applyPrefs(preferences.copy(voice = GroupPreference(enable = it)))
|
||||
}
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
val allowFiles = remember(preferences) { mutableStateOf(preferences.files.enable) }
|
||||
val filesRole = remember(preferences) { mutableStateOf(preferences.files.role) }
|
||||
FeatureSection(GroupFeature.Files, allowFiles, filesRole, groupInfo, preferences, onTTLUpdated) { enable, role ->
|
||||
applyPrefs(preferences.copy(files = RoleGroupPreference(enable = enable, role)))
|
||||
FeatureSection(GroupFeature.Files, allowFiles, groupInfo, preferences, onTTLUpdated) {
|
||||
applyPrefs(preferences.copy(files = GroupPreference(enable = it)))
|
||||
}
|
||||
|
||||
// TODO enable simplexLinks preference in 5.8
|
||||
// SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
// val allowSimplexLinks = remember(preferences) { mutableStateOf(preferences.simplexLinks.enable) }
|
||||
// val simplexLinksRole = remember(preferences) { mutableStateOf(preferences.simplexLinks.role) }
|
||||
// FeatureSection(GroupFeature.SimplexLinks, allowSimplexLinks, simplexLinksRole, groupInfo, preferences, onTTLUpdated) { enable, role ->
|
||||
// applyPrefs(preferences.copy(simplexLinks = RoleGroupPreference(enable = enable, role)))
|
||||
// }
|
||||
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) }
|
||||
FeatureSection(GroupFeature.History, enableHistory, null, groupInfo, preferences, onTTLUpdated) { enable, _ ->
|
||||
applyPrefs(preferences.copy(history = GroupPreference(enable = enable)))
|
||||
FeatureSection(GroupFeature.History, enableHistory, groupInfo, preferences, onTTLUpdated) {
|
||||
applyPrefs(preferences.copy(history = GroupPreference(enable = it)))
|
||||
}
|
||||
if (groupInfo.canEdit) {
|
||||
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
|
||||
@@ -152,11 +134,10 @@ private fun GroupPreferencesLayout(
|
||||
private fun FeatureSection(
|
||||
feature: GroupFeature,
|
||||
enableFeature: State<GroupFeatureEnabled>,
|
||||
enableForRole: State<GroupMemberRole?>? = null,
|
||||
groupInfo: GroupInfo,
|
||||
preferences: FullGroupPreferences,
|
||||
onTTLUpdated: (Int?) -> Unit,
|
||||
onSelected: (GroupFeatureEnabled, GroupMemberRole?) -> Unit
|
||||
onSelected: (GroupFeatureEnabled) -> Unit
|
||||
) {
|
||||
SectionView {
|
||||
val on = enableFeature.value == GroupFeatureEnabled.ON
|
||||
@@ -170,7 +151,7 @@ private fun FeatureSection(
|
||||
iconTint,
|
||||
enableFeature.value == GroupFeatureEnabled.ON,
|
||||
) { checked ->
|
||||
onSelected(if (checked) GroupFeatureEnabled.ON else GroupFeatureEnabled.OFF, enableForRole?.value)
|
||||
onSelected(if (checked) GroupFeatureEnabled.ON else GroupFeatureEnabled.OFF)
|
||||
}
|
||||
if (timedOn) {
|
||||
val ttl = rememberSaveable(preferences.timedMessages) { mutableStateOf(preferences.timedMessages.ttl) }
|
||||
@@ -184,18 +165,6 @@ private fun FeatureSection(
|
||||
onSelected = onTTLUpdated
|
||||
)
|
||||
}
|
||||
if (enableFeature.value == GroupFeatureEnabled.ON && enableForRole != null) {
|
||||
ExposedDropDownSettingRow(
|
||||
generalGetString(MR.strings.feature_enabled_for),
|
||||
featureRoles,
|
||||
enableForRole,
|
||||
// remove in v5.8
|
||||
enabled = remember { mutableStateOf(false) },
|
||||
onSelected = { value ->
|
||||
onSelected(enableFeature.value, value)
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
InfoRow(
|
||||
feature.text,
|
||||
@@ -206,14 +175,6 @@ private fun FeatureSection(
|
||||
if (timedOn) {
|
||||
InfoRow(generalGetString(MR.strings.delete_after), timeText(preferences.timedMessages.ttl))
|
||||
}
|
||||
if (enableFeature.value == GroupFeatureEnabled.ON && enableForRole != null) {
|
||||
InfoRow(generalGetString(MR.strings.feature_enabled_for), featureRoles.firstOrNull { it.first == enableForRole.value }?.second ?: generalGetString(MR.strings.feature_roles_all_members), textColor = MaterialTheme.colors.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyChangeEffect(enableFeature.value) {
|
||||
if (enableFeature.value == GroupFeatureEnabled.OFF) {
|
||||
onSelected(enableFeature.value, null)
|
||||
}
|
||||
}
|
||||
SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.canEdit))
|
||||
|
||||
+6
-7
@@ -16,7 +16,6 @@ import chat.simplex.common.platform.onRightClick
|
||||
|
||||
@Composable
|
||||
fun CIChatFeatureView(
|
||||
chatInfo: ChatInfo,
|
||||
chatItem: ChatItem,
|
||||
feature: Feature,
|
||||
iconColor: Color,
|
||||
@@ -24,7 +23,7 @@ fun CIChatFeatureView(
|
||||
revealed: MutableState<Boolean>,
|
||||
showMenu: MutableState<Boolean>,
|
||||
) {
|
||||
val merged = if (!revealed.value) mergedFeatures(chatItem, chatInfo) else emptyList()
|
||||
val merged = if (!revealed.value) mergedFeatures(chatItem) else emptyList()
|
||||
Box(
|
||||
Modifier
|
||||
.combinedClickable(
|
||||
@@ -71,7 +70,7 @@ private fun Feature.toFeatureInfo(color: Color, param: Int?, type: String): Feat
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun mergedFeatures(chatItem: ChatItem, chatInfo: ChatInfo): List<FeatureInfo>? {
|
||||
private fun mergedFeatures(chatItem: ChatItem): List<FeatureInfo>? {
|
||||
val m = ChatModel
|
||||
val fs: ArrayList<FeatureInfo> = arrayListOf()
|
||||
val icons: MutableSet<PainterBox> = mutableSetOf()
|
||||
@@ -79,7 +78,7 @@ private fun mergedFeatures(chatItem: ChatItem, chatInfo: ChatInfo): List<Feature
|
||||
if (i != null) {
|
||||
val reversedChatItems = m.chatItems.asReversed()
|
||||
while (i < reversedChatItems.size) {
|
||||
val f = featureInfo(reversedChatItems[i], chatInfo) ?: break
|
||||
val f = featureInfo(reversedChatItems[i]) ?: break
|
||||
if (!icons.contains(f.icon)) {
|
||||
fs.add(0, f)
|
||||
icons.add(f.icon)
|
||||
@@ -91,12 +90,12 @@ private fun mergedFeatures(chatItem: ChatItem, chatInfo: ChatInfo): List<Feature
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun featureInfo(ci: ChatItem, chatInfo: ChatInfo): FeatureInfo? =
|
||||
private fun featureInfo(ci: ChatItem): FeatureInfo? =
|
||||
when (ci.content) {
|
||||
is CIContent.RcvChatFeature -> ci.content.feature.toFeatureInfo(ci.content.enabled.iconColor, ci.content.param, ci.content.feature.name)
|
||||
is CIContent.SndChatFeature -> ci.content.feature.toFeatureInfo(ci.content.enabled.iconColor, ci.content.param, ci.content.feature.name)
|
||||
is CIContent.RcvGroupFeature -> ci.content.groupFeature.toFeatureInfo(ci.content.preference.enabled(ci.content.memberRole_, (chatInfo as ChatInfo.Group).groupInfo.membership).iconColor, ci.content.param, ci.content.groupFeature.name)
|
||||
is CIContent.SndGroupFeature -> ci.content.groupFeature.toFeatureInfo(ci.content.preference.enabled(ci.content.memberRole_, (chatInfo as ChatInfo.Group).groupInfo.membership).iconColor, ci.content.param, ci.content.groupFeature.name)
|
||||
is CIContent.RcvGroupFeature -> ci.content.groupFeature.toFeatureInfo(ci.content.preference.enable.iconColor, ci.content.param, ci.content.groupFeature.name)
|
||||
is CIContent.SndGroupFeature -> ci.content.groupFeature.toFeatureInfo(ci.content.preference.enable.iconColor, ci.content.param, ci.content.groupFeature.name)
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -304,7 +304,7 @@ fun ChatItemView(
|
||||
MarkedDeletedItemView(cItem, cInfo.timedMessagesTTL, revealed)
|
||||
MarkedDeletedItemDropdownMenu()
|
||||
} else {
|
||||
if (cItem.quotedItem == null && cItem.meta.itemForwarded == null && cItem.meta.itemDeleted == null && !cItem.meta.isLive) {
|
||||
if (cItem.quotedItem == null && cItem.meta.itemDeleted == null && !cItem.meta.isLive) {
|
||||
if (mc is MsgContent.MCText && isShortEmoji(cItem.content.text)) {
|
||||
EmojiItemView(cItem, cInfo.timedMessagesTTL)
|
||||
} else if (mc is MsgContent.MCVoice && cItem.content.text.isEmpty()) {
|
||||
@@ -442,11 +442,11 @@ fun ChatItemView(
|
||||
MsgContentItemDropdownMenu()
|
||||
}
|
||||
is CIContent.RcvChatFeature -> {
|
||||
CIChatFeatureView(cInfo, cItem, c.feature, c.enabled.iconColor, revealed = revealed, showMenu = showMenu)
|
||||
CIChatFeatureView(cItem, c.feature, c.enabled.iconColor, revealed = revealed, showMenu = showMenu)
|
||||
MsgContentItemDropdownMenu()
|
||||
}
|
||||
is CIContent.SndChatFeature -> {
|
||||
CIChatFeatureView(cInfo, cItem, c.feature, c.enabled.iconColor, revealed = revealed, showMenu = showMenu)
|
||||
CIChatFeatureView(cItem, c.feature, c.enabled.iconColor, revealed = revealed, showMenu = showMenu)
|
||||
MsgContentItemDropdownMenu()
|
||||
}
|
||||
is CIContent.RcvChatPreference -> {
|
||||
@@ -454,23 +454,23 @@ fun ChatItemView(
|
||||
CIFeaturePreferenceView(cItem, ct, c.feature, c.allowed, acceptFeature)
|
||||
}
|
||||
is CIContent.SndChatPreference -> {
|
||||
CIChatFeatureView(cInfo, cItem, c.feature, MaterialTheme.colors.secondary, icon = c.feature.icon, revealed, showMenu = showMenu)
|
||||
CIChatFeatureView(cItem, c.feature, MaterialTheme.colors.secondary, icon = c.feature.icon, revealed, showMenu = showMenu)
|
||||
MsgContentItemDropdownMenu()
|
||||
}
|
||||
is CIContent.RcvGroupFeature -> {
|
||||
CIChatFeatureView(cInfo, cItem, c.groupFeature, c.preference.enabled(c.memberRole_, (cInfo as ChatInfo.Group).groupInfo.membership).iconColor, revealed = revealed, showMenu = showMenu)
|
||||
CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor, revealed = revealed, showMenu = showMenu)
|
||||
MsgContentItemDropdownMenu()
|
||||
}
|
||||
is CIContent.SndGroupFeature -> {
|
||||
CIChatFeatureView(cInfo, cItem, c.groupFeature, c.preference.enabled(c.memberRole_, (cInfo as ChatInfo.Group).groupInfo.membership).iconColor, revealed = revealed, showMenu = showMenu)
|
||||
CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor, revealed = revealed, showMenu = showMenu)
|
||||
MsgContentItemDropdownMenu()
|
||||
}
|
||||
is CIContent.RcvChatFeatureRejected -> {
|
||||
CIChatFeatureView(cInfo, cItem, c.feature, Color.Red, revealed = revealed, showMenu = showMenu)
|
||||
CIChatFeatureView(cItem, c.feature, Color.Red, revealed = revealed, showMenu = showMenu)
|
||||
MsgContentItemDropdownMenu()
|
||||
}
|
||||
is CIContent.RcvGroupFeatureRejected -> {
|
||||
CIChatFeatureView(cInfo, cItem, c.groupFeature, Color.Red, revealed = revealed, showMenu = showMenu)
|
||||
CIChatFeatureView(cItem, c.groupFeature, Color.Red, revealed = revealed, showMenu = showMenu)
|
||||
MsgContentItemDropdownMenu()
|
||||
}
|
||||
is CIContent.SndModerated -> DeletedItem()
|
||||
@@ -724,7 +724,7 @@ fun deleteMessageAlertDialog(chatItem: ChatItem, questionText: String, deleteMes
|
||||
deleteMessage(chatItem.id, CIDeleteMode.cidmInternal)
|
||||
AlertManager.shared.hideAlert()
|
||||
}) { Text(stringResource(MR.strings.for_me_only), color = MaterialTheme.colors.error) }
|
||||
if (chatItem.meta.deletable && !chatItem.localNote) {
|
||||
if (chatItem.meta.editable && !chatItem.localNote) {
|
||||
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||
TextButton(onClick = {
|
||||
deleteMessage(chatItem.id, CIDeleteMode.cidmBroadcast)
|
||||
|
||||
+2
-7
@@ -96,7 +96,6 @@ fun FramedItemView(
|
||||
.fillMaxWidth()
|
||||
.padding(start = 8.dp, top = 6.dp, end = 12.dp, bottom = if (ci.quotedItem == null) 6.dp else 0.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(
|
||||
@@ -185,7 +184,7 @@ fun FramedItemView(
|
||||
}
|
||||
|
||||
val transparentBackground = (ci.content.msgContent is MsgContent.MCImage || ci.content.msgContent is MsgContent.MCVideo) &&
|
||||
!ci.meta.isLive && ci.content.text.isEmpty() && ci.quotedItem == null && ci.meta.itemForwarded == null
|
||||
!ci.meta.isLive && ci.content.text.isEmpty() && ci.quotedItem == null
|
||||
|
||||
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
||||
val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage
|
||||
@@ -220,11 +219,7 @@ fun FramedItemView(
|
||||
} else if (ci.meta.isLive) {
|
||||
FramedItemHeader(stringResource(MR.strings.live), false)
|
||||
}
|
||||
if (ci.quotedItem != null) {
|
||||
ciQuoteView(ci.quotedItem)
|
||||
} else if (ci.meta.itemForwarded != null) {
|
||||
FramedItemHeader(stringResource(MR.strings.forwarded_description), true, painterResource(MR.images.ic_forward))
|
||||
}
|
||||
ci.quotedItem?.let { ciQuoteView(it) }
|
||||
if (ci.file == null && ci.formattedText == null && !ci.meta.isLive && isShortEmoji(ci.content.text)) {
|
||||
Box(Modifier.padding(vertical = 6.dp, horizontal = 12.dp)) {
|
||||
Column(
|
||||
|
||||
+1
-4
@@ -190,7 +190,6 @@ class AlertManager {
|
||||
fun showAlertMsg(
|
||||
title: String, text: String? = null,
|
||||
confirmText: String = generalGetString(MR.strings.ok),
|
||||
onConfirm: (() -> Unit)? = null,
|
||||
hostDevice: Pair<Long?, String>? = null,
|
||||
shareText: Boolean? = null
|
||||
) {
|
||||
@@ -221,7 +220,6 @@ class AlertManager {
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
onConfirm?.invoke()
|
||||
hideAlert()
|
||||
},
|
||||
Modifier.focusRequester(focusRequester)
|
||||
@@ -259,9 +257,8 @@ class AlertManager {
|
||||
title: StringResource,
|
||||
text: StringResource? = null,
|
||||
confirmText: StringResource = MR.strings.ok,
|
||||
onConfirm: (() -> Unit)? = null,
|
||||
hostDevice: Pair<Long?, String>? = null,
|
||||
) = showAlertMsg(generalGetString(title), if (text != null) generalGetString(text) else null, generalGetString(confirmText), onConfirm, hostDevice)
|
||||
) = showAlertMsg(generalGetString(title), if (text != null) generalGetString(text) else null, generalGetString(confirmText), hostDevice)
|
||||
|
||||
@Composable
|
||||
fun showInView() {
|
||||
|
||||
+2
-2
@@ -254,12 +254,12 @@ fun TextIconSpaced(extraPadding: Boolean = false) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InfoRow(title: String, value: String, icon: Painter? = null, iconTint: Color? = null, textColor: Color = MaterialTheme.colors.onBackground) {
|
||||
fun InfoRow(title: String, value: String, icon: Painter? = null, iconTint: Color? = null) {
|
||||
SectionItemViewSpaceBetween {
|
||||
Row {
|
||||
val iconSize = with(LocalDensity.current) { 21.sp.toDp() }
|
||||
if (icon != null) Icon(icon, title, Modifier.padding(end = 8.dp).size(iconSize), tint = iconTint ?: MaterialTheme.colors.secondary)
|
||||
Text(title, color = textColor)
|
||||
Text(title)
|
||||
}
|
||||
Text(value, color = MaterialTheme.colors.secondary)
|
||||
}
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ import chat.simplex.res.MR
|
||||
import com.charleskorn.kaml.decodeFromStream
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import java.io.*
|
||||
import java.net.URI
|
||||
|
||||
+6
-20
@@ -38,7 +38,9 @@ import chat.simplex.common.views.usersettings.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@Composable
|
||||
fun ConnectMobileView() {
|
||||
@@ -267,20 +269,12 @@ fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState<Boolean>, c
|
||||
var cachedR by remember { mutableStateOf<CR.RemoteHostStarted?>(null) }
|
||||
val customAddress = rememberSaveable { mutableStateOf<RemoteCtrlAddress?>(null) }
|
||||
val customPort = rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
var userChangedAddress by rememberSaveable { mutableStateOf(false) }
|
||||
var userChangedPort by rememberSaveable { mutableStateOf(false) }
|
||||
val startRemoteHost = suspend {
|
||||
if (customAddress.value != cachedR.address && cachedR != null) {
|
||||
userChangedAddress = true
|
||||
}
|
||||
if (customPort.value != cachedR.port && cachedR != null) {
|
||||
userChangedPort = true
|
||||
}
|
||||
val r = chatModel.controller.startRemoteHost(
|
||||
rhId = null,
|
||||
multicast = controller.appPrefs.offerRemoteMulticast.get(),
|
||||
address = if (customAddress.value != null && userChangedAddress) customAddress.value else cachedR.rh?.bindAddress_,
|
||||
port = if (customPort.value != null && userChangedPort) customPort.value else cachedR.rh?.bindPort_
|
||||
address = if (customAddress.value?.address != cachedR.address?.address) customAddress.value else cachedR.rh?.bindAddress_,
|
||||
port = if (customPort.value != cachedR.port) customPort.value else cachedR.rh?.bindPort_
|
||||
)
|
||||
if (r != null) {
|
||||
cachedR = r
|
||||
@@ -349,20 +343,12 @@ private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState
|
||||
var cachedR by remember { mutableStateOf<CR.RemoteHostStarted?>(null) }
|
||||
val customAddress = rememberSaveable { mutableStateOf<RemoteCtrlAddress?>(null) }
|
||||
val customPort = rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
var userChangedAddress by rememberSaveable { mutableStateOf(false) }
|
||||
var userChangedPort by rememberSaveable { mutableStateOf(false) }
|
||||
val startRemoteHost = suspend {
|
||||
if (customAddress.value != cachedR.address && cachedR != null) {
|
||||
userChangedAddress = true
|
||||
}
|
||||
if (customPort.value != cachedR.port && cachedR != null) {
|
||||
userChangedPort = true
|
||||
}
|
||||
val r = chatModel.controller.startRemoteHost(
|
||||
rhId = rh.remoteHostId,
|
||||
multicast = controller.appPrefs.offerRemoteMulticast.get(),
|
||||
address = if (customAddress.value != null && userChangedAddress) customAddress.value else cachedR.rh?.bindAddress_ ?: rh.bindAddress_,
|
||||
port = if (customPort.value != null && userChangedPort) customPort.value else cachedR.rh?.bindPort_ ?: rh.bindPort_
|
||||
address = if (customAddress.value?.address != cachedR.address?.address) customAddress.value else cachedR.rh?.bindAddress_ ?: rh.bindAddress_,
|
||||
port = if (customPort.value != cachedR.port) customPort.value else cachedR.rh?.bindPort_ ?: rh.bindPort_
|
||||
)
|
||||
if (r != null) {
|
||||
cachedR = r
|
||||
|
||||
+1
-1
@@ -167,7 +167,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
|
||||
// can't be higher than 130ms to avoid overflow on 32bit systems
|
||||
TimeoutSettingRow(
|
||||
stringResource(MR.strings.network_option_protocol_timeout_per_kb), networkTCPTimeoutPerKb,
|
||||
listOf(2_500, 5_000, 10_000, 15_000, 20_000, 30_000), secondsLabel
|
||||
listOf(15_000, 30_000, 45_000, 60_000, 90_000, 120_000), secondsLabel
|
||||
)
|
||||
}
|
||||
SectionItemView {
|
||||
|
||||
+2
-13
@@ -2,7 +2,6 @@ package chat.simplex.common.views.usersettings
|
||||
|
||||
import SectionBottomSpacer
|
||||
import SectionCustomFooter
|
||||
import SectionDividerSpaced
|
||||
import SectionItemView
|
||||
import SectionItemWithValue
|
||||
import SectionView
|
||||
@@ -22,12 +21,12 @@ import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.font.*
|
||||
import androidx.compose.ui.text.input.*
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.platform.ColumnWithScrollBar
|
||||
import chat.simplex.common.platform.chatModel
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.chat.item.ClickableText
|
||||
import chat.simplex.common.views.helpers.*
|
||||
@@ -191,16 +190,6 @@ fun NetworkAndServersView() {
|
||||
SectionView(generalGetString(MR.strings.settings_section_title_calls)) {
|
||||
SettingsActionItem(painterResource(MR.images.ic_electrical_services), stringResource(MR.strings.webrtc_ice_servers), { ModalManager.start.showModal { RTCServersView(m) } })
|
||||
}
|
||||
|
||||
if (appPlatform.isAndroid) {
|
||||
SectionDividerSpaced()
|
||||
SectionView(generalGetString(MR.strings.settings_section_title_network_connection).uppercase()) {
|
||||
val info = remember { chatModel.networkInfo }.value
|
||||
SettingsActionItemWithContent(icon = null, info.networkType.text) {
|
||||
Icon(painterResource(MR.images.ic_circle_filled), stringResource(MR.strings.icon_descr_server_status_connected), tint = if (info.online) Color.Green else MaterialTheme.colors.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user