mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
306 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| afa6083cb3 | |||
| e20a4e54c3 | |||
| 1f8c69ec23 | |||
| 35c37263b8 | |||
| 9b70599cc5 | |||
| a4a11f99d2 | |||
| 7c8955fcdb | |||
| f922064f5c | |||
| 110839627f | |||
| 160c253966 | |||
| c90b71120f | |||
| 3ec029e489 | |||
| f4c799aced | |||
| d970470702 | |||
| 3d7434c52f | |||
| 9ee74bd36e | |||
| 0a4ff2e35f | |||
| a3550df893 | |||
| 3778698a6e | |||
| cf7a16e857 | |||
| bef1597fa1 | |||
| f06118835d | |||
| fa9e50a904 | |||
| 932a65630c | |||
| b649b64999 | |||
| e94ead3a4f | |||
| 42d7c20a47 | |||
| cf8fd2ea11 | |||
| 5006742577 | |||
| 38d46891a1 | |||
| 6a7e573b42 | |||
| b31a74567d | |||
| b3f0e71ded | |||
| ed50623ef5 | |||
| 53368713a0 | |||
| c78df15cd6 | |||
| 5739b94808 | |||
| 0782408682 | |||
| 41576f80e7 | |||
| 2503a86f07 | |||
| dc713268e8 | |||
| d0c52e43a2 | |||
| 2e8e6cef7e | |||
| 239c815f3e | |||
| 1a6245fe51 | |||
| b3d15f97f0 | |||
| c0b8cfb3e2 | |||
| 818161c6ed | |||
| 4c53620dfa | |||
| ea5afb28d3 | |||
| 060760675d | |||
| 75101ac885 | |||
| 159f66e812 | |||
| 701bef2a50 | |||
| 6d19b48979 | |||
| ce73a15787 | |||
| e6545a1747 | |||
| 601b081cab | |||
| d31dedf132 | |||
| 7441ed9892 | |||
| f6ee6338c4 | |||
| 5a42c0c1d2 | |||
| 240131a023 | |||
| 37e275c3ca | |||
| f893ad15de | |||
| a0763b3a43 | |||
| e6ba82c8ff | |||
| a8bdf1555f | |||
| d278b3dbcd | |||
| ac4b1e9406 | |||
| b04f159970 | |||
| 55331289d3 | |||
| b2b1519aea | |||
| e769abf14a | |||
| 9ca7031f82 | |||
| 7a418918d6 | |||
| 8f1302e1c6 | |||
| ee7c5d2aac | |||
| bf697c722a | |||
| f30dfa0be7 | |||
| 9c73ef9769 | |||
| 560d9b1b03 | |||
| e38db7fb44 | |||
| cb76c8079c | |||
| 8fbba16f53 | |||
| 5384e2826d | |||
| 0975079a93 | |||
| 74e2b7582e | |||
| 413a8f6b76 | |||
| 619cff91b2 | |||
| 2ee16f9398 | |||
| bfab2d9fb6 | |||
| 229ea80499 | |||
| b82349164f | |||
| 30df42d20d | |||
| 01345cd423 | |||
| 38a2f3b458 | |||
| f739c7f226 | |||
| 28fb67fe92 | |||
| d2b643de01 | |||
| bae9685950 | |||
| ccd34f5ab1 | |||
| dd8081a9d4 | |||
| d65909cb53 | |||
| e99b543c2f | |||
| 312d315d73 | |||
| cdf5520084 | |||
| 170e8ddeaf | |||
| 68f7956512 | |||
| e424b3eb66 | |||
| fb7a414e3c | |||
| 5966d6d648 | |||
| c6ebc9a4d9 | |||
| 05ec8b0676 | |||
| 6980f643ff | |||
| b60dd3e3e8 | |||
| 17a5770442 | |||
| 439bbc15a5 | |||
| 3688141972 | |||
| d79f8f8345 | |||
| 923ad7470c | |||
| 833abb252e | |||
| c60207f211 | |||
| d5665a421f | |||
| b2aa0ffc3c | |||
| e50a5cfaa1 | |||
| 73ec44a9be | |||
| 59fe6ab8ce | |||
| ec64be7c89 | |||
| 41984e56c0 | |||
| c435510b2e | |||
| de65d0e0a2 | |||
| 9cde37c6f5 | |||
| 9514acf6aa | |||
| 40d54f3f16 | |||
| 3249c5e463 | |||
| e440fba582 | |||
| 43cedbce26 | |||
| ebad009553 | |||
| 35cf113d98 | |||
| e82d2cbea2 | |||
| a0e056994e | |||
| e4d8ea17fc | |||
| e69db17804 | |||
| 9282cca396 | |||
| ad2eaeb004 | |||
| 3f44c9af24 | |||
| 4296515473 | |||
| 88830a2e09 | |||
| c8a269e391 | |||
| a45897007c | |||
| 42ea1df342 | |||
| 47214e33ea | |||
| ff239d81e5 | |||
| 0a6e47dd61 | |||
| ee75219ed3 | |||
| 2e337a753f | |||
| 9918d91def | |||
| 82dd5751c1 | |||
| de637fab50 | |||
| cb49f6f01d | |||
| 6d0a83aa58 | |||
| 05f57e98ad | |||
| 6a66525927 | |||
| 9530a6055a | |||
| f5ed8debcc | |||
| 355d2449c5 | |||
| f7382cdd6f | |||
| dc8d10d068 | |||
| 951245d33f | |||
| 521b901cc9 | |||
| 6311ba451b | |||
| 754c76d6fd | |||
| aea7ff1c89 | |||
| 77ac972e09 | |||
| 256f85024f | |||
| 870f9e42dd | |||
| 0a4a3a24e1 | |||
| 8a66390a78 | |||
| 3d48eded3d | |||
| 6546426ec0 | |||
| 4a7ceb00fb | |||
| 53560378bb | |||
| cdb3b6aafd | |||
| 9f3d3e8ba4 | |||
| 047aad592e | |||
| 087acd9180 | |||
| 0b822e4a5c | |||
| f8a469488e | |||
| 3b5e806418 | |||
| 79e208193a | |||
| ef5c13b1c1 | |||
| 38533213d2 | |||
| 5f1aa6fa9d | |||
| b0002fe07d | |||
| ef21fd1d26 | |||
| 4a311b9578 | |||
| b8da5e225b | |||
| 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 | |||
| f66405e79b | |||
| 74d186af16 | |||
| 187fef0c5a | |||
| 4782cab507 | |||
| 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 |
@@ -50,6 +50,10 @@ class ItemsModel: ObservableObject {
|
||||
var reversedChatItems: [ChatItem] = [] {
|
||||
willSet { publisher.send() }
|
||||
}
|
||||
var itemAdded = false {
|
||||
willSet { publisher.send() }
|
||||
}
|
||||
|
||||
init() {
|
||||
publisher
|
||||
.throttle(for: 0.25, scheduler: DispatchQueue.main, latest: true)
|
||||
@@ -228,10 +232,17 @@ final class ChatModel: ObservableObject {
|
||||
chats.firstIndex(where: { $0.id == id })
|
||||
}
|
||||
|
||||
func addChat(_ chat: Chat, at position: Int = 0) {
|
||||
withAnimation {
|
||||
chats.insert(chat, at: position)
|
||||
func addChat(_ chat: Chat) {
|
||||
if chatId == nil {
|
||||
withAnimation { addChat_(chat, at: 0) }
|
||||
} else {
|
||||
addChat_(chat, at: 0)
|
||||
}
|
||||
popChatCollector.throttlePopChat(chat.chatInfo.id, currentPosition: 0)
|
||||
}
|
||||
|
||||
func addChat_(_ chat: Chat, at position: Int = 0) {
|
||||
chats.insert(chat, at: position)
|
||||
}
|
||||
|
||||
func updateChatInfo(_ cInfo: ChatInfo) {
|
||||
@@ -305,10 +316,11 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addChat(Chat(c), at: i)
|
||||
addChat_(Chat(c), at: i)
|
||||
}
|
||||
}
|
||||
NtfManager.shared.setNtfBadgeCount(totalUnreadCountForAllUsers())
|
||||
popChatCollector.clear()
|
||||
}
|
||||
|
||||
// func addGroup(_ group: SimpleXChat.Group) {
|
||||
@@ -333,18 +345,9 @@ final class ChatModel: ObservableObject {
|
||||
[cItem]
|
||||
}
|
||||
if case .rcvNew = cItem.meta.itemStatus {
|
||||
chats[i].chatStats.unreadCount = chats[i].chatStats.unreadCount + 1
|
||||
increaseUnreadCounter(user: currentUser!)
|
||||
}
|
||||
if i > 0 {
|
||||
if chatId == nil {
|
||||
withAnimation { popChat_(i) }
|
||||
} else if chatId == cInfo.id {
|
||||
chatToTop = cInfo.id
|
||||
} else {
|
||||
popChat_(i)
|
||||
}
|
||||
unreadCollector.changeUnreadCounter(cInfo.id, by: 1)
|
||||
}
|
||||
popChatCollector.throttlePopChat(cInfo.id, currentPosition: i)
|
||||
} else {
|
||||
addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
|
||||
}
|
||||
@@ -390,6 +393,7 @@ final class ChatModel: ObservableObject {
|
||||
ci.meta.itemStatus = status
|
||||
}
|
||||
im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0)
|
||||
im.itemAdded = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -422,8 +426,8 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
|
||||
func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
|
||||
if cItem.isRcvNew, let chatIndex = getChatIndex(cInfo.id) {
|
||||
decreaseUnreadCounter(chatIndex)
|
||||
if cItem.isRcvNew {
|
||||
unreadCollector.changeUnreadCounter(cInfo.id, by: -1)
|
||||
}
|
||||
// update previews
|
||||
if let chat = getChat(cInfo.id) {
|
||||
@@ -484,6 +488,7 @@ final class ChatModel: ObservableObject {
|
||||
let cItem = ChatItem.liveDummy(chatInfo.chatType)
|
||||
withAnimation {
|
||||
im.reversedChatItems.insert(cItem, at: 0)
|
||||
im.itemAdded = true
|
||||
}
|
||||
return cItem
|
||||
}
|
||||
@@ -572,14 +577,13 @@ final class ChatModel: ObservableObject {
|
||||
func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async {
|
||||
if chatId == cInfo.id,
|
||||
let itemIndex = getChatItemIndex(cItem),
|
||||
let chatIndex = getChatIndex(cInfo.id),
|
||||
im.reversedChatItems[itemIndex].isRcvNew {
|
||||
await MainActor.run {
|
||||
withTransaction(Transaction()) {
|
||||
// update current chat
|
||||
markChatItemRead_(itemIndex)
|
||||
// update preview
|
||||
unreadCollector.decreaseUnreadCounter(chatIndex)
|
||||
unreadCollector.changeUnreadCounter(cInfo.id, by: -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -588,26 +592,92 @@ final class ChatModel: ObservableObject {
|
||||
private let unreadCollector = UnreadCollector()
|
||||
|
||||
class UnreadCollector {
|
||||
private let subject = PassthroughSubject<Int, Never>()
|
||||
private let subject = PassthroughSubject<Void, Never>()
|
||||
private var bag = Set<AnyCancellable>()
|
||||
private var dictionary = Dictionary<Int, Int>()
|
||||
private var unreadCounts: [ChatId: Int] = [:]
|
||||
|
||||
init() {
|
||||
subject
|
||||
.debounce(for: 1, scheduler: DispatchQueue.main)
|
||||
.sink { _ in
|
||||
self.dictionary.forEach { key, value in
|
||||
ChatModel.shared.decreaseUnreadCounter(key, by: value)
|
||||
.sink {
|
||||
let m = ChatModel.shared
|
||||
for (chatId, count) in self.unreadCounts {
|
||||
if let i = m.getChatIndex(chatId) {
|
||||
m.changeUnreadCounter(i, by: count)
|
||||
}
|
||||
}
|
||||
self.dictionary = Dictionary<Int, Int>()
|
||||
self.unreadCounts = [:]
|
||||
}
|
||||
.store(in: &bag)
|
||||
}
|
||||
|
||||
func changeUnreadCounter(_ chatId: ChatId, by count: Int) {
|
||||
DispatchQueue.main.async {
|
||||
self.unreadCounts[chatId] = (self.unreadCounts[chatId] ?? 0) + count
|
||||
}
|
||||
subject.send()
|
||||
}
|
||||
}
|
||||
|
||||
let popChatCollector = PopChatCollector()
|
||||
|
||||
class PopChatCollector {
|
||||
private let subject = PassthroughSubject<Void, Never>()
|
||||
private var bag = Set<AnyCancellable>()
|
||||
private var chatsToPop: [ChatId: Date] = [:]
|
||||
private let popTsComparator = KeyPathComparator<Chat>(\.popTs, order: .reverse)
|
||||
|
||||
init() {
|
||||
subject
|
||||
.throttle(for: 2, scheduler: DispatchQueue.main, latest: true)
|
||||
.sink { self.popCollectedChats() }
|
||||
.store(in: &bag)
|
||||
}
|
||||
|
||||
// Only call from main thread
|
||||
func decreaseUnreadCounter(_ chatIndex: Int) {
|
||||
dictionary[chatIndex] = (dictionary[chatIndex] ?? 0) + 1
|
||||
subject.send(chatIndex)
|
||||
func throttlePopChat(_ chatId: ChatId, currentPosition: Int) {
|
||||
let m = ChatModel.shared
|
||||
if currentPosition > 0 && m.chatId == chatId {
|
||||
m.chatToTop = chatId
|
||||
}
|
||||
if currentPosition > 0 || !chatsToPop.isEmpty {
|
||||
chatsToPop[chatId] = Date.now
|
||||
subject.send()
|
||||
}
|
||||
}
|
||||
|
||||
func clear() {
|
||||
chatsToPop = [:]
|
||||
}
|
||||
|
||||
func popCollectedChats() {
|
||||
let m = ChatModel.shared
|
||||
var ixs: IndexSet = []
|
||||
var chs: [Chat] = []
|
||||
// collect chats that received updates
|
||||
for (chatId, popTs) in self.chatsToPop {
|
||||
// Currently opened chat is excluded, removing it from the list would navigate out of it
|
||||
// It will be popped to top later when user exits from the list.
|
||||
if m.chatId != chatId, let i = m.getChatIndex(chatId) {
|
||||
ixs.insert(i)
|
||||
let ch = m.chats[i]
|
||||
ch.popTs = popTs
|
||||
chs.append(ch)
|
||||
}
|
||||
}
|
||||
|
||||
let removeInsert = {
|
||||
m.chats.remove(atOffsets: ixs)
|
||||
// sort chats by pop timestamp in descending order
|
||||
m.chats.insert(contentsOf: chs.sorted(using: self.popTsComparator), at: 0)
|
||||
}
|
||||
|
||||
if m.chatId == nil {
|
||||
withAnimation { removeInsert() }
|
||||
} else {
|
||||
removeInsert()
|
||||
}
|
||||
|
||||
self.chatsToPop = [:]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,25 +692,24 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func decreaseUnreadCounter(_ chatIndex: Int, by count: Int = 1) {
|
||||
chats[chatIndex].chatStats.unreadCount = chats[chatIndex].chatStats.unreadCount - count
|
||||
decreaseUnreadCounter(user: currentUser!, by: count)
|
||||
func changeUnreadCounter(_ chatIndex: Int, by count: Int) {
|
||||
chats[chatIndex].chatStats.unreadCount = chats[chatIndex].chatStats.unreadCount + count
|
||||
changeUnreadCounter(user: currentUser!, by: count)
|
||||
}
|
||||
|
||||
func increaseUnreadCounter(user: any UserLike) {
|
||||
changeUnreadCounter(user: user, by: 1)
|
||||
NtfManager.shared.incNtfBadgeCount()
|
||||
}
|
||||
|
||||
func decreaseUnreadCounter(user: any UserLike, by: Int = 1) {
|
||||
changeUnreadCounter(user: user, by: -by)
|
||||
NtfManager.shared.decNtfBadgeCount(by: by)
|
||||
}
|
||||
|
||||
private func changeUnreadCounter(user: any UserLike, by: Int) {
|
||||
if let i = users.firstIndex(where: { $0.user.userId == user.userId }) {
|
||||
users[i].unreadCount += by
|
||||
}
|
||||
NtfManager.shared.changeNtfBadgeCount(by: by)
|
||||
}
|
||||
|
||||
func totalUnreadCountForAllUsers() -> Int {
|
||||
@@ -706,6 +775,7 @@ final class ChatModel: ObservableObject {
|
||||
|
||||
func popChat(_ id: String) {
|
||||
if let i = getChatIndex(id) {
|
||||
// no animation here, for it not to look like it just moved when leaving the chat
|
||||
popChat_(i)
|
||||
}
|
||||
}
|
||||
@@ -778,7 +848,13 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return UnreadChatItemCounts(isNearBottom: totalBelow < 16, unreadBelow: unreadBelow)
|
||||
return UnreadChatItemCounts(
|
||||
// TODO these thresholds account for the fact that items are still "visible" while
|
||||
// covered by compose area, they should be replaced with the actual height in pixels below the screen.
|
||||
isNearBottom: totalBelow < 15,
|
||||
isReallyNearBottom: totalBelow < 2,
|
||||
unreadBelow: unreadBelow
|
||||
)
|
||||
}
|
||||
|
||||
func topItemInView(itemsInView: Set<String>) -> ChatItem? {
|
||||
@@ -817,6 +893,7 @@ struct NTFContactRequest {
|
||||
|
||||
struct UnreadChatItemCounts: Equatable {
|
||||
var isNearBottom: Bool
|
||||
var isReallyNearBottom: Bool
|
||||
var unreadBelow: Int
|
||||
}
|
||||
|
||||
@@ -825,6 +902,7 @@ final class Chat: ObservableObject, Identifiable, ChatLike {
|
||||
@Published var chatItems: [ChatItem]
|
||||
@Published var chatStats: ChatStats
|
||||
var created = Date.now
|
||||
fileprivate var popTs: Date?
|
||||
|
||||
init(_ cData: ChatData) {
|
||||
self.chatInfo = cData.chatInfo
|
||||
|
||||
@@ -238,12 +238,8 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
ntfBadgeCountGroupDefault.set(count)
|
||||
}
|
||||
|
||||
func decNtfBadgeCount(by count: Int = 1) {
|
||||
setNtfBadgeCount(max(0, UIApplication.shared.applicationIconBadgeNumber - count))
|
||||
}
|
||||
|
||||
func incNtfBadgeCount(by count: Int = 1) {
|
||||
setNtfBadgeCount(UIApplication.shared.applicationIconBadgeNumber + count)
|
||||
func changeNtfBadgeCount(by count: Int = 1) {
|
||||
setNtfBadgeCount(max(0, UIApplication.shared.applicationIconBadgeNumber + count))
|
||||
}
|
||||
|
||||
private func addNotification(_ content: UNMutableNotificationContent) {
|
||||
|
||||
@@ -137,8 +137,8 @@ func apiGetActiveUser(ctrl: chat_ctrl? = nil) throws -> User? {
|
||||
}
|
||||
}
|
||||
|
||||
func apiCreateActiveUser(_ p: Profile?, sameServers: Bool = false, pastTimestamp: Bool = false, ctrl: chat_ctrl? = nil) throws -> User {
|
||||
let r = chatSendCmdSync(.createActiveUser(profile: p, sameServers: sameServers, pastTimestamp: pastTimestamp), ctrl)
|
||||
func apiCreateActiveUser(_ p: Profile?, pastTimestamp: Bool = false, ctrl: chat_ctrl? = nil) throws -> User {
|
||||
let r = chatSendCmdSync(.createActiveUser(profile: p, pastTimestamp: pastTimestamp), ctrl)
|
||||
if case let .activeUser(user) = r { return user }
|
||||
throw r
|
||||
}
|
||||
@@ -279,8 +279,10 @@ func apiGetAppSettings(settings: AppSettings) throws -> AppSettings {
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiExportArchive(config: ArchiveConfig) async throws {
|
||||
try await sendCommandOkResp(.apiExportArchive(config: config))
|
||||
func apiExportArchive(config: ArchiveConfig) async throws -> [ArchiveError] {
|
||||
let r = await chatSendCmd(.apiExportArchive(config: config))
|
||||
if case let .archiveExported(archiveErrors) = r { return archiveErrors }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiImportArchive(config: ArchiveConfig) async throws -> [ArchiveError] {
|
||||
@@ -338,7 +340,13 @@ func loadChat(chat: Chat, search: String = "") {
|
||||
m.chatItemStatuses = [:]
|
||||
im.reversedChatItems = []
|
||||
let chat = try apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search)
|
||||
m.updateChatInfo(chat.chatInfo)
|
||||
if case let .direct(contact) = chat.chatInfo, !cInfo.chatDeleted, chat.chatInfo.chatDeleted {
|
||||
var updatedContact = contact
|
||||
updatedContact.chatDeleted = false
|
||||
m.updateContact(updatedContact)
|
||||
} else {
|
||||
m.updateChatInfo(chat.chatInfo)
|
||||
}
|
||||
im.reversedChatItems = chat.chatItems.reversed()
|
||||
} catch let error {
|
||||
logger.error("loadChat error: \(responseError(error))")
|
||||
@@ -759,22 +767,38 @@ func apiConnectContactViaAddress(incognito: Bool, contactId: Int64) async -> (Co
|
||||
return (nil, alert)
|
||||
}
|
||||
|
||||
func apiDeleteChat(type: ChatType, id: Int64, notify: Bool? = nil) async throws {
|
||||
func apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async throws {
|
||||
let chatId = type.rawValue + id.description
|
||||
DispatchQueue.main.async { ChatModel.shared.deletedChats.insert(chatId) }
|
||||
defer { DispatchQueue.main.async { ChatModel.shared.deletedChats.remove(chatId) } }
|
||||
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, notify: notify), bgTask: false)
|
||||
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false)
|
||||
if case .direct = type, case .contactDeleted = r { return }
|
||||
if case .contactConnection = type, case .contactConnectionDeleted = r { return }
|
||||
if case .group = type, case .groupDeletedUser = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func deleteChat(_ chat: Chat, notify: Bool? = nil) async {
|
||||
func apiDeleteContact(id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async throws -> Contact {
|
||||
let type: ChatType = .direct
|
||||
let chatId = type.rawValue + id.description
|
||||
if case .full = chatDeleteMode {
|
||||
DispatchQueue.main.async { ChatModel.shared.deletedChats.insert(chatId) }
|
||||
}
|
||||
defer {
|
||||
if case .full = chatDeleteMode {
|
||||
DispatchQueue.main.async { ChatModel.shared.deletedChats.remove(chatId) }
|
||||
}
|
||||
}
|
||||
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false)
|
||||
if case let .contactDeleted(_, contact) = r { return contact }
|
||||
throw r
|
||||
}
|
||||
|
||||
func deleteChat(_ chat: Chat, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async {
|
||||
do {
|
||||
let cInfo = chat.chatInfo
|
||||
try await apiDeleteChat(type: cInfo.chatType, id: cInfo.apiId, notify: notify)
|
||||
DispatchQueue.main.async { ChatModel.shared.removeChat(cInfo.id) }
|
||||
try await apiDeleteChat(type: cInfo.chatType, id: cInfo.apiId, chatDeleteMode: chatDeleteMode)
|
||||
await MainActor.run { ChatModel.shared.removeChat(cInfo.id) }
|
||||
} catch let error {
|
||||
logger.error("deleteChat apiDeleteChat error: \(responseError(error))")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
@@ -784,6 +808,39 @@ func deleteChat(_ chat: Chat, notify: Bool? = nil) async {
|
||||
}
|
||||
}
|
||||
|
||||
func deleteContactChat(_ chat: Chat, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async -> Alert? {
|
||||
do {
|
||||
let cInfo = chat.chatInfo
|
||||
let ct = try await apiDeleteContact(id: cInfo.apiId, chatDeleteMode: chatDeleteMode)
|
||||
await MainActor.run {
|
||||
switch chatDeleteMode {
|
||||
case .full:
|
||||
ChatModel.shared.removeChat(cInfo.id)
|
||||
case .entity:
|
||||
ChatModel.shared.removeChat(cInfo.id)
|
||||
ChatModel.shared.addChat(Chat(
|
||||
chatInfo: .direct(contact: ct),
|
||||
chatItems: chat.chatItems
|
||||
))
|
||||
case .messages:
|
||||
ChatModel.shared.removeChat(cInfo.id)
|
||||
ChatModel.shared.addChat(Chat(
|
||||
chatInfo: .direct(contact: ct),
|
||||
chatItems: []
|
||||
))
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("deleteContactChat apiDeleteContact error: \(responseError(error))")
|
||||
return mkAlert(
|
||||
title: "Error deleting chat!",
|
||||
message: "Error: \(responseError(error))"
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func apiClearChat(type: ChatType, id: Int64) async throws -> ChatInfo {
|
||||
let r = await chatSendCmd(.apiClearChat(type: type, id: id), bgTask: false)
|
||||
if case let .chatCleared(_, updatedChatInfo) = r { return updatedChatInfo }
|
||||
@@ -1112,10 +1169,17 @@ func networkErrorAlert(_ r: ChatResponse) -> Alert? {
|
||||
func acceptContactRequest(incognito: Bool, contactRequest: UserContactRequest) async {
|
||||
if let contact = await apiAcceptContactRequest(incognito: incognito, contactReqId: contactRequest.apiId) {
|
||||
let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: [])
|
||||
DispatchQueue.main.async {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.replaceChat(contactRequest.id, chat)
|
||||
ChatModel.shared.setContactNetworkStatus(contact, .connected)
|
||||
}
|
||||
if contact.sndReady {
|
||||
DispatchQueue.main.async {
|
||||
dismissAllSheets(animated: true) {
|
||||
ChatModel.shared.chatId = chat.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1530,6 +1594,7 @@ func getUserChatData() throws {
|
||||
m.chatItemTTL = try getChatItemTTL()
|
||||
let chats = try apiGetChats()
|
||||
m.chats = chats.map { Chat.init($0) }
|
||||
m.popChatCollector.clear()
|
||||
}
|
||||
|
||||
private func getUserChatDataAsync() async throws {
|
||||
@@ -1542,11 +1607,13 @@ private func getUserChatDataAsync() async throws {
|
||||
m.userAddress = userAddress
|
||||
m.chatItemTTL = chatItemTTL
|
||||
m.chats = chats.map { Chat.init($0) }
|
||||
m.popChatCollector.clear()
|
||||
}
|
||||
} else {
|
||||
await MainActor.run {
|
||||
m.userAddress = nil
|
||||
m.chats = []
|
||||
m.popChatCollector.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1711,6 +1778,11 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
let cItem = aChatItem.chatItem
|
||||
await MainActor.run {
|
||||
if active(user) {
|
||||
if case let .direct(contact) = cInfo, contact.chatDeleted {
|
||||
var updatedContact = contact
|
||||
updatedContact.chatDeleted = false
|
||||
m.updateContact(updatedContact)
|
||||
}
|
||||
m.addChatItem(cInfo, cItem)
|
||||
} else if cItem.isRcvNew && cInfo.ntfsEnabled {
|
||||
m.increaseUnreadCounter(user: user)
|
||||
|
||||
@@ -58,6 +58,7 @@ struct SimpleXApp: App {
|
||||
}
|
||||
.onChange(of: scenePhase) { phase in
|
||||
logger.debug("scenePhase was \(String(describing: scenePhase)), now \(String(describing: phase))")
|
||||
AppSheetState.shared.scenePhaseActive = phase == .active
|
||||
switch (phase) {
|
||||
case .background:
|
||||
// --- authentication
|
||||
|
||||
@@ -94,17 +94,20 @@ struct ChatInfoView: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@ObservedObject var chat: Chat
|
||||
@State var contact: Contact
|
||||
@Binding var connectionStats: ConnectionStats?
|
||||
@Binding var customUserProfile: Profile?
|
||||
@State var localAlias: String
|
||||
@Binding var connectionCode: String?
|
||||
var onSearch: () -> Void
|
||||
@State private var connectionStats: ConnectionStats? = nil
|
||||
@State private var customUserProfile: Profile? = nil
|
||||
@State private var connectionCode: String? = nil
|
||||
@FocusState private var aliasTextFieldFocused: Bool
|
||||
@State private var alert: ChatInfoViewAlert? = nil
|
||||
@State private var showDeleteContactActionSheet = false
|
||||
@State private var actionSheet: SomeActionSheet? = nil
|
||||
@State private var sheet: SomeSheet<AnyView>? = nil
|
||||
@State private var showConnectContactViaAddressDialog = false
|
||||
@State private var sendReceipts = SendReceipts.userDefault(true)
|
||||
@State private var sendReceiptsUserDefault = true
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
|
||||
|
||||
enum ChatInfoViewAlert: Identifiable {
|
||||
case clearChatAlert
|
||||
case networkStatusAlert
|
||||
@@ -112,6 +115,7 @@ struct ChatInfoView: View {
|
||||
case abortSwitchAddressAlert
|
||||
case syncConnectionForceAlert
|
||||
case queueInfo(info: String)
|
||||
case someAlert(alert: SomeAlert)
|
||||
case error(title: LocalizedStringKey, error: LocalizedStringKey?)
|
||||
|
||||
var id: String {
|
||||
@@ -122,11 +126,12 @@ struct ChatInfoView: View {
|
||||
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
|
||||
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
|
||||
case let .queueInfo(info): return "queueInfo \(info)"
|
||||
case let .someAlert(alert): return "chatInfoSomeAlert \(alert.id)"
|
||||
case let .error(title, _): return "error \(title)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
@@ -136,12 +141,29 @@ struct ChatInfoView: View {
|
||||
.onTapGesture {
|
||||
aliasTextFieldFocused = false
|
||||
}
|
||||
|
||||
|
||||
Group {
|
||||
localAliasTextEdit()
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.padding(.bottom, 18)
|
||||
|
||||
GeometryReader { g in
|
||||
HStack(alignment: .center, spacing: 8) {
|
||||
let buttonWidth = g.size.width / 4
|
||||
searchButton(width: buttonWidth)
|
||||
AudioCallButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) }
|
||||
VideoButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) }
|
||||
muteButton(width: buttonWidth)
|
||||
}
|
||||
}
|
||||
.padding(.trailing)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: infoViewActionButtonHeight)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 8))
|
||||
|
||||
if let customUserProfile = customUserProfile {
|
||||
Section(header: Text("Incognito").foregroundColor(theme.colors.secondary)) {
|
||||
@@ -153,7 +175,7 @@ struct ChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Section {
|
||||
Group {
|
||||
if let code = connectionCode { verifyCodeButton(code) }
|
||||
@@ -173,14 +195,18 @@ struct ChatInfoView: View {
|
||||
} label: {
|
||||
Label("Chat theme", systemImage: "photo")
|
||||
}
|
||||
// } else if developerTools {
|
||||
// synchronizeConnectionButtonForce()
|
||||
// }
|
||||
}
|
||||
|
||||
.disabled(!contact.ready || !contact.active)
|
||||
|
||||
if let conn = contact.activeConn {
|
||||
Section {
|
||||
infoRow(Text(String("E2E encryption")), conn.connPQEnabled ? "Quantum resistant" : "Standard")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let contactLink = contact.contactLink {
|
||||
Section {
|
||||
SimpleXLinkQRCode(uri: contactLink)
|
||||
@@ -197,7 +223,7 @@ struct ChatInfoView: View {
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if contact.ready && contact.active {
|
||||
Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) {
|
||||
networkStatusRow()
|
||||
@@ -226,12 +252,12 @@ struct ChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Section {
|
||||
clearChatButton()
|
||||
deleteContactButton()
|
||||
}
|
||||
|
||||
|
||||
if developerTools {
|
||||
Section(header: Text("For console").foregroundColor(theme.colors.secondary)) {
|
||||
infoRow("Local name", chat.chatInfo.localDisplayName)
|
||||
@@ -260,6 +286,24 @@ struct ChatInfoView: View {
|
||||
sendReceiptsUserDefault = currentUser.sendRcptsContacts
|
||||
}
|
||||
sendReceipts = SendReceipts.fromBool(contact.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault)
|
||||
|
||||
|
||||
Task {
|
||||
do {
|
||||
let (stats, profile) = try await apiContactInfo(chat.chatInfo.apiId)
|
||||
let (ct, code) = try await apiGetContactCode(chat.chatInfo.apiId)
|
||||
await MainActor.run {
|
||||
connectionStats = stats
|
||||
customUserProfile = profile
|
||||
connectionCode = code
|
||||
if contact.activeConn?.connectionCode != ct.activeConn?.connectionCode {
|
||||
chat.chatInfo = .direct(contact: ct)
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiContactInfo or apiGetContactCode error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { alertItem in
|
||||
switch(alertItem) {
|
||||
@@ -269,37 +313,26 @@ struct ChatInfoView: View {
|
||||
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress)
|
||||
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncContactConnection(force: true) })
|
||||
case let .queueInfo(info): return queueInfoAlert(info)
|
||||
case let .someAlert(a): return a.alert
|
||||
case let .error(title, error): return mkAlert(title: title, message: error)
|
||||
}
|
||||
}
|
||||
.actionSheet(isPresented: $showDeleteContactActionSheet) {
|
||||
if contact.sndReady && contact.active {
|
||||
return ActionSheet(
|
||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||
buttons: [
|
||||
.destructive(Text("Delete and notify contact")) { deleteContact(notify: true) },
|
||||
.destructive(Text("Delete")) { deleteContact(notify: false) },
|
||||
.cancel()
|
||||
]
|
||||
)
|
||||
.actionSheet(item: $actionSheet) { $0.actionSheet }
|
||||
.sheet(item: $sheet) {
|
||||
if #available(iOS 16.0, *) {
|
||||
$0.content
|
||||
.presentationDetents([.fraction(0.4)])
|
||||
} else {
|
||||
return ActionSheet(
|
||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||
buttons: [
|
||||
.destructive(Text("Delete")) { deleteContact() },
|
||||
.cancel()
|
||||
]
|
||||
)
|
||||
$0.content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func contactInfoHeader() -> some View {
|
||||
VStack {
|
||||
VStack(spacing: 8) {
|
||||
let cInfo = chat.chatInfo
|
||||
ChatInfoImage(chat: chat, size: 192, color: Color(uiColor: .tertiarySystemFill))
|
||||
.padding(.top, 12)
|
||||
.padding()
|
||||
.padding(.vertical, 12)
|
||||
if contact.verified {
|
||||
(
|
||||
Text(Image(systemName: "checkmark.shield"))
|
||||
@@ -328,7 +361,7 @@ struct ChatInfoView: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
|
||||
|
||||
private func localAliasTextEdit() -> some View {
|
||||
TextField("Set contact name…", text: $localAlias)
|
||||
.disableAutocorrection(true)
|
||||
@@ -345,7 +378,7 @@ struct ChatInfoView: View {
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
|
||||
|
||||
private func setContactAlias() {
|
||||
Task {
|
||||
do {
|
||||
@@ -360,6 +393,25 @@ struct ChatInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func searchButton(width: CGFloat) -> some View {
|
||||
InfoViewButton(image: "magnifyingglass", title: "search", width: width) {
|
||||
dismiss()
|
||||
onSearch()
|
||||
}
|
||||
.disabled(!contact.ready || chat.chatItems.isEmpty)
|
||||
}
|
||||
|
||||
private func muteButton(width: CGFloat) -> some View {
|
||||
InfoViewButton(
|
||||
image: chat.chatInfo.ntfsEnabled ? "speaker.slash.fill" : "speaker.wave.2.fill",
|
||||
title: chat.chatInfo.ntfsEnabled ? "mute" : "unmute",
|
||||
width: width
|
||||
) {
|
||||
toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled)
|
||||
}
|
||||
.disabled(!contact.ready || !contact.active)
|
||||
}
|
||||
|
||||
private func verifyCodeButton(_ code: String) -> some View {
|
||||
NavigationLink {
|
||||
VerifyCodeView(
|
||||
@@ -389,7 +441,7 @@ struct ChatInfoView: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func contactPreferencesButton() -> some View {
|
||||
NavigationLink {
|
||||
ContactPreferencesView(
|
||||
@@ -404,7 +456,7 @@ struct ChatInfoView: View {
|
||||
Label("Contact preferences", systemImage: "switch.2")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func sendReceiptsOption() -> some View {
|
||||
Picker(selection: $sendReceipts) {
|
||||
ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in
|
||||
@@ -418,13 +470,13 @@ struct ChatInfoView: View {
|
||||
setSendReceipts()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func setSendReceipts() {
|
||||
var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults
|
||||
chatSettings.sendRcpts = sendReceipts.bool()
|
||||
updateChatSettings(chat, chatSettings: chatSettings)
|
||||
}
|
||||
|
||||
|
||||
private func synchronizeConnectionButton() -> some View {
|
||||
Button {
|
||||
syncContactConnection(force: false)
|
||||
@@ -433,7 +485,7 @@ struct ChatInfoView: View {
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func synchronizeConnectionButtonForce() -> some View {
|
||||
Button {
|
||||
alert = .syncConnectionForceAlert
|
||||
@@ -442,7 +494,7 @@ struct ChatInfoView: View {
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func networkStatusRow() -> some View {
|
||||
HStack {
|
||||
Text("Network status")
|
||||
@@ -455,23 +507,30 @@ struct ChatInfoView: View {
|
||||
serverImage()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func serverImage() -> some View {
|
||||
let status = chatModel.contactNetworkStatus(contact)
|
||||
return Image(systemName: status.imageName)
|
||||
.foregroundColor(status == .connected ? .green : theme.colors.secondary)
|
||||
.font(.system(size: 12))
|
||||
}
|
||||
|
||||
|
||||
private func deleteContactButton() -> some View {
|
||||
Button(role: .destructive) {
|
||||
showDeleteContactActionSheet = true
|
||||
deleteContactDialog(
|
||||
chat,
|
||||
contact,
|
||||
dismissToChatList: true,
|
||||
showAlert: { alert = .someAlert(alert: $0) },
|
||||
showActionSheet: { actionSheet = $0 },
|
||||
showSheetContent: { sheet = $0 }
|
||||
)
|
||||
} label: {
|
||||
Label("Delete contact", systemImage: "trash")
|
||||
Label("Delete contact", systemImage: "person.badge.minus")
|
||||
.foregroundColor(Color.red)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func clearChatButton() -> some View {
|
||||
Button() {
|
||||
alert = .clearChatAlert
|
||||
@@ -480,26 +539,7 @@ struct ChatInfoView: View {
|
||||
.foregroundColor(Color.orange)
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteContact(notify: Bool? = nil) {
|
||||
Task {
|
||||
do {
|
||||
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, notify: notify)
|
||||
await MainActor.run {
|
||||
dismiss()
|
||||
chatModel.chatId = nil
|
||||
chatModel.removeChat(chat.chatInfo.id)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))")
|
||||
let a = getErrorAlert(error, "Error deleting contact")
|
||||
await MainActor.run {
|
||||
alert = .error(title: a.title, error: a.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func clearChatAlert() -> Alert {
|
||||
Alert(
|
||||
title: Text("Clear conversation?"),
|
||||
@@ -513,14 +553,14 @@ struct ChatInfoView: View {
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private func networkStatusAlert() -> Alert {
|
||||
Alert(
|
||||
title: Text("Network status"),
|
||||
message: Text(chatModel.contactNetworkStatus(contact).statusExplanation)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private func switchContactAddress() {
|
||||
Task {
|
||||
do {
|
||||
@@ -539,7 +579,7 @@ struct ChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func abortSwitchContactAddress() {
|
||||
Task {
|
||||
do {
|
||||
@@ -557,7 +597,7 @@ struct ChatInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func syncContactConnection(force: Bool) {
|
||||
Task {
|
||||
do {
|
||||
@@ -578,6 +618,153 @@ struct ChatInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct AudioCallButton: View {
|
||||
var chat: Chat
|
||||
var contact: Contact
|
||||
var width: CGFloat
|
||||
var showAlert: (SomeAlert) -> Void
|
||||
|
||||
var body: some View {
|
||||
CallButton(
|
||||
chat: chat,
|
||||
contact: contact,
|
||||
image: "phone.fill",
|
||||
title: "call",
|
||||
mediaType: .audio,
|
||||
width: width,
|
||||
showAlert: showAlert
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct VideoButton: View {
|
||||
var chat: Chat
|
||||
var contact: Contact
|
||||
var width: CGFloat
|
||||
var showAlert: (SomeAlert) -> Void
|
||||
|
||||
var body: some View {
|
||||
CallButton(
|
||||
chat: chat,
|
||||
contact: contact,
|
||||
image: "video.fill",
|
||||
title: "video",
|
||||
mediaType: .video,
|
||||
width: width,
|
||||
showAlert: showAlert
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private struct CallButton: View {
|
||||
var chat: Chat
|
||||
var contact: Contact
|
||||
var image: String
|
||||
var title: LocalizedStringKey
|
||||
var mediaType: CallMediaType
|
||||
var width: CGFloat
|
||||
var showAlert: (SomeAlert) -> Void
|
||||
|
||||
var body: some View {
|
||||
let canCall = contact.ready && contact.active && chat.chatInfo.featureEnabled(.calls) && ChatModel.shared.activeCall == nil
|
||||
|
||||
InfoViewButton(image: image, title: title, disabledLook: !canCall, width: width) {
|
||||
if canCall {
|
||||
CallController.shared.startCall(contact, mediaType)
|
||||
} else if contact.nextSendGrpInv {
|
||||
showAlert(SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Can't call contact",
|
||||
message: "Send message to enable calls."
|
||||
),
|
||||
id: "can't call contact, send message"
|
||||
))
|
||||
} else if !contact.active {
|
||||
showAlert(SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Can't call contact",
|
||||
message: "Contact is deleted."
|
||||
),
|
||||
id: "can't call contact, contact deleted"
|
||||
))
|
||||
} else if !contact.ready {
|
||||
showAlert(SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Can't call contact",
|
||||
message: "Connecting to contact, please wait or check later!"
|
||||
),
|
||||
id: "can't call contact, contact not ready"
|
||||
))
|
||||
} else if !chat.chatInfo.featureEnabled(.calls) {
|
||||
switch chat.chatInfo.showEnableCallsAlert {
|
||||
case .userEnable:
|
||||
showAlert(SomeAlert(
|
||||
alert: Alert(
|
||||
title: Text("Allow calls?"),
|
||||
message: Text("You need to allow your contact to call to be able to call them."),
|
||||
primaryButton: .default(Text("Allow")) {
|
||||
allowFeatureToContact(contact, .calls)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
),
|
||||
id: "allow calls"
|
||||
))
|
||||
case .askContact:
|
||||
showAlert(SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Calls prohibited!",
|
||||
message: "Please ask your contact to enable calls."
|
||||
),
|
||||
id: "calls prohibited, ask contact"
|
||||
))
|
||||
case .other:
|
||||
showAlert(SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Calls prohibited!",
|
||||
message: "Please check yours and your contact preferences."
|
||||
)
|
||||
, id: "calls prohibited, other"
|
||||
))
|
||||
}
|
||||
} else {
|
||||
showAlert(SomeAlert(
|
||||
alert: mkAlert(title: "Can't call contact"),
|
||||
id: "can't call contact"
|
||||
))
|
||||
}
|
||||
}
|
||||
.disabled(ChatModel.shared.activeCall != nil)
|
||||
}
|
||||
}
|
||||
|
||||
let infoViewActionButtonHeight: CGFloat = 60
|
||||
|
||||
struct InfoViewButton: View {
|
||||
var image: String
|
||||
var title: LocalizedStringKey
|
||||
var disabledLook: Bool = false
|
||||
var width: CGFloat
|
||||
var action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 4) {
|
||||
Image(systemName: image)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
Text(title)
|
||||
.font(.caption)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.foregroundColor(.accentColor)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.cornerRadius(10.0)
|
||||
.frame(width: width, height: infoViewActionButtonHeight)
|
||||
.disabled(disabledLook)
|
||||
.onTapGesture(perform: action)
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatWallpaperEditorSheet: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@@ -763,15 +950,222 @@ func queueInfoAlert(_ info: String) -> Alert {
|
||||
)
|
||||
}
|
||||
|
||||
func deleteContactDialog(
|
||||
_ chat: Chat,
|
||||
_ contact: Contact,
|
||||
dismissToChatList: Bool,
|
||||
showAlert: @escaping (SomeAlert) -> Void,
|
||||
showActionSheet: @escaping (SomeActionSheet) -> Void,
|
||||
showSheetContent: @escaping (SomeSheet<AnyView>) -> Void
|
||||
) {
|
||||
if contact.sndReady && contact.active && !contact.chatDeleted {
|
||||
deleteContactOrConversationDialog(chat, contact, dismissToChatList, showAlert, showActionSheet, showSheetContent)
|
||||
} else if contact.sndReady && contact.active && contact.chatDeleted {
|
||||
deleteContactWithoutConversation(chat, contact, dismissToChatList, showAlert, showActionSheet)
|
||||
} else { // !(contact.sndReady && contact.active)
|
||||
deleteNotReadyContact(chat, contact, dismissToChatList, showAlert, showActionSheet)
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteContactOrConversationDialog(
|
||||
_ chat: Chat,
|
||||
_ contact: Contact,
|
||||
_ dismissToChatList: Bool,
|
||||
_ showAlert: @escaping (SomeAlert) -> Void,
|
||||
_ showActionSheet: @escaping (SomeActionSheet) -> Void,
|
||||
_ showSheetContent: @escaping (SomeSheet<AnyView>) -> Void
|
||||
) {
|
||||
showActionSheet(SomeActionSheet(
|
||||
actionSheet: ActionSheet(
|
||||
title: Text("Delete contact?"),
|
||||
buttons: [
|
||||
.destructive(Text("Only delete conversation")) {
|
||||
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: .messages, dismissToChatList, showAlert)
|
||||
},
|
||||
.destructive(Text("Delete contact")) {
|
||||
showSheetContent(SomeSheet(
|
||||
content: { AnyView(
|
||||
DeleteActiveContactDialog(
|
||||
chat: chat,
|
||||
contact: contact,
|
||||
dismissToChatList: dismissToChatList,
|
||||
showAlert: showAlert
|
||||
)
|
||||
) },
|
||||
id: "DeleteActiveContactDialog"
|
||||
))
|
||||
},
|
||||
.cancel()
|
||||
]
|
||||
),
|
||||
id: "deleteContactOrConversationDialog"
|
||||
))
|
||||
}
|
||||
|
||||
private func deleteContactMaybeErrorAlert(
|
||||
_ chat: Chat,
|
||||
_ contact: Contact,
|
||||
chatDeleteMode: ChatDeleteMode,
|
||||
_ dismissToChatList: Bool,
|
||||
_ showAlert: @escaping (SomeAlert) -> Void
|
||||
) {
|
||||
Task {
|
||||
let alert_ = await deleteContactChat(chat, chatDeleteMode: chatDeleteMode)
|
||||
if let alert = alert_ {
|
||||
showAlert(SomeAlert(alert: alert, id: "deleteContactMaybeErrorAlert, error"))
|
||||
} else {
|
||||
if dismissToChatList {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.chatId = nil
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
dismissAllSheets(animated: true) {
|
||||
if case .messages = chatDeleteMode, showDeleteConversationNoticeDefault.get() {
|
||||
AlertManager.shared.showAlert(deleteConversationNotice(contact))
|
||||
} else if chatDeleteMode.isEntity, showDeleteContactNoticeDefault.get() {
|
||||
AlertManager.shared.showAlert(deleteContactNotice(contact))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if case .messages = chatDeleteMode, showDeleteConversationNoticeDefault.get() {
|
||||
showAlert(SomeAlert(alert: deleteConversationNotice(contact), id: "deleteContactMaybeErrorAlert, deleteConversationNotice"))
|
||||
} else if chatDeleteMode.isEntity, showDeleteContactNoticeDefault.get() {
|
||||
showAlert(SomeAlert(alert: deleteContactNotice(contact), id: "deleteContactMaybeErrorAlert, deleteContactNotice"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteConversationNotice(_ contact: Contact) -> Alert {
|
||||
return Alert(
|
||||
title: Text("Conversation deleted!"),
|
||||
message: Text("You can send messages to \(contact.displayName) from Archived contacts."),
|
||||
primaryButton: .default(Text("Don't show again")) {
|
||||
showDeleteConversationNoticeDefault.set(false)
|
||||
},
|
||||
secondaryButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
|
||||
private func deleteContactNotice(_ contact: Contact) -> Alert {
|
||||
return Alert(
|
||||
title: Text("Contact deleted!"),
|
||||
message: Text("You can still view conversation with \(contact.displayName) in the list of chats."),
|
||||
primaryButton: .default(Text("Don't show again")) {
|
||||
showDeleteContactNoticeDefault.set(false)
|
||||
},
|
||||
secondaryButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
|
||||
enum ContactDeleteMode {
|
||||
case full
|
||||
case entity
|
||||
|
||||
public func toChatDeleteMode(notify: Bool) -> ChatDeleteMode {
|
||||
switch self {
|
||||
case .full: .full(notify: notify)
|
||||
case .entity: .entity(notify: notify)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DeleteActiveContactDialog: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var chat: Chat
|
||||
var contact: Contact
|
||||
var dismissToChatList: Bool
|
||||
var showAlert: (SomeAlert) -> Void
|
||||
@State private var keepConversation = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
Section {
|
||||
Toggle("Keep conversation", isOn: $keepConversation)
|
||||
|
||||
Button(role: .destructive) {
|
||||
dismiss()
|
||||
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: contactDeleteMode.toChatDeleteMode(notify: false), dismissToChatList, showAlert)
|
||||
} label: {
|
||||
Text("Delete without notification")
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
dismiss()
|
||||
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: contactDeleteMode.toChatDeleteMode(notify: true), dismissToChatList, showAlert)
|
||||
} label: {
|
||||
Text("Delete and notify contact")
|
||||
}
|
||||
} footer: {
|
||||
Text("Contact will be deleted - this cannot be undone!")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
}
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
}
|
||||
}
|
||||
|
||||
var contactDeleteMode: ContactDeleteMode {
|
||||
keepConversation ? .entity : .full
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteContactWithoutConversation(
|
||||
_ chat: Chat,
|
||||
_ contact: Contact,
|
||||
_ dismissToChatList: Bool,
|
||||
_ showAlert: @escaping (SomeAlert) -> Void,
|
||||
_ showActionSheet: @escaping (SomeActionSheet) -> Void
|
||||
) {
|
||||
showActionSheet(SomeActionSheet(
|
||||
actionSheet: ActionSheet(
|
||||
title: Text("Confirm contact deletion?"),
|
||||
buttons: [
|
||||
.destructive(Text("Delete and notify contact")) {
|
||||
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: .full(notify: true), dismissToChatList, showAlert)
|
||||
},
|
||||
.destructive(Text("Delete without notification")) {
|
||||
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: .full(notify: false), dismissToChatList, showAlert)
|
||||
},
|
||||
.cancel()
|
||||
]
|
||||
),
|
||||
id: "deleteContactWithoutConversation"
|
||||
))
|
||||
}
|
||||
|
||||
private func deleteNotReadyContact(
|
||||
_ chat: Chat,
|
||||
_ contact: Contact,
|
||||
_ dismissToChatList: Bool,
|
||||
_ showAlert: @escaping (SomeAlert) -> Void,
|
||||
_ showActionSheet: @escaping (SomeActionSheet) -> Void
|
||||
) {
|
||||
showActionSheet(SomeActionSheet(
|
||||
actionSheet: ActionSheet(
|
||||
title: Text("Confirm contact deletion?"),
|
||||
buttons: [
|
||||
.destructive(Text("Confirm")) {
|
||||
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: .full(notify: false), dismissToChatList, showAlert)
|
||||
},
|
||||
.cancel()
|
||||
]
|
||||
),
|
||||
id: "deleteNotReadyContact"
|
||||
))
|
||||
}
|
||||
|
||||
struct ChatInfoView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ChatInfoView(
|
||||
chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []),
|
||||
contact: Contact.sampleData,
|
||||
connectionStats: Binding.constant(nil),
|
||||
customUserProfile: Binding.constant(nil),
|
||||
localAlias: "",
|
||||
connectionCode: Binding.constant(nil)
|
||||
onSearch: {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,7 +440,7 @@ struct ChatItemInfoView: View {
|
||||
|
||||
private func memberDeliveryStatusView(_ member: GroupMember, _ status: GroupSndStatus, _ sentViaProxy: Bool?) -> some View {
|
||||
HStack{
|
||||
ProfileImage(imageStr: member.image, size: 30)
|
||||
MemberProfileImage(member, size: 30)
|
||||
.padding(.trailing, 2)
|
||||
Text(member.chatViewName)
|
||||
.lineLimit(1)
|
||||
|
||||
@@ -47,65 +47,70 @@ struct ChatView: View {
|
||||
@State private var showDeleteSelectedMessages: Bool = false
|
||||
@State private var allowToDeleteSelectedMessagesForAll: Bool = false
|
||||
|
||||
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
let v = viewBody
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.keyboardPadding()
|
||||
if (searchMode) {
|
||||
v.toolbarBackground(.thinMaterial, for: .navigationBar)
|
||||
} else {
|
||||
v.toolbarBackground(.visible, for: .navigationBar)
|
||||
}
|
||||
viewBody
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.keyboardPadding()
|
||||
.toolbarBackground(.hidden, for: .navigationBar)
|
||||
} else {
|
||||
viewBody
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder
|
||||
private var viewBody: some View {
|
||||
let cInfo = chat.chatInfo
|
||||
return VStack(spacing: 0) {
|
||||
if searchMode {
|
||||
searchToolbar()
|
||||
ZStack {
|
||||
let wallpaperImage = theme.wallpaper.type.image
|
||||
let wallpaperType = theme.wallpaper.type
|
||||
let backgroundColor = theme.wallpaper.background ?? wallpaperType.defaultBackgroundColor(theme.base, theme.colors.background)
|
||||
let tintColor = theme.wallpaper.tint ?? wallpaperType.defaultTintColor(theme.base)
|
||||
Color.clear.ignoresSafeArea(.all)
|
||||
.if(wallpaperImage != nil) { view in
|
||||
view.modifier(
|
||||
ChatViewBackground(image: wallpaperImage!, imageType: wallpaperType, background: backgroundColor, tint: tintColor)
|
||||
)
|
||||
}
|
||||
VStack(spacing: 0) {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
chatItemsList()
|
||||
floatingButtons(counts: floatingButtonModel.unreadChatItemCounts)
|
||||
}
|
||||
connectingText()
|
||||
if selectedChatItems == nil {
|
||||
ComposeView(
|
||||
chat: chat,
|
||||
composeState: $composeState,
|
||||
keyboardVisible: $keyboardVisible
|
||||
)
|
||||
.disabled(!cInfo.sendMsgEnabled)
|
||||
} else {
|
||||
SelectedItemsBottomToolbar(
|
||||
chatItems: ItemsModel.shared.reversedChatItems,
|
||||
selectedChatItems: $selectedChatItems,
|
||||
chatInfo: chat.chatInfo,
|
||||
deleteItems: { forAll in
|
||||
allowToDeleteSelectedMessagesForAll = forAll
|
||||
showDeleteSelectedMessages = true
|
||||
},
|
||||
moderateItems: {
|
||||
if case let .group(groupInfo) = chat.chatInfo {
|
||||
showModerateSelectedMessagesAlert(groupInfo)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .top) {
|
||||
VStack(spacing: .zero) {
|
||||
if searchMode { searchToolbar() }
|
||||
Divider()
|
||||
}
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
let wallpaperImage = theme.wallpaper.type.image
|
||||
let wallpaperType = theme.wallpaper.type
|
||||
let backgroundColor = theme.wallpaper.background ?? wallpaperType.defaultBackgroundColor(theme.base, theme.colors.background)
|
||||
let tintColor = theme.wallpaper.tint ?? wallpaperType.defaultTintColor(theme.base)
|
||||
chatItemsList()
|
||||
.if(wallpaperImage != nil) { view in
|
||||
view.modifier(
|
||||
ChatViewBackground(image: wallpaperImage!, imageType: wallpaperType, background: backgroundColor, tint: tintColor)
|
||||
)
|
||||
}
|
||||
floatingButtons(counts: floatingButtonModel.unreadChatItemCounts)
|
||||
}
|
||||
connectingText()
|
||||
if selectedChatItems == nil {
|
||||
ComposeView(
|
||||
chat: chat,
|
||||
composeState: $composeState,
|
||||
keyboardVisible: $keyboardVisible
|
||||
)
|
||||
.disabled(!cInfo.sendMsgEnabled)
|
||||
} else {
|
||||
SelectedItemsBottomToolbar(
|
||||
chatItems: ItemsModel.shared.reversedChatItems,
|
||||
selectedChatItems: $selectedChatItems,
|
||||
chatInfo: chat.chatInfo,
|
||||
deleteItems: { forAll in
|
||||
allowToDeleteSelectedMessagesForAll = forAll
|
||||
showDeleteSelectedMessages = true
|
||||
},
|
||||
moderateItems: {
|
||||
if case let .group(groupInfo) = chat.chatInfo {
|
||||
showModerateSelectedMessagesAlert(groupInfo)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.background(ToolbarMaterial.material(toolbarMaterial))
|
||||
}
|
||||
.navigationTitle(cInfo.chatViewName)
|
||||
.background(theme.colors.background)
|
||||
@@ -157,7 +162,6 @@ struct ChatView: View {
|
||||
VideoPlayerView.players.removeAll()
|
||||
stopAudioPlayer()
|
||||
if chatModel.chatId == cInfo.id && !presentationMode.wrappedValue.isPresented {
|
||||
chatModel.chatId = nil
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
|
||||
if chatModel.chatId == nil {
|
||||
chatModel.chatItemStatuses = [:]
|
||||
@@ -179,32 +183,18 @@ struct ChatView: View {
|
||||
} else if case let .direct(contact) = cInfo {
|
||||
Button {
|
||||
Task {
|
||||
do {
|
||||
let (stats, profile) = try await apiContactInfo(chat.chatInfo.apiId)
|
||||
let (ct, code) = try await apiGetContactCode(chat.chatInfo.apiId)
|
||||
await MainActor.run {
|
||||
connectionStats = stats
|
||||
customUserProfile = profile
|
||||
connectionCode = code
|
||||
if contact.activeConn?.connectionCode != ct.activeConn?.connectionCode {
|
||||
chat.chatInfo = .direct(contact: ct)
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiContactInfo or apiGetContactCode error: \(responseError(error))")
|
||||
}
|
||||
await MainActor.run { showChatInfoSheet = true }
|
||||
showChatInfoSheet = true
|
||||
}
|
||||
} label: {
|
||||
ChatInfoToolbar(chat: chat)
|
||||
}
|
||||
.appSheet(isPresented: $showChatInfoSheet, onDismiss: {
|
||||
connectionStats = nil
|
||||
customUserProfile = nil
|
||||
connectionCode = nil
|
||||
theme = buildTheme()
|
||||
}) {
|
||||
ChatInfoView(chat: chat, contact: contact, connectionStats: $connectionStats, customUserProfile: $customUserProfile, localAlias: chat.chatInfo.localAlias, connectionCode: $connectionCode)
|
||||
.appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) {
|
||||
ChatInfoView(
|
||||
chat: chat,
|
||||
contact: contact,
|
||||
localAlias: chat.chatInfo.localAlias,
|
||||
onSearch: { focusSearch() }
|
||||
)
|
||||
}
|
||||
} else if case let .group(groupInfo) = cInfo {
|
||||
Button {
|
||||
@@ -222,7 +212,8 @@ struct ChatView: View {
|
||||
chat.chatInfo = .group(groupInfo: gInfo)
|
||||
chat.created = Date.now
|
||||
}
|
||||
)
|
||||
),
|
||||
onSearch: { focusSearch() }
|
||||
)
|
||||
}
|
||||
} else if case .local = cInfo {
|
||||
@@ -365,7 +356,6 @@ struct ChatView: View {
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 8)
|
||||
.background(.thinMaterial)
|
||||
}
|
||||
|
||||
private func voiceWithoutFrame(_ ci: ChatItem) -> Bool {
|
||||
@@ -376,7 +366,7 @@ struct ChatView: View {
|
||||
reversedChatItems
|
||||
.enumerated()
|
||||
.filter { (index, chatItem) in
|
||||
if let mergeCategory = chatItem.mergeCategory, index > .zero {
|
||||
if let mergeCategory = chatItem.mergeCategory, index > 0 {
|
||||
mergeCategory != reversedChatItems[index - 1].mergeCategory
|
||||
} else {
|
||||
true
|
||||
@@ -399,31 +389,49 @@ struct ChatView: View {
|
||||
: voiceNoFrame
|
||||
? (g.size.width - 32)
|
||||
: (g.size.width - 32) * 0.84
|
||||
return chatItemView(ci, maxWidth)
|
||||
.onAppear {
|
||||
floatingButtonModel.appeared(viewId: ci.viewId)
|
||||
}
|
||||
.onDisappear {
|
||||
floatingButtonModel.disappeared(viewId: ci.viewId)
|
||||
}
|
||||
.id(ci.id) // Required to trigger `onAppear` on iOS15
|
||||
return ChatItemWithMenu(
|
||||
chat: $chat,
|
||||
chatItem: ci,
|
||||
maxWidth: maxWidth,
|
||||
composeState: $composeState,
|
||||
selectedMember: $selectedMember,
|
||||
revealedChatItem: $revealedChatItem,
|
||||
selectedChatItems: $selectedChatItems
|
||||
)
|
||||
.onAppear {
|
||||
floatingButtonModel.appeared(viewId: ci.viewId)
|
||||
}
|
||||
.onDisappear {
|
||||
floatingButtonModel.disappeared(viewId: ci.viewId)
|
||||
}
|
||||
.id(ci.id) // Required to trigger `onAppear` on iOS15
|
||||
} loadPage: {
|
||||
loadChatItems(cInfo)
|
||||
}
|
||||
.onTapGesture { hideKeyboard() }
|
||||
.onChange(of: searchText) { _ in
|
||||
loadChat(chat: chat, search: searchText)
|
||||
.padding(.vertical, -InvertedTableView.inset)
|
||||
.onTapGesture { hideKeyboard() }
|
||||
.onChange(of: searchText) { _ in
|
||||
loadChat(chat: chat, search: searchText)
|
||||
}
|
||||
.onChange(of: chatModel.chatId) { chatId in
|
||||
if let chatId, let c = chatModel.getChat(chatId) {
|
||||
chat = c
|
||||
showChatInfoSheet = false
|
||||
loadChat(chat: c)
|
||||
scrollModel.scrollToBottom()
|
||||
}
|
||||
.onChange(of: chatModel.chatId) { chatId in
|
||||
if let chatId, let c = chatModel.getChat(chatId) {
|
||||
chat = c
|
||||
showChatInfoSheet = false
|
||||
loadChat(chat: c)
|
||||
}
|
||||
.onChange(of: im.reversedChatItems) { _ in
|
||||
floatingButtonModel.chatItemsChanged()
|
||||
}
|
||||
.onChange(of: im.itemAdded) { added in
|
||||
if added {
|
||||
im.itemAdded = false
|
||||
if floatingButtonModel.unreadChatItemCounts.isReallyNearBottom {
|
||||
scrollModel.scrollToBottom()
|
||||
}
|
||||
}
|
||||
.onChange(of: im.reversedChatItems) { _ in
|
||||
floatingButtonModel.chatItemsChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,19 +464,19 @@ struct ChatView: View {
|
||||
init() {
|
||||
unreadChatItemCounts = UnreadChatItemCounts(
|
||||
isNearBottom: true,
|
||||
unreadBelow: .zero
|
||||
isReallyNearBottom: true,
|
||||
unreadBelow: 0
|
||||
)
|
||||
events
|
||||
.receive(on: DispatchQueue.global(qos: .background))
|
||||
.scan(Set<String>()) { itemsInView, event in
|
||||
return switch event {
|
||||
case let .appeared(viewId):
|
||||
itemsInView.union([viewId])
|
||||
case let .disappeared(viewId):
|
||||
itemsInView.subtracting([viewId])
|
||||
case .chatItemsChanged:
|
||||
itemsInView
|
||||
var updated = itemsInView
|
||||
switch event {
|
||||
case let .appeared(viewId): updated.insert(viewId)
|
||||
case let .disappeared(viewId): updated.remove(viewId)
|
||||
case .chatItemsChanged: ()
|
||||
}
|
||||
return updated
|
||||
}
|
||||
.map { ChatModel.shared.unreadChatItemCounts(itemsInView: $0) }
|
||||
.removeDuplicates()
|
||||
@@ -564,14 +572,18 @@ struct ChatView: View {
|
||||
|
||||
private func searchButton() -> some View {
|
||||
Button {
|
||||
searchMode = true
|
||||
searchFocussed = true
|
||||
searchText = ""
|
||||
focusSearch()
|
||||
} label: {
|
||||
Label("Search", systemImage: "magnifyingglass")
|
||||
}
|
||||
}
|
||||
|
||||
private func focusSearch() {
|
||||
searchMode = true
|
||||
searchFocussed = true
|
||||
searchText = ""
|
||||
}
|
||||
|
||||
private func addMembersButton() -> some View {
|
||||
Button {
|
||||
if case let .group(gInfo) = chat.chatInfo {
|
||||
@@ -672,22 +684,10 @@ struct ChatView: View {
|
||||
VoiceItemState.chatView = [:]
|
||||
}
|
||||
|
||||
@ViewBuilder private func chatItemView(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View {
|
||||
ChatItemWithMenu(
|
||||
chat: chat,
|
||||
chatItem: ci,
|
||||
maxWidth: maxWidth,
|
||||
composeState: $composeState,
|
||||
selectedMember: $selectedMember,
|
||||
revealedChatItem: $revealedChatItem,
|
||||
selectedChatItems: $selectedChatItems
|
||||
)
|
||||
}
|
||||
|
||||
private struct ChatItemWithMenu: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@ObservedObject var chat: Chat
|
||||
@Binding @ObservedObject var chat: Chat
|
||||
let chatItem: ChatItem
|
||||
let maxWidth: CGFloat
|
||||
@Binding var composeState: ComposeState
|
||||
@@ -813,7 +813,7 @@ struct ChatView: View {
|
||||
.padding(.trailing, 12)
|
||||
}
|
||||
HStack(alignment: .top, spacing: 8) {
|
||||
ProfileImage(imageStr: member.memberProfile.image, size: memberImageSize, backgroundColor: theme.colors.background)
|
||||
MemberProfileImage(member, size: memberImageSize, backgroundColor: theme.colors.background)
|
||||
.onTapGesture {
|
||||
if m.membersLoaded {
|
||||
selectedMember = m.getGroupMember(member.groupMemberId)
|
||||
@@ -1098,7 +1098,7 @@ struct ChatView: View {
|
||||
}
|
||||
|
||||
func reactions(from: Int? = nil, till: Int? = nil) -> some View {
|
||||
ForEach(availableReactions[(from ?? .zero)..<(till ?? availableReactions.count)]) { reaction in
|
||||
ForEach(availableReactions[(from ?? 0)..<(till ?? availableReactions.count)]) { reaction in
|
||||
Button(reaction.text) {
|
||||
setReaction(chatItem, add: true, reaction: reaction)
|
||||
}
|
||||
|
||||
@@ -281,6 +281,7 @@ struct ComposeView: View {
|
||||
@State private var stopPlayback: Bool = false
|
||||
|
||||
@AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true
|
||||
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
@@ -381,7 +382,7 @@ struct ComposeView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(.thinMaterial)
|
||||
.background(ToolbarMaterial.material(toolbarMaterial))
|
||||
.onChange(of: composeState.message) { msg in
|
||||
if composeState.linkPreviewAllowed {
|
||||
if msg.count > 0 {
|
||||
|
||||
@@ -47,14 +47,13 @@ struct AddGroupMembersViewCommon: View {
|
||||
|
||||
var body: some View {
|
||||
if creatingGroup {
|
||||
NavigationView {
|
||||
addGroupMembersView()
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button ("Skip") { addedMembersCb(selectedContacts) }
|
||||
}
|
||||
addGroupMembersView()
|
||||
.navigationBarBackButtonHidden()
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button ("Skip") { addedMembersCb(selectedContacts) }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addGroupMembersView()
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@ struct GroupChatInfoView: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@ObservedObject var chat: Chat
|
||||
@Binding var groupInfo: GroupInfo
|
||||
var onSearch: () -> Void
|
||||
@State private var alert: GroupChatInfoViewAlert? = nil
|
||||
@State private var groupLink: String?
|
||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||
@State private var showAddMembersSheet: Bool = false
|
||||
@State private var groupLinkNavLinkActive: Bool = false
|
||||
@State private var addMembersNavLinkActive: Bool = false
|
||||
@State private var connectionStats: ConnectionStats?
|
||||
@State private var connectionCode: String?
|
||||
@State private var sendReceipts = SendReceipts.userDefault(true)
|
||||
@@ -68,6 +70,15 @@ struct GroupChatInfoView: View {
|
||||
List {
|
||||
groupInfoHeader()
|
||||
.listRowBackground(Color.clear)
|
||||
.padding(.bottom, 18)
|
||||
|
||||
infoActionButtons()
|
||||
.padding(.horizontal)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: infoViewActionButtonHeight)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
Section {
|
||||
if groupInfo.canEdit {
|
||||
@@ -198,24 +209,95 @@ struct GroupChatInfoView: View {
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
|
||||
func infoActionButtons() -> some View {
|
||||
GeometryReader { g in
|
||||
let buttonWidth = g.size.width / 4
|
||||
HStack(alignment: .center, spacing: 8) {
|
||||
searchButton(width: buttonWidth)
|
||||
if groupInfo.canAddMembers {
|
||||
addMembersActionButton(width: buttonWidth)
|
||||
}
|
||||
muteButton(width: buttonWidth)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
}
|
||||
|
||||
private func searchButton(width: CGFloat) -> some View {
|
||||
InfoViewButton(image: "magnifyingglass", title: "search", width: width) {
|
||||
dismiss()
|
||||
onSearch()
|
||||
}
|
||||
.disabled(!groupInfo.ready || chat.chatItems.isEmpty)
|
||||
}
|
||||
|
||||
@ViewBuilder private func addMembersActionButton(width: CGFloat) -> some View {
|
||||
if chat.chatInfo.incognito {
|
||||
ZStack {
|
||||
InfoViewButton(image: "link.badge.plus", title: "invite", width: width) {
|
||||
groupLinkNavLinkActive = true
|
||||
}
|
||||
|
||||
NavigationLink(isActive: $groupLinkNavLinkActive) {
|
||||
groupLinkDestinationView()
|
||||
} label: {
|
||||
EmptyView()
|
||||
}
|
||||
.frame(width: 1, height: 1)
|
||||
.hidden()
|
||||
}
|
||||
.disabled(!groupInfo.ready)
|
||||
} else {
|
||||
ZStack {
|
||||
InfoViewButton(image: "person.fill.badge.plus", title: "invite", width: width) {
|
||||
addMembersNavLinkActive = true
|
||||
}
|
||||
|
||||
NavigationLink(isActive: $addMembersNavLinkActive) {
|
||||
addMembersDestinationView()
|
||||
} label: {
|
||||
EmptyView()
|
||||
}
|
||||
.frame(width: 1, height: 1)
|
||||
.hidden()
|
||||
}
|
||||
.disabled(!groupInfo.ready)
|
||||
}
|
||||
}
|
||||
|
||||
private func muteButton(width: CGFloat) -> some View {
|
||||
InfoViewButton(
|
||||
image: chat.chatInfo.ntfsEnabled ? "speaker.slash.fill" : "speaker.wave.2.fill",
|
||||
title: chat.chatInfo.ntfsEnabled ? "mute" : "unmute",
|
||||
width: width
|
||||
) {
|
||||
toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled)
|
||||
}
|
||||
.disabled(!groupInfo.ready)
|
||||
}
|
||||
|
||||
private func addMembersButton() -> some View {
|
||||
NavigationLink {
|
||||
AddGroupMembersView(chat: chat, groupInfo: groupInfo)
|
||||
.onAppear {
|
||||
searchFocussed = false
|
||||
Task {
|
||||
let groupMembers = await apiListMembers(groupInfo.groupId)
|
||||
await MainActor.run {
|
||||
chatModel.groupMembers = groupMembers.map { GMember.init($0) }
|
||||
chatModel.populateGroupMembersIndexes()
|
||||
}
|
||||
}
|
||||
}
|
||||
addMembersDestinationView()
|
||||
} label: {
|
||||
Label("Invite members", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
|
||||
private func addMembersDestinationView() -> some View {
|
||||
AddGroupMembersView(chat: chat, groupInfo: groupInfo)
|
||||
.onAppear {
|
||||
searchFocussed = false
|
||||
Task {
|
||||
let groupMembers = await apiListMembers(groupInfo.groupId)
|
||||
await MainActor.run {
|
||||
chatModel.groupMembers = groupMembers.map { GMember.init($0) }
|
||||
chatModel.populateGroupMembersIndexes()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct MemberRowView: View {
|
||||
var groupInfo: GroupInfo
|
||||
@ObservedObject var groupMember: GMember
|
||||
@@ -226,7 +308,7 @@ struct GroupChatInfoView: View {
|
||||
var body: some View {
|
||||
let member = groupMember.wrapped
|
||||
let v = HStack{
|
||||
ProfileImage(imageStr: member.image, size: 38)
|
||||
MemberProfileImage(member, size: 38)
|
||||
.padding(.trailing, 2)
|
||||
// TODO server connection status
|
||||
VStack(alignment: .leading) {
|
||||
@@ -352,16 +434,7 @@ struct GroupChatInfoView: View {
|
||||
|
||||
private func groupLinkButton() -> some View {
|
||||
NavigationLink {
|
||||
GroupLinkView(
|
||||
groupId: groupInfo.groupId,
|
||||
groupLink: $groupLink,
|
||||
groupLinkMemberRole: $groupLinkMemberRole,
|
||||
showTitle: false,
|
||||
creatingGroup: false
|
||||
)
|
||||
.navigationBarTitle("Group link")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
groupLinkDestinationView()
|
||||
} label: {
|
||||
if groupLink == nil {
|
||||
Label("Create group link", systemImage: "link.badge.plus")
|
||||
@@ -371,6 +444,19 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func groupLinkDestinationView() -> some View {
|
||||
GroupLinkView(
|
||||
groupId: groupInfo.groupId,
|
||||
groupLink: $groupLink,
|
||||
groupLinkMemberRole: $groupLinkMemberRole,
|
||||
showTitle: false,
|
||||
creatingGroup: false
|
||||
)
|
||||
.navigationBarTitle("Group link")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
}
|
||||
|
||||
private func editGroupButton() -> some View {
|
||||
NavigationLink {
|
||||
GroupProfileView(
|
||||
@@ -577,7 +663,8 @@ struct GroupChatInfoView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
GroupChatInfoView(
|
||||
chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []),
|
||||
groupInfo: Binding.constant(GroupInfo.sampleData)
|
||||
groupInfo: Binding.constant(GroupInfo.sampleData),
|
||||
onSearch: {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,14 +34,13 @@ struct GroupLinkView: View {
|
||||
|
||||
var body: some View {
|
||||
if creatingGroup {
|
||||
NavigationView {
|
||||
groupLinkView()
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button ("Continue") { linkCreatedCb?() }
|
||||
}
|
||||
groupLinkView()
|
||||
.navigationBarBackButtonHidden()
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button ("Continue") { linkCreatedCb?() }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
groupLinkView()
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ struct GroupMemberInfoView: View {
|
||||
case syncConnectionForceAlert
|
||||
case planAndConnectAlert(alert: PlanAndConnectAlert)
|
||||
case queueInfo(info: String)
|
||||
case someAlert(alert: SomeAlert)
|
||||
case error(title: LocalizedStringKey, error: LocalizedStringKey?)
|
||||
|
||||
var id: String {
|
||||
@@ -52,6 +53,7 @@ struct GroupMemberInfoView: View {
|
||||
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
|
||||
case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)"
|
||||
case let .queueInfo(info): return "queueInfo \(info)"
|
||||
case let .someAlert(alert): return "someAlert \(alert.id)"
|
||||
case let .error(title, _): return "error \(title)"
|
||||
}
|
||||
}
|
||||
@@ -65,10 +67,11 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func knownDirectChat(_ contactId: Int64) -> Chat? {
|
||||
private func knownDirectChat(_ contactId: Int64) -> (Chat, Contact)? {
|
||||
if let chat = chatModel.getContactChat(contactId),
|
||||
chat.chatInfo.contact?.directOrUsed == true {
|
||||
return chat
|
||||
let contact = chat.chatInfo.contact,
|
||||
contact.directOrUsed == true {
|
||||
return (chat, contact)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@@ -80,21 +83,22 @@ struct GroupMemberInfoView: View {
|
||||
List {
|
||||
groupMemberInfoHeader(member)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.padding(.bottom, 18)
|
||||
|
||||
infoActionButtons(member)
|
||||
.padding(.horizontal)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: infoViewActionButtonHeight)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
if member.memberActive {
|
||||
Section {
|
||||
if let contactId = member.memberContactId, let chat = knownDirectChat(contactId) {
|
||||
knownDirectChatButton(chat)
|
||||
} else if groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) {
|
||||
if let contactId = member.memberContactId {
|
||||
newDirectChatButton(contactId)
|
||||
} else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false {
|
||||
createMemberContactButton()
|
||||
}
|
||||
}
|
||||
if let code = connectionCode { verifyCodeButton(code) }
|
||||
if let connStats = connectionStats,
|
||||
connStats.ratchetSyncAllowed {
|
||||
connStats.ratchetSyncAllowed {
|
||||
synchronizeConnectionButton()
|
||||
}
|
||||
// } else if developerTools {
|
||||
@@ -237,6 +241,7 @@ struct GroupMemberInfoView: View {
|
||||
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) })
|
||||
case let .planAndConnectAlert(alert): return planAndConnectAlert(alert, dismiss: true)
|
||||
case let .queueInfo(info): return queueInfoAlert(info)
|
||||
case let .someAlert(a): return a.alert
|
||||
case let .error(title, error): return mkAlert(title: title, message: error)
|
||||
}
|
||||
}
|
||||
@@ -249,6 +254,57 @@ struct GroupMemberInfoView: View {
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
}
|
||||
|
||||
func infoActionButtons(_ member: GroupMember) -> some View {
|
||||
GeometryReader { g in
|
||||
let buttonWidth = g.size.width / 4
|
||||
HStack(alignment: .center, spacing: 8) {
|
||||
if let contactId = member.memberContactId, let (chat, contact) = knownDirectChat(contactId) {
|
||||
knownDirectChatButton(chat, width: buttonWidth)
|
||||
AudioCallButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) }
|
||||
VideoButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) }
|
||||
} else if groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) {
|
||||
if let contactId = member.memberContactId {
|
||||
newDirectChatButton(contactId, width: buttonWidth)
|
||||
} else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false {
|
||||
createMemberContactButton(width: buttonWidth)
|
||||
}
|
||||
InfoViewButton(image: "phone.fill", title: "call", disabledLook: true, width: buttonWidth) { showSendMessageToEnableCallsAlert()
|
||||
}
|
||||
InfoViewButton(image: "video.fill", title: "video", disabledLook: true, width: buttonWidth) { showSendMessageToEnableCallsAlert()
|
||||
}
|
||||
} else { // no known contact chat && directMessages are off
|
||||
InfoViewButton(image: "message.fill", title: "message", disabledLook: true, width: buttonWidth) { showDirectMessagesProhibitedAlert("Can't message member")
|
||||
}
|
||||
InfoViewButton(image: "phone.fill", title: "call", disabledLook: true, width: buttonWidth) { showDirectMessagesProhibitedAlert("Can't call member")
|
||||
}
|
||||
InfoViewButton(image: "video.fill", title: "video", disabledLook: true, width: buttonWidth) { showDirectMessagesProhibitedAlert("Can't call member")
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
}
|
||||
|
||||
func showSendMessageToEnableCallsAlert() {
|
||||
alert = .someAlert(alert: SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Can't call member",
|
||||
message: "Send message to enable calls."
|
||||
),
|
||||
id: "can't call member, send message"
|
||||
))
|
||||
}
|
||||
|
||||
func showDirectMessagesProhibitedAlert(_ title: LocalizedStringKey) {
|
||||
alert = .someAlert(alert: SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: title,
|
||||
message: "Direct messages between members are prohibited in this group."
|
||||
),
|
||||
id: "can't message member, direct messages prohibited"
|
||||
))
|
||||
}
|
||||
|
||||
func connectViaAddressButton(_ contactLink: String) -> some View {
|
||||
Button {
|
||||
planAndConnect(
|
||||
@@ -263,19 +319,17 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func knownDirectChatButton(_ chat: Chat) -> some View {
|
||||
Button {
|
||||
func knownDirectChatButton(_ chat: Chat, width: CGFloat) -> some View {
|
||||
InfoViewButton(image: "message.fill", title: "message", width: width) {
|
||||
dismissAllSheets(animated: true)
|
||||
DispatchQueue.main.async {
|
||||
chatModel.chatId = chat.id
|
||||
}
|
||||
} label: {
|
||||
Label("Send direct message", systemImage: "message")
|
||||
}
|
||||
}
|
||||
|
||||
func newDirectChatButton(_ contactId: Int64) -> some View {
|
||||
Button {
|
||||
func newDirectChatButton(_ contactId: Int64, width: CGFloat) -> some View {
|
||||
InfoViewButton(image: "message.fill", title: "message", width: width) {
|
||||
do {
|
||||
let chat = try apiGetChat(type: .direct, id: contactId)
|
||||
chatModel.addChat(chat)
|
||||
@@ -286,13 +340,11 @@ struct GroupMemberInfoView: View {
|
||||
} catch let error {
|
||||
logger.error("openDirectChatButton apiGetChat error: \(responseError(error))")
|
||||
}
|
||||
} label: {
|
||||
Label("Send direct message", systemImage: "message")
|
||||
}
|
||||
}
|
||||
|
||||
func createMemberContactButton() -> some View {
|
||||
Button {
|
||||
func createMemberContactButton(width: CGFloat) -> some View {
|
||||
InfoViewButton(image: "message.fill", title: "message", width: width) {
|
||||
progressIndicator = true
|
||||
Task {
|
||||
do {
|
||||
@@ -313,14 +365,12 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label("Send direct message", systemImage: "message")
|
||||
}
|
||||
}
|
||||
|
||||
private func groupMemberInfoHeader(_ mem: GroupMember) -> some View {
|
||||
VStack {
|
||||
ProfileImage(imageStr: mem.image, size: 192, color: Color(uiColor: .tertiarySystemFill))
|
||||
MemberProfileImage(mem, size: 192, color: Color(uiColor: .tertiarySystemFill))
|
||||
.padding(.top, 12)
|
||||
.padding()
|
||||
if mem.verified {
|
||||
@@ -582,6 +632,21 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func MemberProfileImage(
|
||||
_ mem: GroupMember,
|
||||
size: CGFloat,
|
||||
color: Color = Color(uiColor: .tertiarySystemGroupedBackground),
|
||||
backgroundColor: Color? = nil
|
||||
) -> some View {
|
||||
ProfileImage(
|
||||
imageStr: mem.image,
|
||||
size: size,
|
||||
color: color,
|
||||
backgroundColor: backgroundColor,
|
||||
blurred: mem.blocked
|
||||
)
|
||||
}
|
||||
|
||||
func blockMemberAlert(_ gInfo: GroupInfo, _ mem: GroupMember) -> Alert {
|
||||
Alert(
|
||||
title: Text("Block member?"),
|
||||
|
||||
@@ -11,7 +11,6 @@ import Combine
|
||||
|
||||
/// A List, which displays it's items in reverse order - from bottom to top
|
||||
struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIViewControllerRepresentable {
|
||||
|
||||
let items: Array<Item>
|
||||
|
||||
@Binding var scrollState: ReverseListScrollModel<Item>.State
|
||||
@@ -33,7 +32,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
|
||||
case let .item(id):
|
||||
controller.scroll(to: items.firstIndex(where: { $0.id == id }), position: .bottom)
|
||||
case .bottom:
|
||||
controller.scroll(to: .zero, position: .top)
|
||||
controller.scroll(to: 0, position: .top)
|
||||
}
|
||||
} else {
|
||||
controller.update(items: items)
|
||||
@@ -45,7 +44,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
|
||||
private enum Section { case main }
|
||||
private let representer: ReverseList
|
||||
private var dataSource: UITableViewDiffableDataSource<Section, Item>!
|
||||
private var itemCount: Int = .zero
|
||||
private var itemCount: Int = 0
|
||||
private var bag = Set<AnyCancellable>()
|
||||
|
||||
init(representer: ReverseList) {
|
||||
@@ -53,6 +52,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
|
||||
super.init(style: .plain)
|
||||
|
||||
// 1. Style
|
||||
tableView = InvertedTableView()
|
||||
tableView.separatorStyle = .none
|
||||
tableView.transform = .verticalFlip
|
||||
tableView.backgroundColor = .clear
|
||||
@@ -80,7 +80,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath)
|
||||
if #available(iOS 16.0, *) {
|
||||
cell.contentConfiguration = UIHostingConfiguration { self.representer.content(item) }
|
||||
.margins(.all, .zero)
|
||||
.margins(.all, 0)
|
||||
.minSize(height: 1) // Passing zero will result in system default of 44 points being used
|
||||
} else {
|
||||
if let cell = cell as? HostingCell<Content> {
|
||||
@@ -132,6 +132,11 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
|
||||
NotificationCenter.default.post(name: .chatViewWillBeginScrolling, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
tableView.clipsToBounds = false
|
||||
parent?.viewIfLoaded?.clipsToBounds = false
|
||||
}
|
||||
|
||||
/// Scrolls up
|
||||
func scrollToNextPage() {
|
||||
tableView.setContentOffset(
|
||||
@@ -153,7 +158,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
|
||||
animated = true
|
||||
}
|
||||
tableView.scrollToRow(
|
||||
at: IndexPath(row: index, section: .zero),
|
||||
at: IndexPath(row: index, section: 0),
|
||||
at: position,
|
||||
animated: animated
|
||||
)
|
||||
@@ -168,7 +173,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
|
||||
dataSource.defaultRowAnimation = .none
|
||||
dataSource.apply(
|
||||
snapshot,
|
||||
animatingDifferences: itemCount != .zero && abs(items.count - itemCount) == 1
|
||||
animatingDifferences: itemCount != 0 && abs(items.count - itemCount) == 1
|
||||
)
|
||||
itemCount = items.count
|
||||
}
|
||||
@@ -272,3 +277,28 @@ func withConditionalAnimation<Result>(
|
||||
try body()
|
||||
}
|
||||
}
|
||||
|
||||
class InvertedTableView: UITableView {
|
||||
static let inset = CGFloat(100)
|
||||
|
||||
static let insets = UIEdgeInsets(
|
||||
top: inset,
|
||||
left: .zero,
|
||||
bottom: inset,
|
||||
right: .zero
|
||||
)
|
||||
|
||||
override var contentInsetAdjustmentBehavior: UIScrollView.ContentInsetAdjustmentBehavior {
|
||||
get { .never }
|
||||
set { }
|
||||
}
|
||||
|
||||
override var contentInset: UIEdgeInsets {
|
||||
get { Self.insets }
|
||||
set { }
|
||||
}
|
||||
|
||||
override var adjustedContentInset: UIEdgeInsets {
|
||||
Self.insets
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,19 +104,22 @@ struct SelectedItemsBottomToolbar: View {
|
||||
allButtonsDisabled = count == 0 || count > 20
|
||||
canModerate = possibleToModerate(chatInfo)
|
||||
if let selected = selectedItems {
|
||||
(deleteEnabled, deleteForEveryoneEnabled, moderateEnabled, _, selectedChatItems) = chatItems.reduce((true, true, true, true, [])) { (r, ci) in
|
||||
let me: Bool
|
||||
let onlyOwnGroupItems: Bool
|
||||
(deleteEnabled, deleteForEveryoneEnabled, me, onlyOwnGroupItems, selectedChatItems) = chatItems.reduce((true, true, true, true, [])) { (r, ci) in
|
||||
if selected.contains(ci.id) {
|
||||
var (de, dee, me, onlyOwnGroupItems, sel) = r
|
||||
de = de && ci.canBeDeletedForSelf
|
||||
dee = dee && ci.meta.deletable && !ci.localNote
|
||||
onlyOwnGroupItems = onlyOwnGroupItems && ci.chatDir == .groupSnd
|
||||
me = me && !onlyOwnGroupItems && ci.content.msgContent != nil && ci.memberToModerate(chatInfo) != nil
|
||||
me = me && ci.content.msgContent != nil && ci.memberToModerate(chatInfo) != nil
|
||||
sel.insert(ci.id) // we are collecting new selected items here to account for any changes in chat items list
|
||||
return (de, dee, me, onlyOwnGroupItems, sel)
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
}
|
||||
moderateEnabled = me && !onlyOwnGroupItems
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import SwiftUI
|
||||
struct ChatHelp: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Binding var showSettings: Bool
|
||||
@State private var newChatMenuOption: NewChatMenuOption? = nil
|
||||
|
||||
var body: some View {
|
||||
ScrollView { chatHelp() }
|
||||
@@ -39,7 +38,7 @@ struct ChatHelp: View {
|
||||
|
||||
HStack(spacing: 8) {
|
||||
Text("Tap button ")
|
||||
NewChatMenuButton(newChatMenuOption: $newChatMenuOption)
|
||||
NewChatMenuButton()
|
||||
Text("above, then choose:")
|
||||
}
|
||||
|
||||
|
||||
@@ -44,17 +44,20 @@ struct ChatListNavLink: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
|
||||
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = false
|
||||
@ObservedObject var chat: Chat
|
||||
@State private var showContactRequestDialog = false
|
||||
@State private var showJoinGroupDialog = false
|
||||
@State private var showContactConnectionInfo = false
|
||||
@State private var showInvalidJSON = false
|
||||
@State private var showDeleteContactActionSheet = false
|
||||
@State private var alert: SomeAlert? = nil
|
||||
@State private var actionSheet: SomeActionSheet? = nil
|
||||
@State private var sheet: SomeSheet<AnyView>? = nil
|
||||
@State private var showConnectContactViaAddressDialog = false
|
||||
@State private var inProgress = false
|
||||
@State private var progressByTimeout = false
|
||||
|
||||
var dynamicRowHeight: CGFloat { dynamicSizes[userFont]?.rowHeight ?? 80 }
|
||||
var dynamicRowHeight: CGFloat { dynamicSize(userFont).rowHeight }
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
@@ -83,17 +86,24 @@ struct ChatListNavLink: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder private func contactNavLink(_ contact: Contact) -> some View {
|
||||
Group {
|
||||
if contact.activeConn == nil && contact.profile.contactLink != nil {
|
||||
if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active {
|
||||
ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false))
|
||||
.frame(height: dynamicRowHeight)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button {
|
||||
showDeleteContactActionSheet = true
|
||||
deleteContactDialog(
|
||||
chat,
|
||||
contact,
|
||||
dismissToChatList: false,
|
||||
showAlert: { alert = $0 },
|
||||
showActionSheet: { actionSheet = $0 },
|
||||
showSheetContent: { sheet = $0 }
|
||||
)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
deleteLabel
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
@@ -111,44 +121,37 @@ struct ChatListNavLink: View {
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
markReadButton()
|
||||
toggleFavoriteButton()
|
||||
ToggleNtfsButton(chat: chat)
|
||||
toggleNtfsButton(chat: chat)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if !chat.chatItems.isEmpty {
|
||||
clearChatButton()
|
||||
}
|
||||
Button {
|
||||
if contact.sndReady || !contact.active {
|
||||
showDeleteContactActionSheet = true
|
||||
} else {
|
||||
AlertManager.shared.showAlert(deletePendingContactAlert(chat, contact))
|
||||
}
|
||||
deleteContactDialog(
|
||||
chat,
|
||||
contact,
|
||||
dismissToChatList: false,
|
||||
showAlert: { alert = $0 },
|
||||
showActionSheet: { actionSheet = $0 },
|
||||
showSheetContent: { sheet = $0 }
|
||||
)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
deleteLabel
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
.frame(height: dynamicRowHeight)
|
||||
}
|
||||
}
|
||||
.actionSheet(isPresented: $showDeleteContactActionSheet) {
|
||||
if contact.sndReady && contact.active {
|
||||
return ActionSheet(
|
||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||
buttons: [
|
||||
.destructive(Text("Delete and notify contact")) { Task { await deleteChat(chat, notify: true) } },
|
||||
.destructive(Text("Delete")) { Task { await deleteChat(chat, notify: false) } },
|
||||
.cancel()
|
||||
]
|
||||
)
|
||||
.alert(item: $alert) { $0.alert }
|
||||
.actionSheet(item: $actionSheet) { $0.actionSheet }
|
||||
.sheet(item: $sheet) {
|
||||
if #available(iOS 16.0, *) {
|
||||
$0.content
|
||||
.presentationDetents([.fraction(0.4)])
|
||||
} else {
|
||||
return ActionSheet(
|
||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||
buttons: [
|
||||
.destructive(Text("Delete")) { Task { await deleteChat(chat) } },
|
||||
.cancel()
|
||||
]
|
||||
)
|
||||
$0.content
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +203,7 @@ struct ChatListNavLink: View {
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
markReadButton()
|
||||
toggleFavoriteButton()
|
||||
ToggleNtfsButton(chat: chat)
|
||||
toggleNtfsButton(chat: chat)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if !chat.chatItems.isEmpty {
|
||||
@@ -241,7 +244,7 @@ struct ChatListNavLink: View {
|
||||
await MainActor.run { inProgress = false }
|
||||
}
|
||||
} label: {
|
||||
Label("Join", systemImage: chat.chatInfo.incognito ? "theatermasks" : "ipad.and.arrow.forward")
|
||||
SwipeLabel(NSLocalizedString("Join", comment: "swipe action"), systemImage: chat.chatInfo.incognito ? "theatermasks" : "ipad.and.arrow.forward", inverted: oneHandUI)
|
||||
}
|
||||
.tint(chat.chatInfo.incognito ? .indigo : theme.colors.primary)
|
||||
}
|
||||
@@ -251,14 +254,14 @@ struct ChatListNavLink: View {
|
||||
Button {
|
||||
Task { await markChatRead(chat) }
|
||||
} label: {
|
||||
Label("Read", systemImage: "checkmark")
|
||||
SwipeLabel(NSLocalizedString("Read", comment: "swipe action"), systemImage: "checkmark", inverted: oneHandUI)
|
||||
}
|
||||
.tint(theme.colors.primary)
|
||||
} else {
|
||||
Button {
|
||||
Task { await markChatUnread(chat) }
|
||||
} label: {
|
||||
Label("Unread", systemImage: "circlebadge.fill")
|
||||
SwipeLabel(NSLocalizedString("Unread", comment: "swipe action"), systemImage: "circlebadge.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(theme.colors.primary)
|
||||
}
|
||||
@@ -270,24 +273,36 @@ struct ChatListNavLink: View {
|
||||
Button {
|
||||
toggleChatFavorite(chat, favorite: false)
|
||||
} label: {
|
||||
Label("Unfav.", systemImage: "star.slash")
|
||||
SwipeLabel(NSLocalizedString("Unfav.", comment: "swipe action"), systemImage: "star.slash.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.green)
|
||||
} else {
|
||||
Button {
|
||||
toggleChatFavorite(chat, favorite: true)
|
||||
} label: {
|
||||
Label("Favorite", systemImage: "star.fill")
|
||||
SwipeLabel(NSLocalizedString("Favorite", comment: "swipe action"), systemImage: "star.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.green)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func toggleNtfsButton(chat: Chat) -> some View {
|
||||
Button {
|
||||
toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled)
|
||||
} label: {
|
||||
if chat.chatInfo.ntfsEnabled {
|
||||
SwipeLabel(NSLocalizedString("Mute", comment: "swipe action"), systemImage: "speaker.slash.fill", inverted: oneHandUI)
|
||||
} else {
|
||||
SwipeLabel(NSLocalizedString("Unmute", comment: "swipe action"), systemImage: "speaker.wave.2.fill", inverted: oneHandUI)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func clearChatButton() -> some View {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(clearChatAlert())
|
||||
} label: {
|
||||
Label("Clear", systemImage: "gobackward")
|
||||
SwipeLabel(NSLocalizedString("Clear", comment: "swipe action"), systemImage: "gobackward", inverted: oneHandUI)
|
||||
}
|
||||
.tint(Color.orange)
|
||||
}
|
||||
@@ -296,7 +311,7 @@ struct ChatListNavLink: View {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(clearNoteFolderAlert())
|
||||
} label: {
|
||||
Label("Clear", systemImage: "gobackward")
|
||||
SwipeLabel(NSLocalizedString("Clear", comment: "swipe action"), systemImage: "gobackward", inverted: oneHandUI)
|
||||
}
|
||||
.tint(Color.orange)
|
||||
}
|
||||
@@ -305,7 +320,7 @@ struct ChatListNavLink: View {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(leaveGroupAlert(groupInfo))
|
||||
} label: {
|
||||
Label("Leave", systemImage: "rectangle.portrait.and.arrow.right")
|
||||
SwipeLabel(NSLocalizedString("Leave", comment: "swipe action"), systemImage: "rectangle.portrait.and.arrow.right.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(Color.yellow)
|
||||
}
|
||||
@@ -314,7 +329,7 @@ struct ChatListNavLink: View {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(deleteGroupAlert(groupInfo))
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
deleteLabel
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
@@ -324,18 +339,18 @@ struct ChatListNavLink: View {
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) }
|
||||
} label: { Label("Accept", systemImage: "checkmark") }
|
||||
} label: { SwipeLabel(NSLocalizedString("Accept", comment: "swipe action"), systemImage: "checkmark", inverted: oneHandUI) }
|
||||
.tint(theme.colors.primary)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) }
|
||||
} label: {
|
||||
Label("Accept incognito", systemImage: "theatermasks")
|
||||
SwipeLabel(NSLocalizedString("Accept incognito", comment: "swipe action"), systemImage: "theatermasks.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequest))
|
||||
} label: {
|
||||
Label("Reject", systemImage: "multiply")
|
||||
SwipeLabel(NSLocalizedString("Reject", comment: "swipe action"), systemImage: "multiply.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
@@ -356,14 +371,14 @@ struct ChatListNavLink: View {
|
||||
AlertManager.shared.showAlertMsg(title: a.title, message: a.message)
|
||||
})
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
deleteLabel
|
||||
}
|
||||
.tint(.red)
|
||||
|
||||
Button {
|
||||
showContactConnectionInfo = true
|
||||
} label: {
|
||||
Label("Name", systemImage: "pencil")
|
||||
SwipeLabel(NSLocalizedString("Name", comment: "swipe action"), systemImage: "pencil", inverted: oneHandUI)
|
||||
}
|
||||
.tint(theme.colors.primary)
|
||||
}
|
||||
@@ -382,6 +397,10 @@ struct ChatListNavLink: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var deleteLabel: some View {
|
||||
SwipeLabel(NSLocalizedString("Delete", comment: "swipe action"), systemImage: "trash.fill", inverted: oneHandUI)
|
||||
}
|
||||
|
||||
private func deleteGroupAlert(_ groupInfo: GroupInfo) -> Alert {
|
||||
Alert(
|
||||
title: Text("Delete group?"),
|
||||
@@ -430,28 +449,6 @@ struct ChatListNavLink: View {
|
||||
)
|
||||
}
|
||||
|
||||
private func rejectContactRequestAlert(_ contactRequest: UserContactRequest) -> Alert {
|
||||
Alert(
|
||||
title: Text("Reject contact request"),
|
||||
message: Text("The sender will NOT be notified"),
|
||||
primaryButton: .destructive(Text("Reject")) {
|
||||
Task { await rejectContactRequest(contactRequest) }
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
private func pendingContactAlert(_ chat: Chat, _ contact: Contact) -> Alert {
|
||||
Alert(
|
||||
title: Text("Contact is not connected yet!"),
|
||||
message: Text("Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)."),
|
||||
primaryButton: .cancel(),
|
||||
secondaryButton: .destructive(Text("Delete Contact")) {
|
||||
removePendingContact(chat, contact)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func groupInvitationAcceptedAlert() -> Alert {
|
||||
Alert(
|
||||
title: Text("Joining group"),
|
||||
@@ -459,30 +456,6 @@ struct ChatListNavLink: View {
|
||||
)
|
||||
}
|
||||
|
||||
private func deletePendingContactAlert(_ chat: Chat, _ contact: Contact) -> Alert {
|
||||
Alert(
|
||||
title: Text("Delete pending connection"),
|
||||
message: Text("Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)."),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
removePendingContact(chat, contact)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
private func removePendingContact(_ chat: Chat, _ contact: Contact) {
|
||||
Task {
|
||||
do {
|
||||
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
|
||||
DispatchQueue.main.async {
|
||||
chatModel.removeChat(contact.id)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("ChatListNavLink.removePendingContact apiDeleteChat error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func invalidJSONPreview(_ json: String) -> some View {
|
||||
Text("invalid chat data")
|
||||
.foregroundColor(.red)
|
||||
@@ -497,16 +470,28 @@ struct ChatListNavLink: View {
|
||||
|
||||
private func connectContactViaAddress_(_ contact: Contact, _ incognito: Bool) {
|
||||
Task {
|
||||
let ok = await connectContactViaAddress(contact.contactId, incognito)
|
||||
let ok = await connectContactViaAddress(contact.contactId, incognito, showAlert: { AlertManager.shared.showAlert($0) })
|
||||
if ok {
|
||||
await MainActor.run {
|
||||
chatModel.chatId = contact.id
|
||||
}
|
||||
AlertManager.shared.showAlert(connReqSentAlert(.contact))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rejectContactRequestAlert(_ contactRequest: UserContactRequest) -> Alert {
|
||||
Alert(
|
||||
title: Text("Reject contact request"),
|
||||
message: Text("The sender will NOT be notified"),
|
||||
primaryButton: .destructive(Text("Reject")) {
|
||||
Task { await rejectContactRequest(contactRequest) }
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
func deleteContactConnectionAlert(_ contactConnection: PendingContactConnection, showError: @escaping (ErrorAlert) -> Void, success: @escaping () -> Void = {}) -> Alert {
|
||||
Alert(
|
||||
title: Text("Delete pending connection?"),
|
||||
@@ -533,15 +518,14 @@ func deleteContactConnectionAlert(_ contactConnection: PendingContactConnection,
|
||||
)
|
||||
}
|
||||
|
||||
func connectContactViaAddress(_ contactId: Int64, _ incognito: Bool) async -> Bool {
|
||||
func connectContactViaAddress(_ contactId: Int64, _ incognito: Bool, showAlert: (Alert) -> Void) async -> Bool {
|
||||
let (contact, alert) = await apiConnectContactViaAddress(incognito: incognito, contactId: contactId)
|
||||
if let alert = alert {
|
||||
AlertManager.shared.showAlert(alert)
|
||||
showAlert(alert)
|
||||
return false
|
||||
} else if let contact = contact {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateContact(contact)
|
||||
AlertManager.shared.showAlert(connReqSentAlert(.contact))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -18,11 +18,14 @@ struct ChatListView: View {
|
||||
@State private var searchText = ""
|
||||
@State private var searchShowingSimplexLink = false
|
||||
@State private var searchChatFilteredBySimplexLink: String? = nil
|
||||
@State private var newChatMenuOption: NewChatMenuOption? = nil
|
||||
@State private var userPickerVisible = false
|
||||
@State private var showConnectDesktop = false
|
||||
@State private var scrollToSearchBar = false
|
||||
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true
|
||||
@AppStorage(DEFAULT_ONE_HAND_UI_CARD_SHOWN) private var oneHandUICardShown = false
|
||||
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
@@ -33,21 +36,16 @@ struct ChatListView: View {
|
||||
}
|
||||
|
||||
private var viewBody: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
ZStack(alignment: oneHandUI ? .bottomLeading : .topLeading) {
|
||||
NavStackCompat(
|
||||
isActive: Binding(
|
||||
get: { chatModel.chatId != nil },
|
||||
set: { _ in }
|
||||
set: { active in
|
||||
if !active { chatModel.chatId = nil }
|
||||
}
|
||||
),
|
||||
destination: chatView
|
||||
) {
|
||||
VStack {
|
||||
if chatModel.chats.isEmpty {
|
||||
onboardingButtons()
|
||||
}
|
||||
chatListView
|
||||
}
|
||||
}
|
||||
) { chatListView }
|
||||
if userPickerVisible {
|
||||
Rectangle().fill(.white.opacity(0.001)).onTapGesture {
|
||||
withAnimation {
|
||||
@@ -67,9 +65,14 @@ struct ChatListView: View {
|
||||
}
|
||||
|
||||
private var chatListView: some View {
|
||||
VStack {
|
||||
let tm = ToolbarMaterial.material(toolbarMaterial)
|
||||
return withToolbar(tm) {
|
||||
chatList
|
||||
.background(theme.colors.background)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarHidden(searchMode || oneHandUI)
|
||||
}
|
||||
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
|
||||
.onDisappear() { withAnimation { userPickerVisible = false } }
|
||||
.refreshable {
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
@@ -87,55 +90,111 @@ struct ChatListView: View {
|
||||
secondaryButton: .cancel()
|
||||
))
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.background(theme.colors.background)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarHidden(searchMode)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
let user = chatModel.currentUser ?? User.sampleData
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ProfileImage(imageStr: user.image, size: 32, color: Color(uiColor: .quaternaryLabel))
|
||||
.padding(.trailing, 4)
|
||||
let allRead = chatModel.users
|
||||
.filter { u in !u.user.activeUser && !u.user.hidden }
|
||||
.allSatisfy { u in u.unreadCount == 0 }
|
||||
if !allRead {
|
||||
unreadBadge(size: 12)
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
if chatModel.users.filter({ u in u.user.activeUser || !u.user.hidden }).count > 1 {
|
||||
withAnimation {
|
||||
userPickerVisible.toggle()
|
||||
}
|
||||
} else {
|
||||
showSettings = true
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .top) {
|
||||
if oneHandUI { Divider().background(tm) }
|
||||
}
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
if oneHandUI {
|
||||
Divider().padding(.bottom, Self.hasHomeIndicator ? 0 : 8).background(tm)
|
||||
}
|
||||
ToolbarItem(placement: .principal) {
|
||||
HStack(spacing: 4) {
|
||||
Text("Chats")
|
||||
.font(.headline)
|
||||
SubsStatusIndicator()
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
}
|
||||
|
||||
static var hasHomeIndicator: Bool = {
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = windowScene.windows.first {
|
||||
window.safeAreaInsets.bottom > 0
|
||||
} else { false }
|
||||
}()
|
||||
|
||||
@ViewBuilder func withToolbar(_ material: Material, content: () -> some View) -> some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
if oneHandUI {
|
||||
content()
|
||||
.toolbarBackground(.hidden, for: .bottomBar)
|
||||
.toolbar { bottomToolbar }
|
||||
} else {
|
||||
content()
|
||||
.toolbarBackground(.automatic, for: .navigationBar)
|
||||
.toolbarBackground(material)
|
||||
.toolbar { topToolbar }
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
switch chatModel.chatRunning {
|
||||
case .some(true): NewChatMenuButton(newChatMenuOption: $newChatMenuOption)
|
||||
case .some(false): chatStoppedIcon()
|
||||
case .none: EmptyView()
|
||||
}
|
||||
} else {
|
||||
if oneHandUI {
|
||||
content().toolbar { bottomToolbarGroup }
|
||||
} else {
|
||||
content().toolbar { topToolbar }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder var topToolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .topBarLeading) { leadingToolbarItem }
|
||||
ToolbarItem(placement: .principal) { SubsStatusIndicator() }
|
||||
ToolbarItem(placement: .topBarTrailing) { trailingToolbarItem }
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder var bottomToolbar: some ToolbarContent {
|
||||
let padding: Double = Self.hasHomeIndicator ? 0 : 14
|
||||
ToolbarItem(placement: .bottomBar) {
|
||||
HStack {
|
||||
leadingToolbarItem.padding(.bottom, padding)
|
||||
Spacer()
|
||||
SubsStatusIndicator().padding(.bottom, padding)
|
||||
Spacer()
|
||||
trailingToolbarItem.padding(.bottom, padding)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { scrollToSearchBar = true }
|
||||
}
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder var bottomToolbarGroup: some ToolbarContent {
|
||||
let padding: Double = Self.hasHomeIndicator ? 0 : 14
|
||||
ToolbarItemGroup(placement: .bottomBar) {
|
||||
leadingToolbarItem.padding(.bottom, padding)
|
||||
Spacer()
|
||||
SubsStatusIndicator().padding(.bottom, padding)
|
||||
Spacer()
|
||||
trailingToolbarItem.padding(.bottom, padding)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder var leadingToolbarItem: some View {
|
||||
let user = chatModel.currentUser ?? User.sampleData
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ProfileImage(imageStr: user.image, size: 32, color: Color(uiColor: .quaternaryLabel))
|
||||
.padding(.trailing, 4)
|
||||
let allRead = chatModel.users
|
||||
.filter { u in !u.user.activeUser && !u.user.hidden }
|
||||
.allSatisfy { u in u.unreadCount == 0 }
|
||||
if !allRead {
|
||||
unreadBadge(size: 12)
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
if chatModel.users.filter({ u in u.user.activeUser || !u.user.hidden }).count > 1 {
|
||||
withAnimation {
|
||||
userPickerVisible.toggle()
|
||||
}
|
||||
} else {
|
||||
showSettings = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder var trailingToolbarItem: some View {
|
||||
switch chatModel.chatRunning {
|
||||
case .some(true): NewChatMenuButton()
|
||||
case .some(false): chatStoppedIcon()
|
||||
case .none: EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var chatList: some View {
|
||||
let cs = filteredChats()
|
||||
ZStack {
|
||||
VStack {
|
||||
ScrollViewReader { scrollProxy in
|
||||
List {
|
||||
if !chatModel.chats.isEmpty {
|
||||
ChatListSearchBar(
|
||||
@@ -145,31 +204,50 @@ struct ChatListView: View {
|
||||
searchShowingSimplexLink: $searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
|
||||
)
|
||||
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowBackground(Color.clear)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, oneHandUI ? 8 : 0)
|
||||
.id("searchBar")
|
||||
}
|
||||
if !oneHandUICardShown {
|
||||
OneHandUICard()
|
||||
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
ForEach(cs, id: \.viewId) { chat in
|
||||
ChatListNavLink(chat: chat)
|
||||
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
|
||||
.padding(.trailing, -16)
|
||||
.disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id))
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
.offset(x: -8)
|
||||
}
|
||||
}
|
||||
.onChange(of: chatModel.chatId) { _ in
|
||||
if chatModel.chatId == nil, let chatId = chatModel.chatToTop {
|
||||
chatModel.chatToTop = nil
|
||||
chatModel.popChat(chatId)
|
||||
.listStyle(.plain)
|
||||
.onChange(of: chatModel.chatId) { currentChatId in
|
||||
if let chatId = chatModel.chatToTop, currentChatId != chatId {
|
||||
chatModel.chatToTop = nil
|
||||
chatModel.popChat(chatId)
|
||||
}
|
||||
stopAudioPlayer()
|
||||
}
|
||||
.onChange(of: chatModel.currentUser?.userId) { _ in
|
||||
stopAudioPlayer()
|
||||
}
|
||||
.onChange(of: scrollToSearchBar) { scrollToSearchBar in
|
||||
if scrollToSearchBar {
|
||||
Task { self.scrollToSearchBar = false }
|
||||
withAnimation { scrollProxy.scrollTo("searchBar") }
|
||||
}
|
||||
}
|
||||
stopAudioPlayer()
|
||||
}
|
||||
.onChange(of: chatModel.currentUser?.userId) { _ in
|
||||
stopAudioPlayer()
|
||||
}
|
||||
if cs.isEmpty && !chatModel.chats.isEmpty {
|
||||
Text("No filtered chats").foregroundColor(theme.colors.secondary)
|
||||
Text("No filtered chats")
|
||||
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,42 +258,6 @@ struct ChatListView: View {
|
||||
.foregroundColor(theme.colors.primary)
|
||||
}
|
||||
|
||||
private func onboardingButtons() -> some View {
|
||||
VStack(alignment: .trailing, spacing: 0) {
|
||||
Path { p in
|
||||
p.move(to: CGPoint(x: 8, y: 0))
|
||||
p.addLine(to: CGPoint(x: 16, y: 10))
|
||||
p.addLine(to: CGPoint(x: 0, y: 10))
|
||||
p.addLine(to: CGPoint(x: 8, y: 0))
|
||||
}
|
||||
.fill(theme.colors.primary)
|
||||
.frame(width: 20, height: 10)
|
||||
.padding(.trailing, 12)
|
||||
|
||||
connectButton("Tap to start a new chat") {
|
||||
newChatMenuOption = .newContact
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Text("You have no chats")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.trailing, 6)
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
|
||||
private func connectButton(_ label: LocalizedStringKey, action: @escaping () -> Void) -> some View {
|
||||
Button(action: action) {
|
||||
Text(label)
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
.background(theme.colors.primary)
|
||||
.foregroundColor(.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||
}
|
||||
|
||||
@ViewBuilder private func chatView() -> some View {
|
||||
if let chatId = chatModel.chatId, let chat = chatModel.getChat(chatId) {
|
||||
ChatView(chat: chat)
|
||||
@@ -233,16 +275,20 @@ struct ChatListView: View {
|
||||
} else {
|
||||
let s = searchString()
|
||||
return s == "" && !showUnreadAndFavorites
|
||||
? chatModel.chats
|
||||
? chatModel.chats.filter { chat in
|
||||
!chat.chatInfo.chatDeleted && chatContactType(chat: chat) != ContactType.card
|
||||
}
|
||||
: chatModel.chats.filter { chat in
|
||||
let cInfo = chat.chatInfo
|
||||
switch cInfo {
|
||||
case let .direct(contact):
|
||||
return s == ""
|
||||
? filtered(chat)
|
||||
: (viewNameContains(cInfo, s) ||
|
||||
contact.profile.displayName.localizedLowercase.contains(s) ||
|
||||
contact.fullName.localizedLowercase.contains(s))
|
||||
return !contact.chatDeleted && chatContactType(chat: chat) != ContactType.card && (
|
||||
s == ""
|
||||
? filtered(chat)
|
||||
: (viewNameContains(cInfo, s) ||
|
||||
contact.profile.displayName.localizedLowercase.contains(s) ||
|
||||
contact.fullName.localizedLowercase.contains(s))
|
||||
)
|
||||
case let .group(gInfo):
|
||||
return s == ""
|
||||
? (filtered(chat) || gInfo.membership.memberStatus == .memInvited)
|
||||
@@ -288,6 +334,7 @@ struct SubsStatusIndicator: View {
|
||||
showServersSummary = true
|
||||
} label: {
|
||||
HStack(spacing: 4) {
|
||||
Text("Chats").foregroundStyle(Color.primary).fixedSize().font(.headline)
|
||||
SubscriptionStatusIndicatorView(subs: subs, hasSess: hasSess)
|
||||
if showSubscriptionPercentage {
|
||||
SubscriptionStatusPercentageView(subs: subs, hasSess: hasSess)
|
||||
@@ -300,8 +347,9 @@ struct SubsStatusIndicator: View {
|
||||
.onDisappear {
|
||||
stopTimer()
|
||||
}
|
||||
.sheet(isPresented: $showServersSummary) {
|
||||
.appSheet(isPresented: $showServersSummary) {
|
||||
ServersSummaryView()
|
||||
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,7 +421,6 @@ struct ChatListSearchBar: View {
|
||||
toggleFilterButton()
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
.onChange(of: searchFocussed) { sf in
|
||||
withAnimation { searchMode = sf }
|
||||
|
||||
@@ -275,7 +275,7 @@ struct ChatPreviewView: View {
|
||||
} else {
|
||||
switch (chat.chatInfo) {
|
||||
case let .direct(contact):
|
||||
if contact.activeConn == nil && contact.profile.contactLink != nil {
|
||||
if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active {
|
||||
chatPreviewInfoText("Tap to Connect")
|
||||
.foregroundColor(theme.colors.primary)
|
||||
} else if !contact.sndReady && contact.activeConn != nil {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// OneHandUICard.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by EP on 06/08/2024.
|
||||
// Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct OneHandUICard: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
|
||||
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true
|
||||
@AppStorage(DEFAULT_ONE_HAND_UI_CARD_SHOWN) private var oneHandUICardShown = false
|
||||
@State private var showOneHandUIAlert = false
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Toggle chat list:").font(.title3)
|
||||
Toggle("Reachable chat toolbar", isOn: $oneHandUI)
|
||||
}
|
||||
Image(systemName: "multiply")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.onTapGesture {
|
||||
showOneHandUIAlert = true
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(theme.appColors.sentMessage)
|
||||
.cornerRadius(12)
|
||||
.frame(height: dynamicSize(userFont).rowHeight)
|
||||
.padding(.vertical, 12)
|
||||
.alert(isPresented: $showOneHandUIAlert) {
|
||||
Alert(
|
||||
title: Text("Reachable chat toolbar"),
|
||||
message: Text("You can change it in Appearance settings."),
|
||||
dismissButton: .default(Text("Ok")) {
|
||||
withAnimation {
|
||||
oneHandUICardShown = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
OneHandUICard()
|
||||
}
|
||||
@@ -448,19 +448,23 @@ func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHos
|
||||
let noConnColorAndPercent: (Color, Double, Double, Double) = (Color(uiColor: .tertiaryLabel), 1, 1, 0)
|
||||
let activeSubsRounded = roundedToQuarter(subs.shareOfActive)
|
||||
|
||||
return online && subs.total > 0
|
||||
? (
|
||||
subs.ssActive == 0
|
||||
? (
|
||||
hasSess ? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) : noConnColorAndPercent
|
||||
return !online
|
||||
? noConnColorAndPercent
|
||||
: (
|
||||
subs.total == 0 && !hasSess
|
||||
? (activeColor, 0, 0.33, 0) // On freshly installed app (without chats) and on app start
|
||||
: (
|
||||
subs.ssActive == 0
|
||||
? (
|
||||
hasSess ? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) : noConnColorAndPercent
|
||||
)
|
||||
: ( // ssActive > 0
|
||||
hasSess
|
||||
? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive)
|
||||
: (.orange, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) // This would mean implementation error
|
||||
)
|
||||
)
|
||||
: ( // ssActive > 0
|
||||
hasSess
|
||||
? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive)
|
||||
: (.orange, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) // This would mean implementation error
|
||||
)
|
||||
)
|
||||
: noConnColorAndPercent
|
||||
}
|
||||
|
||||
struct SMPServerSummaryView: View {
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
//
|
||||
// ContactListNavLink.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Diogo Cunha on 01/08/2024.
|
||||
// Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct ContactListNavLink: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@ObservedObject var chat: Chat
|
||||
var showDeletedChatIcon: Bool
|
||||
@State private var alert: SomeAlert? = nil
|
||||
@State private var actionSheet: SomeActionSheet? = nil
|
||||
@State private var sheet: SomeSheet<AnyView>? = nil
|
||||
@State private var showConnectContactViaAddressDialog = false
|
||||
@State private var showContactRequestDialog = false
|
||||
|
||||
var body: some View {
|
||||
let contactType = chatContactType(chat: chat)
|
||||
|
||||
Group {
|
||||
switch (chat.chatInfo) {
|
||||
case let .direct(contact):
|
||||
switch contactType {
|
||||
case .recent:
|
||||
recentContactNavLink(contact)
|
||||
case .chatDeleted:
|
||||
deletedChatNavLink(contact)
|
||||
case .card:
|
||||
contactCardNavLink(contact)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
case let .contactRequest(contactRequest):
|
||||
contactRequestNavLink(contactRequest)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { $0.alert }
|
||||
.actionSheet(item: $actionSheet) { $0.actionSheet }
|
||||
.sheet(item: $sheet) {
|
||||
if #available(iOS 16.0, *) {
|
||||
$0.content
|
||||
.presentationDetents([.fraction(0.4)])
|
||||
} else {
|
||||
$0.content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func recentContactNavLink(_ contact: Contact) -> some View {
|
||||
Button {
|
||||
dismissAllSheets(animated: true) {
|
||||
ChatModel.shared.chatId = contact.id
|
||||
}
|
||||
} label: {
|
||||
contactPreview(contact, titleColor: theme.colors.onBackground)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button {
|
||||
deleteContactDialog(
|
||||
chat,
|
||||
contact,
|
||||
dismissToChatList: false,
|
||||
showAlert: { alert = $0 },
|
||||
showActionSheet: { actionSheet = $0 },
|
||||
showSheetContent: { sheet = $0 }
|
||||
)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
|
||||
func deletedChatNavLink(_ contact: Contact) -> some View {
|
||||
Button {
|
||||
Task {
|
||||
await MainActor.run {
|
||||
var updatedContact = contact
|
||||
updatedContact.chatDeleted = false
|
||||
ChatModel.shared.updateContact(updatedContact)
|
||||
dismissAllSheets(animated: true) {
|
||||
ChatModel.shared.chatId = contact.id
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
contactPreview(contact, titleColor: theme.colors.onBackground)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button {
|
||||
deleteContactDialog(
|
||||
chat,
|
||||
contact,
|
||||
dismissToChatList: false,
|
||||
showAlert: { alert = $0 },
|
||||
showActionSheet: { actionSheet = $0 },
|
||||
showSheetContent: { sheet = $0 }
|
||||
)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
|
||||
func contactPreview(_ contact: Contact, titleColor: Color) -> some View {
|
||||
HStack{
|
||||
ProfileImage(imageStr: contact.image, size: 30)
|
||||
|
||||
previewTitle(contact, titleColor: titleColor)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
if showDeletedChatIcon && contact.chatDeleted {
|
||||
Image(systemName: "archivebox")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 18, height: 18)
|
||||
.foregroundColor(.secondary.opacity(0.65))
|
||||
} else if chat.chatInfo.chatSettings?.favorite ?? false {
|
||||
Image(systemName: "star.fill")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 18, height: 18)
|
||||
.foregroundColor(.secondary.opacity(0.65))
|
||||
}
|
||||
if contact.contactConnIncognito {
|
||||
Image(systemName: "theatermasks")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 22, height: 22)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func previewTitle(_ contact: Contact, titleColor: Color) -> some View {
|
||||
let t = Text(chat.chatInfo.chatViewName).foregroundColor(titleColor)
|
||||
(
|
||||
contact.verified == true
|
||||
? verifiedIcon + t
|
||||
: t
|
||||
)
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
private var verifiedIcon: Text {
|
||||
(Text(Image(systemName: "checkmark.shield")) + Text(" "))
|
||||
.foregroundColor(.secondary)
|
||||
.baselineOffset(1)
|
||||
.kerning(-2)
|
||||
}
|
||||
|
||||
func contactCardNavLink(_ contact: Contact) -> some View {
|
||||
Button {
|
||||
showConnectContactViaAddressDialog = true
|
||||
} label: {
|
||||
contactCardPreview(contact)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button {
|
||||
deleteContactDialog(
|
||||
chat,
|
||||
contact,
|
||||
dismissToChatList: false,
|
||||
showAlert: { alert = $0 },
|
||||
showActionSheet: { actionSheet = $0 },
|
||||
showSheetContent: { sheet = $0 }
|
||||
)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
.confirmationDialog("Connect with \(contact.chatViewName)", isPresented: $showConnectContactViaAddressDialog, titleVisibility: .visible) {
|
||||
Button("Use current profile") { connectContactViaAddress_(contact, false) }
|
||||
Button("Use new incognito profile") { connectContactViaAddress_(contact, true) }
|
||||
}
|
||||
}
|
||||
|
||||
private func connectContactViaAddress_(_ contact: Contact, _ incognito: Bool) {
|
||||
Task {
|
||||
let ok = await connectContactViaAddress(contact.contactId, incognito, showAlert: { alert = SomeAlert(alert: $0, id: "ContactListNavLink connectContactViaAddress") })
|
||||
if ok {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.chatId = contact.id
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
dismissAllSheets(animated: true) {
|
||||
AlertManager.shared.showAlert(connReqSentAlert(.contact))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func contactCardPreview(_ contact: Contact) -> some View {
|
||||
HStack{
|
||||
ProfileImage(imageStr: contact.image, size: 30)
|
||||
|
||||
Text(chat.chatInfo.chatViewName)
|
||||
.foregroundColor(.accentColor)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "envelope")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 14, height: 14)
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
func contactRequestNavLink(_ contactRequest: UserContactRequest) -> some View {
|
||||
Button {
|
||||
showContactRequestDialog = true
|
||||
} label: {
|
||||
contactRequestPreview(contactRequest)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) }
|
||||
} label: { Label("Accept", systemImage: "checkmark") }
|
||||
.tint(theme.colors.primary)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) }
|
||||
} label: {
|
||||
Label("Accept incognito", systemImage: "theatermasks")
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
alert = SomeAlert(alert: rejectContactRequestAlert(contactRequest), id: "rejectContactRequestAlert")
|
||||
} label: {
|
||||
Label("Reject", systemImage: "multiply")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
.confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) {
|
||||
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } }
|
||||
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) } }
|
||||
Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest) } }
|
||||
}
|
||||
}
|
||||
|
||||
func contactRequestPreview(_ contactRequest: UserContactRequest) -> some View {
|
||||
HStack{
|
||||
ProfileImage(imageStr: contactRequest.image, size: 30)
|
||||
|
||||
Text(chat.chatInfo.chatViewName)
|
||||
.foregroundColor(.accentColor)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "checkmark")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 14, height: 14)
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ enum DatabaseAlert: Identifiable {
|
||||
case importArchive
|
||||
case archiveImported
|
||||
case archiveImportedWithErrors(archiveErrors: [ArchiveError])
|
||||
case archiveExportedWithErrors(archivePath: URL, archiveErrors: [ArchiveError])
|
||||
case deleteChat
|
||||
case chatDeleted
|
||||
case deleteLegacyDatabase
|
||||
@@ -29,6 +30,7 @@ enum DatabaseAlert: Identifiable {
|
||||
case .importArchive: return "importArchive"
|
||||
case .archiveImported: return "archiveImported"
|
||||
case .archiveImportedWithErrors: return "archiveImportedWithErrors"
|
||||
case .archiveExportedWithErrors: return "archiveExportedWithErrors"
|
||||
case .deleteChat: return "deleteChat"
|
||||
case .chatDeleted: return "chatDeleted"
|
||||
case .deleteLegacyDatabase: return "deleteLegacyDatabase"
|
||||
@@ -265,10 +267,18 @@ struct DatabaseView: View {
|
||||
title: Text("Chat database imported"),
|
||||
message: Text("Restart the app to use imported chat database")
|
||||
)
|
||||
case .archiveImportedWithErrors:
|
||||
case let .archiveImportedWithErrors(errs):
|
||||
return Alert(
|
||||
title: Text("Chat database imported"),
|
||||
message: Text("Restart the app to use imported chat database") + Text("\n") + Text("Some non-fatal errors occurred during import - you may see Chat console for more details.")
|
||||
message: Text("Restart the app to use imported chat database") + Text(verbatim: "\n\n") + Text("Some non-fatal errors occurred during import:") + archiveErrorsText(errs)
|
||||
)
|
||||
case let .archiveExportedWithErrors(archivePath, errs):
|
||||
return Alert(
|
||||
title: Text("Chat database exported"),
|
||||
message: Text("You may save the exported archive.") + Text(verbatim: "\n\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs),
|
||||
dismissButton: .default(Text("Continue")) {
|
||||
showShareSheet(items: [archivePath])
|
||||
}
|
||||
)
|
||||
case .deleteChat:
|
||||
return Alert(
|
||||
@@ -349,9 +359,16 @@ struct DatabaseView: View {
|
||||
progressIndicator = true
|
||||
Task {
|
||||
do {
|
||||
let archivePath = try await exportChatArchive()
|
||||
showShareSheet(items: [archivePath])
|
||||
await MainActor.run { progressIndicator = false }
|
||||
let (archivePath, archiveErrors) = try await exportChatArchive()
|
||||
if archiveErrors.isEmpty {
|
||||
showShareSheet(items: [archivePath])
|
||||
await MainActor.run { progressIndicator = false }
|
||||
} else {
|
||||
await MainActor.run {
|
||||
alert = .archiveExportedWithErrors(archivePath: archivePath, archiveErrors: archiveErrors)
|
||||
progressIndicator = false
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
await MainActor.run {
|
||||
alert = .error(title: "Error exporting chat database", error: responseError(error))
|
||||
@@ -486,6 +503,17 @@ struct DatabaseView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func archiveErrorsText(_ errs: [ArchiveError]) -> Text {
|
||||
return Text("\n" + errs.map(showArchiveError).joined(separator: "\n"))
|
||||
|
||||
func showArchiveError(_ err: ArchiveError) -> String {
|
||||
switch err {
|
||||
case let .import(importError): importError
|
||||
case let .fileError(file, fileError): "\(file): \(fileError)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stopChatAsync() async throws {
|
||||
try await apiStopChat()
|
||||
ChatReceiver.shared.stop()
|
||||
|
||||
@@ -190,7 +190,7 @@ struct MigrateToAppGroupView: View {
|
||||
do {
|
||||
try apiSaveAppSettings(settings: AppSettings.current.prepareForExport())
|
||||
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
|
||||
try await apiExportArchive(config: config)
|
||||
_ = try await apiExportArchive(config: config)
|
||||
await MainActor.run { setV3DBMigration(.exported) }
|
||||
} catch let error {
|
||||
await MainActor.run {
|
||||
@@ -222,7 +222,7 @@ struct MigrateToAppGroupView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func exportChatArchive(_ storagePath: URL? = nil) async throws -> URL {
|
||||
func exportChatArchive(_ storagePath: URL? = nil) async throws -> (URL, [ArchiveError]) {
|
||||
let archiveTime = Date.now
|
||||
let ts = archiveTime.ISO8601Format(Date.ISO8601FormatStyle(timeSeparator: .omitted))
|
||||
let archiveName = "simplex-chat.\(ts).zip"
|
||||
@@ -233,13 +233,13 @@ func exportChatArchive(_ storagePath: URL? = nil) async throws -> URL {
|
||||
try apiSaveAppSettings(settings: AppSettings.current.prepareForExport())
|
||||
}
|
||||
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
|
||||
try await apiExportArchive(config: config)
|
||||
let errs = try await apiExportArchive(config: config)
|
||||
if storagePath == nil {
|
||||
deleteOldArchive()
|
||||
UserDefaults.standard.set(archiveName, forKey: DEFAULT_CHAT_ARCHIVE_NAME)
|
||||
chatArchiveTimeDefault.set(archiveTime)
|
||||
}
|
||||
return archivePath
|
||||
return (archivePath, errs)
|
||||
}
|
||||
|
||||
func deleteOldArchive() {
|
||||
|
||||
@@ -8,41 +8,21 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
private struct SheetIsPresented<C>: ViewModifier where C: View {
|
||||
var isPresented: Binding<Bool>
|
||||
var onDismiss: (() -> Void)?
|
||||
var sheetContent: () -> C
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content.sheet(isPresented: isPresented, onDismiss: onDismiss) {
|
||||
sheetContent().modifier(PrivacySensitive())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct SheetForItem<T, C>: ViewModifier where T: Identifiable, C: View {
|
||||
var item: Binding<T?>
|
||||
var onDismiss: (() -> Void)?
|
||||
var sheetContent: (T) -> C
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content.sheet(item: item, onDismiss: onDismiss) { it in
|
||||
sheetContent(it).modifier(PrivacySensitive())
|
||||
}
|
||||
}
|
||||
class AppSheetState: ObservableObject {
|
||||
static let shared = AppSheetState()
|
||||
@Published var scenePhaseActive: Bool = false
|
||||
}
|
||||
|
||||
private struct PrivacySensitive: ViewModifier {
|
||||
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
// Screen protection doesn't work for appSheet on iOS 16 if @Environment(\.scenePhase) is used instead of global state
|
||||
@ObservedObject var appSheetState: AppSheetState = AppSheetState.shared
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if case .active = scenePhase {
|
||||
if !protectScreen {
|
||||
content
|
||||
} else {
|
||||
content.privacySensitive(protectScreen).redacted(reason: .privacy)
|
||||
content.privacySensitive(!appSheetState.scenePhaseActive).redacted(reason: .privacy)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +33,9 @@ extension View {
|
||||
onDismiss: (() -> Void)? = nil,
|
||||
content: @escaping () -> Content
|
||||
) -> some View where Content: View {
|
||||
modifier(SheetIsPresented(isPresented: isPresented, onDismiss: onDismiss, sheetContent: content))
|
||||
sheet(isPresented: isPresented, onDismiss: onDismiss) {
|
||||
content().modifier(PrivacySensitive())
|
||||
}
|
||||
}
|
||||
|
||||
func appSheet<T, Content>(
|
||||
@@ -61,6 +43,8 @@ extension View {
|
||||
onDismiss: (() -> Void)? = nil,
|
||||
content: @escaping (T) -> Content
|
||||
) -> some View where T: Identifiable, Content: View {
|
||||
modifier(SheetForItem(item: item, onDismiss: onDismiss, sheetContent: content))
|
||||
sheet(item: item, onDismiss: onDismiss) { it in
|
||||
content(it).modifier(PrivacySensitive())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ struct ChatViewBackground: ViewModifier {
|
||||
case WallpaperType.empty: ()
|
||||
}
|
||||
}
|
||||
)
|
||||
).ignoresSafeArea(.all)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@ struct NavStackCompat <C: View, D: View>: View {
|
||||
if #available(iOS 16, *) {
|
||||
NavigationStack(path: Binding(
|
||||
get: { isActive.wrappedValue ? [true] : [] },
|
||||
set: { _ in }
|
||||
set: { path in
|
||||
if path.isEmpty { isActive.wrappedValue = false }
|
||||
}
|
||||
)) {
|
||||
ZStack {
|
||||
NavigationLink(value: true) { EmptyView() }
|
||||
|
||||
@@ -16,11 +16,12 @@ struct ProfileImage: View {
|
||||
var size: CGFloat
|
||||
var color = Color(uiColor: .tertiarySystemGroupedBackground)
|
||||
var backgroundColor: Color? = nil
|
||||
var blurred = false
|
||||
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner
|
||||
|
||||
var body: some View {
|
||||
if let uiImage = UIImage(base64Encoded: imageStr) {
|
||||
clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius)
|
||||
clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius, blurred: blurred)
|
||||
} else {
|
||||
let c = color.asAnotherColorFromSecondaryVariant(theme)
|
||||
Image(systemName: iconName)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// SwipeLabel.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Levitating Pineapple on 06/08/2024.
|
||||
// Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SwipeLabel: View {
|
||||
private let text: String
|
||||
private let systemImage: String
|
||||
private let inverted: Bool
|
||||
|
||||
init(_ text: String, systemImage: String, inverted: Bool) {
|
||||
self.text = text
|
||||
self.systemImage = systemImage
|
||||
self.inverted = inverted
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if inverted {
|
||||
Image(
|
||||
uiImage: SwipeActionView(
|
||||
systemName: systemImage,
|
||||
text: text
|
||||
).snapshot(inverted: inverted)
|
||||
)
|
||||
} else {
|
||||
Label(text, systemImage: systemImage)
|
||||
}
|
||||
}
|
||||
|
||||
private class SwipeActionView: UIView {
|
||||
private let imageView = UIImageView()
|
||||
private let label = UILabel()
|
||||
private let fontSize: CGFloat
|
||||
|
||||
init(systemName: String, text: String) {
|
||||
fontSize = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .subheadline).pointSize
|
||||
super.init(frame: CGRect(x: 0, y: 0, width: 64, height: 32 + fontSize))
|
||||
imageView.image = UIImage(systemName: systemName)
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
label.text = text
|
||||
label.textAlignment = .center
|
||||
label.font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
|
||||
addSubview(imageView)
|
||||
addSubview(label)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
imageView.frame = CGRect(
|
||||
x: 20,
|
||||
y: 0,
|
||||
width: 24,
|
||||
height: 24
|
||||
)
|
||||
label.frame = CGRect(
|
||||
x: 0,
|
||||
y: 32,
|
||||
width: 64,
|
||||
height: fontSize
|
||||
)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) { fatalError("not implemented") }
|
||||
|
||||
func snapshot(inverted: Bool) -> UIImage {
|
||||
UIGraphicsImageRenderer(bounds: bounds).image { context in
|
||||
if inverted {
|
||||
context.cgContext.scaleBy(x: 1, y: -1)
|
||||
context.cgContext.translateBy(x: 0, y: -bounds.height)
|
||||
}
|
||||
layer.render(in: context.cgContext)
|
||||
}.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
@ViewBuilder func `if`<Content: View>(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View {
|
||||
if condition() {
|
||||
@ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
|
||||
if condition {
|
||||
transform(self)
|
||||
} else {
|
||||
self
|
||||
|
||||
@@ -66,6 +66,7 @@ struct LocalAuthView: View {
|
||||
m.chatId = nil
|
||||
ItemsModel.shared.reversedChatItems = []
|
||||
m.chats = []
|
||||
m.popChatCollector.clear()
|
||||
m.users = []
|
||||
_ = kcAppPassword.set(password)
|
||||
_ = kcSelfDestructPassword.remove()
|
||||
|
||||
@@ -32,6 +32,7 @@ private enum MigrateFromDeviceViewAlert: Identifiable {
|
||||
case keychainError(_ title: LocalizedStringKey = "Keychain error")
|
||||
case databaseError(_ title: LocalizedStringKey = "Database error", message: String)
|
||||
case unknownError(_ title: LocalizedStringKey = "Unknown error", message: String)
|
||||
case archiveExportedWithErrors(archivePath: URL, archiveErrors: [ArchiveError])
|
||||
|
||||
case error(title: LocalizedStringKey, error: String = "")
|
||||
|
||||
@@ -45,6 +46,7 @@ private enum MigrateFromDeviceViewAlert: Identifiable {
|
||||
case .keychainError: return "keychainError"
|
||||
case let .databaseError(title, message): return "\(title) \(message)"
|
||||
case let .unknownError(title, message): return "\(title) \(message)"
|
||||
case let .archiveExportedWithErrors(path, _): return "archiveExportedWithErrors \(path)"
|
||||
|
||||
case let .error(title, _): return "error \(title)"
|
||||
}
|
||||
@@ -166,6 +168,14 @@ struct MigrateFromDevice: View {
|
||||
return Alert(title: Text(title), message: Text(message))
|
||||
case let .unknownError(title, message):
|
||||
return Alert(title: Text(title), message: Text(message))
|
||||
case let .archiveExportedWithErrors(archivePath, errs):
|
||||
return Alert(
|
||||
title: Text("Chat database exported"),
|
||||
message: Text("You may migrate the exported database.") + Text(verbatim: "\n\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs),
|
||||
dismissButton: .default(Text("Continue")) {
|
||||
Task { await uploadArchive(path: archivePath) }
|
||||
}
|
||||
)
|
||||
case let .error(title, error):
|
||||
return Alert(title: Text(title), message: Text(error))
|
||||
}
|
||||
@@ -449,15 +459,12 @@ struct MigrateFromDevice: View {
|
||||
Task {
|
||||
do {
|
||||
try? FileManager.default.createDirectory(at: getMigrationTempFilesDirectory(), withIntermediateDirectories: true)
|
||||
let archivePath = try await exportChatArchive(getMigrationTempFilesDirectory())
|
||||
if let attrs = try? FileManager.default.attributesOfItem(atPath: archivePath.path),
|
||||
let totalBytes = attrs[.size] as? Int64 {
|
||||
await MainActor.run {
|
||||
migrationState = .uploadProgress(uploadedBytes: 0, totalBytes: totalBytes, fileId: 0, archivePath: archivePath, ctrl: nil)
|
||||
}
|
||||
let (archivePath, errs) = try await exportChatArchive(getMigrationTempFilesDirectory())
|
||||
if errs.isEmpty {
|
||||
await uploadArchive(path: archivePath)
|
||||
} else {
|
||||
await MainActor.run {
|
||||
alert = .error(title: "Exported file doesn't exist")
|
||||
alert = .archiveExportedWithErrors(archivePath: archivePath, archiveErrors: errs)
|
||||
migrationState = .uploadConfirmation
|
||||
}
|
||||
}
|
||||
@@ -469,6 +476,20 @@ struct MigrateFromDevice: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func uploadArchive(path archivePath: URL) async {
|
||||
if let attrs = try? FileManager.default.attributesOfItem(atPath: archivePath.path),
|
||||
let totalBytes = attrs[.size] as? Int64 {
|
||||
await MainActor.run {
|
||||
migrationState = .uploadProgress(uploadedBytes: 0, totalBytes: totalBytes, fileId: 0, archivePath: archivePath, ctrl: nil)
|
||||
}
|
||||
} else {
|
||||
await MainActor.run {
|
||||
alert = .error(title: "Exported file doesn't exist")
|
||||
migrationState = .uploadConfirmation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func initTemporaryDatabase() -> (chat_ctrl, User)? {
|
||||
let (status, ctrl) = chatInitTemporaryDatabase(url: tempDatabaseUrl)
|
||||
|
||||
@@ -30,7 +30,7 @@ struct AddContactLearnMore: View {
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
.modifier(ThemedBackground())
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,24 +35,28 @@ struct AddGroupView: View {
|
||||
creatingGroup: true,
|
||||
showFooterCounter: false
|
||||
) { _ in
|
||||
dismiss()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
m.chatId = groupInfo.id
|
||||
dismissAllSheets(animated: true) {
|
||||
m.chatId = groupInfo.id
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
} else {
|
||||
GroupLinkView(
|
||||
groupId: groupInfo.groupId,
|
||||
groupLink: $groupLink,
|
||||
groupLinkMemberRole: $groupLinkMemberRole,
|
||||
showTitle: true,
|
||||
showTitle: false,
|
||||
creatingGroup: true
|
||||
) {
|
||||
dismiss()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
m.chatId = groupInfo.id
|
||||
dismissAllSheets(animated: true) {
|
||||
m.chatId = groupInfo.id
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Group link")
|
||||
}
|
||||
} else {
|
||||
createGroupView().keyboardPadding()
|
||||
@@ -62,13 +66,6 @@ struct AddGroupView: View {
|
||||
func createGroupView() -> some View {
|
||||
List {
|
||||
Group {
|
||||
Text("Create secret group")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.bottom, 24)
|
||||
.onTapGesture(perform: hideKeyboard)
|
||||
|
||||
ZStack(alignment: .center) {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ProfileImage(imageStr: profile.image, size: 128)
|
||||
@@ -204,13 +201,14 @@ struct AddGroupView: View {
|
||||
chat = c
|
||||
}
|
||||
} catch {
|
||||
dismiss()
|
||||
AlertManager.shared.showAlert(
|
||||
Alert(
|
||||
title: Text("Error creating group"),
|
||||
message: Text(responseError(error))
|
||||
dismissAllSheets(animated: true) {
|
||||
AlertManager.shared.showAlert(
|
||||
Alert(
|
||||
title: Text("Error creating group"),
|
||||
message: Text(responseError(error))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,53 +7,499 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
enum NewChatMenuOption: Identifiable {
|
||||
case newContact
|
||||
case scanPaste
|
||||
case newGroup
|
||||
|
||||
var id: Self { self }
|
||||
enum ContactType: Int {
|
||||
case card, request, recent, chatDeleted, unlisted
|
||||
}
|
||||
|
||||
struct NewChatMenuButton: View {
|
||||
@Binding var newChatMenuOption: NewChatMenuOption?
|
||||
@State private var showNewChatSheet = false
|
||||
@State private var alert: SomeAlert? = nil
|
||||
@State private var globalAlert: SomeAlert? = nil
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
Button {
|
||||
newChatMenuOption = .newContact
|
||||
} label: {
|
||||
Text("Add contact")
|
||||
}
|
||||
Button {
|
||||
newChatMenuOption = .scanPaste
|
||||
} label: {
|
||||
Text("Scan / Paste link")
|
||||
}
|
||||
Button {
|
||||
newChatMenuOption = .newGroup
|
||||
} label: {
|
||||
Text("Create group")
|
||||
}
|
||||
showNewChatSheet = true
|
||||
} label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 24, height: 24)
|
||||
}
|
||||
.sheet(item: $newChatMenuOption) { opt in
|
||||
switch opt {
|
||||
case .newContact: NewChatView(selection: .invite)
|
||||
case .scanPaste: NewChatView(selection: .connect, showQRCodeScanner: true)
|
||||
case .newGroup: AddGroupView()
|
||||
.appSheet(isPresented: $showNewChatSheet) {
|
||||
NewChatSheet(alert: $alert)
|
||||
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
|
||||
.alert(item: $alert) { a in
|
||||
return a.alert
|
||||
}
|
||||
}
|
||||
// This is a workaround to show "Keep unused invitation" alert in both following cases:
|
||||
// - on going back from NewChatView to NewChatSheet,
|
||||
// - on dismissing NewChatMenuButton sheet while on NewChatView (skipping NewChatSheet)
|
||||
.onChange(of: alert?.id) { a in
|
||||
if !showNewChatSheet && alert != nil {
|
||||
globalAlert = alert
|
||||
alert = nil
|
||||
}
|
||||
}
|
||||
.alert(item: $globalAlert) { a in
|
||||
return a.alert
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NewChatMenuButton(
|
||||
newChatMenuOption: Binding.constant(nil)
|
||||
)
|
||||
private var indent: CGFloat = 36
|
||||
|
||||
struct NewChatSheet: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@State private var baseContactTypes: [ContactType] = [.card, .request, .recent]
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@State private var searchMode = false
|
||||
@FocusState var searchFocussed: Bool
|
||||
@State private var searchText = ""
|
||||
@State private var searchShowingSimplexLink = false
|
||||
@State private var searchChatFilteredBySimplexLink: String? = nil
|
||||
@Binding var alert: SomeAlert?
|
||||
|
||||
// Sheet height management
|
||||
@State private var isAddContactActive = false
|
||||
@State private var isScanPasteLinkActive = false
|
||||
@State private var isLargeSheet = false
|
||||
@State private var allowSmallSheet = true
|
||||
|
||||
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true
|
||||
|
||||
var body: some View {
|
||||
let showArchive = !filterContactTypes(chats: chatModel.chats, contactTypes: [.chatDeleted]).isEmpty
|
||||
let v = NavigationView {
|
||||
viewBody(showArchive)
|
||||
.navigationTitle("New message")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.navigationBarHidden(searchMode)
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
}
|
||||
if #available(iOS 16.0, *), oneHandUI {
|
||||
let sheetHeight: CGFloat = showArchive ? 575 : 500
|
||||
v.presentationDetents(
|
||||
allowSmallSheet ? [.height(sheetHeight), .large] : [.large],
|
||||
selection: Binding(
|
||||
get: { isLargeSheet || !allowSmallSheet ? .large : .height(sheetHeight) },
|
||||
set: { isLargeSheet = $0 == .large }
|
||||
)
|
||||
)
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func viewBody(_ showArchive: Bool) -> some View {
|
||||
List {
|
||||
HStack {
|
||||
ContactsListSearchBar(
|
||||
searchMode: $searchMode,
|
||||
searchFocussed: $searchFocussed,
|
||||
searchText: $searchText,
|
||||
searchShowingSimplexLink: $searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
if (searchText.isEmpty) {
|
||||
Section {
|
||||
NavigationLink(isActive: $isAddContactActive) {
|
||||
NewChatView(selection: .invite, parentAlert: $alert)
|
||||
.navigationTitle("New chat")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
navigateOnTap(Label("Add contact", systemImage: "link.badge.plus")) {
|
||||
isAddContactActive = true
|
||||
}
|
||||
}
|
||||
NavigationLink(isActive: $isScanPasteLinkActive) {
|
||||
NewChatView(selection: .connect, showQRCodeScanner: true, parentAlert: $alert)
|
||||
.navigationTitle("New chat")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
navigateOnTap(Label("Scan / Paste link", systemImage: "qrcode")) {
|
||||
isScanPasteLinkActive = true
|
||||
}
|
||||
}
|
||||
NavigationLink {
|
||||
AddGroupView()
|
||||
.navigationTitle("Create secret group")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
Label("Create group", systemImage: "person.2.circle.fill")
|
||||
}
|
||||
}
|
||||
|
||||
if (showArchive) {
|
||||
Section {
|
||||
NavigationLink {
|
||||
DeletedChats()
|
||||
} label: {
|
||||
newChatActionButton("archivebox", color: theme.colors.secondary) { Text("Archived contacts") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContactsList(
|
||||
baseContactTypes: $baseContactTypes,
|
||||
searchMode: $searchMode,
|
||||
searchText: $searchText,
|
||||
header: "Your Contacts",
|
||||
searchFocussed: $searchFocussed,
|
||||
searchShowingSimplexLink: $searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink,
|
||||
showDeletedChatIcon: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends label's tap area to match `.insetGrouped` list row insets
|
||||
private func navigateOnTap<L: View>(_ label: L, setActive: @escaping () -> Void) -> some View {
|
||||
label
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 16).padding(.vertical, 8).padding(.trailing, 32)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
isLargeSheet = true
|
||||
DispatchQueue.main.async {
|
||||
allowSmallSheet = false
|
||||
setActive()
|
||||
}
|
||||
}
|
||||
.padding(.leading, -16).padding(.vertical, -8).padding(.trailing, -32)
|
||||
}
|
||||
|
||||
func newChatActionButton<Content : View>(_ icon: String, color: Color/* = .secondary*/, content: @escaping () -> Content) -> some View {
|
||||
ZStack(alignment: .leading) {
|
||||
Image(systemName: icon)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(maxWidth: 24, maxHeight: 24, alignment: .center)
|
||||
.symbolRenderingMode(.monochrome)
|
||||
.foregroundColor(color)
|
||||
content().foregroundColor(theme.colors.onBackground).padding(.leading, indent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func chatContactType(chat: Chat) -> ContactType {
|
||||
switch chat.chatInfo {
|
||||
case .contactRequest:
|
||||
return .request
|
||||
case let .direct(contact):
|
||||
if contact.activeConn == nil && contact.profile.contactLink != nil {
|
||||
return .card
|
||||
} else if contact.chatDeleted {
|
||||
return .chatDeleted
|
||||
} else if contact.contactStatus == .active {
|
||||
return .recent
|
||||
} else {
|
||||
return .unlisted
|
||||
}
|
||||
default:
|
||||
return .unlisted
|
||||
}
|
||||
}
|
||||
|
||||
private func filterContactTypes(chats: [Chat], contactTypes: [ContactType]) -> [Chat] {
|
||||
return chats.filter { chat in
|
||||
contactTypes.contains(chatContactType(chat: chat))
|
||||
}
|
||||
}
|
||||
|
||||
struct ContactsList: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Binding var baseContactTypes: [ContactType]
|
||||
@Binding var searchMode: Bool
|
||||
@Binding var searchText: String
|
||||
var header: String? = nil
|
||||
@FocusState.Binding var searchFocussed: Bool
|
||||
@Binding var searchShowingSimplexLink: Bool
|
||||
@Binding var searchChatFilteredBySimplexLink: String?
|
||||
var showDeletedChatIcon: Bool
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
|
||||
var body: some View {
|
||||
let contactTypes = contactTypesSearchTargets(baseContactTypes: baseContactTypes, searchEmpty: searchText.isEmpty)
|
||||
let contactChats = filterContactTypes(chats: chatModel.chats, contactTypes: contactTypes)
|
||||
let filteredContactChats = filteredContactChats(
|
||||
showUnreadAndFavorites: showUnreadAndFavorites,
|
||||
searchShowingSimplexLink: searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: searchChatFilteredBySimplexLink,
|
||||
searchText: searchText,
|
||||
contactChats: contactChats
|
||||
)
|
||||
|
||||
if !filteredContactChats.isEmpty {
|
||||
Section(header: Group {
|
||||
if let header = header {
|
||||
Text(header)
|
||||
.textCase(.uppercase)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
}
|
||||
) {
|
||||
ForEach(filteredContactChats, id: \.viewId) { chat in
|
||||
ContactListNavLink(chat: chat, showDeletedChatIcon: showDeletedChatIcon)
|
||||
.disabled(chatModel.chatRunning != true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if filteredContactChats.isEmpty && !contactChats.isEmpty {
|
||||
noResultSection(text: "No filtered contacts")
|
||||
} else if contactChats.isEmpty {
|
||||
noResultSection(text: "No contacts")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func noResultSection(text: String) -> some View {
|
||||
Section {
|
||||
Text(text)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowInsets(EdgeInsets(top: 7, leading: 0, bottom: 7, trailing: 0))
|
||||
}
|
||||
|
||||
private func contactTypesSearchTargets(baseContactTypes: [ContactType], searchEmpty: Bool) -> [ContactType] {
|
||||
if baseContactTypes.contains(.chatDeleted) || searchEmpty {
|
||||
return baseContactTypes
|
||||
} else {
|
||||
return baseContactTypes + [.chatDeleted]
|
||||
}
|
||||
}
|
||||
|
||||
private func chatsByTypeComparator(chat1: Chat, chat2: Chat) -> Bool {
|
||||
let chat1Type = chatContactType(chat: chat1)
|
||||
let chat2Type = chatContactType(chat: chat2)
|
||||
|
||||
if chat1Type.rawValue < chat2Type.rawValue {
|
||||
return true
|
||||
} else if chat1Type.rawValue > chat2Type.rawValue {
|
||||
return false
|
||||
} else {
|
||||
return chat2.chatInfo.chatTs < chat1.chatInfo.chatTs
|
||||
}
|
||||
}
|
||||
|
||||
private func filterChat(chat: Chat, searchText: String, showUnreadAndFavorites: Bool) -> Bool {
|
||||
var meetsPredicate = true
|
||||
let s = searchText.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
let cInfo = chat.chatInfo
|
||||
|
||||
if !searchText.isEmpty {
|
||||
if (!cInfo.chatViewName.lowercased().contains(searchText.lowercased())) {
|
||||
if case let .direct(contact) = cInfo {
|
||||
meetsPredicate = contact.profile.displayName.lowercased().contains(s) || contact.fullName.lowercased().contains(s)
|
||||
} else {
|
||||
meetsPredicate = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if showUnreadAndFavorites {
|
||||
meetsPredicate = meetsPredicate && (cInfo.chatSettings?.favorite ?? false)
|
||||
}
|
||||
|
||||
return meetsPredicate
|
||||
}
|
||||
|
||||
func filteredContactChats(
|
||||
showUnreadAndFavorites: Bool,
|
||||
searchShowingSimplexLink: Bool,
|
||||
searchChatFilteredBySimplexLink: String?,
|
||||
searchText: String,
|
||||
contactChats: [Chat]
|
||||
) -> [Chat] {
|
||||
let linkChatId = searchChatFilteredBySimplexLink
|
||||
let s = searchShowingSimplexLink ? "" : searchText.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
|
||||
let filteredChats: [Chat]
|
||||
|
||||
if let linkChatId = linkChatId {
|
||||
filteredChats = contactChats.filter { $0.id == linkChatId }
|
||||
} else {
|
||||
filteredChats = contactChats.filter { chat in
|
||||
filterChat(chat: chat, searchText: s, showUnreadAndFavorites: showUnreadAndFavorites)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredChats.sorted(by: chatsByTypeComparator)
|
||||
}
|
||||
}
|
||||
|
||||
struct ContactsListSearchBar: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Binding var searchMode: Bool
|
||||
@FocusState.Binding var searchFocussed: Bool
|
||||
@Binding var searchText: String
|
||||
@Binding var searchShowingSimplexLink: Bool
|
||||
@Binding var searchChatFilteredBySimplexLink: String?
|
||||
@State private var ignoreSearchTextChange = false
|
||||
@State private var alert: PlanAndConnectAlert?
|
||||
@State private var sheet: PlanAndConnectActionSheet?
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 4) {
|
||||
Spacer()
|
||||
.frame(width: 8)
|
||||
Image(systemName: "magnifyingglass")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 16, height: 16)
|
||||
TextField("Search or paste SimpleX link", text: $searchText)
|
||||
.foregroundColor(searchShowingSimplexLink ? theme.colors.secondary : theme.colors.onBackground)
|
||||
.disabled(searchShowingSimplexLink)
|
||||
.focused($searchFocussed)
|
||||
.frame(maxWidth: .infinity)
|
||||
if !searchText.isEmpty {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 16, height: 16)
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.background(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
.cornerRadius(10.0)
|
||||
|
||||
if searchFocussed {
|
||||
Text("Cancel")
|
||||
.foregroundColor(theme.colors.primary)
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
searchFocussed = false
|
||||
}
|
||||
} else if m.chats.count > 0 {
|
||||
toggleFilterButton()
|
||||
}
|
||||
}
|
||||
.padding(.top, 24)
|
||||
.onChange(of: searchFocussed) { sf in
|
||||
withAnimation { searchMode = sf }
|
||||
}
|
||||
.onChange(of: searchText) { t in
|
||||
if ignoreSearchTextChange {
|
||||
ignoreSearchTextChange = false
|
||||
} else {
|
||||
if let link = strHasSingleSimplexLink(t.trimmingCharacters(in: .whitespaces)) { // if SimpleX link is pasted, show connection dialogue
|
||||
searchFocussed = false
|
||||
if case let .simplexLink(linkType, _, smpHosts) = link.format {
|
||||
ignoreSearchTextChange = true
|
||||
searchText = simplexLinkText(linkType, smpHosts)
|
||||
}
|
||||
searchShowingSimplexLink = true
|
||||
searchChatFilteredBySimplexLink = nil
|
||||
connect(link.text)
|
||||
} else {
|
||||
if t != "" { // if some other text is pasted, enter search mode
|
||||
searchFocussed = true
|
||||
}
|
||||
searchShowingSimplexLink = false
|
||||
searchChatFilteredBySimplexLink = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { a in
|
||||
planAndConnectAlert(a, dismiss: true, cleanup: { searchText = "" })
|
||||
}
|
||||
.actionSheet(item: $sheet) { s in
|
||||
planAndConnectActionSheet(s, dismiss: true, cleanup: { searchText = "" })
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleFilterButton() -> some View {
|
||||
ZStack {
|
||||
Color.clear
|
||||
.frame(width: 22, height: 22)
|
||||
Image(systemName: showUnreadAndFavorites ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundColor(showUnreadAndFavorites ? theme.colors.primary : theme.colors.secondary)
|
||||
.frame(width: showUnreadAndFavorites ? 22 : 16, height: showUnreadAndFavorites ? 22 : 16)
|
||||
.onTapGesture {
|
||||
showUnreadAndFavorites = !showUnreadAndFavorites
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func connect(_ link: String) {
|
||||
planAndConnect(
|
||||
link,
|
||||
showAlert: { alert = $0 },
|
||||
showActionSheet: { sheet = $0 },
|
||||
dismiss: true,
|
||||
incognito: nil,
|
||||
filterKnownContact: { searchChatFilteredBySimplexLink = $0.id }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct DeletedChats: View {
|
||||
@State private var baseContactTypes: [ContactType] = [.chatDeleted]
|
||||
@State private var searchMode = false
|
||||
@FocusState var searchFocussed: Bool
|
||||
@State private var searchText = ""
|
||||
@State private var searchShowingSimplexLink = false
|
||||
@State private var searchChatFilteredBySimplexLink: String? = nil
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ContactsListSearchBar(
|
||||
searchMode: $searchMode,
|
||||
searchFocussed: $searchFocussed,
|
||||
searchText: $searchText,
|
||||
searchShowingSimplexLink: $searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
|
||||
)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
ContactsList(
|
||||
baseContactTypes: $baseContactTypes,
|
||||
searchMode: $searchMode,
|
||||
searchText: $searchText,
|
||||
searchFocussed: $searchFocussed,
|
||||
searchShowingSimplexLink: $searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink,
|
||||
showDeletedChatIcon: false
|
||||
)
|
||||
}
|
||||
.navigationTitle("Archived contacts")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.navigationBarHidden(searchMode)
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NewChatMenuButton()
|
||||
}
|
||||
|
||||
@@ -17,10 +17,19 @@ struct SomeAlert: Identifiable {
|
||||
var id: String
|
||||
}
|
||||
|
||||
struct SomeActionSheet: Identifiable {
|
||||
var actionSheet: ActionSheet
|
||||
var id: String
|
||||
}
|
||||
|
||||
struct SomeSheet<Content: View>: Identifiable {
|
||||
@ViewBuilder var content: Content
|
||||
var id: String
|
||||
}
|
||||
|
||||
private enum NewChatViewAlert: Identifiable {
|
||||
case planAndConnectAlert(alert: PlanAndConnectAlert)
|
||||
case newChatSomeAlert(alert: SomeAlert)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)"
|
||||
@@ -47,22 +56,10 @@ struct NewChatView: View {
|
||||
@State private var creatingConnReq = false
|
||||
@State private var pastedLink: String = ""
|
||||
@State private var alert: NewChatViewAlert?
|
||||
@Binding var parentAlert: SomeAlert?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text("New chat")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
InfoSheetButton {
|
||||
AddContactLearnMore(showTitle: true)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.padding(.top)
|
||||
|
||||
Picker("New chat", selection: $selection) {
|
||||
Label("Add contact", systemImage: "link")
|
||||
.tag(NewChatOption.invite)
|
||||
@@ -88,6 +85,7 @@ struct NewChatView: View {
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.background(
|
||||
// Rectangle is needed for swipe gesture to work on mostly empty views (creatingLinkProgressView and retryButton)
|
||||
Rectangle()
|
||||
@@ -110,6 +108,13 @@ struct NewChatView: View {
|
||||
}
|
||||
)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
InfoSheetButton {
|
||||
AddContactLearnMore(showTitle: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.onChange(of: invitationUsed) { used in
|
||||
if used && !(m.showingInvitation?.connChatUsed ?? true) {
|
||||
@@ -119,19 +124,22 @@ struct NewChatView: View {
|
||||
.onDisappear {
|
||||
if !(m.showingInvitation?.connChatUsed ?? true),
|
||||
let conn = contactConnection {
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
title: Text("Keep unused invitation?"),
|
||||
message: Text("You can view invitation link again in connection details."),
|
||||
primaryButton: .default(Text("Keep")) {},
|
||||
secondaryButton: .destructive(Text("Delete")) {
|
||||
Task {
|
||||
await deleteChat(Chat(
|
||||
chatInfo: .contactConnection(contactConnection: conn),
|
||||
chatItems: []
|
||||
))
|
||||
parentAlert = SomeAlert(
|
||||
alert: Alert(
|
||||
title: Text("Keep unused invitation?"),
|
||||
message: Text("You can view invitation link again in connection details."),
|
||||
primaryButton: .default(Text("Keep")) {},
|
||||
secondaryButton: .destructive(Text("Delete")) {
|
||||
Task {
|
||||
await deleteChat(Chat(
|
||||
chatInfo: .contactConnection(contactConnection: conn),
|
||||
chatItems: []
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
),
|
||||
id: "keepUnusedInvitation"
|
||||
)
|
||||
}
|
||||
m.showingInvitation = nil
|
||||
}
|
||||
@@ -837,7 +845,10 @@ private func connectContactViaAddress_(_ contact: Contact, dismiss: Bool, incogn
|
||||
dismissAllSheets(animated: true)
|
||||
}
|
||||
}
|
||||
_ = await connectContactViaAddress(contact.contactId, incognito)
|
||||
let ok = await connectContactViaAddress(contact.contactId, incognito, showAlert: { AlertManager.shared.showAlert($0) })
|
||||
if ok {
|
||||
AlertManager.shared.showAlert(connReqSentAlert(.contact))
|
||||
}
|
||||
cleanup?()
|
||||
}
|
||||
}
|
||||
@@ -961,8 +972,13 @@ func connReqSentAlert(_ type: ConnReqType) -> Alert {
|
||||
)
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NewChatView(
|
||||
selection: .invite
|
||||
)
|
||||
struct NewChatView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
@State var parentAlert: SomeAlert?
|
||||
|
||||
NewChatView(
|
||||
selection: .invite,
|
||||
parentAlert: $parentAlert
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@ private struct VersionDescription {
|
||||
}
|
||||
|
||||
private struct FeatureDescription {
|
||||
var icon: String
|
||||
var icon: String?
|
||||
var title: LocalizedStringKey
|
||||
var description: LocalizedStringKey
|
||||
var description: LocalizedStringKey?
|
||||
var subfeatures: [(icon: String, description: LocalizedStringKey)] = []
|
||||
}
|
||||
|
||||
private let versionDescriptions: [VersionDescription] = [
|
||||
@@ -428,6 +429,44 @@ private let versionDescriptions: [VersionDescription] = [
|
||||
)
|
||||
]
|
||||
),
|
||||
VersionDescription(
|
||||
version: "v6.0",
|
||||
post: URL(string: "https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html"),
|
||||
features: [
|
||||
FeatureDescription(
|
||||
icon: nil,
|
||||
title: "New chat experience 🎉",
|
||||
description: nil,
|
||||
subfeatures: [
|
||||
("link.badge.plus", "Connect to your friends faster."),
|
||||
("archivebox", "Archive contacts to chat later."),
|
||||
("trash", "Delete up to 20 messages at once."),
|
||||
("platter.filled.bottom.and.arrow.down.iphone", "Use the app with one hand."),
|
||||
("paintpalette", "Color chats with the new themes."),
|
||||
]
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: nil,
|
||||
title: "New media options",
|
||||
description: nil,
|
||||
subfeatures: [
|
||||
("square.and.arrow.up", "Share from other apps."),
|
||||
("play.circle", "Play from the chat list."),
|
||||
("circle.filled.pattern.diagonalline.rectangle", "Blur for better privacy.")
|
||||
]
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "arrow.forward",
|
||||
title: "Private message routing 🚀",
|
||||
description: "It protects your IP address and connections."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "network",
|
||||
title: "Better networking",
|
||||
description: "Connection and servers status."
|
||||
)
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
private let lastVersion = versionDescriptions.last!.version
|
||||
@@ -453,35 +492,37 @@ struct WhatsNewView: View {
|
||||
VStack {
|
||||
TabView(selection: $currentVersion) {
|
||||
ForEach(Array(versionDescriptions.enumerated()), id: \.0) { (i, v) in
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("New in \(v.version)")
|
||||
.font(.title)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical)
|
||||
ForEach(v.features, id: \.icon) { f in
|
||||
featureDescription(f.icon, f.title, f.description)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
if let post = v.post {
|
||||
Link(destination: post) {
|
||||
HStack {
|
||||
Text("Read more")
|
||||
Image(systemName: "arrow.up.right.circle")
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("New in \(v.version)")
|
||||
.font(.title)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical)
|
||||
ForEach(v.features, id: \.title) { f in
|
||||
featureDescription(f)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
if let post = v.post {
|
||||
Link(destination: post) {
|
||||
HStack {
|
||||
Text("Read more")
|
||||
Image(systemName: "arrow.up.right.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !viaSettings {
|
||||
Spacer()
|
||||
Button("Ok") {
|
||||
dismiss()
|
||||
if !viaSettings {
|
||||
Spacer()
|
||||
Button("Ok") {
|
||||
dismiss()
|
||||
}
|
||||
.font(.title3)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
Spacer()
|
||||
}
|
||||
.font(.title3)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
.tag(i)
|
||||
}
|
||||
}
|
||||
@@ -495,18 +536,37 @@ struct WhatsNewView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func featureDescription(_ icon: String, _ title: LocalizedStringKey, _ description: LocalizedStringKey) -> some View {
|
||||
private func featureDescription(_ f: FeatureDescription) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
Image(systemName: icon)
|
||||
.symbolRenderingMode(.monochrome)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.frame(minWidth: 30, alignment: .center)
|
||||
Text(title).font(.title3).bold()
|
||||
if let icon = f.icon {
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
Image(systemName: icon)
|
||||
.symbolRenderingMode(.monochrome)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.frame(minWidth: 30, alignment: .center)
|
||||
Text(f.title).font(.title3).bold()
|
||||
}
|
||||
} else {
|
||||
Text(f.title).font(.title3).bold()
|
||||
}
|
||||
if let d = f.description {
|
||||
Text(d)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(10)
|
||||
}
|
||||
if f.subfeatures.count > 0 {
|
||||
ForEach(f.subfeatures, id: \.icon) { s in
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
Image(systemName: s.icon)
|
||||
.symbolRenderingMode(.monochrome)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.frame(minWidth: 30, alignment: .center)
|
||||
Text(s.description)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(3)
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(description)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(10)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,34 +24,114 @@ enum NetworkSettingsAlert: Identifiable {
|
||||
}
|
||||
|
||||
struct AdvancedNetworkSettings: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false
|
||||
@State private var netCfg = NetCfg.defaults
|
||||
@State private var currentNetCfg = NetCfg.defaults
|
||||
@State private var cfgLoaded = false
|
||||
@State private var enableKeepAlive = true
|
||||
@State private var keepAliveOpts = KeepAliveOpts.defaults
|
||||
@State private var showSettingsAlert: NetworkSettingsAlert?
|
||||
@State private var onionHosts: OnionHosts = .no
|
||||
@State private var showSaveDialog = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
Section {
|
||||
Button {
|
||||
updateNetCfgView(NetCfg.defaults)
|
||||
showSettingsAlert = .update
|
||||
NavigationLink {
|
||||
List {
|
||||
Section {
|
||||
SelectionListView(list: SMPProxyMode.values, selection: $netCfg.smpProxyMode) { mode in
|
||||
netCfg.smpProxyMode = mode
|
||||
}
|
||||
} footer: {
|
||||
Text(proxyModeInfo(netCfg.smpProxyMode))
|
||||
.font(.callout)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Private routing")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
} label: {
|
||||
Text("Reset to defaults")
|
||||
HStack {
|
||||
Text("Private routing")
|
||||
Spacer()
|
||||
Text(netCfg.smpProxyMode.label)
|
||||
}
|
||||
}
|
||||
.disabled(currentNetCfg == NetCfg.defaults)
|
||||
|
||||
Button {
|
||||
updateNetCfgView(NetCfg.proxyDefaults)
|
||||
showSettingsAlert = .update
|
||||
|
||||
NavigationLink {
|
||||
List {
|
||||
Section {
|
||||
SelectionListView(list: SMPProxyFallback.values, selection: $netCfg.smpProxyFallback) { mode in
|
||||
netCfg.smpProxyFallback = mode
|
||||
}
|
||||
.disabled(netCfg.smpProxyMode == .never)
|
||||
} footer: {
|
||||
Text(proxyFallbackInfo(netCfg.smpProxyFallback))
|
||||
.font(.callout)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Allow downgrade")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
} label: {
|
||||
Text("Set timeouts for proxy/VPN")
|
||||
HStack {
|
||||
Text("Allow downgrade")
|
||||
Spacer()
|
||||
Text(netCfg.smpProxyFallback.label)
|
||||
}
|
||||
}
|
||||
.disabled(currentNetCfg == NetCfg.proxyDefaults)
|
||||
|
||||
Toggle("Show message status", isOn: $showSentViaProxy)
|
||||
} header: {
|
||||
Text("Private message routing")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
} footer: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("To protect your IP address, private routing uses your SMP servers to deliver messages.")
|
||||
if showSentViaProxy {
|
||||
Text("Show → on messages sent via private routing.")
|
||||
}
|
||||
}
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker("Use .onion hosts", selection: $onionHosts) {
|
||||
ForEach(OnionHosts.values, id: \.self) { Text($0.text) }
|
||||
}
|
||||
.frame(height: 36)
|
||||
} footer: {
|
||||
Text(onionHostsInfo(onionHosts))
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
.onChange(of: onionHosts) { hosts in
|
||||
if hosts != OnionHosts(netCfg: currentNetCfg) {
|
||||
let (hostMode, requiredHostMode) = hosts.hostMode
|
||||
netCfg.hostMode = hostMode
|
||||
netCfg.requiredHostMode = requiredHostMode
|
||||
}
|
||||
}
|
||||
|
||||
if developerTools {
|
||||
Section {
|
||||
Picker("Transport isolation", selection: $netCfg.sessionMode) {
|
||||
ForEach(TransportSessionMode.values, id: \.self) { Text($0.text) }
|
||||
}
|
||||
.frame(height: 36)
|
||||
} footer: {
|
||||
Text(sessionModeInfo(netCfg.sessionMode))
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
Section("TCP connection") {
|
||||
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [10_000000, 15_000000, 20_000000, 30_000000, 45_000000, 60_000000, 90_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)
|
||||
@@ -72,24 +152,21 @@ struct AdvancedNetworkSettings: View {
|
||||
}
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
} header: {
|
||||
Text("")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
} footer: {
|
||||
HStack {
|
||||
Button {
|
||||
updateNetCfgView(currentNetCfg)
|
||||
} label: {
|
||||
Label("Revert", systemImage: "arrow.counterclockwise").font(.callout)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Button("Reset to defaults") {
|
||||
updateNetCfgView(NetCfg.defaults)
|
||||
}
|
||||
.disabled(netCfg == NetCfg.defaults)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
showSettingsAlert = .update
|
||||
} label: {
|
||||
Label("Save", systemImage: "checkmark").font(.callout)
|
||||
}
|
||||
Button("Set timeouts for proxy/VPN") {
|
||||
updateNetCfgView(netCfg.withProxyTimeouts)
|
||||
}
|
||||
.disabled(netCfg.hasProxyTimeouts)
|
||||
|
||||
Button("Save and reconnect") {
|
||||
showSettingsAlert = .update
|
||||
}
|
||||
.disabled(netCfg == currentNetCfg)
|
||||
}
|
||||
@@ -111,10 +188,10 @@ struct AdvancedNetworkSettings: View {
|
||||
switch a {
|
||||
case .update:
|
||||
return Alert(
|
||||
title: Text("Update network settings?"),
|
||||
title: Text("Update settings?"),
|
||||
message: Text("Updating settings will re-connect the client to all servers."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
saveNetCfg()
|
||||
_ = saveNetCfg()
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
@@ -125,23 +202,43 @@ struct AdvancedNetworkSettings: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
.modifier(BackButton(disabled: Binding.constant(false)) {
|
||||
if netCfg == currentNetCfg {
|
||||
dismiss()
|
||||
cfgLoaded = false
|
||||
} else {
|
||||
showSaveDialog = true
|
||||
}
|
||||
})
|
||||
.confirmationDialog("Update network settings?", isPresented: $showSaveDialog, titleVisibility: .visible) {
|
||||
Button("Save and reconnect") {
|
||||
if saveNetCfg() {
|
||||
dismiss()
|
||||
cfgLoaded = false
|
||||
}
|
||||
}
|
||||
Button("Exit without saving") { dismiss() }
|
||||
}
|
||||
}
|
||||
|
||||
private func updateNetCfgView(_ cfg: NetCfg) {
|
||||
netCfg = cfg
|
||||
onionHosts = OnionHosts(netCfg: netCfg)
|
||||
enableKeepAlive = netCfg.enableKeepAlive
|
||||
keepAliveOpts = netCfg.tcpKeepAlive ?? KeepAliveOpts.defaults
|
||||
}
|
||||
|
||||
private func saveNetCfg() {
|
||||
private func saveNetCfg() -> Bool {
|
||||
do {
|
||||
try setNetworkConfig(netCfg)
|
||||
currentNetCfg = netCfg
|
||||
setNetCfg(netCfg)
|
||||
return true
|
||||
} catch let error {
|
||||
let err = responseError(error)
|
||||
showSettingsAlert = .error(err: err)
|
||||
logger.error("\(err)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +261,38 @@ struct AdvancedNetworkSettings: View {
|
||||
}
|
||||
.frame(height: 36)
|
||||
}
|
||||
|
||||
private func onionHostsInfo(_ hosts: OnionHosts) -> LocalizedStringKey {
|
||||
switch hosts {
|
||||
case .no: return "Onion hosts will not be used."
|
||||
case .prefer: return "Onion hosts will be used when available.\nRequires compatible VPN."
|
||||
case .require: return "Onion hosts will be **required** for connection.\nRequires compatible VPN."
|
||||
}
|
||||
}
|
||||
|
||||
private func sessionModeInfo(_ mode: TransportSessionMode) -> LocalizedStringKey {
|
||||
switch mode {
|
||||
case .user: return "A separate TCP connection will be used **for each chat profile you have in the app**."
|
||||
case .entity: return "A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail."
|
||||
}
|
||||
}
|
||||
|
||||
private func proxyModeInfo(_ mode: SMPProxyMode) -> LocalizedStringKey {
|
||||
switch mode {
|
||||
case .always: return "Always use private routing."
|
||||
case .unknown: return "Use private routing with unknown servers."
|
||||
case .unprotected: return "Use private routing with unknown servers when IP address is not protected."
|
||||
case .never: return "Do NOT use private routing."
|
||||
}
|
||||
}
|
||||
|
||||
private func proxyFallbackInfo(_ proxyFallback: SMPProxyFallback) -> LocalizedStringKey {
|
||||
switch proxyFallback {
|
||||
case .allow: return "Send messages directly when your or destination server does not support private routing."
|
||||
case .allowProtected: return "Send messages directly when IP address is protected and your or destination server does not support private routing."
|
||||
case .prohibit: return "Do NOT send messages directly, even if your or destination server does not support private routing."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AdvancedNetworkSettings_Previews: PreviewProvider {
|
||||
|
||||
@@ -28,7 +28,10 @@ extension AppSettings {
|
||||
privacyAcceptImagesGroupDefault.set(val)
|
||||
def.setValue(val, forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES)
|
||||
}
|
||||
if let val = privacyLinkPreviews { def.setValue(val, forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) }
|
||||
if let val = privacyLinkPreviews {
|
||||
privacyLinkPreviewsGroupDefault.set(val)
|
||||
def.setValue(val, forKey: DEFAULT_PRIVACY_LINK_PREVIEWS)
|
||||
}
|
||||
if let val = privacyShowChatPreviews { def.setValue(val, forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) }
|
||||
if let val = privacySaveLastDraft { def.setValue(val, forKey: DEFAULT_PRIVACY_SAVE_LAST_DRAFT) }
|
||||
if let val = privacyProtectScreen { def.setValue(val, forKey: DEFAULT_PRIVACY_PROTECT_SCREEN) }
|
||||
@@ -45,11 +48,15 @@ extension AppSettings {
|
||||
if let val = androidCallOnLockScreen { def.setValue(val.rawValue, forKey: ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN) }
|
||||
if let val = iosCallKitEnabled { callKitEnabledGroupDefault.set(val) }
|
||||
if let val = iosCallKitCallsInRecents { def.setValue(val, forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS) }
|
||||
if let val = uiProfileImageCornerRadius { def.setValue(val, forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) }
|
||||
if let val = uiProfileImageCornerRadius {
|
||||
profileImageCornerRadiusGroupDefault.set(val)
|
||||
def.setValue(val, forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS)
|
||||
}
|
||||
if let val = uiColorScheme { def.setValue(val, forKey: DEFAULT_CURRENT_THEME) }
|
||||
if let val = uiDarkColorScheme { def.setValue(val, forKey: DEFAULT_SYSTEM_DARK_THEME) }
|
||||
if let val = uiCurrentThemeIds { def.setValue(val, forKey: DEFAULT_CURRENT_THEME_IDS) }
|
||||
if let val = uiThemes { def.setValue(val.skipDuplicates(), forKey: DEFAULT_THEME_OVERRIDES) }
|
||||
if let val = oneHandUI { groupDefaults.setValue(val, forKey: GROUP_DEFAULT_ONE_HAND_UI) }
|
||||
}
|
||||
|
||||
public static var current: AppSettings {
|
||||
@@ -81,6 +88,7 @@ extension AppSettings {
|
||||
c.uiDarkColorScheme = systemDarkThemeDefault.get()
|
||||
c.uiCurrentThemeIds = currentThemeIdsDefault.get()
|
||||
c.uiThemes = themeOverridesDefault.get()
|
||||
c.oneHandUI = groupDefaults.bool(forKey: GROUP_DEFAULT_ONE_HAND_UI)
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ struct AppearanceSettings: View {
|
||||
}()
|
||||
@State private var darkModeTheme: String = UserDefaults.standard.string(forKey: DEFAULT_SYSTEM_DARK_THEME) ?? DefaultTheme.DARK.themeName
|
||||
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var profileImageCornerRadius = defaultProfileImageCorner
|
||||
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true
|
||||
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
|
||||
|
||||
@State var themeUserDestination: (Int64, ThemeModeOverrides?)? = {
|
||||
if let currentUser = ChatModel.shared.currentUser, let uiThemes = currentUser.uiThemes, uiThemes.preferredMode(!CurrentColors.colors.isLight) != nil {
|
||||
@@ -62,6 +64,16 @@ struct AppearanceSettings: View {
|
||||
}
|
||||
}
|
||||
|
||||
Section("Chat list") {
|
||||
Toggle("Reachable chat toolbar", isOn: $oneHandUI)
|
||||
Picker("Toolbar opacity", selection: $toolbarMaterial) {
|
||||
ForEach(ToolbarMaterial.allCases, id: \.rawValue) { tm in
|
||||
Text(tm.text).tag(tm.rawValue)
|
||||
}
|
||||
}
|
||||
.frame(height: 36)
|
||||
}
|
||||
|
||||
Section {
|
||||
ThemeDestinationPicker(themeUserDestination: $themeUserDestination, themeUserDest: themeUserDestination?.0, customizeThemeIsOpen: $customizeThemeIsOpen)
|
||||
|
||||
@@ -291,6 +303,43 @@ struct AppearanceSettings: View {
|
||||
}
|
||||
}
|
||||
|
||||
enum ToolbarMaterial: String, CaseIterable {
|
||||
case bar
|
||||
case ultraThin
|
||||
case thin
|
||||
case regular
|
||||
case thick
|
||||
case ultraThick
|
||||
|
||||
static func material(_ s: String) -> Material {
|
||||
ToolbarMaterial(rawValue: s)?.material ?? Material.bar
|
||||
}
|
||||
|
||||
static let defaultMaterial: String = ToolbarMaterial.regular.rawValue
|
||||
|
||||
var material: Material {
|
||||
switch self {
|
||||
case .bar: .bar
|
||||
case .ultraThin: .ultraThin
|
||||
case .thin: .thin
|
||||
case .regular: .regular
|
||||
case .thick: .thick
|
||||
case .ultraThick: .ultraThick
|
||||
}
|
||||
}
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case .bar: "System"
|
||||
case .ultraThin: "Ultra thin"
|
||||
case .thin: "Thin"
|
||||
case .regular: "Regular"
|
||||
case .thick: "Thick"
|
||||
case .ultraThick: "Ultra thick"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatThemePreview: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var base: DefaultTheme
|
||||
|
||||
@@ -13,6 +13,8 @@ struct DeveloperView: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@AppStorage(GROUP_DEFAULT_CONFIRM_DB_UPGRADES, store: groupDefaults) private var confirmDatabaseUpgrades = false
|
||||
@State private var hintsUnchanged = hintDefaultsUnchanged()
|
||||
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
var body: some View {
|
||||
@@ -33,8 +35,9 @@ struct DeveloperView: View {
|
||||
} label: {
|
||||
settingsRow("terminal", color: theme.colors.secondary) { Text("Chat console") }
|
||||
}
|
||||
settingsRow("internaldrive", color: theme.colors.secondary) {
|
||||
Toggle("Confirm database upgrades", isOn: $confirmDatabaseUpgrades)
|
||||
settingsRow("lightbulb.max", color: theme.colors.secondary) {
|
||||
Button("Reset all hints", action: resetHintDefaults)
|
||||
.disabled(hintsUnchanged)
|
||||
}
|
||||
settingsRow("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) {
|
||||
Toggle("Show developer options", isOn: $developerTools)
|
||||
@@ -45,9 +48,34 @@ struct DeveloperView: View {
|
||||
((developerTools ? Text("Show:") : Text("Hide:")) + Text(" ") + Text("Database IDs and Transport isolation option."))
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
|
||||
if developerTools {
|
||||
Section {
|
||||
settingsRow("internaldrive", color: theme.colors.secondary) {
|
||||
Toggle("Confirm database upgrades", isOn: $confirmDatabaseUpgrades)
|
||||
}
|
||||
} header: {
|
||||
Text("Developer options")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func resetHintDefaults() {
|
||||
for def in hintDefaults {
|
||||
if let val = appDefaults[def] as? Bool {
|
||||
UserDefaults.standard.set(val, forKey: def)
|
||||
}
|
||||
}
|
||||
hintsUnchanged = true
|
||||
}
|
||||
}
|
||||
|
||||
private func hintDefaultsUnchanged() -> Bool {
|
||||
hintDefaults.allSatisfy { def in
|
||||
appDefaults[def] as? Bool == UserDefaults.standard.bool(forKey: def)
|
||||
}
|
||||
}
|
||||
|
||||
struct DeveloperView_Previews: PreviewProvider {
|
||||
|
||||
@@ -10,18 +10,10 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
private enum NetworkAlert: Identifiable {
|
||||
case updateOnionHosts(hosts: OnionHosts)
|
||||
case updateSessionMode(mode: TransportSessionMode)
|
||||
case updateSMPProxyMode(proxyMode: SMPProxyMode)
|
||||
case updateSMPProxyFallback(proxyFallback: SMPProxyFallback)
|
||||
case error(err: String)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .updateOnionHosts(hosts): return "updateOnionHosts \(hosts)"
|
||||
case let .updateSessionMode(mode): return "updateSessionMode \(mode)"
|
||||
case let .updateSMPProxyMode(proxyMode): return "updateSMPProxyMode \(proxyMode)"
|
||||
case let .updateSMPProxyFallback(proxyFallback): return "updateSMPProxyFallback \(proxyFallback)"
|
||||
case let .error(err): return "error \(err)"
|
||||
}
|
||||
}
|
||||
@@ -30,16 +22,6 @@ private enum NetworkAlert: Identifiable {
|
||||
struct NetworkAndServers: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false
|
||||
@State private var cfgLoaded = false
|
||||
@State private var currentNetCfg = NetCfg.defaults
|
||||
@State private var netCfg = NetCfg.defaults
|
||||
@State private var onionHosts: OnionHosts = .no
|
||||
@State private var sessionMode: TransportSessionMode = .user
|
||||
@State private var proxyMode: SMPProxyMode = .never
|
||||
@State private var proxyFallback: SMPProxyFallback = .allow
|
||||
@State private var alert: NetworkAlert?
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
@@ -50,7 +32,7 @@ struct NetworkAndServers: View {
|
||||
.navigationTitle("Your SMP servers")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
} label: {
|
||||
Text("SMP servers")
|
||||
Text("Message servers")
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
@@ -58,24 +40,12 @@ struct NetworkAndServers: View {
|
||||
.navigationTitle("Your XFTP servers")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
} label: {
|
||||
Text("XFTP servers")
|
||||
}
|
||||
|
||||
Picker("Use .onion hosts", selection: $onionHosts) {
|
||||
ForEach(OnionHosts.values, id: \.self) { Text($0.text) }
|
||||
}
|
||||
.frame(height: 36)
|
||||
|
||||
if developerTools {
|
||||
Picker("Transport isolation", selection: $sessionMode) {
|
||||
ForEach(TransportSessionMode.values, id: \.self) { Text($0.text) }
|
||||
}
|
||||
.frame(height: 36)
|
||||
Text("Media & file servers")
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
AdvancedNetworkSettings()
|
||||
.navigationTitle("Network settings")
|
||||
.navigationTitle("Advanced settings")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
} label: {
|
||||
Text("Advanced network settings")
|
||||
@@ -83,35 +53,6 @@ struct NetworkAndServers: View {
|
||||
} header: {
|
||||
Text("Messages & files")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
} footer: {
|
||||
Text("Using .onion hosts requires compatible VPN provider.")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker("Private routing", selection: $proxyMode) {
|
||||
ForEach(SMPProxyMode.values, id: \.self) { Text($0.text) }
|
||||
}
|
||||
.frame(height: 36)
|
||||
|
||||
Picker("Allow downgrade", selection: $proxyFallback) {
|
||||
ForEach(SMPProxyFallback.values, id: \.self) { Text($0.text) }
|
||||
}
|
||||
.disabled(proxyMode == .never)
|
||||
.frame(height: 36)
|
||||
|
||||
Toggle("Show message status", isOn: $showSentViaProxy)
|
||||
} header: {
|
||||
Text("Private message routing")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
} footer: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("To protect your IP address, private routing uses your SMP servers to deliver messages.")
|
||||
if showSentViaProxy {
|
||||
Text("Show → on messages sent via private routing.")
|
||||
}
|
||||
}
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
|
||||
Section(header: Text("Calls").foregroundColor(theme.colors.secondary)) {
|
||||
@@ -133,147 +74,6 @@ struct NetworkAndServers: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if cfgLoaded { return }
|
||||
cfgLoaded = true
|
||||
currentNetCfg = getNetCfg()
|
||||
resetNetCfgView()
|
||||
}
|
||||
.onChange(of: onionHosts) { hosts in
|
||||
if hosts != OnionHosts(netCfg: currentNetCfg) {
|
||||
alert = .updateOnionHosts(hosts: hosts)
|
||||
}
|
||||
}
|
||||
.onChange(of: sessionMode) { mode in
|
||||
if mode != netCfg.sessionMode {
|
||||
alert = .updateSessionMode(mode: mode)
|
||||
}
|
||||
}
|
||||
.onChange(of: proxyMode) { mode in
|
||||
if mode != netCfg.smpProxyMode {
|
||||
alert = .updateSMPProxyMode(proxyMode: mode)
|
||||
}
|
||||
}
|
||||
.onChange(of: proxyFallback) { fallbackMode in
|
||||
if fallbackMode != netCfg.smpProxyFallback {
|
||||
alert = .updateSMPProxyFallback(proxyFallback: fallbackMode)
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { a in
|
||||
switch a {
|
||||
case let .updateOnionHosts(hosts):
|
||||
return Alert(
|
||||
title: Text("Update .onion hosts setting?"),
|
||||
message: Text(onionHostsInfo(hosts)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
let (hostMode, requiredHostMode) = hosts.hostMode
|
||||
netCfg.hostMode = hostMode
|
||||
netCfg.requiredHostMode = requiredHostMode
|
||||
saveNetCfg()
|
||||
},
|
||||
secondaryButton: .cancel() {
|
||||
resetNetCfgView()
|
||||
}
|
||||
)
|
||||
case let .updateSessionMode(mode):
|
||||
return Alert(
|
||||
title: Text("Update transport isolation mode?"),
|
||||
message: Text(sessionModeInfo(mode)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
netCfg.sessionMode = mode
|
||||
saveNetCfg()
|
||||
},
|
||||
secondaryButton: .cancel() {
|
||||
resetNetCfgView()
|
||||
}
|
||||
)
|
||||
case let .updateSMPProxyMode(proxyMode):
|
||||
return Alert(
|
||||
title: Text("Message routing mode"),
|
||||
message: Text(proxyModeInfo(proxyMode)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
netCfg.smpProxyMode = proxyMode
|
||||
saveNetCfg()
|
||||
},
|
||||
secondaryButton: .cancel() {
|
||||
resetNetCfgView()
|
||||
}
|
||||
)
|
||||
case let .updateSMPProxyFallback(proxyFallback):
|
||||
return Alert(
|
||||
title: Text("Message routing fallback"),
|
||||
message: Text(proxyFallbackInfo(proxyFallback)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
netCfg.smpProxyFallback = proxyFallback
|
||||
saveNetCfg()
|
||||
},
|
||||
secondaryButton: .cancel() {
|
||||
resetNetCfgView()
|
||||
}
|
||||
)
|
||||
case let .error(err):
|
||||
return Alert(
|
||||
title: Text("Error updating settings"),
|
||||
message: Text(err)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func saveNetCfg() {
|
||||
do {
|
||||
let def = netCfg.hostMode == .onionHost ? NetCfg.proxyDefaults : NetCfg.defaults
|
||||
netCfg.tcpConnectTimeout = def.tcpConnectTimeout
|
||||
netCfg.tcpTimeout = def.tcpTimeout
|
||||
try setNetworkConfig(netCfg)
|
||||
currentNetCfg = netCfg
|
||||
setNetCfg(netCfg)
|
||||
} catch let error {
|
||||
let err = responseError(error)
|
||||
resetNetCfgView()
|
||||
alert = .error(err: err)
|
||||
logger.error("\(err)")
|
||||
}
|
||||
}
|
||||
|
||||
private func resetNetCfgView() {
|
||||
netCfg = currentNetCfg
|
||||
onionHosts = OnionHosts(netCfg: netCfg)
|
||||
sessionMode = netCfg.sessionMode
|
||||
proxyMode = netCfg.smpProxyMode
|
||||
proxyFallback = netCfg.smpProxyFallback
|
||||
}
|
||||
|
||||
private func onionHostsInfo(_ hosts: OnionHosts) -> LocalizedStringKey {
|
||||
switch hosts {
|
||||
case .no: return "Onion hosts will not be used."
|
||||
case .prefer: return "Onion hosts will be used when available. Requires enabling VPN."
|
||||
case .require: return "Onion hosts will be required for connection. Requires enabling VPN."
|
||||
}
|
||||
}
|
||||
|
||||
private func sessionModeInfo(_ mode: TransportSessionMode) -> LocalizedStringKey {
|
||||
switch mode {
|
||||
case .user: return "A separate TCP connection will be used **for each chat profile you have in the app**."
|
||||
case .entity: return "A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail."
|
||||
}
|
||||
}
|
||||
|
||||
private func proxyModeInfo(_ mode: SMPProxyMode) -> LocalizedStringKey {
|
||||
switch mode {
|
||||
case .always: return "Always use private routing."
|
||||
case .unknown: return "Use private routing with unknown servers."
|
||||
case .unprotected: return "Use private routing with unknown servers when IP address is not protected."
|
||||
case .never: return "Do NOT use private routing."
|
||||
}
|
||||
}
|
||||
|
||||
private func proxyFallbackInfo(_ proxyFallback: SMPProxyFallback) -> LocalizedStringKey {
|
||||
switch proxyFallback {
|
||||
case .allow: return "Send messages directly when your or destination server does not support private routing."
|
||||
case .allowProtected: return "Send messages directly when IP address is protected and your or destination server does not support private routing."
|
||||
case .prohibit: return "Do NOT send messages directly, even if your or destination server does not support private routing."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ struct PrivacySettings: View {
|
||||
privacyAcceptImagesGroupDefault.set($0)
|
||||
}
|
||||
}
|
||||
settingsRow("circle.rectangle.filled.pattern.diagonalline", color: theme.colors.secondary) {
|
||||
settingsRow("circle.filled.pattern.diagonalline.rectangle", color: theme.colors.secondary) {
|
||||
Picker("Blur media", selection: $privacyMediaBlurRadius) {
|
||||
let values = [0, 12, 24, 48] + ([0, 12, 24, 48].contains(privacyMediaBlurRadius) ? [] : [privacyMediaBlurRadius])
|
||||
ForEach(values, id: \.self) { radius in
|
||||
@@ -470,7 +470,7 @@ struct SimplexLockView: View {
|
||||
}
|
||||
}
|
||||
.onChange(of: performLA) { performLAToggle in
|
||||
performLAGroupDefault.set(performLAToggle)
|
||||
appLocalAuthEnabledGroupDefault.set(performLAToggle)
|
||||
prefLANoticeShown = true
|
||||
if performLAToggleReset {
|
||||
performLAToggleReset = false
|
||||
|
||||
@@ -130,7 +130,7 @@ struct ProtocolServersView: View {
|
||||
showSaveDialog = true
|
||||
}
|
||||
})
|
||||
.confirmationDialog("Save servers?", isPresented: $showSaveDialog) {
|
||||
.confirmationDialog("Save servers?", isPresented: $showSaveDialog, titleVisibility: .visible) {
|
||||
Button("Save") {
|
||||
saveServers()
|
||||
dismiss()
|
||||
|
||||
@@ -47,6 +47,8 @@ let DEFAULT_ACCENT_COLOR_GREEN = "accentColorGreen" // deprecated, only used for
|
||||
let DEFAULT_ACCENT_COLOR_BLUE = "accentColorBlue" // deprecated, only used for migration
|
||||
let DEFAULT_USER_INTERFACE_STYLE = "userInterfaceStyle" // deprecated, only used for migration
|
||||
let DEFAULT_PROFILE_IMAGE_CORNER_RADIUS = "profileImageCornerRadius"
|
||||
let DEFAULT_ONE_HAND_UI_CARD_SHOWN = "oneHandUICardShown"
|
||||
let DEFAULT_TOOLBAR_MATERIAL = "toolbarMaterial"
|
||||
let DEFAULT_CONNECT_VIA_LINK_TAB = "connectViaLinkTab"
|
||||
let DEFAULT_LIVE_MESSAGE_ALERT_SHOWN = "liveMessageAlertShown"
|
||||
let DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE = "showHiddenProfilesNotice"
|
||||
@@ -61,6 +63,8 @@ let DEFAULT_DEVICE_NAME_FOR_REMOTE_ACCESS = "deviceNameForRemoteAccess"
|
||||
let DEFAULT_CONFIRM_REMOTE_SESSIONS = "confirmRemoteSessions"
|
||||
let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST = "connectRemoteViaMulticast"
|
||||
let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "connectRemoteViaMulticastAuto"
|
||||
let DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE = "showDeleteConversationNotice"
|
||||
let DEFAULT_SHOW_DELETE_CONTACT_NOTICE = "showDeleteContactNotice"
|
||||
let DEFAULT_SHOW_SENT_VIA_RPOXY = "showSentViaProxy"
|
||||
let DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE = "showSubscriptionPercentage"
|
||||
|
||||
@@ -94,6 +98,8 @@ let appDefaults: [String: Any] = [
|
||||
DEFAULT_DEVELOPER_TOOLS: false,
|
||||
DEFAULT_ENCRYPTION_STARTED: false,
|
||||
DEFAULT_PROFILE_IMAGE_CORNER_RADIUS: defaultProfileImageCorner,
|
||||
DEFAULT_ONE_HAND_UI_CARD_SHOWN: false,
|
||||
DEFAULT_TOOLBAR_MATERIAL: ToolbarMaterial.defaultMaterial,
|
||||
DEFAULT_CONNECT_VIA_LINK_TAB: ConnectViaLinkTab.scan.rawValue,
|
||||
DEFAULT_LIVE_MESSAGE_ALERT_SHOWN: false,
|
||||
DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE: true,
|
||||
@@ -104,6 +110,8 @@ let appDefaults: [String: Any] = [
|
||||
DEFAULT_CONFIRM_REMOTE_SESSIONS: false,
|
||||
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST: true,
|
||||
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO: true,
|
||||
DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE: true,
|
||||
DEFAULT_SHOW_DELETE_CONTACT_NOTICE: true,
|
||||
DEFAULT_SHOW_SENT_VIA_RPOXY: false,
|
||||
DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE: false,
|
||||
ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN: AppSettingsLockScreenCalls.show.rawValue,
|
||||
@@ -114,6 +122,18 @@ let appDefaults: [String: Any] = [
|
||||
DEFAULT_CURRENT_THEME_IDS: "{}"
|
||||
]
|
||||
|
||||
// only Bool defaults can be used here,
|
||||
// or hintDefaultsUnchanged and resetHintDefaults need to be changed
|
||||
let hintDefaults = [
|
||||
DEFAULT_LA_NOTICE_SHOWN,
|
||||
DEFAULT_ONE_HAND_UI_CARD_SHOWN,
|
||||
DEFAULT_LIVE_MESSAGE_ALERT_SHOWN,
|
||||
DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE,
|
||||
DEFAULT_SHOW_MUTE_PROFILE_ALERT,
|
||||
DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE,
|
||||
DEFAULT_SHOW_DELETE_CONTACT_NOTICE
|
||||
]
|
||||
|
||||
// not used anymore
|
||||
enum ConnectViaLinkTab: String {
|
||||
case scan
|
||||
@@ -158,6 +178,9 @@ let onboardingStageDefault = EnumDefault<OnboardingStage>(defaults: UserDefaults
|
||||
|
||||
let customDisappearingMessageTimeDefault = IntDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CUSTOM_DISAPPEARING_MESSAGE_TIME)
|
||||
|
||||
let showDeleteConversationNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE)
|
||||
let showDeleteContactNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONTACT_NOTICE)
|
||||
|
||||
let currentThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME, withDefault: DefaultTheme.SYSTEM_THEME_NAME)
|
||||
let systemDarkThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SYSTEM_DARK_THEME, withDefault: DefaultTheme.DARK.themeName)
|
||||
let currentThemeIdsDefault = CodableDefault<[String: String]>(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME_IDS, withDefault: [:] )
|
||||
@@ -165,7 +188,7 @@ let themeOverridesDefault: CodableDefault<[ThemeOverrides]> = CodableDefault(def
|
||||
|
||||
func setGroupDefaults() {
|
||||
privacyAcceptImagesGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES))
|
||||
performLAGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA))
|
||||
appLocalAuthEnabledGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA))
|
||||
privacyLinkPreviewsGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS))
|
||||
profileImageCornerRadiusGroupDefault.set(UserDefaults.standard.double(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS))
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="!1 colored!" xml:space="preserve" approved="no">
|
||||
<source>!1 colored!</source>
|
||||
<target state="translated">! 1 ملون!</target>
|
||||
<target state="translated">! 1 مُلوَّن!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="#secret#" xml:space="preserve" approved="no">
|
||||
@@ -69,7 +69,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is not verified" xml:space="preserve" approved="no">
|
||||
<source>%@ is not verified</source>
|
||||
<target state="translated">%@ لم يتم التحقق منها</target>
|
||||
<target state="translated">%@ لم يتم التحقق منه</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is verified" xml:space="preserve" approved="no">
|
||||
@@ -107,8 +107,9 @@
|
||||
<target state="translated">%d ثانية</target>
|
||||
<note>message ttl</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d skipped message(s)" xml:space="preserve">
|
||||
<trans-unit id="%d skipped message(s)" xml:space="preserve" approved="no">
|
||||
<source>%d skipped message(s)</source>
|
||||
<target state="translated">%d الرسائل المتخطية</target>
|
||||
<note>integrity error chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld" xml:space="preserve" approved="no">
|
||||
@@ -121,12 +122,14 @@
|
||||
<target state="needs-translation">%lld %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld contact(s) selected" xml:space="preserve">
|
||||
<trans-unit id="%lld contact(s) selected" xml:space="preserve" approved="no">
|
||||
<source>%lld contact(s) selected</source>
|
||||
<target state="translated">%lld تم اختيار جهات الاتصال</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld file(s) with total size of %@" xml:space="preserve">
|
||||
<trans-unit id="%lld file(s) with total size of %@" xml:space="preserve" approved="no">
|
||||
<source>%lld file(s) with total size of %@</source>
|
||||
<target state="translated">%lld الملفات ذات الحجم الإجمالي %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld members" xml:space="preserve" approved="no">
|
||||
@@ -134,8 +137,9 @@
|
||||
<target state="translated">%lld أعضاء</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld second(s)" xml:space="preserve">
|
||||
<trans-unit id="%lld second(s)" xml:space="preserve" approved="no">
|
||||
<source>%lld second(s)</source>
|
||||
<target state="translated">%lld ثوانى</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lldd" xml:space="preserve" approved="no">
|
||||
@@ -185,7 +189,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve" approved="no">
|
||||
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
|
||||
<target state="translated">** إضافة جهة اتصال جديدة **: لإنشاء رمز QR لمرة واحدة أو رابط جهة الاتصال الخاصة بك.</target>
|
||||
<target state="translated">** إضافة جهة اتصال جديدة **: لإنشاء رمز QR لمرة واحدة أو رابط جهة الاتصال الخاصة بكم.</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" approved="no">
|
||||
@@ -195,12 +199,12 @@
|
||||
</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">
|
||||
<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">** المزيد من الخصوصية **: تحقق من الرسائل الجديدة كل 20 دقيقة. تتم مشاركة رمز الجهاز مع خادم SimpleX Chat ، ولكن ليس عدد جهات الاتصال أو الرسائل لديك.</target>
|
||||
<target state="translated">** المزيد من الخصوصية **: تحققوا من الرسائل الجديدة كل 20 دقيقة. تتم مشاركة رمز الجهاز مع خادم SimpleX Chat ، ولكن ليس عدد جهات الاتصال أو الرسائل لديكم.</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">
|
||||
<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">** الأكثر خصوصية **: لا تستخدم خادم إشعارات SimpleX Chat ، وتحقق من الرسائل بشكل دوري في الخلفية (يعتمد على عدد مرات استخدامك للتطبيق).</target>
|
||||
<target state="translated">** الأكثر خصوصية **: لا تستخدم خادم إشعارات SimpleX Chat ، وتحقق من الرسائل بشكل دوري في الخلفية (يعتمد على عدد مرات استخدامكم للتطبيق).</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" approved="no">
|
||||
@@ -210,7 +214,7 @@
|
||||
</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">
|
||||
<source>**Please note**: you will NOT be able to recover or change passphrase if you lose it.</source>
|
||||
<target state="translated">** يرجى ملاحظة **: لن تتمكن من استعادة أو تغيير عبارة المرور إذا فقدتها.</target>
|
||||
<target state="translated">** يرجى ملاحظة **: لن تتمكنوا من استعادة أو تغيير عبارة المرور إذا فقدتموها.</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">
|
||||
@@ -305,7 +309,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="A separate TCP connection will be used **for each chat profile you have in the app**." xml:space="preserve" approved="no">
|
||||
<source>A separate TCP connection will be used **for each chat profile you have in the app**.</source>
|
||||
<target state="translated">سيتم استخدام اتصال TCP منفصل ** لكل ملف تعريف دردشة لديك في التطبيق **.</target>
|
||||
<target state="translated">سيتم استخدام اتصال TCP منفصل ** لكل ملف تعريف دردشة لديكم في التطبيق **.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="A separate TCP connection will be used **for each contact and group member**. **Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." xml:space="preserve" approved="no">
|
||||
@@ -355,24 +359,29 @@
|
||||
<source>Accept requests</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add preset servers" xml:space="preserve">
|
||||
<trans-unit id="Add preset servers" xml:space="preserve" approved="no">
|
||||
<source>Add preset servers</source>
|
||||
<target state="translated">إضافة خوادم محددة مسبقا</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add profile" xml:space="preserve">
|
||||
<trans-unit id="Add profile" xml:space="preserve" approved="no">
|
||||
<source>Add profile</source>
|
||||
<target state="translated">إضافة الملف الشخصي</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add servers by scanning QR codes." xml:space="preserve">
|
||||
<trans-unit id="Add servers by scanning QR codes." xml:space="preserve" approved="no">
|
||||
<source>Add servers by scanning QR codes.</source>
|
||||
<target state="translated">إضافة خوادم عن طريق مسح رموز QR.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add server" xml:space="preserve">
|
||||
<trans-unit id="Add server" xml:space="preserve" approved="no">
|
||||
<source>Add server</source>
|
||||
<target state="translated">أضف الخادم</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add to another device" xml:space="preserve">
|
||||
<trans-unit id="Add to another device" xml:space="preserve" approved="no">
|
||||
<source>Add to another device</source>
|
||||
<target state="translated">أضف إلى جهاز آخر</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Admins can create the links to join groups." xml:space="preserve">
|
||||
@@ -3667,7 +3676,7 @@ SimpleX servers cannot see your profile.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="## In reply to" xml:space="preserve" approved="no">
|
||||
<source>## In reply to</source>
|
||||
<target state="translated">## ردًا على</target>
|
||||
<target state="translated">## ردًّا على</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@ connected" xml:space="preserve" approved="no">
|
||||
@@ -3675,6 +3684,208 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target state="translated">%@ و %@ متصل</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ downloaded" xml:space="preserve" approved="no">
|
||||
<source>%@ downloaded</source>
|
||||
<target state="translated">%@ تم التنزيل</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@" xml:space="preserve" approved="no">
|
||||
<source>%@ and %@</source>
|
||||
<target state="translated">%@ و %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ connected" xml:space="preserve" approved="no">
|
||||
<source>%@ connected</source>
|
||||
<target state="translated">%@ متصل</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld minutes" xml:space="preserve" approved="no">
|
||||
<source>%lld minutes</source>
|
||||
<target state="translated">%lld دقائق</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld members" xml:space="preserve" approved="no">
|
||||
<source>%@, %@ and %lld members</source>
|
||||
<target state="translated">%@, %@ و %lld أعضاء</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d weeks" xml:space="preserve" approved="no">
|
||||
<source>%d weeks</source>
|
||||
<target state="translated">%d أسابيع</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ uploaded" xml:space="preserve" approved="no">
|
||||
<source>%@ uploaded</source>
|
||||
<target state="translated">%@ تم الرفع</target>
|
||||
</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">%@, %@ و %lld أعضاء آخرين متصلين</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld seconds" xml:space="preserve" approved="no">
|
||||
<source>%lld seconds</source>
|
||||
<target state="translated">%lld ثواني</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%u messages failed to decrypt." xml:space="preserve" approved="no">
|
||||
<source>%u messages failed to decrypt.</source>
|
||||
<target state="translated">%u فشلت عملية فك تشفير الرسائل.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages marked deleted" xml:space="preserve" approved="no">
|
||||
<source>%lld messages marked deleted</source>
|
||||
<target state="translated">%lld الرسائل معلمه بالحذف</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages moderated by %@" xml:space="preserve" approved="no">
|
||||
<source>%lld messages moderated by %@</source>
|
||||
<target state="translated">%lld رسائل تمت إدارتها بواسطة %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld new interface languages" xml:space="preserve" approved="no">
|
||||
<source>%lld new interface languages</source>
|
||||
<target state="translated">%lld لغات واجهة جديدة</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld group events" xml:space="preserve" approved="no">
|
||||
<source>%lld group events</source>
|
||||
<target state="translated">%lld أحداث المجموعة</target>
|
||||
</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 رسائل محظورة بواسطه المسؤول</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages blocked" xml:space="preserve" approved="no">
|
||||
<source>%lld messages blocked</source>
|
||||
<target state="translated">%lld رسائل تم حظرها</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="%u messages skipped." xml:space="preserve" approved="no">
|
||||
<source>%u messages skipped.</source>
|
||||
<target state="translated">%u تم تخطي الرسائل.</target>
|
||||
</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">**إضافة جهة اتصال**: لإنشاء رابط دعوة جديد، أو الاتصال عبر الرابط الذي تلقيتوهم.</target>
|
||||
</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">**إنشاء مجموعة**: لإنشاء مجموعة جديدة.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="(this device v%@)" xml:space="preserve" approved="no">
|
||||
<source>(this device v%@)</source>
|
||||
<target state="translated">(هذا الجهاز v%@)</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="(new)" xml:space="preserve" approved="no">
|
||||
<source>(new)</source>
|
||||
<target state="translated">(جديد)</target>
|
||||
</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">**يرجى الملاحظة**: سيؤدي استخدام نفس قاعدة البيانات على جهازين إلى كسر فك تشفير الرسائل من اتصالاتكم كحماية أمنية.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="A new random profile will be shared." xml:space="preserve" approved="no">
|
||||
<source>A new random profile will be shared.</source>
|
||||
<target state="translated">سيتم مشاركة ملف تعريفي عشوائي جديد.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="30 seconds" xml:space="preserve" approved="no">
|
||||
<source>30 seconds</source>
|
||||
<target state="translated">30 ثانيه</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="- more stable message delivery. - a bit better groups. - and more!" xml:space="preserve" approved="no">
|
||||
<source>- more stable message delivery.
|
||||
- a bit better groups.
|
||||
- and more!</source>
|
||||
<target state="translated">- تسليم رسائل أكثر استقرارًا.
|
||||
- مجموعات أفضل قليلاً.
|
||||
- والمزيد!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="0 sec" xml:space="preserve" approved="no">
|
||||
<source>0 sec</source>
|
||||
<target state="translated">0 ثانيه</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1 minute" xml:space="preserve" approved="no">
|
||||
<source>1 minute</source>
|
||||
<target state="translated">1 دقيقة</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5 minutes" xml:space="preserve" approved="no">
|
||||
<source>5 minutes</source>
|
||||
<target state="translated">5 دقائق</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="<p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p>" xml:space="preserve" approved="no">
|
||||
<source><p>Hi!</p>
|
||||
<p><a href="%@">Connect to me via SimpleX Chat</a></p></source>
|
||||
<target state="translated"><p>مرحبا!</p>
|
||||
<p><a href="%@">أتصل بى من خلال SimpleX Chat</a></p></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="0s" xml:space="preserve" approved="no">
|
||||
<source>0s</source>
|
||||
<target state="translated">0 ث</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="A few more things" xml:space="preserve" approved="no">
|
||||
<source>A few more things</source>
|
||||
<target state="translated">بعض الأشياء الأخرى</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="- connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)! - delivery receipts (up to 20 members). - faster and more stable." xml:space="preserve" approved="no">
|
||||
<source>- connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
|
||||
- delivery receipts (up to 20 members).
|
||||
- faster and more stable.</source>
|
||||
<target state="translated">- أتصل بـ [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!
|
||||
- delivery receipts (up to 20 members).
|
||||
- أسرع و أكثر اسْتِقْرارًا.</target>
|
||||
</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">**تحذير**: سيتم إزالة الأرشيف.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="- optionally notify deleted contacts. - profile names with spaces. - and more!" xml:space="preserve" approved="no">
|
||||
<source>- optionally notify deleted contacts.
|
||||
- profile names with spaces.
|
||||
- and more!</source>
|
||||
<target state="translated">- إخطار جهات الاتصال المحذوفة بشكل اختياري.
|
||||
- أسماء الملفات الشخصية مع المسافات.
|
||||
- والمزيد!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="- voice messages up to 5 minutes. - custom time to disappear. - editing history." xml:space="preserve" approved="no">
|
||||
<source>- voice messages up to 5 minutes.
|
||||
- custom time to disappear.
|
||||
- editing history.</source>
|
||||
<target state="translated">- رسائل صوتية تصل مدتها إلى 5 دقائق.
|
||||
- وقت مخصص للاختفاء.
|
||||
- تعديل السجل.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add welcome message" xml:space="preserve" approved="no">
|
||||
<source>Add welcome message</source>
|
||||
<target state="translated">إضافة رسالة ترحيب</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Abort changing address?" xml:space="preserve" approved="no">
|
||||
<source>Abort changing address?</source>
|
||||
<target state="translated">هل تريد إلغاء تغيير العنوان؟</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add contact" xml:space="preserve" approved="no">
|
||||
<source>Add contact</source>
|
||||
<target state="translated">إضافة جهة اتصال</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Abort" xml:space="preserve" approved="no">
|
||||
<source>Abort</source>
|
||||
<target state="translated">إحباط</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="About SimpleX address" xml:space="preserve" approved="no">
|
||||
<source>About SimpleX address</source>
|
||||
<target state="translated">حول عنوان SimpleX</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Accept connection request?" xml:space="preserve" approved="no">
|
||||
<source>Accept connection request?</source>
|
||||
<target state="translated">قبول طلب الاتصال؟</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Acknowledged" xml:space="preserve" approved="no">
|
||||
<source>Acknowledged</source>
|
||||
<target state="translated">معترف به</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Acknowledgement errors" xml:space="preserve" approved="no">
|
||||
<source>Acknowledgement errors</source>
|
||||
<target state="translated">أخطاء الإقرار</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." xml:space="preserve" approved="no">
|
||||
<source>Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts.</source>
|
||||
<target state="translated">أضف عنوانًا إلى ملفكم الشخصي، حتى تتمكن جهات الاتصال الخاصة بكم من مشاركته مع أشخاص اخرين. سيتم إرسال تحديث الملف الشخصي إلى جهات الاتصال الخاصة بكم.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Abort changing address" xml:space="preserve" approved="no">
|
||||
<source>Abort changing address</source>
|
||||
<target state="translated">إحباط تغيير العنوان</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Active connections" xml:space="preserve" approved="no">
|
||||
<source>Active connections</source>
|
||||
<target state="translated">اتصالات نشطة</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="ar" datatype="plaintext">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5316,6 +5316,278 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target state="translated">%@ ו-%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect automatically" xml:space="preserve" approved="no">
|
||||
<source>Connect automatically</source>
|
||||
<target state="translated">התבר אוטומטי</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create profile" xml:space="preserve" approved="no">
|
||||
<source>Create profile</source>
|
||||
<target state="translated">צור פרופיל</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created at: %@" xml:space="preserve" approved="no">
|
||||
<source>Created at: %@</source>
|
||||
<target state="translated">נוצר ב:%@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop devices" xml:space="preserve" approved="no">
|
||||
<source>Desktop devices</source>
|
||||
<target state="translated">מכשירי מחשב</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Discover via local network" xml:space="preserve" approved="no">
|
||||
<source>Discover via local network</source>
|
||||
<target state="translated">גלה באמצעות הרשת המקומית</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forward" xml:space="preserve" approved="no">
|
||||
<source>Forward</source>
|
||||
<target state="translated">העבר</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group already exists" xml:space="preserve" approved="no">
|
||||
<source>Group already exists</source>
|
||||
<target state="translated">קבוצה כבר קיימת</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected to desktop" xml:space="preserve" approved="no">
|
||||
<source>Connected to desktop</source>
|
||||
<target state="translated">מחובר למחשב</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group already exists!" xml:space="preserve" approved="no">
|
||||
<source>Group already exists!</source>
|
||||
<target state="translated">קבוצה כבר קיימת!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Confirm upload" xml:space="preserve" approved="no">
|
||||
<source>Confirm upload</source>
|
||||
<target state="translated">אשר ההעלאה</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block for all" xml:space="preserve" approved="no">
|
||||
<source>Block for all</source>
|
||||
<target state="translated">חסום לכולם</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blocked by admin" xml:space="preserve" approved="no">
|
||||
<source>Blocked by admin</source>
|
||||
<target state="translated">נחסם ע"י מנהל</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block member for all?" xml:space="preserve" approved="no">
|
||||
<source>Block member for all?</source>
|
||||
<target state="translated">לחסום את החבר לכולם?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Camera not available" xml:space="preserve" approved="no">
|
||||
<source>Camera not available</source>
|
||||
<target state="translated">מצלמה לא זמינה</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to desktop" xml:space="preserve" approved="no">
|
||||
<source>Connect to desktop</source>
|
||||
<target state="translated">חבר למחשב</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created at" xml:space="preserve" approved="no">
|
||||
<source>Created at</source>
|
||||
<target state="translated">נוצר ב</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="(new)" xml:space="preserve" approved="no">
|
||||
<source>(new)</source>
|
||||
<target state="translated">(חדש)</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block member" xml:space="preserve" approved="no">
|
||||
<source>Block member</source>
|
||||
<target state="translated">חבר חסום</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block member?" xml:space="preserve" approved="no">
|
||||
<source>Block member?</source>
|
||||
<target state="translated">לחסום חבר?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Creating link…" xml:space="preserve" approved="no">
|
||||
<source>Creating link…</source>
|
||||
<target state="translated">יוצר קישור…</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Files" xml:space="preserve" approved="no">
|
||||
<source>Files</source>
|
||||
<target state="translated">קבצים</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disabled" xml:space="preserve" approved="no">
|
||||
<source>Disabled</source>
|
||||
<target state="translated">מושבת</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter passphrase" xml:space="preserve" approved="no">
|
||||
<source>Enter passphrase</source>
|
||||
<target state="translated">הכנס סיסמא</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Apply" xml:space="preserve" approved="no">
|
||||
<source>Apply</source>
|
||||
<target state="translated">החל</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Apply to" xml:space="preserve" approved="no">
|
||||
<source>Apply to</source>
|
||||
<target state="translated">החל ל</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Background" xml:space="preserve" approved="no">
|
||||
<source>Background</source>
|
||||
<target state="translated">ברקע</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Black" xml:space="preserve" approved="no">
|
||||
<source>Black</source>
|
||||
<target state="translated">שחור</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blur media" xml:space="preserve" approved="no">
|
||||
<source>Blur media</source>
|
||||
<target state="translated">טשטש מדיה</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat theme" xml:space="preserve" approved="no">
|
||||
<source>Chat theme</source>
|
||||
<target state="translated">צבע ערכת נושא</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Completed" xml:space="preserve" approved="no">
|
||||
<source>Completed</source>
|
||||
<target state="translated">הושלם</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected" xml:space="preserve" approved="no">
|
||||
<source>Connected</source>
|
||||
<target state="translated">מחובר</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection notifications" xml:space="preserve" approved="no">
|
||||
<source>Connection notifications</source>
|
||||
<target state="translated">התראות חיבור</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connections" xml:space="preserve" approved="no">
|
||||
<source>Connections</source>
|
||||
<target state="translated">חיבורים</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Current profile" xml:space="preserve" approved="no">
|
||||
<source>Current profile</source>
|
||||
<target state="translated">פרופיל נוכחי</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disconnect desktop?" xml:space="preserve" approved="no">
|
||||
<source>Disconnect desktop?</source>
|
||||
<target state="translated">להתנתק מהמחשב?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Discover and join groups" xml:space="preserve" approved="no">
|
||||
<source>Discover and join groups</source>
|
||||
<target state="translated">גלה והצטרף לקבוצות</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled" xml:space="preserve" approved="no">
|
||||
<source>Enabled</source>
|
||||
<target state="translated">מופעל</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error opening chat" xml:space="preserve" approved="no">
|
||||
<source>Error opening chat</source>
|
||||
<target state="translated">שגיאה בפתיחת הצ'אט</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Good morning!" xml:space="preserve" approved="no">
|
||||
<source>Good morning!</source>
|
||||
<target state="translated">בוקר טוב!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to yourself? This is your own SimpleX address!" xml:space="preserve" approved="no">
|
||||
<source>Connect to yourself?
|
||||
This is your own SimpleX address!</source>
|
||||
<target state="translated">להתחבר אליך?
|
||||
זו כתובת הSimpleX שלך!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to yourself?" xml:space="preserve" approved="no">
|
||||
<source>Connect to yourself?</source>
|
||||
<target state="translated">להתחבר אליך?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to yourself? This is your own one-time link!" xml:space="preserve" approved="no">
|
||||
<source>Connect to yourself?
|
||||
This is your own one-time link!</source>
|
||||
<target state="translated">להתחבר אליך?
|
||||
זו כתובת ההזמנה החד-פעמי שלך!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected desktop" xml:space="preserve" approved="no">
|
||||
<source>Connected desktop</source>
|
||||
<target state="translated">מחשב מחובר</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected servers" xml:space="preserve" approved="no">
|
||||
<source>Connected servers</source>
|
||||
<target state="translated">שרתים מחוברים</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter group name…" xml:space="preserve" approved="no">
|
||||
<source>Enter group name…</source>
|
||||
<target state="translated">הכנס שם לקבוצה…</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter this device name…" xml:space="preserve" approved="no">
|
||||
<source>Enter this device name…</source>
|
||||
<target state="translated">הכנס שם למכשיר הזה…</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter your name…" xml:space="preserve" approved="no">
|
||||
<source>Enter your name…</source>
|
||||
<target state="translated">הכנס את השם שלך…</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error decrypting file" xml:space="preserve" approved="no">
|
||||
<source>Error decrypting file</source>
|
||||
<target state="translated">שגיאה בפענוח הקובץ</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Errors" xml:space="preserve" approved="no">
|
||||
<source>Errors</source>
|
||||
<target state="translated">שגיאות</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="File status" xml:space="preserve" approved="no">
|
||||
<source>File status</source>
|
||||
<target state="translated">מצב הקובץ</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting" xml:space="preserve" approved="no">
|
||||
<source>Connecting</source>
|
||||
<target state="translated">מתחבר</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting to desktop" xml:space="preserve" approved="no">
|
||||
<source>Connecting to desktop</source>
|
||||
<target state="translated">מתחבר למחשב</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Deleted" xml:space="preserve" approved="no">
|
||||
<source>Deleted</source>
|
||||
<target state="translated">נמחק</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Deletion errors" xml:space="preserve" approved="no">
|
||||
<source>Deletion errors</source>
|
||||
<target state="translated">שגיאות במחיקה</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Details" xml:space="preserve" approved="no">
|
||||
<source>Details</source>
|
||||
<target state="translated">פרטים</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarded" xml:space="preserve" approved="no">
|
||||
<source>Forwarded</source>
|
||||
<target state="translated">הועבר</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Found desktop" xml:space="preserve" approved="no">
|
||||
<source>Found desktop</source>
|
||||
<target state="translated">נמצא מחשב</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Good afternoon!" xml:space="preserve" approved="no">
|
||||
<source>Good afternoon!</source>
|
||||
<target state="translated">אחר צהריים טובים!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop address" xml:space="preserve" approved="no">
|
||||
<source>Desktop address</source>
|
||||
<target state="translated">כתובת מחשב</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarded from" xml:space="preserve" approved="no">
|
||||
<source>Forwarded from</source>
|
||||
<target state="translated">הועבר מ</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="History is not sent to new members." xml:space="preserve" approved="no">
|
||||
<source>History is not sent to new members.</source>
|
||||
<target state="translated">היסטוריה לא נשלחת לחברים חדשים.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created" xml:space="preserve" approved="no">
|
||||
<source>Created</source>
|
||||
<target state="translated">נוצר</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy error" xml:space="preserve" approved="no">
|
||||
<source>Copy error</source>
|
||||
<target state="translated">שגיאת העתקה</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create group" xml:space="preserve" approved="no">
|
||||
<source>Create group</source>
|
||||
<target state="translated">צור קבוצה</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled for" xml:space="preserve" approved="no">
|
||||
<source>Enabled for</source>
|
||||
<target state="translated">מופעל עבור</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating message" xml:space="preserve" approved="no">
|
||||
<source>Error creating message</source>
|
||||
<target state="translated">שגיאה ביצירת הודעה</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="File error" xml:space="preserve" approved="no">
|
||||
<source>File error</source>
|
||||
<target state="translated">שגיאה בקובץ</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="he" datatype="plaintext">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -19,16 +19,17 @@ private let MAX_DOWNSAMPLE_SIZE: Int64 = 2000
|
||||
|
||||
class ShareModel: ObservableObject {
|
||||
@Published var sharedContent: SharedContent?
|
||||
@Published var chats = Array<ChatData>()
|
||||
@Published var profileImages = Dictionary<ChatInfo.ID, UIImage>()
|
||||
@Published var search = String()
|
||||
@Published var comment = String()
|
||||
@Published var chats: [ChatData] = []
|
||||
@Published var profileImages: [ChatInfo.ID: UIImage] = [:]
|
||||
@Published var search = ""
|
||||
@Published var comment = ""
|
||||
@Published var selected: ChatData?
|
||||
@Published var isLoaded = false
|
||||
@Published var bottomBar: BottomBar = .loadingSpinner
|
||||
@Published var errorAlert: ErrorAlert?
|
||||
@Published var hasSimplexLink = false
|
||||
@Published var alertRequiresPassword = false
|
||||
var networkTimeout = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
enum BottomBar {
|
||||
case sendButton
|
||||
@@ -51,6 +52,13 @@ class ShareModel: ObservableObject {
|
||||
private var itemProvider: NSItemProvider?
|
||||
|
||||
var isSendDisbled: Bool { sharedContent == nil || selected == nil || isProhibited(selected) }
|
||||
|
||||
var isLinkPreview: Bool {
|
||||
switch sharedContent {
|
||||
case .url: true
|
||||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
func isProhibited(_ chat: ChatData?) -> Bool {
|
||||
if let chat, let sharedContent {
|
||||
@@ -58,7 +66,7 @@ class ShareModel: ObservableObject {
|
||||
} else { false }
|
||||
}
|
||||
|
||||
var filteredChats: Array<ChatData> {
|
||||
var filteredChats: [ChatData] {
|
||||
search.isEmpty
|
||||
? filterChatsToForwardTo(chats: chats)
|
||||
: filterChatsToForwardTo(chats: chats)
|
||||
@@ -66,7 +74,7 @@ class ShareModel: ObservableObject {
|
||||
}
|
||||
|
||||
func setup(context: NSExtensionContext) {
|
||||
if performLAGroupDefault.get() && !allowShareExtensionGroupDefault.get() {
|
||||
if appLocalAuthEnabledGroupDefault.get() && !allowShareExtensionGroupDefault.get() {
|
||||
errorAlert = ErrorAlert(title: "App is locked!", message: "You can allow sharing in Privacy & Security / SimpleX Lock settings.")
|
||||
return
|
||||
}
|
||||
@@ -111,7 +119,7 @@ class ShareModel: ObservableObject {
|
||||
}
|
||||
// Process Attachment
|
||||
Task {
|
||||
switch await self.itemProvider!.sharedContent() {
|
||||
switch await getSharedContent(self.itemProvider!) {
|
||||
case let .success(chatItemContent):
|
||||
await MainActor.run {
|
||||
self.sharedContent = chatItemContent
|
||||
@@ -141,7 +149,7 @@ class ShareModel: ObservableObject {
|
||||
if selected.chatInfo.chatType == .local {
|
||||
completion()
|
||||
} else {
|
||||
await MainActor.run { self.bottomBar = .loadingBar(progress: .zero) }
|
||||
await MainActor.run { self.bottomBar = .loadingBar(progress: 0) }
|
||||
if let e = await handleEvents(
|
||||
isGroupChat: ci.chatInfo.chatType == .group,
|
||||
isWithoutFile: sharedContent.cryptoFile == nil,
|
||||
@@ -283,14 +291,13 @@ class ShareModel: ObservableObject {
|
||||
CompletionHandler.isEventLoopEnabled = true
|
||||
let ch = CompletionHandler()
|
||||
if isWithoutFile { await ch.completeFile() }
|
||||
var networkTimeout = CFAbsoluteTimeGetCurrent()
|
||||
networkTimeout = CFAbsoluteTimeGetCurrent()
|
||||
while await ch.isRunning {
|
||||
if CFAbsoluteTimeGetCurrent() - networkTimeout > 30 {
|
||||
networkTimeout = CFAbsoluteTimeGetCurrent()
|
||||
await MainActor.run {
|
||||
self.errorAlert = ErrorAlert(title: "No network connection") {
|
||||
Button("Keep Trying", role: .cancel) { }
|
||||
Button("Dismiss Sheet", role: .destructive) { self.completion() }
|
||||
self.errorAlert = ErrorAlert(title: "Slow network?", message: "Sending a message takes longer than expected.") {
|
||||
Button("Wait", role: .cancel) { self.networkTimeout = CFAbsoluteTimeGetCurrent() }
|
||||
Button("Cancel", role: .destructive) { self.completion() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,7 +388,7 @@ enum SharedContent {
|
||||
switch self {
|
||||
case let .image(preview, _): .image(text: comment, image: preview)
|
||||
case let .movie(preview, duration, _): .video(text: comment, image: preview, duration: duration)
|
||||
case let .url(preview): .link(text: comment, preview: preview)
|
||||
case let .url(preview): .link(text: preview.uri.absoluteString + (comment == "" ? "" : "\n" + comment), preview: preview)
|
||||
case .text: .text(comment)
|
||||
case .data: .file(comment)
|
||||
}
|
||||
@@ -396,91 +403,89 @@ enum SharedContent {
|
||||
}
|
||||
}
|
||||
|
||||
extension NSItemProvider {
|
||||
fileprivate func sharedContent() async -> Result<SharedContent, ErrorAlert> {
|
||||
if let type = firstMatching(of: [.image, .movie, .fileURL, .url, .text]) {
|
||||
switch type {
|
||||
// Prepare Image message
|
||||
case .image:
|
||||
|
||||
// Animated
|
||||
return if hasItemConformingToTypeIdentifier(UTType.gif.identifier) {
|
||||
if let url = try? await inPlaceUrl(type: type),
|
||||
let data = try? Data(contentsOf: url),
|
||||
let image = UIImage(data: data),
|
||||
let cryptoFile = saveFile(data, generateNewFileName("IMG", "gif"), encrypted: privacyEncryptLocalFilesGroupDefault.get()),
|
||||
let preview = resizeImageToStrSize(image, maxDataSize: MAX_DATA_SIZE) {
|
||||
.success(.image(preview: preview, cryptoFile: cryptoFile))
|
||||
} else { .failure(ErrorAlert("Error preparing message")) }
|
||||
|
||||
// Static
|
||||
} else {
|
||||
if let image = await staticImage(),
|
||||
let cryptoFile = saveImage(image),
|
||||
let preview = resizeImageToStrSize(image, maxDataSize: MAX_DATA_SIZE) {
|
||||
.success(.image(preview: preview, cryptoFile: cryptoFile))
|
||||
} else { .failure(ErrorAlert("Error preparing message")) }
|
||||
}
|
||||
|
||||
// Prepare Movie message
|
||||
case .movie:
|
||||
fileprivate func getSharedContent(_ ip: NSItemProvider) async -> Result<SharedContent, ErrorAlert> {
|
||||
if let type = firstMatching(of: [.image, .movie, .fileURL, .url, .text]) {
|
||||
switch type {
|
||||
// Prepare Image message
|
||||
case .image:
|
||||
// Animated
|
||||
return if ip.hasItemConformingToTypeIdentifier(UTType.gif.identifier) {
|
||||
if let url = try? await inPlaceUrl(type: type),
|
||||
let trancodedUrl = await transcodeVideo(from: url),
|
||||
let (image, duration) = AVAsset(url: trancodedUrl).generatePreview(),
|
||||
let preview = resizeImageToStrSize(image, maxDataSize: MAX_DATA_SIZE),
|
||||
let cryptoFile = moveTempFileFromURL(trancodedUrl) {
|
||||
try? FileManager.default.removeItem(at: trancodedUrl)
|
||||
return .success(.movie(preview: preview, duration: duration, cryptoFile: cryptoFile))
|
||||
} else { return .failure(ErrorAlert("Error preparing message")) }
|
||||
|
||||
// Prepare Data message
|
||||
case .fileURL:
|
||||
if let url = try? await inPlaceUrl(type: .data) {
|
||||
if isFileTooLarge(for: url) {
|
||||
let sizeString = ByteCountFormatter.string(
|
||||
fromByteCount: Int64(getMaxFileSize(.xftp)),
|
||||
countStyle: .binary
|
||||
)
|
||||
return .failure(
|
||||
ErrorAlert(
|
||||
title: "Large file!",
|
||||
message: "Currently maximum supported file size is \(sizeString)."
|
||||
)
|
||||
)
|
||||
}
|
||||
if let file = saveFileFromURL(url) {
|
||||
return .success(.data(cryptoFile: file))
|
||||
}
|
||||
}
|
||||
return .failure(ErrorAlert("Error preparing file"))
|
||||
|
||||
// Prepare Link message
|
||||
case .url:
|
||||
if let url = try? await loadItem(forTypeIdentifier: type.identifier) as? URL {
|
||||
let content: SharedContent =
|
||||
if privacyLinkPreviewsGroupDefault.get(), let linkPreview = await getLinkPreview(for: url) {
|
||||
.url(preview: linkPreview)
|
||||
} else {
|
||||
.text(string: url.absoluteString)
|
||||
}
|
||||
return .success(content)
|
||||
} else { return .failure(ErrorAlert("Error preparing message")) }
|
||||
|
||||
// Prepare Text message
|
||||
case .text:
|
||||
return if let text = try? await loadItem(forTypeIdentifier: type.identifier) as? String {
|
||||
.success(.text(string: text))
|
||||
let data = try? Data(contentsOf: url),
|
||||
let image = UIImage(data: data),
|
||||
let cryptoFile = saveFile(data, generateNewFileName("IMG", "gif"), encrypted: privacyEncryptLocalFilesGroupDefault.get()),
|
||||
let preview = resizeImageToStrSize(image, maxDataSize: MAX_DATA_SIZE) {
|
||||
.success(.image(preview: preview, cryptoFile: cryptoFile))
|
||||
} else { .failure(ErrorAlert("Error preparing message")) }
|
||||
|
||||
// Static
|
||||
} else {
|
||||
if let image = await staticImage(),
|
||||
let cryptoFile = saveImage(image),
|
||||
let preview = resizeImageToStrSize(image, maxDataSize: MAX_DATA_SIZE) {
|
||||
.success(.image(preview: preview, cryptoFile: cryptoFile))
|
||||
} else { .failure(ErrorAlert("Error preparing message")) }
|
||||
default: return .failure(ErrorAlert("Unsupported format"))
|
||||
}
|
||||
} else {
|
||||
return .failure(ErrorAlert("Unsupported format"))
|
||||
|
||||
// Prepare Movie message
|
||||
case .movie:
|
||||
if let url = try? await inPlaceUrl(type: type),
|
||||
let trancodedUrl = await transcodeVideo(from: url),
|
||||
let (image, duration) = AVAsset(url: trancodedUrl).generatePreview(),
|
||||
let preview = resizeImageToStrSize(image, maxDataSize: MAX_DATA_SIZE),
|
||||
let cryptoFile = moveTempFileFromURL(trancodedUrl) {
|
||||
try? FileManager.default.removeItem(at: trancodedUrl)
|
||||
return .success(.movie(preview: preview, duration: duration, cryptoFile: cryptoFile))
|
||||
} else { return .failure(ErrorAlert("Error preparing message")) }
|
||||
|
||||
// Prepare Data message
|
||||
case .fileURL:
|
||||
if let url = try? await inPlaceUrl(type: .data) {
|
||||
if isFileTooLarge(for: url) {
|
||||
let sizeString = ByteCountFormatter.string(
|
||||
fromByteCount: Int64(getMaxFileSize(.xftp)),
|
||||
countStyle: .binary
|
||||
)
|
||||
return .failure(
|
||||
ErrorAlert(
|
||||
title: "Large file!",
|
||||
message: "Currently maximum supported file size is \(sizeString)."
|
||||
)
|
||||
)
|
||||
}
|
||||
if let file = saveFileFromURL(url) {
|
||||
return .success(.data(cryptoFile: file))
|
||||
}
|
||||
}
|
||||
return .failure(ErrorAlert("Error preparing file"))
|
||||
|
||||
// Prepare Link message
|
||||
case .url:
|
||||
if let url = try? await ip.loadItem(forTypeIdentifier: type.identifier) as? URL {
|
||||
let content: SharedContent =
|
||||
if privacyLinkPreviewsGroupDefault.get(), let linkPreview = await getLinkPreview(for: url) {
|
||||
.url(preview: linkPreview)
|
||||
} else {
|
||||
.text(string: url.absoluteString)
|
||||
}
|
||||
return .success(content)
|
||||
} else { return .failure(ErrorAlert("Error preparing message")) }
|
||||
|
||||
// Prepare Text message
|
||||
case .text:
|
||||
return if let text = try? await ip.loadItem(forTypeIdentifier: type.identifier) as? String {
|
||||
.success(.text(string: text))
|
||||
} else { .failure(ErrorAlert("Error preparing message")) }
|
||||
default: return .failure(ErrorAlert("Unsupported format"))
|
||||
}
|
||||
} else {
|
||||
return .failure(ErrorAlert("Unsupported format"))
|
||||
}
|
||||
|
||||
private func inPlaceUrl(type: UTType) async throws -> URL {
|
||||
|
||||
func inPlaceUrl(type: UTType) async throws -> URL {
|
||||
try await withCheckedThrowingContinuation { cont in
|
||||
let _ = loadInPlaceFileRepresentation(forTypeIdentifier: type.identifier) { url, bool, error in
|
||||
let _ = ip.loadInPlaceFileRepresentation(forTypeIdentifier: type.identifier) { url, bool, error in
|
||||
if let url = url {
|
||||
cont.resume(returning: url)
|
||||
} else if let error = error {
|
||||
@@ -492,21 +497,21 @@ extension NSItemProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private func firstMatching(of types: Array<UTType>) -> UTType? {
|
||||
func firstMatching(of types: Array<UTType>) -> UTType? {
|
||||
for type in types {
|
||||
if hasItemConformingToTypeIdentifier(type.identifier) { return type }
|
||||
if ip.hasItemConformingToTypeIdentifier(type.identifier) { return type }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func staticImage() async -> UIImage? {
|
||||
func staticImage() async -> UIImage? {
|
||||
if let url = try? await inPlaceUrl(type: .image),
|
||||
let downsampledImage = downsampleImage(at: url, to: MAX_DOWNSAMPLE_SIZE) {
|
||||
downsampledImage
|
||||
} else {
|
||||
/// Fallback to loading image directly from `ItemProvider`
|
||||
/// in case loading from disk is not possible. Required for sharing screenshots.
|
||||
try? await loadItem(forTypeIdentifier: UTType.image.identifier) as? UIImage
|
||||
try? await ip.loadItem(forTypeIdentifier: UTType.image.identifier) as? UIImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ struct ShareView: View {
|
||||
}
|
||||
|
||||
private func compose(isLoading: Bool) -> some View {
|
||||
VStack(spacing: .zero) {
|
||||
VStack(spacing: 0) {
|
||||
Divider()
|
||||
if let content = model.sharedContent {
|
||||
itemPreview(content)
|
||||
@@ -179,7 +179,7 @@ struct ShareView: View {
|
||||
|
||||
private func loadingBar(progress: Double) -> some View {
|
||||
VStack {
|
||||
Text("Sending File")
|
||||
Text("Sending message…")
|
||||
ProgressView(value: progress)
|
||||
}
|
||||
.padding()
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX SE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX SE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. Alle Rechte vorbehalten.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,111 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
/* No comment provided by engineer. */
|
||||
"%@" = "%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App is locked!" = "Die App ist gesperrt!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Abbrechen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot access keychain to save database password" = "Es ist nicht möglich, auf den Schlüsselbund zuzugreifen, um das Datenbankpasswort zu speichern";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot forward message" = "Nachricht kann nicht weitergeleitet werden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Comment" = "Kommentieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Currently maximum supported file size is %@." = "Die maximale erlaubte Dateigröße beträgt aktuell %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database downgrade required" = "Datenbank-Herabstufung erforderlich";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database encrypted!" = "Datenbank verschlüsselt!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database error" = "Datenbankfehler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is different from saved in the keychain." = "Das Datenbank-Passwort unterscheidet sich vom im Schlüsselbund gespeicherten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is required to open chat." = "Ein Datenbank-Passwort ist erforderlich, um den Chat zu öffnen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database upgrade required" = "Datenbank-Aktualisierung erforderlich";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing file" = "Fehler beim Vorbereiten der Datei";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing message" = "Fehler beim Vorbereiten der Nachricht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error: %@" = "Fehler: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"File error" = "Dateifehler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incompatible database version" = "Datenbank-Version nicht kompatibel";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid migration confirmation" = "Migrations-Bestätigung ungültig";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keychain error" = "Schlüsselbund-Fehler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Large file!" = "Große Datei!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No active profile" = "Kein aktives Profil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Ok" = "OK";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to downgrade the database." = "Öffne die App, um die Datenbank herabzustufen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to upgrade the database." = "Öffne die App, um die Datenbank zu aktualisieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Passphrase" = "Passwort";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please create a profile in the SimpleX app" = "Bitte erstelle ein Profil in der SimpleX-App";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending a message takes longer than expected." = "Das Senden einer Nachricht dauert länger als erwartet.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending message…" = "Nachricht wird gesendet…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share" = "Teilen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Slow network?" = "Langsames Netzwerk?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown database error: %@" = "Unbekannter Datenbankfehler: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unsupported format" = "Nicht unterstütztes Format";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wait" = "Warten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong database passphrase" = "Falsches Datenbank-Passwort";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Du kannst das Teilen in den Einstellungen zu Datenschutz & Sicherheit - SimpleX-Sperre erlauben.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX SE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX SE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. Todos los derechos reservados.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,111 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
/* No comment provided by engineer. */
|
||||
"%@" = "%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App is locked!" = "¡Aplicación bloqueada!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Cancelar";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot access keychain to save database password" = "Keychain inaccesible para guardar la contraseña de la base de datos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot forward message" = "No se puede reenviar el mensaje";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Comment" = "Comentario";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Currently maximum supported file size is %@." = "El tamaño máximo de archivo admitido es %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database downgrade required" = "Se requiere volver a versión anterior de la base de datos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database encrypted!" = "¡Base de datos cifrada!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database error" = "Error en base de datos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is different from saved in the keychain." = "La contraseña de la base de datos es distinta a la almacenada en keychain.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is required to open chat." = "Se requiere la contraseña de la base de datos para abrir la aplicación.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database upgrade required" = "Se requiere actualizar la base de datos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing file" = "Error al preparar el archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing message" = "Error al preparar el mensaje";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error: %@" = "Error: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"File error" = "Error de archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incompatible database version" = "Versión de base de datos incompatible";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid migration confirmation" = "Confirmación de migración no válida";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keychain error" = "Error en keychain";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Large file!" = "¡Archivo grande!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No active profile" = "Ningún perfil activo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Ok" = "Ok";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to downgrade the database." = "Abre la aplicación para volver a versión anterior de la base de datos.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to upgrade the database." = "Abre la aplicación para actualizar la base de datos.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Passphrase" = "Frase de contraseña";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please create a profile in the SimpleX app" = "Por favor, crea un perfil en SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Las preferencias seleccionadas no permiten este mensaje.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending a message takes longer than expected." = "Enviar el mensaje lleva más tiempo del esperado.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending message…" = "Enviando mensaje…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share" = "Compartir";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Slow network?" = "¿Red lenta?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown database error: %@" = "Error desconocido en la base de datos: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unsupported format" = "Formato sin soporte";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wait" = "Espera";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong database passphrase" = "Contraseña incorrecta de la base de datos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Puedes dar permiso para compartir en Privacidad y Seguridad / Bloque SimpleX.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX SE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX SE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. Tous droits réservés.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,99 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
/* No comment provided by engineer. */
|
||||
"%@" = "%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App is locked!" = "L'app est verrouillée !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Annuler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot access keychain to save database password" = "Impossible d'accéder à la keychain pour enregistrer le mot de passe de la base de données";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot forward message" = "Impossible de transférer le message";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Comment" = "Commenter";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Currently maximum supported file size is %@." = "Actuellement, la taille maximale des fichiers supportés est de %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database downgrade required" = "Mise à jour de la base de données nécessaire";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database encrypted!" = "Base de données chiffrée !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database error" = "Erreur de base de données";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is different from saved in the keychain." = "La phrase secrète de la base de données est différente de celle enregistrée dans la keychain.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is required to open chat." = "La phrase secrète de la base de données est nécessaire pour ouvrir le chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database upgrade required" = "Mise à niveau de la base de données nécessaire";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing file" = "Erreur lors de la préparation du fichier";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing message" = "Erreur lors de la préparation du message";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error: %@" = "Erreur : %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"File error" = "Erreur de fichier";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incompatible database version" = "Version de la base de données incompatible";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid migration confirmation" = "Confirmation de migration invalide";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keychain error" = "Erreur de la keychain";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Large file!" = "Fichier trop lourd !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No active profile" = "Pas de profil actif";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Ok" = "Ok";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to downgrade the database." = "Ouvrez l'app pour rétrograder la base de données.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to upgrade the database." = "Ouvrez l'app pour mettre à jour la base de données.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Passphrase" = "Phrase secrète";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please create a profile in the SimpleX app" = "Veuillez créer un profil dans l'app SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Les paramètres de chat sélectionnés ne permettent pas l'envoi de ce message.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share" = "Partager";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown database error: %@" = "Erreur inconnue de la base de données : %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unsupported format" = "Format non pris en charge";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong database passphrase" = "Mauvaise phrase secrète pour la base de données";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Vous pouvez autoriser le partage dans les paramètres Confidentialité et sécurité / SimpleX Lock.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX SE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX SE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. Minden jog fenntartva.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,111 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
/* No comment provided by engineer. */
|
||||
"%@" = "%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App is locked!" = "Az alkalmazás zárolva!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Mégse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot access keychain to save database password" = "Nem lehet hozzáférni a kulcstartóhoz az adatbázis jelszavának mentéséhez";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot forward message" = "Nem lehet továbbítani az üzenetet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Comment" = "Hozzászólás";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Currently maximum supported file size is %@." = "Jelenleg a maximális támogatott fájlméret %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database downgrade required" = "Adatbázis visszafejlesztése szükséges";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database encrypted!" = "Adatbázis titkosítva!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database error" = "Adatbázis hiba";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is different from saved in the keychain." = "Az adatbázis jelmondata eltér a kulcstartóban lévőtől.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is required to open chat." = "Adatbázis jelmondat szükséges a csevegés megnyitásához.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database upgrade required" = "Adatbázis fejlesztése szükséges";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing file" = "Hiba a fájl előkészítésekor";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing message" = "Hiba az üzenet előkészítésekor";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error: %@" = "Hiba: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"File error" = "Fájlhiba";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incompatible database version" = "Nem kompatibilis adatbázis verzió";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid migration confirmation" = "Érvénytelen átköltöztetési visszaigazolás";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keychain error" = "Kulcstartó hiba";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Large file!" = "Nagy fájl!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No active profile" = "Nincs aktív profil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Ok" = "Rendben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to downgrade the database." = "Nyissa meg az alkalmazást az adatbázis visszafejlesztéséhez.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to upgrade the database." = "Nyissa meg az alkalmazást az adatbázis fejlesztéséhez.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Passphrase" = "Jelmondat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please create a profile in the SimpleX app" = "Hozzon létre egy profilt a SimpleX alkalmazásban";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "A kiválasztott csevegési beállítások tiltják ezt az üzenetet.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending a message takes longer than expected." = "Az üzenet elküldése a vártnál tovább tart.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending message…" = "Üzenet küldése…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share" = "Megosztás";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Slow network?" = "Lassú internetkapcsolat?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown database error: %@" = "Ismeretlen adatbázis hiba: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unsupported format" = "Nem támogatott formátum";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wait" = "Várjon";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong database passphrase" = "Hibás adatbázis jelmondat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "A megosztást az Adatvédelem és biztonság / SimpleX zár menüben engedélyezheti.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX SE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX SE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. Tutti i diritti riservati.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,111 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
/* No comment provided by engineer. */
|
||||
"%@" = "%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App is locked!" = "L'app è bloccata!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Annulla";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot access keychain to save database password" = "Impossibile accedere al portachiavi per salvare la password del database";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot forward message" = "Impossibile inoltrare il messaggio";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Comment" = "Commento";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Currently maximum supported file size is %@." = "Attualmente la dimensione massima supportata è di %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database downgrade required" = "Downgrade del database necessario";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database encrypted!" = "Database crittografato!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database error" = "Errore del database";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is different from saved in the keychain." = "La password del database è diversa da quella salvata nel portachiavi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is required to open chat." = "La password del database è necessaria per aprire la chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database upgrade required" = "Aggiornamento del database necessario";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing file" = "Errore nella preparazione del file";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing message" = "Errore nella preparazione del messaggio";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error: %@" = "Errore: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"File error" = "Errore del file";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incompatible database version" = "Versione del database incompatibile";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid migration confirmation" = "Conferma di migrazione non valida";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keychain error" = "Errore del portachiavi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Large file!" = "File grande!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No active profile" = "Nessun profilo attivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Ok" = "Ok";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to downgrade the database." = "Apri l'app per eseguire il downgrade del database.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to upgrade the database." = "Apri l'app per aggiornare il database.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Passphrase" = "Password";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please create a profile in the SimpleX app" = "Crea un profilo nell'app SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Le preferenze della chat selezionata vietano questo messaggio.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending a message takes longer than expected." = "L'invio di un messaggio richiede più tempo del previsto.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending message…" = "Invio messaggio…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share" = "Condividi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Slow network?" = "Rete lenta?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown database error: %@" = "Errore del database sconosciuto: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unsupported format" = "Formato non supportato";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wait" = "Attendi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong database passphrase" = "Password del database sbagliata";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Puoi consentire la condivisione in Privacy e sicurezza / impostazioni di SimpleX Lock.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX SE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX SE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. All rights reserved.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,111 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
/* No comment provided by engineer. */
|
||||
"%@" = "%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App is locked!" = "App is vergrendeld!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Annuleren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot access keychain to save database password" = "Kan geen toegang krijgen tot de keychain om het database wachtwoord op te slaan";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot forward message" = "Kan bericht niet doorsturen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Comment" = "Opmerking";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Currently maximum supported file size is %@." = "De momenteel maximaal ondersteunde bestandsgrootte is %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database downgrade required" = "Database downgrade vereist";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database encrypted!" = "Database versleuteld!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database error" = "Database fout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is different from saved in the keychain." = "Het wachtwoord van de database verschilt van het wachtwoord die in de keychain is opgeslagen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is required to open chat." = "Database wachtwoord is vereist om je gesprekken te openen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database upgrade required" = "Database upgrade vereist";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing file" = "Fout bij voorbereiden bestand";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing message" = "Fout bij het voorbereiden van bericht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error: %@" = "Fout: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"File error" = "Bestandsfout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incompatible database version" = "Incompatibele database versie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid migration confirmation" = "Ongeldige migratie bevestiging";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keychain error" = "Keychain fout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Large file!" = "Groot bestand!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No active profile" = "Geen actief profiel";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Ok" = "Ok";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to downgrade the database." = "Open de app om de database te downgraden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to upgrade the database." = "Open de app om de database te upgraden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Passphrase" = "Wachtwoord";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please create a profile in the SimpleX app" = "Maak een profiel aan in de SimpleX app";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Geselecteerde chat voorkeuren verbieden dit bericht.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending a message takes longer than expected." = "Het verzenden van een bericht duurt langer dan verwacht.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending message…" = "Bericht versturen…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share" = "Deel";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Slow network?" = "Traag netwerk?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown database error: %@" = "Onbekende database fout: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unsupported format" = "Niet ondersteund formaat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wait" = "wachten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong database passphrase" = "Verkeerde database wachtwoord";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "U kunt delen toestaan in de instellingen voor Privacy en beveiliging / SimpleX Lock.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX SE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX SE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. Все права защищены.";
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,111 @@
|
||||
/* No comment provided by engineer. */
|
||||
"%@" = "%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App is locked!" = "Приложение заблокировано!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Отменить";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot access keychain to save database password" = "Невозможно сохранить пароль в keychain";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cannot forward message" = "Невозможно переслать сообщение";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Comment" = "Комментарий";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Currently maximum supported file size is %@." = "В настоящее время максимальный поддерживаемый размер файла составляет %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database downgrade required" = "Требуется откат базы данных";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database encrypted!" = "База данных зашифрована!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database error" = "Ошибка базы данных";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is different from saved in the keychain." = "Пароль базы данных отличается от сохраненного в keychain.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is required to open chat." = "Введите пароль базы данных, чтобы открыть чат.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database upgrade required" = "Требуется обновление базы данных";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing file" = "Ошибка подготовки файла";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error preparing message" = "Ошибка подготовки сообщения";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error: %@" = "Ошибка: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"File error" = "Ошибка файла";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incompatible database version" = "Несовместимая версия базы данных";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid migration confirmation" = "Ошибка подтверждения миграции";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keychain error" = "Ошибка keychain";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Large file!" = "Большой файл!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No active profile" = "Нет активного профиля";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Ok" = "Ок";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to downgrade the database." = "Откройте приложение, чтобы откатить базу данных.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open the app to upgrade the database." = "Откройте приложение, чтобы обновить базу данных.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Passphrase" = "Пароль";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please create a profile in the SimpleX app" = "Пожалуйста, создайте профиль в приложении SimpleX.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Выбранные настройки чата запрещают это сообщение.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending a message takes longer than expected." = "Отправка сообщения занимает дольше ожиданного.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending message…" = "Отправка сообщения…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share" = "Поделиться";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Slow network?" = "Медленная сеть?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown database error: %@" = "Неизвестная ошибка базы данных: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unsupported format" = "Неподдерживаемый формат";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wait" = "Подождать";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong database passphrase" = "Неправильный пароль базы данных";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Вы можете разрешить функцию Поделиться в настройках Конфиденциальности / Блокировка SimpleX.";
|
||||
|
||||
|
||||
@@ -156,6 +156,11 @@
|
||||
64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */; };
|
||||
64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DCB29FFE3E800E3D48D /* MailView.swift */; };
|
||||
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; };
|
||||
6449333A2AF8E51000AC506E /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933352AF8E51000AC506E /* libgmpxx.a */; };
|
||||
6449333B2AF8E51000AC506E /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933362AF8E51000AC506E /* libgmp.a */; };
|
||||
6449333C2AF8E51000AC506E /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933372AF8E51000AC506E /* libffi.a */; };
|
||||
6449333D2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */; };
|
||||
6449333E2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */; };
|
||||
644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; };
|
||||
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; };
|
||||
644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; };
|
||||
@@ -194,11 +199,13 @@
|
||||
8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */; };
|
||||
8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; };
|
||||
8CE848A32C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */; };
|
||||
B76E6C312C5C41D900EC11AA /* ContactListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B76E6C302C5C41D900EC11AA /* ContactListNavLink.swift */; };
|
||||
CE1EB0E42C459A660099D896 /* ShareAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1EB0E32C459A660099D896 /* ShareAPI.swift */; };
|
||||
CE2AD9CE2C452A4D00E844E3 /* ChatUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */; };
|
||||
CE3097FB2C4C0C9F00180898 /* ErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */; };
|
||||
CE38A29A2C3FCA54005ED185 /* ImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */; };
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; };
|
||||
CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; };
|
||||
CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; };
|
||||
CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; };
|
||||
CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; };
|
||||
@@ -212,16 +219,16 @@
|
||||
D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; };
|
||||
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; };
|
||||
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; };
|
||||
E50581062C3DDD9D009C3F71 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = E50581052C3DDD9D009C3F71 /* Yams */; };
|
||||
E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; };
|
||||
E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; };
|
||||
E5DCF8E12C5847EB007928CC /* libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5DCF8DC2C5847EB007928CC /* libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB.a */; };
|
||||
E5DCF8E22C5847EB007928CC /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5DCF8DD2C5847EB007928CC /* libgmp.a */; };
|
||||
E5DCF8E32C5847EB007928CC /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5DCF8DE2C5847EB007928CC /* libgmpxx.a */; };
|
||||
E5DCF8E42C5847EB007928CC /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5DCF8DF2C5847EB007928CC /* libffi.a */; };
|
||||
E5DCF8E52C5847EB007928CC /* libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5DCF8E02C5847EB007928CC /* libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB-ghc9.6.3.a */; };
|
||||
E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; };
|
||||
E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; };
|
||||
E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; };
|
||||
E5E218492C6813750013B4C6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218442C6813750013B4C6 /* libgmpxx.a */; };
|
||||
E5E2184A2C6813750013B4C6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218452C6813750013B4C6 /* libffi.a */; };
|
||||
E5E2184B2C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218462C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a */; };
|
||||
E5E2184C2C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218472C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a */; };
|
||||
E5E2184D2C6813750013B4C6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E218482C6813750013B4C6 /* libgmp.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -492,6 +499,11 @@
|
||||
64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = "<group>"; };
|
||||
64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = "<group>"; };
|
||||
6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = "<group>"; };
|
||||
644933352AF8E51000AC506E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
644933362AF8E51000AC506E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
644933372AF8E51000AC506E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a"; sourceTree = "<group>"; };
|
||||
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramedCIVoiceView.swift; sourceTree = "<group>"; };
|
||||
@@ -531,9 +543,11 @@
|
||||
8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallAudioDeviceManager.swift; sourceTree = "<group>"; };
|
||||
8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = "<group>"; };
|
||||
8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableChatItemToolbars.swift; sourceTree = "<group>"; };
|
||||
B76E6C302C5C41D900EC11AA /* ContactListNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactListNavLink.swift; sourceTree = "<group>"; };
|
||||
CE1EB0E32C459A660099D896 /* ShareAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAPI.swift; sourceTree = "<group>"; };
|
||||
CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatUtils.swift; sourceTree = "<group>"; };
|
||||
CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = "<group>"; };
|
||||
CE7548092C622630009579B7 /* SwipeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeLabel.swift; sourceTree = "<group>"; };
|
||||
CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemClipShape.swift; sourceTree = "<group>"; };
|
||||
CEDE70212C48FD9500233B1F /* SEChatState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEChatState.swift; sourceTree = "<group>"; };
|
||||
CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -547,11 +561,7 @@
|
||||
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; };
|
||||
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
E5DCF8DC2C5847EB007928CC /* libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB.a"; sourceTree = "<group>"; };
|
||||
E5DCF8DD2C5847EB007928CC /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
E5DCF8DE2C5847EB007928CC /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
E5DCF8DF2C5847EB007928CC /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
E5DCF8E02C5847EB007928CC /* libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = "<group>"; };
|
||||
E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
@@ -604,6 +614,11 @@
|
||||
E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
E5E218442C6813750013B4C6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
E5E218452C6813750013B4C6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
E5E218462C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
E5E218472C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a"; sourceTree = "<group>"; };
|
||||
E5E218482C6813750013B4C6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -642,15 +657,14 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E5E218492C6813750013B4C6 /* libgmpxx.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
E5E2184D2C6813750013B4C6 /* libgmp.a in Frameworks */,
|
||||
E5E2184C2C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
E50581062C3DDD9D009C3F71 /* Yams in Frameworks */,
|
||||
E5DCF8E52C5847EB007928CC /* libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB-ghc9.6.3.a in Frameworks */,
|
||||
E5DCF8E42C5847EB007928CC /* libffi.a in Frameworks */,
|
||||
E5DCF8E12C5847EB007928CC /* libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB.a in Frameworks */,
|
||||
E5DCF8E22C5847EB007928CC /* libgmp.a in Frameworks */,
|
||||
E5E2184B2C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a in Frameworks */,
|
||||
E5E2184A2C6813750013B4C6 /* libffi.a in Frameworks */,
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||
E5DCF8E32C5847EB007928CC /* libgmpxx.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -685,6 +699,7 @@
|
||||
5C2E260D27A30E2400F70299 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B76E6C2F2C5C41C300EC11AA /* Contacts */,
|
||||
5CB0BA8C282711BC00B3292C /* Onboarding */,
|
||||
3C714775281C080100CB4D4B /* Call */,
|
||||
5C971E1F27AEBF7000C8A3CE /* Helpers */,
|
||||
@@ -726,11 +741,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E5DCF8DF2C5847EB007928CC /* libffi.a */,
|
||||
E5DCF8DD2C5847EB007928CC /* libgmp.a */,
|
||||
E5DCF8DE2C5847EB007928CC /* libgmpxx.a */,
|
||||
E5DCF8E02C5847EB007928CC /* libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB-ghc9.6.3.a */,
|
||||
E5DCF8DC2C5847EB007928CC /* libHSsimplex-chat-6.0.0.3-5yF58NJ20MV4vZ7jQKFAxB.a */,
|
||||
E5E218452C6813750013B4C6 /* libffi.a */,
|
||||
E5E218482C6813750013B4C6 /* libgmp.a */,
|
||||
E5E218442C6813750013B4C6 /* libgmpxx.a */,
|
||||
E5E218462C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf-ghc9.6.3.a */,
|
||||
E5E218472C6813750013B4C6 /* libHSsimplex-chat-6.0.0.7-JAEd2ymO3CfCyjlINhbKkf.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -786,6 +801,7 @@
|
||||
8C74C3ED2C1B942300039E77 /* ChatWallpaper.swift */,
|
||||
8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */,
|
||||
CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */,
|
||||
CE7548092C622630009579B7 /* SwipeLabel.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
@@ -932,6 +948,7 @@
|
||||
5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */,
|
||||
18415835CBD939A9ABDC108A /* UserPicker.swift */,
|
||||
64EEB0F62C353F1C00972D62 /* ServersSummaryView.swift */,
|
||||
E51CC1E52C62085600DB91FE /* OneHandUICard.swift */,
|
||||
);
|
||||
path = ChatList;
|
||||
sourceTree = "<group>";
|
||||
@@ -1076,6 +1093,14 @@
|
||||
path = Theme;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B76E6C2F2C5C41C300EC11AA /* Contacts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B76E6C302C5C41D900EC11AA /* ContactListNavLink.swift */,
|
||||
);
|
||||
path = Contacts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEE723A82C3BD3D70009AE93 /* SimpleX SE */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1188,7 +1213,6 @@
|
||||
);
|
||||
name = SimpleXChat;
|
||||
packageProductDependencies = (
|
||||
E50581052C3DDD9D009C3F71 /* Yams */,
|
||||
CE38A29B2C3FCD72005ED185 /* SwiftyGif */,
|
||||
);
|
||||
productName = SimpleXChat;
|
||||
@@ -1350,6 +1374,7 @@
|
||||
5C93292F29239A170090FFF9 /* ProtocolServersView.swift in Sources */,
|
||||
5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */,
|
||||
5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */,
|
||||
E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */,
|
||||
5C65DAF929D0CC20003CEE45 /* DeveloperView.swift in Sources */,
|
||||
5C36027327F47AD5009F19D9 /* AppDelegate.swift in Sources */,
|
||||
5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */,
|
||||
@@ -1382,6 +1407,7 @@
|
||||
64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */,
|
||||
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */,
|
||||
5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */,
|
||||
B76E6C312C5C41D900EC11AA /* ContactListNavLink.swift in Sources */,
|
||||
5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */,
|
||||
D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */,
|
||||
CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */,
|
||||
@@ -1431,6 +1457,7 @@
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */,
|
||||
8CE848A32C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift in Sources */,
|
||||
64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */,
|
||||
CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */,
|
||||
5C971E2127AEBF8300C8A3CE /* ChatInfoImage.swift in Sources */,
|
||||
5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */,
|
||||
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */,
|
||||
@@ -1866,7 +1893,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 229;
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -1915,7 +1942,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 229;
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -1956,11 +1983,11 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 6.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1976,11 +2003,11 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 6.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -2001,7 +2028,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 229;
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
@@ -2038,7 +2065,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 229;
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
@@ -2075,7 +2102,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 229;
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2126,7 +2153,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 229;
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2177,7 +2204,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -2192,7 +2219,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 6.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -2211,7 +2238,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -2226,7 +2253,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 6.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -2371,11 +2398,6 @@
|
||||
package = D7F0E33729964E7D0068AF69 /* XCRemoteSwiftPackageReference "lzstring-swift" */;
|
||||
productName = LZString;
|
||||
};
|
||||
E50581052C3DDD9D009C3F71 /* Yams */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */;
|
||||
productName = Yams;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 5CA059BE279559F40002BEB4 /* Project object */;
|
||||
|
||||
@@ -8,14 +8,13 @@
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Yams
|
||||
|
||||
public let jsonDecoder = getJSONDecoder()
|
||||
public let jsonEncoder = getJSONEncoder()
|
||||
|
||||
public enum ChatCommand {
|
||||
case showActiveUser
|
||||
case createActiveUser(profile: Profile?, sameServers: Bool, pastTimestamp: Bool)
|
||||
case createActiveUser(profile: Profile?, pastTimestamp: Bool)
|
||||
case listUsers
|
||||
case apiSetActiveUser(userId: Int64, viewPwd: String?)
|
||||
case setAllContactReceipts(enable: Bool)
|
||||
@@ -101,7 +100,7 @@ public enum ChatCommand {
|
||||
case apiConnectPlan(userId: Int64, connReq: String)
|
||||
case apiConnect(userId: Int64, incognito: Bool, connReq: String)
|
||||
case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64)
|
||||
case apiDeleteChat(type: ChatType, id: Int64, notify: Bool?)
|
||||
case apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode)
|
||||
case apiClearChat(type: ChatType, id: Int64)
|
||||
case apiListContacts(userId: Int64)
|
||||
case apiUpdateProfile(userId: Int64, profile: Profile)
|
||||
@@ -156,8 +155,8 @@ public enum ChatCommand {
|
||||
get {
|
||||
switch self {
|
||||
case .showActiveUser: return "/u"
|
||||
case let .createActiveUser(profile, sameServers, pastTimestamp):
|
||||
let user = NewUser(profile: profile, sameServers: sameServers, pastTimestamp: pastTimestamp)
|
||||
case let .createActiveUser(profile, pastTimestamp):
|
||||
let user = NewUser(profile: profile, pastTimestamp: pastTimestamp)
|
||||
return "/_create user \(encodeJSON(user))"
|
||||
case .listUsers: return "/users"
|
||||
case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))"
|
||||
@@ -266,11 +265,7 @@ public enum ChatCommand {
|
||||
case let .apiConnectPlan(userId, connReq): return "/_connect plan \(userId) \(connReq)"
|
||||
case let .apiConnect(userId, incognito, connReq): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connReq)"
|
||||
case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)"
|
||||
case let .apiDeleteChat(type, id, notify): if let notify = notify {
|
||||
return "/_delete \(ref(type, id)) notify=\(onOff(notify))"
|
||||
} else {
|
||||
return "/_delete \(ref(type, id))"
|
||||
}
|
||||
case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id)) \(chatDeleteMode.cmdString)"
|
||||
case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))"
|
||||
case let .apiListContacts(userId): return "/_contacts \(userId)"
|
||||
case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))"
|
||||
@@ -505,10 +500,6 @@ public enum ChatCommand {
|
||||
return nil
|
||||
}
|
||||
|
||||
private func onOff(_ b: Bool) -> String {
|
||||
b ? "on" : "off"
|
||||
}
|
||||
|
||||
private func onOffParam(_ param: String, _ b: Bool?) -> String {
|
||||
if let b = b {
|
||||
return " \(param)=\(onOff(b))"
|
||||
@@ -521,6 +512,10 @@ public enum ChatCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private func onOff(_ b: Bool) -> String {
|
||||
b ? "on" : "off"
|
||||
}
|
||||
|
||||
public struct APIResponse: Decodable {
|
||||
var resp: ChatResponse
|
||||
}
|
||||
@@ -689,6 +684,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case agentSubsSummary(user: UserRef, subsSummary: SMPServerSubs)
|
||||
case chatCmdError(user_: UserRef?, chatError: ChatError)
|
||||
case chatError(user_: UserRef?, chatError: ChatError)
|
||||
case archiveExported(archiveErrors: [ArchiveError])
|
||||
case archiveImported(archiveErrors: [ArchiveError])
|
||||
case appSettings(appSettings: AppSettings)
|
||||
|
||||
@@ -851,6 +847,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .agentSubsSummary: return "agentSubsSummary"
|
||||
case .chatCmdError: return "chatCmdError"
|
||||
case .chatError: return "chatError"
|
||||
case .archiveExported: return "archiveExported"
|
||||
case .archiveImported: return "archiveImported"
|
||||
case .appSettings: return "appSettings"
|
||||
}
|
||||
@@ -1021,6 +1018,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary))
|
||||
case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError))
|
||||
case let .chatError(u, chatError): return withUser(u, String(describing: chatError))
|
||||
case let .archiveExported(archiveErrors): return String(describing: archiveErrors)
|
||||
case let .archiveImported(archiveErrors): return String(describing: archiveErrors)
|
||||
case let .appSettings(appSettings): return String(describing: appSettings)
|
||||
}
|
||||
@@ -1045,6 +1043,27 @@ public func chatError(_ chatResponse: ChatResponse) -> ChatErrorType? {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChatDeleteMode: Codable {
|
||||
case full(notify: Bool)
|
||||
case entity(notify: Bool)
|
||||
case messages
|
||||
|
||||
var cmdString: String {
|
||||
switch self {
|
||||
case let .full(notify): "full notify=\(onOff(notify))"
|
||||
case let .entity(notify): "entity notify=\(onOff(notify))"
|
||||
case .messages: "messages"
|
||||
}
|
||||
}
|
||||
|
||||
public var isEntity: Bool {
|
||||
switch self {
|
||||
case .entity: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConnectionPlan: Decodable, Hashable {
|
||||
case invitationLink(invitationLinkPlan: InvitationLinkPlan)
|
||||
case contactAddress(contactAddressPlan: ContactAddressPlan)
|
||||
@@ -1077,7 +1096,6 @@ public enum GroupLinkPlan: Decodable, Hashable {
|
||||
|
||||
struct NewUser: Encodable, Hashable {
|
||||
var profile: Profile?
|
||||
var sameServers: Bool
|
||||
var pastTimestamp: Bool
|
||||
}
|
||||
|
||||
@@ -1322,13 +1340,31 @@ public struct NetCfg: Codable, Equatable, Hashable {
|
||||
smpPingInterval: 1200_000_000
|
||||
)
|
||||
|
||||
public static let proxyDefaults: NetCfg = NetCfg(
|
||||
static let proxyDefaults: NetCfg = NetCfg(
|
||||
tcpConnectTimeout: 35_000_000,
|
||||
tcpTimeout: 20_000_000,
|
||||
tcpTimeoutPerKb: 15_000,
|
||||
rcvConcurrency: 8,
|
||||
smpPingInterval: 1200_000_000
|
||||
)
|
||||
|
||||
public var withProxyTimeouts: NetCfg {
|
||||
var cfg = self
|
||||
cfg.tcpConnectTimeout = NetCfg.proxyDefaults.tcpConnectTimeout
|
||||
cfg.tcpTimeout = NetCfg.proxyDefaults.tcpTimeout
|
||||
cfg.tcpTimeoutPerKb = NetCfg.proxyDefaults.tcpTimeoutPerKb
|
||||
cfg.rcvConcurrency = NetCfg.proxyDefaults.rcvConcurrency
|
||||
cfg.smpPingInterval = NetCfg.proxyDefaults.smpPingInterval
|
||||
return cfg
|
||||
}
|
||||
|
||||
public var hasProxyTimeouts: Bool {
|
||||
tcpConnectTimeout == NetCfg.proxyDefaults.tcpConnectTimeout &&
|
||||
tcpTimeout == NetCfg.proxyDefaults.tcpTimeout &&
|
||||
tcpTimeoutPerKb == NetCfg.proxyDefaults.tcpTimeoutPerKb &&
|
||||
rcvConcurrency == NetCfg.proxyDefaults.rcvConcurrency &&
|
||||
smpPingInterval == NetCfg.proxyDefaults.smpPingInterval
|
||||
}
|
||||
|
||||
public var enableKeepAlive: Bool { tcpKeepAlive != nil }
|
||||
}
|
||||
@@ -1344,16 +1380,16 @@ public enum SocksMode: String, Codable, Hashable {
|
||||
case onion = "onion"
|
||||
}
|
||||
|
||||
public enum SMPProxyMode: String, Codable, Hashable {
|
||||
public enum SMPProxyMode: String, Codable, Hashable, SelectableItem {
|
||||
case always = "always"
|
||||
case unknown = "unknown"
|
||||
case unprotected = "unprotected"
|
||||
case never = "never"
|
||||
|
||||
public var text: LocalizedStringKey {
|
||||
public var label: LocalizedStringKey {
|
||||
switch self {
|
||||
case .always: return "always"
|
||||
case .unknown: return "unknown relays"
|
||||
case .unknown: return "unknown servers"
|
||||
case .unprotected: return "unprotected"
|
||||
case .never: return "never"
|
||||
}
|
||||
@@ -1364,12 +1400,12 @@ public enum SMPProxyMode: String, Codable, Hashable {
|
||||
public static let values: [SMPProxyMode] = [.always, .unknown, .unprotected, .never]
|
||||
}
|
||||
|
||||
public enum SMPProxyFallback: String, Codable, Hashable {
|
||||
public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem {
|
||||
case allow = "allow"
|
||||
case allowProtected = "allowProtected"
|
||||
case prohibit = "prohibit"
|
||||
|
||||
public var text: LocalizedStringKey {
|
||||
public var label: LocalizedStringKey {
|
||||
switch self {
|
||||
case .allow: return "yes"
|
||||
case .allowProtected: return "when IP hidden"
|
||||
@@ -1896,7 +1932,7 @@ public enum DatabaseError: Decodable, Hashable {
|
||||
|
||||
public enum SQLiteError: Decodable, Hashable {
|
||||
case errorNotADatabase
|
||||
case error(String)
|
||||
case error(dbError: String)
|
||||
}
|
||||
|
||||
public enum AgentErrorType: Decodable, Hashable {
|
||||
@@ -2036,8 +2072,8 @@ public enum SMPAgentError: Decodable, Hashable {
|
||||
}
|
||||
|
||||
public enum ArchiveError: Decodable, Hashable {
|
||||
case `import`(chatError: ChatError)
|
||||
case importFile(file: String, chatError: ChatError)
|
||||
case `import`(importError: String)
|
||||
case fileError(file: String, fileError: String)
|
||||
}
|
||||
|
||||
public enum RemoteCtrlError: Decodable, Hashable {
|
||||
@@ -2120,7 +2156,8 @@ public struct AppSettings: Codable, Equatable, Hashable {
|
||||
public var uiDarkColorScheme: String? = nil
|
||||
public var uiCurrentThemeIds: [String: String]? = nil
|
||||
public var uiThemes: [ThemeOverrides]? = nil
|
||||
|
||||
public var oneHandUI: Bool? = nil
|
||||
|
||||
public func prepareForExport() -> AppSettings {
|
||||
var empty = AppSettings()
|
||||
let def = AppSettings.defaults
|
||||
@@ -2150,6 +2187,7 @@ public struct AppSettings: Codable, Equatable, Hashable {
|
||||
if uiDarkColorScheme != def.uiDarkColorScheme { empty.uiDarkColorScheme = uiDarkColorScheme }
|
||||
if uiCurrentThemeIds != def.uiCurrentThemeIds { empty.uiCurrentThemeIds = uiCurrentThemeIds }
|
||||
if uiThemes != def.uiThemes { empty.uiThemes = uiThemes }
|
||||
if oneHandUI != def.oneHandUI { empty.oneHandUI = oneHandUI }
|
||||
return empty
|
||||
}
|
||||
|
||||
@@ -2180,7 +2218,8 @@ public struct AppSettings: Codable, Equatable, Hashable {
|
||||
uiColorScheme: DefaultTheme.SYSTEM_THEME_NAME,
|
||||
uiDarkColorScheme: DefaultTheme.SIMPLEX.themeName,
|
||||
uiCurrentThemeIds: nil as [String: String]?,
|
||||
uiThemes: nil as [ThemeOverrides]?
|
||||
uiThemes: nil as [ThemeOverrides]?,
|
||||
oneHandUI: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ let GROUP_DEFAULT_NTF_PREVIEW_MODE = "ntfPreviewMode"
|
||||
public let GROUP_DEFAULT_NTF_ENABLE_LOCAL = "ntfEnableLocal" // no longer used
|
||||
public let GROUP_DEFAULT_NTF_ENABLE_PERIODIC = "ntfEnablePeriodic" // no longer used
|
||||
// replaces DEFAULT_PERFORM_LA
|
||||
let GROUP_DEFAULT_PERFORM_LA = "performLocalAuthentication"
|
||||
let GROUP_DEFAULT_APP_LOCAL_AUTH_ENABLED = "appLocalAuthEnabled"
|
||||
public let GROUP_DEFAULT_ALLOW_SHARE_EXTENSION = "allowShareExtension"
|
||||
// replaces DEFAULT_PRIVACY_LINK_PREVIEWS
|
||||
let GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews"
|
||||
@@ -55,6 +55,7 @@ public let GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE = "initialRandomDBPassphra
|
||||
public let GROUP_DEFAULT_CONFIRM_DB_UPGRADES = "confirmDBUpgrades"
|
||||
public let GROUP_DEFAULT_CALL_KIT_ENABLED = "callKitEnabled"
|
||||
public let GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED = "pqExperimentalEnabled" // no longer used
|
||||
public let GROUP_DEFAULT_ONE_HAND_UI = "oneHandUI"
|
||||
|
||||
public let APP_GROUP_NAME = "group.chat.simplex.app"
|
||||
|
||||
@@ -66,8 +67,8 @@ public func registerGroupDefaults() {
|
||||
GROUP_DEFAULT_NTF_ENABLE_PERIODIC: false,
|
||||
GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS: OnionHosts.no.rawValue,
|
||||
GROUP_DEFAULT_NETWORK_SESSION_MODE: TransportSessionMode.user.rawValue,
|
||||
GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE: SMPProxyMode.never.rawValue,
|
||||
GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK: SMPProxyFallback.allow.rawValue,
|
||||
GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE: SMPProxyMode.unknown.rawValue,
|
||||
GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK: SMPProxyFallback.allowProtected.rawValue,
|
||||
GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT: NetCfg.defaults.tcpConnectTimeout,
|
||||
GROUP_DEFAULT_NETWORK_TCP_TIMEOUT: NetCfg.defaults.tcpTimeout,
|
||||
GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB: NetCfg.defaults.tcpTimeoutPerKb,
|
||||
@@ -81,7 +82,7 @@ public func registerGroupDefaults() {
|
||||
GROUP_DEFAULT_INCOGNITO: false,
|
||||
GROUP_DEFAULT_STORE_DB_PASSPHRASE: true,
|
||||
GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE: false,
|
||||
GROUP_DEFAULT_PERFORM_LA: true,
|
||||
GROUP_DEFAULT_APP_LOCAL_AUTH_ENABLED: true,
|
||||
GROUP_DEFAULT_ALLOW_SHARE_EXTENSION: false,
|
||||
GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS: false,
|
||||
GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES: true,
|
||||
@@ -92,6 +93,7 @@ public func registerGroupDefaults() {
|
||||
GROUP_DEFAULT_CONFIRM_DB_UPGRADES: false,
|
||||
GROUP_DEFAULT_CALL_KIT_ENABLED: true,
|
||||
GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED: false,
|
||||
GROUP_DEFAULT_ONE_HAND_UI: true
|
||||
])
|
||||
}
|
||||
|
||||
@@ -203,7 +205,7 @@ public let ntfPreviewModeGroupDefault = EnumDefault<NotificationPreviewMode>(
|
||||
|
||||
public let incognitoGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_INCOGNITO)
|
||||
|
||||
public let performLAGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PERFORM_LA)
|
||||
public let appLocalAuthEnabledGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_APP_LOCAL_AUTH_ENABLED)
|
||||
|
||||
public let allowShareExtensionGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_ALLOW_SHARE_EXTENSION)
|
||||
|
||||
@@ -235,13 +237,13 @@ public let networkSessionModeGroupDefault = EnumDefault<TransportSessionMode>(
|
||||
public let networkSMPProxyModeGroupDefault = EnumDefault<SMPProxyMode>(
|
||||
defaults: groupDefaults,
|
||||
forKey: GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE,
|
||||
withDefault: .never
|
||||
withDefault: .unknown
|
||||
)
|
||||
|
||||
public let networkSMPProxyFallbackGroupDefault = EnumDefault<SMPProxyFallback>(
|
||||
defaults: groupDefaults,
|
||||
forKey: GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK,
|
||||
withDefault: .allow
|
||||
withDefault: .allowProtected
|
||||
)
|
||||
|
||||
public let storeDBPassphraseGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_STORE_DB_PASSPHRASE)
|
||||
|
||||
@@ -1289,6 +1289,15 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var chatDeleted: Bool {
|
||||
get {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.chatDeleted
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var sendMsgEnabled: Bool {
|
||||
get {
|
||||
@@ -1401,6 +1410,27 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ShowEnableCallsAlert: Hashable {
|
||||
case userEnable
|
||||
case askContact
|
||||
case other
|
||||
}
|
||||
|
||||
public var showEnableCallsAlert: ShowEnableCallsAlert {
|
||||
switch self {
|
||||
case let .direct(contact):
|
||||
if contact.mergedPreferences.calls.userPreference.preference.allow == .no {
|
||||
return .userEnable
|
||||
} else if contact.mergedPreferences.calls.contactPreference.allow == .no {
|
||||
return .askContact
|
||||
} else {
|
||||
return .other
|
||||
}
|
||||
default:
|
||||
return .other
|
||||
}
|
||||
}
|
||||
|
||||
public var ntfsEnabled: Bool {
|
||||
self.chatSettings?.enableNtfs == .all
|
||||
}
|
||||
@@ -1508,7 +1538,8 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
|
||||
var contactGroupMemberId: Int64?
|
||||
var contactGrpInvSent: Bool
|
||||
public var uiThemes: ThemeModeOverrides?
|
||||
|
||||
public var chatDeleted: Bool
|
||||
|
||||
public var id: ChatId { get { "@\(contactId)" } }
|
||||
public var apiId: Int64 { get { contactId } }
|
||||
public var ready: Bool { get { activeConn?.connStatus == .ready } }
|
||||
@@ -1575,13 +1606,15 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
|
||||
mergedPreferences: ContactUserPreferences.sampleData,
|
||||
createdAt: .now,
|
||||
updatedAt: .now,
|
||||
contactGrpInvSent: false
|
||||
contactGrpInvSent: false,
|
||||
chatDeleted: false
|
||||
)
|
||||
}
|
||||
|
||||
public enum ContactStatus: String, Decodable, Hashable {
|
||||
case active = "active"
|
||||
case deleted = "deleted"
|
||||
case deletedByUser = "deletedByUser"
|
||||
}
|
||||
|
||||
public struct ContactRef: Decodable, Equatable, Hashable {
|
||||
|
||||
@@ -72,25 +72,25 @@ extension View {
|
||||
_ errorAlert: Binding<ErrorAlert?>,
|
||||
@ViewBuilder actions: (ErrorAlert) -> A = { _ in EmptyView() }
|
||||
) -> some View {
|
||||
if let alert = errorAlert.wrappedValue {
|
||||
self.alert(
|
||||
alert.title,
|
||||
isPresented: Binding<Bool>(
|
||||
get: { errorAlert.wrappedValue != nil },
|
||||
set: { if !$0 { errorAlert.wrappedValue = nil } }
|
||||
),
|
||||
actions: {
|
||||
if let actions_ = alert.actions {
|
||||
actions_()
|
||||
} else {
|
||||
actions(alert)
|
||||
}
|
||||
},
|
||||
message: {
|
||||
if let message = alert.message { Text(message) }
|
||||
alert(
|
||||
errorAlert.wrappedValue?.title ?? "",
|
||||
isPresented: Binding<Bool>(
|
||||
get: { errorAlert.wrappedValue != nil },
|
||||
set: { if !$0 { errorAlert.wrappedValue = nil } }
|
||||
),
|
||||
actions: {
|
||||
if let actions_ = errorAlert.wrappedValue?.actions {
|
||||
actions_()
|
||||
} else {
|
||||
if let alert = errorAlert.wrappedValue { actions(alert) }
|
||||
}
|
||||
)
|
||||
} else { self }
|
||||
},
|
||||
message: {
|
||||
if let message = errorAlert.wrappedValue?.message {
|
||||
Text(message)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ public func downsampleImage(at url: URL, to size: Int64) -> UIImage? {
|
||||
if let source = CGImageSourceCreateWithURL(url as CFURL, nil) {
|
||||
CGImageSourceCreateThumbnailAtIndex(
|
||||
source,
|
||||
Int.zero,
|
||||
0,
|
||||
[
|
||||
kCGImageSourceCreateThumbnailFromImageAlways: true,
|
||||
kCGImageSourceShouldCacheImmediately: true,
|
||||
@@ -433,17 +433,25 @@ private let squareToCircleRatio = 0.935
|
||||
|
||||
private let radiusFactor = (1 - squareToCircleRatio) / 50
|
||||
|
||||
@ViewBuilder public func clipProfileImage(_ img: Image, size: CGFloat, radius: Double) -> some View {
|
||||
let v = img.resizable()
|
||||
@ViewBuilder public func clipProfileImage(_ img: Image, size: CGFloat, radius: Double, blurred: Bool = false) -> some View {
|
||||
if radius >= 50 {
|
||||
v.frame(width: size, height: size).clipShape(Circle())
|
||||
blurredFrame(img, size, blurred).clipShape(Circle())
|
||||
} else if radius <= 0 {
|
||||
let sz = size * squareToCircleRatio
|
||||
v.frame(width: sz, height: sz).padding((size - sz) / 2)
|
||||
blurredFrame(img, sz, blurred).padding((size - sz) / 2)
|
||||
} else {
|
||||
let sz = size * (squareToCircleRatio + radius * radiusFactor)
|
||||
v.frame(width: sz, height: sz)
|
||||
blurredFrame(img, sz, blurred)
|
||||
.clipShape(RoundedRectangle(cornerRadius: sz * radius / 100, style: .continuous))
|
||||
.padding((size - sz) / 2)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func blurredFrame(_ img: Image, _ size: CGFloat, _ blurred: Bool) -> some View {
|
||||
let v = img.resizable().frame(width: size, height: size)
|
||||
if blurred {
|
||||
v.blur(radius: size / 4)
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +335,8 @@
|
||||
"above, then choose:" = "по-горе, след това избери:";
|
||||
|
||||
/* accept contact request via notification
|
||||
accept incoming call via notification */
|
||||
accept incoming call via notification
|
||||
swipe action */
|
||||
"Accept" = "Приеми";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -344,7 +345,8 @@
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "Приемане на заявка за контакт от %@?";
|
||||
|
||||
/* accept contact request via notification */
|
||||
/* accept contact request via notification
|
||||
swipe action */
|
||||
"Accept incognito" = "Приеми инкогнито";
|
||||
|
||||
/* call status */
|
||||
@@ -641,7 +643,7 @@
|
||||
/* rcv group event chat item */
|
||||
"blocked %@" = "блокиран %@";
|
||||
|
||||
/* blocked chat item */
|
||||
/* marked deleted chat item preview text */
|
||||
"blocked by admin" = "блокиран от админ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -807,7 +809,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Choose from library" = "Избери от библиотеката";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Clear" = "Изчисти";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -984,9 +986,6 @@
|
||||
/* notification */
|
||||
"Contact is connected" = "Контактът е свързан";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact is not connected yet!" = "Контактът все още не е свързан!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact name" = "Име на контакт";
|
||||
|
||||
@@ -1161,7 +1160,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "по подразбиране (да)";
|
||||
|
||||
/* chat item action */
|
||||
/* chat item action
|
||||
swipe action */
|
||||
"Delete" = "Изтрий";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1200,12 +1200,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact" = "Изтрий контакт";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "Изтрий контакт";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact?\nThis cannot be undone!" = "Изтрий контакт?\nТова не може да бъде отменено!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Изтрий базата данни";
|
||||
|
||||
@@ -1260,9 +1254,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete old database?" = "Изтрий старата база данни?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection" = "Изтрий предстоящата връзка";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection?" = "Изтрий предстоящата връзка?";
|
||||
|
||||
@@ -1653,9 +1644,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting connection" = "Грешка при изтриване на връзката";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting contact" = "Грешка при изтриване на контакт";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting database" = "Грешка при изтриване на базата данни";
|
||||
|
||||
@@ -1816,7 +1804,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Faster joining and more reliable messages." = "По-бързо присъединяване и по-надеждни съобщения.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Favorite" = "Любим";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2284,7 +2272,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Japanese interface" = "Японски интерфейс";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Join" = "Присъединяване";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2335,7 +2323,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Learn more" = "Научете повече";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Leave" = "Напусни";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2563,13 +2551,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Multiple chat profiles" = "Множество профили за чат";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Mute" = "Без звук";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Muted when inactive!" = "Без звук при неактивност!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Name" = "Име";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2719,10 +2707,10 @@
|
||||
"One-time invitation link" = "Линк за еднократна покана";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be required for connection. Requires enabling VPN." = "За свързване ще са необходими Onion хостове. Изисква се активиране на VPN.";
|
||||
"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "За свързване ще са **необходими** Onion хостове.\nИзисква се активиране на VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be used when available. Requires enabling VPN." = "Ще се използват Onion хостове, когато са налични. Изисква се активиране на VPN.";
|
||||
"Onion hosts will be used when available.\nRequires compatible VPN." = "Ще се използват Onion хостове, когато са налични.\nИзисква се активиране на VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will not be used." = "Няма се използват Onion хостове.";
|
||||
@@ -3021,7 +3009,7 @@
|
||||
/* chat item menu */
|
||||
"React…" = "Реагирай…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Read" = "Прочетено";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3096,7 +3084,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reduced battery usage" = "Намалена консумация на батерията";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
/* reject incoming call via notification
|
||||
swipe action */
|
||||
"Reject" = "Отхвърляне";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3204,9 +3193,6 @@
|
||||
/* chat item action */
|
||||
"Reveal" = "Покажи";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revert" = "Отмени промените";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revoke" = "Отзови";
|
||||
|
||||
@@ -3336,7 +3322,7 @@
|
||||
/* chat item text */
|
||||
"security code changed" = "кодът за сигурност е променен";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat item action */
|
||||
"Select" = "Избери";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3363,9 +3349,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"send direct message" = "изпрати лично съобщение";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Изпрати лично съобщение";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "Изпрати лично съобщение за свързване";
|
||||
|
||||
@@ -3588,9 +3571,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Small groups (max 20)" = "Малки групи (максимум 20)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SMP servers" = "SMP сървъри";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Някои не-фатални грешки са възникнали по време на импортиране - може да видите конзолата за повече подробности.";
|
||||
|
||||
@@ -3690,9 +3670,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to scan" = "Докосни за сканиране";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "Докосни за започване на нов чат";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP connection timeout" = "Времето на изчакване за установяване на TCP връзка";
|
||||
|
||||
@@ -3906,7 +3883,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "Неочаквано състояние на миграция";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unfav." = "Премахни от любимите";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3954,10 +3931,10 @@
|
||||
/* authentication reason */
|
||||
"Unlock app" = "Отключи приложението";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unmute" = "Уведомявай";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unread" = "Непрочетено";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3966,18 +3943,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Update" = "Актуализация";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update .onion hosts setting?" = "Актуализиране на настройката за .onion хостове?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update database passphrase" = "Актуализирай паролата на базата данни";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update network settings?" = "Актуализиране на мрежовите настройки?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update transport isolation mode?" = "Актуализиране на режима на изолация на транспорта?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "актуализиран профил на групата";
|
||||
|
||||
@@ -3987,9 +3958,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Updating settings will re-connect the client to all servers." = "Актуализирането на настройките ще свърже отново клиента към всички сървъри.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating this setting will re-connect the client to all servers." = "Актуализирането на тази настройка ще свърже повторно клиента към всички сървъри.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "Актуализирай и отвори чата";
|
||||
|
||||
@@ -4038,9 +4006,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"User profile" = "Потребителски профил";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using .onion hosts requires compatible VPN provider." = "Използването на .onion хостове изисква съвместим VPN доставчик.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using SimpleX Chat servers." = "Използват се сървърите на SimpleX Chat.";
|
||||
|
||||
@@ -4209,9 +4174,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong passphrase!" = "Грешна парола!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP servers" = "XFTP сървъри";
|
||||
|
||||
/* pref value */
|
||||
"yes" = "да";
|
||||
|
||||
@@ -4347,9 +4309,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Вече сте направили заявката за връзка!\nИзпрати отново заявката за свързване?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have no chats" = "Нямате чатове";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Трябва да въвеждате парола при всяко стартиране на приложението - тя не се съхранява на устройството.";
|
||||
|
||||
@@ -4440,9 +4399,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Вашите чат профили";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Вашият контакт трябва да бъде онлайн, за да осъществите връзката.\nМожете да откажете тази връзка и да премахнете контакта (и да опитате по -късно с нов линк).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Вашият контакт изпрати файл, който е по-голям от поддържания в момента максимален размер (%@).";
|
||||
|
||||
|
||||
@@ -287,7 +287,8 @@
|
||||
"above, then choose:" = "výše, pak vyberte:";
|
||||
|
||||
/* accept contact request via notification
|
||||
accept incoming call via notification */
|
||||
accept incoming call via notification
|
||||
swipe action */
|
||||
"Accept" = "Přijmout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -296,7 +297,8 @@
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "Přijmout žádost o kontakt od %@?";
|
||||
|
||||
/* accept contact request via notification */
|
||||
/* accept contact request via notification
|
||||
swipe action */
|
||||
"Accept incognito" = "Přijmout inkognito";
|
||||
|
||||
/* call status */
|
||||
@@ -657,7 +659,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Choose from library" = "Vybrat z knihovny";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Clear" = "Vyčistit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -786,9 +788,6 @@
|
||||
/* notification */
|
||||
"Contact is connected" = "Kontakt je připojen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact is not connected yet!" = "Kontakt ještě není připojen!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact name" = "Jméno kontaktu";
|
||||
|
||||
@@ -939,7 +938,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "výchozí (ano)";
|
||||
|
||||
/* chat item action */
|
||||
/* chat item action
|
||||
swipe action */
|
||||
"Delete" = "Smazat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -972,9 +972,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact" = "Smazat kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "Smazat kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Odstranění databáze";
|
||||
|
||||
@@ -1026,9 +1023,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete old database?" = "Smazat starou databázi?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection" = "Smazat čekající připojení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection?" = "Smazat čekající připojení?";
|
||||
|
||||
@@ -1353,9 +1347,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting connection" = "Chyba při mazání připojení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting contact" = "Chyba mazání kontaktu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting database" = "Chyba při mazání databáze";
|
||||
|
||||
@@ -1486,7 +1477,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Fast and no wait until the sender is online!" = "Rychle a bez čekání, než bude odesílatel online!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Favorite" = "Oblíbené";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1870,7 +1861,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Japanese interface" = "Japonské rozhraní";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Join" = "Připojte se na";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1900,7 +1891,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Learn more" = "Zjistit více";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Leave" = "Opustit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2077,13 +2068,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Multiple chat profiles" = "Více chatovacích profilů";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Mute" = "Ztlumit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Muted when inactive!" = "Ztlumit při neaktivitě!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Name" = "Jméno";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2215,10 +2206,10 @@
|
||||
"One-time invitation link" = "Jednorázový zvací odkaz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be required for connection. Requires enabling VPN." = "Pro připojení budou vyžadováni Onion hostitelé. Vyžaduje povolení sítě VPN.";
|
||||
"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Pro připojení budou vyžadováni Onion hostitelé.\nVyžaduje povolení sítě VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be used when available. Requires enabling VPN." = "Onion hostitelé budou použiti, pokud jsou k dispozici. Vyžaduje povolení sítě VPN.";
|
||||
"Onion hosts will be used when available.\nRequires compatible VPN." = "Onion hostitelé budou použiti, pokud jsou k dispozici.\nVyžaduje povolení sítě VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will not be used." = "Onion hostitelé nebudou použiti.";
|
||||
@@ -2445,7 +2436,7 @@
|
||||
/* chat item menu */
|
||||
"React…" = "Reagovat…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Read" = "Číst";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2511,7 +2502,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reduced battery usage" = "Snížení spotřeby baterie";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
/* reject incoming call via notification
|
||||
swipe action */
|
||||
"Reject" = "Odmítnout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2595,9 +2587,6 @@
|
||||
/* chat item action */
|
||||
"Reveal" = "Odhalit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revert" = "Vrátit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revoke" = "Odvolat";
|
||||
|
||||
@@ -2700,7 +2689,7 @@
|
||||
/* chat item text */
|
||||
"security code changed" = "bezpečnostní kód změněn";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat item action */
|
||||
"Select" = "Vybrat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2727,9 +2716,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"send direct message" = "odeslat přímou zprávu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Odeslat přímou zprávu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "Odeslat přímou zprávu pro připojení";
|
||||
|
||||
@@ -2922,9 +2908,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Small groups (max 20)" = "Malé skupiny (max. 20)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SMP servers" = "SMP servery";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Během importu došlo k nezávažným chybám - podrobnosti naleznete v chat konzoli.";
|
||||
|
||||
@@ -3000,9 +2983,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to join incognito" = "Klepnutím se připojíte inkognito";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "Klepnutím na zahájíte nový chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP connection timeout" = "Časový limit připojení TCP";
|
||||
|
||||
@@ -3168,7 +3148,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "Neočekávaný stav přenášení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unfav." = "Odobl.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3207,36 +3187,27 @@
|
||||
/* authentication reason */
|
||||
"Unlock app" = "Odemknout aplikaci";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unmute" = "Zrušit ztlumení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unread" = "Nepřečtený";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update" = "Aktualizovat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update .onion hosts setting?" = "Aktualizovat nastavení hostitelů .onion?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update database passphrase" = "Aktualizovat přístupovou frázi databáze";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update network settings?" = "Aktualizovat nastavení sítě?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update transport isolation mode?" = "Aktualizovat režim dopravní izolace?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "aktualizoval profil skupiny";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating settings will re-connect the client to all servers." = "Aktualizací nastavení se klient znovu připojí ke všem serverům.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating this setting will re-connect the client to all servers." = "Aktualizace tohoto nastavení znovu připojí klienta ke všem serverům.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "Zvýšit a otevřít chat";
|
||||
|
||||
@@ -3270,9 +3241,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"User profile" = "Profil uživatele";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using .onion hosts requires compatible VPN provider." = "Použití hostitelů .onion vyžaduje kompatibilního poskytovatele VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using SimpleX Chat servers." = "Používat servery SimpleX Chat.";
|
||||
|
||||
@@ -3387,9 +3355,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong passphrase!" = "Špatná přístupová fráze!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP servers" = "XFTP servery";
|
||||
|
||||
/* pref value */
|
||||
"yes" = "ano";
|
||||
|
||||
@@ -3480,9 +3445,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "Nemohli jste být ověřeni; Zkuste to prosím znovu.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have no chats" = "Nemáte žádné konverzace";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Musíte zadat přístupovou frázi při každém spuštění aplikace - není uložena v zařízení.";
|
||||
|
||||
@@ -3564,9 +3526,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Vaše chat profily";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "K dokončení připojení, musí být váš kontakt online.\nToto připojení můžete zrušit a kontakt odebrat (a zkusit to později s novým odkazem).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Kontakt odeslal soubor, který je větší než aktuálně podporovaná maximální velikost (%@).";
|
||||
|
||||
|
||||
@@ -314,13 +314,13 @@
|
||||
"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "**Für jeden Kontakt und jedes Gruppenmitglied** wird eine separate TCP-Verbindung genutzt.\n**Bitte beachten Sie**: Wenn Sie viele Verbindungen haben, kann der Batterieverbrauch und die Datennutzung wesentlich höher sein und einige Verbindungen können scheitern.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort" = "Abbrechen";
|
||||
"Abort" = "Beenden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort changing address" = "Wechsel der Empfängeradresse abbrechen";
|
||||
"Abort changing address" = "Wechsel der Empfängeradresse beenden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort changing address?" = "Wechsel der Empfängeradresse abbrechen?";
|
||||
"Abort changing address?" = "Wechsel der Empfängeradresse beenden?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"About SimpleX" = "Über SimpleX";
|
||||
@@ -338,7 +338,8 @@
|
||||
"Accent" = "Akzent";
|
||||
|
||||
/* accept contact request via notification
|
||||
accept incoming call via notification */
|
||||
accept incoming call via notification
|
||||
swipe action */
|
||||
"Accept" = "Annehmen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -347,7 +348,8 @@
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "Die Kontaktanfrage von %@ annehmen?";
|
||||
|
||||
/* accept contact request via notification */
|
||||
/* accept contact request via notification
|
||||
swipe action */
|
||||
"Accept incognito" = "Inkognito akzeptieren";
|
||||
|
||||
/* call status */
|
||||
@@ -399,7 +401,7 @@
|
||||
"Address" = "Adresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Address change will be aborted. Old receiving address will be used." = "Der Wechsel der Empfängeradresse wird abgebrochen. Die bisherige Adresse wird weiter verwendet.";
|
||||
"Address change will be aborted. Old receiving address will be used." = "Der Wechsel der Empfängeradresse wird beendet. Die bisherige Adresse wird weiter verwendet.";
|
||||
|
||||
/* member role */
|
||||
"admin" = "Admin";
|
||||
@@ -470,6 +472,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Allow calls only if your contact allows them." = "Erlauben Sie Anrufe nur dann, wenn es Ihr Kontakt ebenfalls erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow calls?" = "Anrufe erlauben?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow disappearing messages only if your contact allows it to you." = "Erlauben Sie verschwindende Nachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt.";
|
||||
|
||||
@@ -491,6 +496,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Allow sending disappearing messages." = "Das Senden von verschwindenden Nachrichten erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow sharing" = "Teilen erlauben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages. (24 hours)" = "Unwiederbringliches löschen von gesendeten Nachrichten erlauben. (24 Stunden)";
|
||||
|
||||
@@ -587,6 +595,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Archive and upload" = "Archivieren und Hochladen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Archive contacts to chat later." = "Kontakte für spätere Chats archivieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Archived contacts" = "Archivierte Kontakte";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Archiving database" = "Datenbank wird archiviert";
|
||||
|
||||
@@ -662,6 +676,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Better messages" = "Verbesserungen bei Nachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Better networking" = "Bessere Vernetzung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Black" = "Schwarz";
|
||||
|
||||
@@ -689,12 +706,18 @@
|
||||
/* rcv group event chat item */
|
||||
"blocked %@" = "%@ wurde blockiert";
|
||||
|
||||
/* blocked chat item */
|
||||
/* marked deleted chat item preview text */
|
||||
"blocked by admin" = "wurde vom Administrator blockiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blocked by admin" = "wurde vom Administrator blockiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blur for better privacy." = "Für bessere Privatsphäre verpixeln.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blur media" = "Medium unscharf machen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"bold" = "fett";
|
||||
|
||||
@@ -719,6 +742,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Per Chat-Profil (Voreinstellung) oder [per Verbindung](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"call" = "Anrufen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Call already ended!" = "Anruf ist bereits beendet!";
|
||||
|
||||
@@ -734,15 +760,27 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Calls" = "Anrufe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Calls prohibited!" = "Anrufe nicht zugelassen!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Camera not available" = "Kamera nicht verfügbar";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't call contact" = "Kontakt kann nicht angerufen werden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't call member" = "Mitglied kann nicht angerufen werden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't invite contact!" = "Kontakt kann nicht eingeladen werden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't invite contacts!" = "Kontakte können nicht eingeladen werden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't message member" = "Mitglied kann nicht benachrichtigt werden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Abbrechen";
|
||||
|
||||
@@ -828,6 +866,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chat database deleted" = "Chat-Datenbank gelöscht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat database exported" = "Chat-Datenbank wurde exportiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat database imported" = "Chat-Datenbank importiert";
|
||||
|
||||
@@ -840,6 +881,9 @@
|
||||
/* 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." = "Der Chat ist angehalten. Wenn Sie diese Datenbank bereits auf einem anderen Gerät genutzt haben, sollten Sie diese vor dem Starten des Chats wieder zurückspielen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat list" = "Chat-Liste";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat migrated!" = "Chat wurde migriert!";
|
||||
|
||||
@@ -876,7 +920,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chunks uploaded" = "Daten-Pakete hochgeladen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Clear" = "Löschen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -891,6 +935,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Clear verification" = "Überprüfung zurücknehmen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Color chats with the new themes." = "Farbige Chats mit neuen Designs.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Color mode" = "Farbvariante";
|
||||
|
||||
@@ -918,6 +965,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm" = "Bestätigen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm contact deletion?" = "Löschen des Kontakts bestätigen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm database upgrades" = "Datenbank-Aktualisierungen bestätigen";
|
||||
|
||||
@@ -957,6 +1007,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"connect to SimpleX Chat developers." = "Mit den SimpleX Chat-Entwicklern verbinden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect to your friends faster." = "Schneller mit Ihren Freunden verbinden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect to yourself?" = "Mit Ihnen selbst verbinden?";
|
||||
|
||||
@@ -1023,6 +1076,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting server… (error: %@)" = "Mit dem Server verbinden… (Fehler: %@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting to contact, please wait or check later!" = "Verbinde mit Kontakt, bitte warten oder später erneut überprüfen!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting to desktop" = "Mit dem Desktop verbinden";
|
||||
|
||||
@@ -1032,6 +1088,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connection" = "Verbindung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection and servers status." = "Verbindungs- und Server-Status.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection error" = "Verbindungsfehler";
|
||||
|
||||
@@ -1041,6 +1100,9 @@
|
||||
/* chat list item title (it should not be shown */
|
||||
"connection established" = "Verbindung hergestellt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection notifications" = "Verbindungsbenachrichtigungen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection request sent!" = "Verbindungsanfrage wurde gesendet!";
|
||||
|
||||
@@ -1068,6 +1130,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact already exists" = "Der Kontakt ist bereits vorhanden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact deleted!" = "Kontakt gelöscht!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"contact has e2e encryption" = "Kontakt nutzt E2E-Verschlüsselung";
|
||||
|
||||
@@ -1081,7 +1146,7 @@
|
||||
"Contact is connected" = "Mit Ihrem Kontakt verbunden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact is not connected yet!" = "Ihr Kontakt ist noch nicht verbunden!";
|
||||
"Contact is deleted." = "Kontakt wurde gelöscht.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact name" = "Kontaktname";
|
||||
@@ -1089,6 +1154,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact preferences" = "Kontakt-Präferenzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact will be deleted - this cannot be undone!" = "Kontakt wird gelöscht. Dies kann nicht rückgängig gemacht werden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contacts" = "Kontakte";
|
||||
|
||||
@@ -1098,6 +1166,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "Weiter";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Conversation deleted!" = "Unterhaltung gelöscht!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Copy" = "Kopieren";
|
||||
|
||||
@@ -1278,9 +1349,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "Voreinstellung (Ja)";
|
||||
|
||||
/* chat item action */
|
||||
/* chat item action
|
||||
swipe action */
|
||||
"Delete" = "Löschen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete %lld messages of members?" = "%lld Nachrichten der Mitglieder löschen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete %lld messages?" = "%lld Nachrichten löschen?";
|
||||
|
||||
@@ -1318,10 +1393,7 @@
|
||||
"Delete contact" = "Kontakt löschen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "Kontakt löschen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact?\nThis cannot be undone!" = "Kontakt löschen?\nDies kann nicht rückgängig gemacht werden!";
|
||||
"Delete contact?" = "Kontakt löschen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Datenbank löschen";
|
||||
@@ -1377,9 +1449,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete old database?" = "Alte Datenbank löschen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection" = "Ausstehende Verbindung löschen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection?" = "Ausstehende Verbindung löschen?";
|
||||
|
||||
@@ -1389,9 +1458,15 @@
|
||||
/* server test step */
|
||||
"Delete queue" = "Lösche Warteschlange";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete up to 20 messages at once." = "Löschen Sie bis zu 20 Nachrichten auf einmal.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete user profile?" = "Benutzerprofil löschen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete without notification" = "Ohne Benachrichtigung löschen";
|
||||
|
||||
/* deleted chat item */
|
||||
"deleted" = "Gelöscht";
|
||||
|
||||
@@ -1434,9 +1509,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Desktop devices" = "Desktop-Geräte";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Destination server address of %@ is incompatible with forwarding server %@ settings." = "Adresse des Zielservers von %@ ist nicht kompatibel mit den Einstellungen des Weiterleitungsservers %@.";
|
||||
|
||||
/* snd error text */
|
||||
"Destination server error: %@" = "Zielserver-Fehler: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Destination server version of %@ is incompatible with forwarding server %@." = "Die Version des Zielservers %@ ist nicht kompatibel mit dem Weiterleitungsserver %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Detailed statistics" = "Detaillierte Statistiken";
|
||||
|
||||
@@ -1446,6 +1527,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Develop" = "Entwicklung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Developer options" = "Optionen für Entwickler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Developer tools" = "Entwicklertools";
|
||||
|
||||
@@ -1485,6 +1569,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"disabled" = "deaktiviert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disabled" = "Deaktiviert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disappearing message" = "Verschwindende Nachricht";
|
||||
|
||||
@@ -1632,6 +1719,9 @@
|
||||
/* enabled status */
|
||||
"enabled" = "Aktiviert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enabled" = "Aktiviert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enabled for" = "Aktiviert für";
|
||||
|
||||
@@ -1753,7 +1843,7 @@
|
||||
"Error" = "Fehler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error aborting address change" = "Fehler beim Abbrechen des Adresswechsels";
|
||||
"Error aborting address change" = "Fehler beim Beenden des Adresswechsels";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error accepting contact request" = "Fehler beim Annehmen der Kontaktanfrage";
|
||||
@@ -1773,6 +1863,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error changing setting" = "Fehler beim Ändern der Einstellung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error connecting to forwarding server %@. Please try later." = "Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es später erneut.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating address" = "Fehler beim Erstellen der Adresse";
|
||||
|
||||
@@ -1803,9 +1896,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting connection" = "Fehler beim Löschen der Verbindung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting contact" = "Fehler beim Löschen des Kontakts";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting database" = "Fehler beim Löschen der Datenbank";
|
||||
|
||||
@@ -1987,7 +2077,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Faster joining and more reliable messages." = "Schnellerer Gruppenbeitritt und zuverlässigere Nachrichtenzustellung.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Favorite" = "Favorit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2086,6 +2176,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarded from" = "Weitergeleitet aus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Weiterleitungsserver %@ konnte sich nicht mit dem Zielserver %@ verbinden. Bitte versuchen Sie es später erneut.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server address is incompatible with network settings: %@." = "Adresse des Weiterleitungsservers ist nicht kompatibel mit den Netzwerkeinstellungen: %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server version is incompatible with network settings: %@." = "Version des Weiterleitungsservers ist nicht kompatibel mit den Netzwerkeinstellungen: %@.";
|
||||
|
||||
/* snd error text */
|
||||
"Forwarding server: %@\nDestination server error: %@" = "Weiterleitungsserver: %1$@\nZielserver Fehler: %2$@";
|
||||
|
||||
@@ -2437,6 +2536,9 @@
|
||||
/* group name */
|
||||
"invitation to group %@" = "Einladung zur Gruppe %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"invite" = "Einladen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invite friends" = "Freunde einladen";
|
||||
|
||||
@@ -2482,6 +2584,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Dies kann unter folgenden Umständen passieren:\n1. Die Nachrichten verfallen auf dem sendenden Client-System nach 2 Tagen oder auf dem Server nach 30 Tagen.\n2. Die Nachrichten-Entschlüsselung ist fehlgeschlagen, da von Ihnen oder Ihrem Kontakt ein altes Datenbank-Backup genutzt wurde.\n3. Die Verbindung wurde kompromittiert.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"It protects your IP address and connections." = "Ihre IP-Adresse und Verbindungen werden geschützt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Es sieht so aus, als ob Sie bereits über diesen Link verbunden sind. Wenn das nicht der Fall ist, gab es einen Fehler (%@).";
|
||||
|
||||
@@ -2494,7 +2599,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Japanese interface" = "Japanische Benutzeroberfläche";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Join" = "Beitreten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2524,6 +2629,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Keep" = "Behalten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep conversation" = "Unterhaltung behalten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep the app open to use it from desktop" = "Die App muss geöffnet bleiben, um sie vom Desktop aus nutzen zu können";
|
||||
|
||||
@@ -2545,7 +2653,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Learn more" = "Mehr erfahren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Leave" = "Verlassen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2635,6 +2743,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Max 30 seconds, received instantly." = "Max. 30 Sekunden, sofort erhalten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Media & file servers" = "Medien- und Datei-Server";
|
||||
|
||||
/* blur media */
|
||||
"Medium" = "Medium";
|
||||
|
||||
/* member role */
|
||||
"member" = "Mitglied";
|
||||
|
||||
@@ -2662,6 +2776,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Menus" = "Menüs";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"message" = "Nachricht";
|
||||
|
||||
/* item status text */
|
||||
"Message delivery error" = "Fehler bei der Nachrichtenzustellung";
|
||||
|
||||
@@ -2699,10 +2816,7 @@
|
||||
"Message reception" = "Nachrichtenempfang";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message routing fallback" = "Fallback für das Nachrichten-Routing";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message routing mode" = "Modus für das Nachrichten-Routing";
|
||||
"Message servers" = "Nachrichten-Server";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message source remains private." = "Die Nachrichtenquelle bleibt privat.";
|
||||
@@ -2813,12 +2927,15 @@
|
||||
"Multiple chat profiles" = "Mehrere Chat-Profile";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"mute" = "Stummschalten";
|
||||
|
||||
/* swipe action */
|
||||
"Mute" = "Stummschalten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Muted when inactive!" = "Bei Inaktivität stummgeschaltet!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Name" = "Name";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2845,6 +2962,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New chat" = "Neuer Chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New chat experience 🎉" = "Neue Chat-Erfahrung 🎉";
|
||||
|
||||
/* notification */
|
||||
"New contact request" = "Neue Kontaktanfrage";
|
||||
|
||||
@@ -2863,6 +2983,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New in %@" = "Neu in %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New media options" = "Neue Medien-Optionen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New member role" = "Neue Mitgliedsrolle";
|
||||
|
||||
@@ -2932,6 +3055,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "Nicht kompatibel!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Nothing selected" = "Nichts ausgewählt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Notifications" = "Benachrichtigungen";
|
||||
|
||||
@@ -2977,10 +3103,10 @@
|
||||
"One-time invitation link" = "Einmal-Einladungslink";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be required for connection. Requires enabling VPN." = "Für diese Verbindung werden Onion-Hosts benötigt. Dies erfordert die Aktivierung eines VPNs.";
|
||||
"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Für diese Verbindung werden Onion-Hosts benötigt.\nDies erfordert die Aktivierung eines VPNs.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be used when available. Requires enabling VPN." = "Wenn Onion-Hosts verfügbar sind, werden sie verwendet. Dies erfordert die Aktivierung eines VPNs.";
|
||||
"Onion hosts will be used when available.\nRequires compatible VPN." = "Wenn Onion-Hosts verfügbar sind, werden sie verwendet.\nDies erfordert die Aktivierung eines VPNs.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will not be used." = "Onion-Hosts werden nicht verwendet.";
|
||||
@@ -2988,6 +3114,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only delete conversation" = "Nur die Unterhaltung löschen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can change group preferences." = "Gruppen-Präferenzen können nur von Gruppen-Eigentümern geändert werden.";
|
||||
|
||||
@@ -3144,6 +3273,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"PING interval" = "PING-Intervall";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Play from the chat list." = "Direkt aus der Chat-Liste abspielen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please ask your contact to enable calls." = "Bitten Sie Ihren Kontakt darum, Anrufe zu aktivieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please ask your contact to enable sending voice messages." = "Bitten Sie Ihren Kontakt darum, das Senden von Sprachnachrichten zu aktivieren.";
|
||||
|
||||
@@ -3324,10 +3459,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Rate the app" = "Bewerten Sie die App";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reachable chat toolbar" = "Erreichbare Chat-Symbolleiste";
|
||||
|
||||
/* chat item menu */
|
||||
"React…" = "Reagiere…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Read" = "Gelesen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3429,7 +3567,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reduced battery usage" = "Reduzierter Batterieverbrauch";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
/* reject incoming call via notification
|
||||
swipe action */
|
||||
"Reject" = "Ablehnen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3510,6 +3649,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reset" = "Zurücksetzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reset all hints" = "Alle Hinweise zurücksetzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reset all statistics" = "Alle Statistiken zurücksetzen";
|
||||
|
||||
@@ -3552,9 +3694,6 @@
|
||||
/* chat item action */
|
||||
"Reveal" = "Aufdecken";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revert" = "Zurückkehren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revoke" = "Widerrufen";
|
||||
|
||||
@@ -3588,6 +3727,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Save and notify group members" = "Speichern und Gruppenmitglieder benachrichtigen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Save and reconnect" = "Speichern und neu verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Save and update group profile" = "Gruppen-Profil sichern und aktualisieren";
|
||||
|
||||
@@ -3663,6 +3805,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Scan server QR code" = "Scannen Sie den QR-Code des Servers";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"search" = "Suchen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Search" = "Suche";
|
||||
|
||||
@@ -3699,9 +3844,12 @@
|
||||
/* chat item text */
|
||||
"security code changed" = "Sicherheitscode wurde geändert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat item action */
|
||||
"Select" = "Auswählen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected %lld" = "%lld ausgewählt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt.";
|
||||
|
||||
@@ -3729,9 +3877,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"send direct message" = "Direktnachricht senden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Direktnachricht senden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "Eine Direktnachricht zum Verbinden senden";
|
||||
|
||||
@@ -3747,6 +3892,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send live message" = "Live Nachricht senden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send message to enable calls." = "Nachricht senden, um Anrufe zu aktivieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Nachrichten werden direkt versendet, wenn die IP-Adresse geschützt ist, und Ihr oder der Zielserver kein privates Routing unterstützt.";
|
||||
|
||||
@@ -3927,12 +4075,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Share address with contacts?" = "Die Adresse mit Kontakten teilen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share from other apps." = "Aus anderen Apps heraus teilen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share link" = "Link teilen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share this 1-time invite link" = "Teilen Sie diesen Einmal-Einladungslink";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share to SimpleX" = "Mit SimpleX teilen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Mit Kontakten teilen";
|
||||
|
||||
@@ -4026,12 +4180,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"SMP server" = "SMP-Server";
|
||||
|
||||
/* blur media */
|
||||
"Soft" = "Weich";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SMP servers" = "SMP-Server";
|
||||
"Some file(s) were not exported:" = "Einzelne Datei(en) wurde(n) nicht exportiert:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Während des Imports sind einige nicht schwerwiegende Fehler aufgetreten - in der Chat-Konsole finden Sie weitere Einzelheiten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import:" = "Während des Imports traten ein paar nicht schwerwiegende Fehler auf:";
|
||||
|
||||
/* notification title */
|
||||
"Somebody" = "Jemand";
|
||||
|
||||
@@ -4098,6 +4258,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"strike" = "durchstreichen";
|
||||
|
||||
/* blur media */
|
||||
"Strong" = "Hart";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Submit" = "Bestätigen";
|
||||
|
||||
@@ -4144,7 +4307,7 @@
|
||||
"Tap to scan" = "Zum Scannen tippen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "Zum Starten eines neuen Chats tippen";
|
||||
"TCP connection" = "TCP-Verbindung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP connection timeout" = "Timeout der TCP-Verbindung";
|
||||
@@ -4221,6 +4384,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The message will be marked as moderated for all members." = "Diese Nachricht wird für alle Mitglieder als moderiert gekennzeichnet.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The messages will be deleted for all members." = "Die Nachrichten werden für alle Mitglieder gelöscht werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The messages will be marked as moderated for all members." = "Die Nachrichten werden für alle Mitglieder als moderiert gekennzeichnet werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The next generation of private messaging" = "Die nächste Generation von privatem Messaging";
|
||||
|
||||
@@ -4332,9 +4501,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Um die Ende-zu-Ende-Verschlüsselung mit Ihrem Kontakt zu überprüfen, müssen Sie den Sicherheitscode in Ihren Apps vergleichen oder scannen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle chat list:" = "Chat-Liste umschalten:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "Inkognito beim Verbinden einschalten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toolbar opacity" = "Deckkraft der Symbolleiste";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Total" = "Summe aller Abonnements";
|
||||
|
||||
@@ -4383,7 +4558,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "Unerwarteter Migrationsstatus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unfav." = "Fav. entf.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4411,7 +4586,7 @@
|
||||
"Unknown error" = "Unbekannter Fehler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unknown relays" = "Unbekannte Relais";
|
||||
"unknown servers" = "Unbekannte Relais";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown servers!" = "Unbekannte Server!";
|
||||
@@ -4438,12 +4613,15 @@
|
||||
"Unlock app" = "App entsperren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unmute" = "Stummschaltung aufheben";
|
||||
|
||||
/* swipe action */
|
||||
"Unmute" = "Stummschaltung aufheben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unprotected" = "Ungeschützt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unread" = "Ungelesen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4452,9 +4630,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Update" = "Aktualisieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update .onion hosts setting?" = "Einstellung für .onion-Hosts aktualisieren?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update database passphrase" = "Datenbank-Passwort aktualisieren";
|
||||
|
||||
@@ -4462,7 +4637,7 @@
|
||||
"Update network settings?" = "Netzwerkeinstellungen aktualisieren?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update transport isolation mode?" = "Transport-Isolations-Modus aktualisieren?";
|
||||
"Update settings?" = "Einstellungen aktualisieren?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "Aktualisiertes Gruppenprofil";
|
||||
@@ -4473,9 +4648,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Updating settings will re-connect the client to all servers." = "Die Aktualisierung der Einstellungen wird den Client wieder mit allen Servern verbinden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating this setting will re-connect the client to all servers." = "Die Aktualisierung dieser Einstellung wird den Client wieder mit allen Servern verbinden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "Aktualisieren und den Chat öffnen";
|
||||
|
||||
@@ -4536,15 +4708,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Use the app while in the call." = "Die App kann während eines Anrufs genutzt werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use the app with one hand." = "Die App mit einer Hand nutzen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"User profile" = "Benutzerprofil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"User selection" = "Benutzer-Auswahl";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using .onion hosts requires compatible VPN provider." = "Für die Nutzung von .onion-Hosts sind kompatible VPN-Anbieter erforderlich.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using SimpleX Chat servers." = "Verwendung von SimpleX-Chat-Servern.";
|
||||
|
||||
@@ -4593,6 +4765,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Via secure quantum resistant protocol." = "Über ein sicheres quantenbeständiges Protokoll.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"video" = "Video";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Video call" = "Videoanruf";
|
||||
|
||||
@@ -4737,9 +4912,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP server" = "XFTP-Server";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP servers" = "XFTP-Server";
|
||||
|
||||
/* pref value */
|
||||
"yes" = "Ja";
|
||||
|
||||
@@ -4806,6 +4978,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can accept calls from lock screen, without device and app authentication." = "Sie können Anrufe ohne Geräte- und App-Authentifizierung vom Sperrbildschirm aus annehmen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can change it in Appearance settings." = "Kann von Ihnen in den Erscheinungsbild-Einstellungen geändert werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can create it later" = "Sie können dies später erstellen";
|
||||
|
||||
@@ -4827,6 +5002,9 @@
|
||||
/* notification body */
|
||||
"You can now chat with %@" = "Sie können nun Nachrichten an %@ versenden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can send messages to %@ from Archived contacts." = "Sie können aus den archivierten Kontakten heraus Nachrichten an %@ versenden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can set lock screen notification preview via settings." = "Über die Geräte-Einstellungen können Sie die Benachrichtigungsvorschau im Sperrbildschirm erlauben.";
|
||||
|
||||
@@ -4842,6 +5020,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can start chat via app Settings / Database or by restarting the app" = "Sie können den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can still view conversation with %@ in the list of chats." = "Sie können in der Chatliste weiterhin die Unterhaltung mit %@ einsehen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can turn on SimpleX Lock via Settings." = "Sie können die SimpleX-Sperre über die Einstellungen aktivieren.";
|
||||
|
||||
@@ -4878,9 +5059,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Sie haben bereits ein Verbindungsanfrage beantragt!\nVerbindungsanfrage wiederholen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have no chats" = "Sie haben keine Chats";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Sie müssen das Passwort jedes Mal eingeben, wenn die App startet. Es wird nicht auf dem Gerät gespeichert.";
|
||||
|
||||
@@ -4896,9 +5074,18 @@
|
||||
/* snd group event chat item */
|
||||
"you left" = "Sie haben verlassen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You may migrate the exported database." = "Sie können die exportierte Datenbank migrieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You may save the exported archive." = "Sie können das exportierte Archiv speichern.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "Sie dürfen die neueste Version Ihrer Chat-Datenbank NUR auf einem Gerät verwenden, andernfalls erhalten Sie möglicherweise keine Nachrichten mehr von einigen Ihrer Kontakte.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You need to allow your contact to call to be able to call them." = "Sie müssen Ihrem Kontakt Anrufe zu Ihnen erlauben, bevor Sie ihn selbst anrufen können.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You need to allow your contact to send voice messages to be able to send them." = "Sie müssen Ihrem Kontakt das Senden von Sprachnachrichten erlauben, um diese senden zu können.";
|
||||
|
||||
@@ -4971,9 +5158,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Ihre Chat-Profile";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Damit die Verbindung hergestellt werden kann, muss Ihr Kontakt online sein.\nSie können diese Verbindung abbrechen und den Kontakt entfernen (und es später nochmals mit einem neuen Link versuchen).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%@).";
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -278,7 +278,8 @@
|
||||
"above, then choose:" = "edellä, valitse sitten:";
|
||||
|
||||
/* accept contact request via notification
|
||||
accept incoming call via notification */
|
||||
accept incoming call via notification
|
||||
swipe action */
|
||||
"Accept" = "Hyväksy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -287,7 +288,8 @@
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "Hyväksy kontaktipyyntö %@:ltä?";
|
||||
|
||||
/* accept contact request via notification */
|
||||
/* accept contact request via notification
|
||||
swipe action */
|
||||
"Accept incognito" = "Hyväksy tuntematon";
|
||||
|
||||
/* call status */
|
||||
@@ -642,7 +644,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Choose from library" = "Valitse kirjastosta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Clear" = "Tyhjennä";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -768,9 +770,6 @@
|
||||
/* notification */
|
||||
"Contact is connected" = "Kontakti on yhdistetty";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact is not connected yet!" = "Kontaktia ei ole vielä yhdistetty!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact name" = "Kontaktin nimi";
|
||||
|
||||
@@ -921,7 +920,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "oletusarvo (kyllä)";
|
||||
|
||||
/* chat item action */
|
||||
/* chat item action
|
||||
swipe action */
|
||||
"Delete" = "Poista";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -954,9 +954,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact" = "Poista kontakti";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "Poista kontakti";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Poista tietokanta";
|
||||
|
||||
@@ -1008,9 +1005,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete old database?" = "Poista vanha tietokanta?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection" = "Poista vireillä oleva yhteys";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection?" = "Poistetaanko odottava yhteys?";
|
||||
|
||||
@@ -1329,9 +1323,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting connection" = "Virhe yhteyden poistamisessa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting contact" = "Virhe kontaktin poistamisessa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting database" = "Virhe tietokannan poistamisessa";
|
||||
|
||||
@@ -1462,7 +1453,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Fast and no wait until the sender is online!" = "Nopea ja ei odotusta, kunnes lähettäjä on online-tilassa!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Favorite" = "Suosikki";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1846,7 +1837,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Japanese interface" = "Japanilainen käyttöliittymä";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Join" = "Liity";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1876,7 +1867,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Learn more" = "Lue lisää";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Leave" = "Poistu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2053,13 +2044,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Multiple chat profiles" = "Useita keskusteluprofiileja";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Mute" = "Mykistä";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Muted when inactive!" = "Mykistetty ei-aktiivisena!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Name" = "Nimi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2188,10 +2179,10 @@
|
||||
"One-time invitation link" = "Kertakutsulinkki";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be required for connection. Requires enabling VPN." = "Yhteyden muodostamiseen tarvitaan Onion-isäntiä. Edellyttää VPN:n sallimista.";
|
||||
"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Yhteyden muodostamiseen tarvitaan Onion-isäntiä.\nEdellyttää VPN:n sallimista.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be used when available. Requires enabling VPN." = "Onion-isäntiä käytetään, kun niitä on saatavilla. Edellyttää VPN:n sallimista.";
|
||||
"Onion hosts will be used when available.\nRequires compatible VPN." = "Onion-isäntiä käytetään, kun niitä on saatavilla.\nEdellyttää VPN:n sallimista.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will not be used." = "Onion-isäntiä ei käytetä.";
|
||||
@@ -2415,7 +2406,7 @@
|
||||
/* chat item menu */
|
||||
"React…" = "Reagoi…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Read" = "Lue";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2481,7 +2472,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reduced battery usage" = "Pienempi akun käyttö";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
/* reject incoming call via notification
|
||||
swipe action */
|
||||
"Reject" = "Hylkää";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2565,9 +2557,6 @@
|
||||
/* chat item action */
|
||||
"Reveal" = "Paljasta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revert" = "Palauta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revoke" = "Peruuta";
|
||||
|
||||
@@ -2670,7 +2659,7 @@
|
||||
/* chat item text */
|
||||
"security code changed" = "turvakoodi on muuttunut";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat item action */
|
||||
"Select" = "Valitse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2694,9 +2683,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send delivery receipts to" = "Lähetä toimituskuittaukset vastaanottajalle";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Lähetä yksityisviesti";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send disappearing message" = "Lähetä katoava viesti";
|
||||
|
||||
@@ -2883,9 +2869,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Small groups (max 20)" = "Pienryhmät (max 20)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SMP servers" = "SMP-palvelimet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Tuonnin aikana tapahtui joitakin ei-vakavia virheitä – saatat nähdä Chat-konsolissa lisätietoja.";
|
||||
|
||||
@@ -2961,9 +2944,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to join incognito" = "Napauta liittyäksesi incognito-tilassa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "Aloita uusi keskustelu napauttamalla";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP connection timeout" = "TCP-yhteyden aikakatkaisu";
|
||||
|
||||
@@ -3126,7 +3106,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "Odottamaton siirtotila";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unfav." = "Epäsuotuisa.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3165,36 +3145,27 @@
|
||||
/* authentication reason */
|
||||
"Unlock app" = "Avaa sovellus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unmute" = "Poista mykistys";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unread" = "Lukematon";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update" = "Päivitä";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update .onion hosts setting?" = "Päivitä .onion-isäntien asetus?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update database passphrase" = "Päivitä tietokannan tunnuslause";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update network settings?" = "Päivitä verkkoasetukset?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update transport isolation mode?" = "Päivitä kuljetuksen eristystila?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "päivitetty ryhmäprofiili";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating settings will re-connect the client to all servers." = "Asetusten päivittäminen yhdistää asiakkaan uudelleen kaikkiin palvelimiin.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating this setting will re-connect the client to all servers." = "Tämän asetuksen päivittäminen yhdistää asiakkaan uudelleen kaikkiin palvelimiin.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "Päivitä ja avaa keskustelu";
|
||||
|
||||
@@ -3228,9 +3199,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"User profile" = "Käyttäjäprofiili";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using .onion hosts requires compatible VPN provider." = ".onion-isäntien käyttäminen vaatii yhteensopivan VPN-palveluntarjoajan.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using SimpleX Chat servers." = "Käyttää SimpleX Chat -palvelimia.";
|
||||
|
||||
@@ -3345,9 +3313,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong passphrase!" = "Väärä tunnuslause!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP servers" = "XFTP-palvelimet";
|
||||
|
||||
/* pref value */
|
||||
"yes" = "kyllä";
|
||||
|
||||
@@ -3438,9 +3403,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "Sinua ei voitu todentaa; yritä uudelleen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have no chats" = "Sinulla ei ole keskusteluja";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Sinun on annettava tunnuslause aina, kun sovellus käynnistyy - sitä ei tallenneta laitteeseen.";
|
||||
|
||||
@@ -3522,9 +3484,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Keskusteluprofiilisi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Kontaktin tulee olla online-tilassa, jotta yhteys voidaan muodostaa.\nVoit peruuttaa tämän yhteyden ja poistaa kontaktin (ja yrittää myöhemmin uudella linkillä).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Yhteyshenkilösi lähetti tiedoston, joka on suurempi kuin tällä hetkellä tuettu enimmäiskoko (%@).";
|
||||
|
||||
|
||||
@@ -338,7 +338,8 @@
|
||||
"Accent" = "Principale";
|
||||
|
||||
/* accept contact request via notification
|
||||
accept incoming call via notification */
|
||||
accept incoming call via notification
|
||||
swipe action */
|
||||
"Accept" = "Accepter";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -347,7 +348,8 @@
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "Accepter la demande de contact de %@ ?";
|
||||
|
||||
/* accept contact request via notification */
|
||||
/* accept contact request via notification
|
||||
swipe action */
|
||||
"Accept incognito" = "Accepter en incognito";
|
||||
|
||||
/* call status */
|
||||
@@ -491,6 +493,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Allow sending disappearing messages." = "Autorise l’envoi de messages éphémères.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow sharing" = "Autoriser le partage";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages. (24 hours)" = "Autoriser la suppression irréversible de messages envoyés. (24 heures)";
|
||||
|
||||
@@ -689,12 +694,15 @@
|
||||
/* rcv group event chat item */
|
||||
"blocked %@" = "%@ bloqué";
|
||||
|
||||
/* blocked chat item */
|
||||
/* marked deleted chat item preview text */
|
||||
"blocked by admin" = "bloqué par l'administrateur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blocked by admin" = "Bloqué par l'administrateur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blur media" = "Flouter les médias";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"bold" = "gras";
|
||||
|
||||
@@ -876,7 +884,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chunks uploaded" = "Chunks téléversés";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Clear" = "Effacer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1041,6 +1049,9 @@
|
||||
/* chat list item title (it should not be shown */
|
||||
"connection established" = "connexion établie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection notifications" = "Notifications de connexion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection request sent!" = "Demande de connexion envoyée !";
|
||||
|
||||
@@ -1080,9 +1091,6 @@
|
||||
/* notification */
|
||||
"Contact is connected" = "Le contact est connecté";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact is not connected yet!" = "Le contact n'est pas encore connecté !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact name" = "Nom du contact";
|
||||
|
||||
@@ -1278,9 +1286,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "par défaut (oui)";
|
||||
|
||||
/* chat item action */
|
||||
/* chat item action
|
||||
swipe action */
|
||||
"Delete" = "Supprimer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete %lld messages of members?" = "Supprimer %lld messages de membres ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete %lld messages?" = "Supprimer %lld messages ?";
|
||||
|
||||
@@ -1317,12 +1329,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact" = "Supprimer le contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "Supprimer le contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact?\nThis cannot be undone!" = "Supprimer le contact ?\nCette opération ne peut être annulée !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Supprimer la base de données";
|
||||
|
||||
@@ -1377,9 +1383,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete old database?" = "Supprimer l'ancienne base de données ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection" = "Supprimer la connexion en attente";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection?" = "Supprimer la connexion en attente ?";
|
||||
|
||||
@@ -1434,9 +1437,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Desktop devices" = "Appareils de bureau";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Destination server address of %@ is incompatible with forwarding server %@ settings." = "L'adresse du serveur de destination %@ est incompatible avec les paramètres du serveur de redirection %@.";
|
||||
|
||||
/* snd error text */
|
||||
"Destination server error: %@" = "Erreur du serveur de destination : %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Destination server version of %@ is incompatible with forwarding server %@." = "La version du serveur de destination %@ est incompatible avec le serveur de redirection %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Detailed statistics" = "Statistiques détaillées";
|
||||
|
||||
@@ -1485,6 +1494,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"disabled" = "désactivé";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disabled" = "Désactivé";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disappearing message" = "Message éphémère";
|
||||
|
||||
@@ -1632,6 +1644,9 @@
|
||||
/* enabled status */
|
||||
"enabled" = "activé";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enabled" = "Activé";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enabled for" = "Activé pour";
|
||||
|
||||
@@ -1773,6 +1788,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error changing setting" = "Erreur de changement de paramètre";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error connecting to forwarding server %@. Please try later." = "Erreur de connexion au serveur de redirection %@. Veuillez réessayer plus tard.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating address" = "Erreur lors de la création de l'adresse";
|
||||
|
||||
@@ -1803,9 +1821,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting connection" = "Erreur lors de la suppression de la connexion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting contact" = "Erreur lors de la suppression du contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting database" = "Erreur lors de la suppression de la base de données";
|
||||
|
||||
@@ -1987,7 +2002,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Faster joining and more reliable messages." = "Connexion plus rapide et messages plus fiables.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Favorite" = "Favoris";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2086,6 +2101,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarded from" = "Transféré depuis";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Le serveur de redirection %@ n'a pas réussi à se connecter au serveur de destination %@. Veuillez réessayer plus tard.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server address is incompatible with network settings: %@." = "L'adresse du serveur de redirection est incompatible avec les paramètres du réseau : %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server version is incompatible with network settings: %@." = "La version du serveur de redirection est incompatible avec les paramètres du réseau : %@.";
|
||||
|
||||
/* snd error text */
|
||||
"Forwarding server: %@\nDestination server error: %@" = "Serveur de transfert : %1$@\nErreur du serveur de destination : %2$@";
|
||||
|
||||
@@ -2494,7 +2518,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Japanese interface" = "Interface en japonais";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Join" = "Rejoindre";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2545,7 +2569,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Learn more" = "En savoir plus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Leave" = "Quitter";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2635,6 +2659,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Max 30 seconds, received instantly." = "Max 30 secondes, réception immédiate.";
|
||||
|
||||
/* blur media */
|
||||
"Medium" = "Modéré";
|
||||
|
||||
/* member role */
|
||||
"member" = "membre";
|
||||
|
||||
@@ -2698,12 +2725,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Message reception" = "Réception de message";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message routing fallback" = "Rabattement du routage des messages";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message routing mode" = "Mode de routage des messages";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message source remains private." = "La source du message reste privée.";
|
||||
|
||||
@@ -2812,13 +2833,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Multiple chat profiles" = "Différents profils de chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Mute" = "Muet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Muted when inactive!" = "Mute en cas d'inactivité !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Name" = "Nom";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2932,6 +2953,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "Non compatible !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Nothing selected" = "Aucune sélection";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Notifications" = "Notifications";
|
||||
|
||||
@@ -2977,10 +3001,10 @@
|
||||
"One-time invitation link" = "Lien d'invitation unique";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be required for connection. Requires enabling VPN." = "Les hôtes .onion seront nécessaires pour la connexion. Nécessite l'activation d'un VPN.";
|
||||
"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Les hôtes .onion seront **nécessaires** pour la connexion.\nNécessite l'activation d'un VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be used when available. Requires enabling VPN." = "Les hôtes .onion seront utilisés dès que possible. Nécessite l'activation d'un VPN.";
|
||||
"Onion hosts will be used when available.\nRequires compatible VPN." = "Les hôtes .onion seront utilisés dès que possible.\nNécessite l'activation d'un VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will not be used." = "Les hôtes .onion ne seront pas utilisés.";
|
||||
@@ -3327,7 +3351,7 @@
|
||||
/* chat item menu */
|
||||
"React…" = "Réagissez…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Read" = "Lire";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3429,7 +3453,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reduced battery usage" = "Réduction de la consommation de batterie";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
/* reject incoming call via notification
|
||||
swipe action */
|
||||
"Reject" = "Rejeter";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3552,9 +3577,6 @@
|
||||
/* chat item action */
|
||||
"Reveal" = "Révéler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revert" = "Revenir en arrière";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revoke" = "Révoquer";
|
||||
|
||||
@@ -3699,9 +3721,12 @@
|
||||
/* chat item text */
|
||||
"security code changed" = "code de sécurité modifié";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat item action */
|
||||
"Select" = "Choisir";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected %lld" = "%lld sélectionné(s)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Les préférences de chat sélectionnées interdisent ce message.";
|
||||
|
||||
@@ -3729,9 +3754,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"send direct message" = "envoyer un message direct";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Envoyer un message direct";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "Envoyer un message direct pour vous connecter";
|
||||
|
||||
@@ -3933,6 +3955,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Share this 1-time invite link" = "Partager ce lien d'invitation unique";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share to SimpleX" = "Partager sur SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Partager avec vos contacts";
|
||||
|
||||
@@ -3963,6 +3988,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Show:" = "Afficher :";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SimpleX" = "SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SimpleX address" = "Adresse SimpleX";
|
||||
|
||||
@@ -4023,8 +4051,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"SMP server" = "Serveur SMP";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SMP servers" = "Serveurs SMP";
|
||||
/* blur media */
|
||||
"Soft" = "Léger";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Des erreurs non fatales se sont produites lors de l'importation - vous pouvez consulter la console de chat pour plus de détails.";
|
||||
@@ -4095,6 +4123,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"strike" = "barré";
|
||||
|
||||
/* blur media */
|
||||
"Strong" = "Fort";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Submit" = "Soumettre";
|
||||
|
||||
@@ -4140,9 +4171,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to scan" = "Appuyez pour scanner";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "Appuyez ici pour démarrer une nouvelle discussion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP connection timeout" = "Délai de connexion TCP";
|
||||
|
||||
@@ -4218,6 +4246,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The message will be marked as moderated for all members." = "Le message sera marqué comme modéré pour tous les membres.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The messages will be deleted for all members." = "Les messages seront supprimés pour tous les membres.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The messages will be marked as moderated for all members." = "Les messages seront marqués comme modérés pour tous les membres.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The next generation of private messaging" = "La nouvelle génération de messagerie privée";
|
||||
|
||||
@@ -4380,7 +4414,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "État de la migration inattendu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unfav." = "Unfav.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4408,7 +4442,7 @@
|
||||
"Unknown error" = "Erreur inconnue";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unknown relays" = "relais inconnus";
|
||||
"unknown servers" = "relais inconnus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown servers!" = "Serveurs inconnus !";
|
||||
@@ -4434,13 +4468,13 @@
|
||||
/* authentication reason */
|
||||
"Unlock app" = "Déverrouiller l'app";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unmute" = "Démute";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unprotected" = "non protégé";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unread" = "Non lu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4449,18 +4483,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Update" = "Mise à jour";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update .onion hosts setting?" = "Mettre à jour le paramètre des hôtes .onion ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update database passphrase" = "Mise à jour de la phrase secrète de la base de données";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update network settings?" = "Mettre à jour les paramètres réseau ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update transport isolation mode?" = "Mettre à jour le mode d'isolement du transport ?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "mise à jour du profil de groupe";
|
||||
|
||||
@@ -4470,9 +4498,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Updating settings will re-connect the client to all servers." = "La mise à jour des ces paramètres reconnectera le client à tous les serveurs.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating this setting will re-connect the client to all servers." = "La mise à jour de ce paramètre reconnectera le client à tous les serveurs.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "Mettre à niveau et ouvrir le chat";
|
||||
|
||||
@@ -4539,9 +4564,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"User selection" = "Sélection de l'utilisateur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using .onion hosts requires compatible VPN provider." = "L'utilisation des hôtes .onion nécessite un fournisseur VPN compatible.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using SimpleX Chat servers." = "Vous utilisez les serveurs SimpleX.";
|
||||
|
||||
@@ -4734,9 +4756,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP server" = "Serveur XFTP";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP servers" = "Serveurs XFTP";
|
||||
|
||||
/* pref value */
|
||||
"yes" = "oui";
|
||||
|
||||
@@ -4875,9 +4894,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Vous avez déjà demandé une connexion !\nRépéter la demande de connexion ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have no chats" = "Vous n'avez aucune discussion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Vous devez saisir la phrase secrète à chaque fois que l'application démarre - elle n'est pas stockée sur l'appareil.";
|
||||
|
||||
@@ -4968,9 +4984,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Vos profils de chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Votre contact a besoin d'être en ligne pour completer la connexion.\nVous pouvez annuler la connexion et supprimer le contact (et réessayer plus tard avec un autre lien).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Votre contact a envoyé un fichier plus grand que la taille maximale supportée actuellement(%@).";
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -338,7 +338,8 @@
|
||||
"Accent" = "Principale";
|
||||
|
||||
/* accept contact request via notification
|
||||
accept incoming call via notification */
|
||||
accept incoming call via notification
|
||||
swipe action */
|
||||
"Accept" = "Accetta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -347,7 +348,8 @@
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "Accettare la richiesta di contatto da %@?";
|
||||
|
||||
/* accept contact request via notification */
|
||||
/* accept contact request via notification
|
||||
swipe action */
|
||||
"Accept incognito" = "Accetta in incognito";
|
||||
|
||||
/* call status */
|
||||
@@ -470,6 +472,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Allow calls only if your contact allows them." = "Consenti le chiamate solo se il tuo contatto le consente.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow calls?" = "Consentire le chiamate?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow disappearing messages only if your contact allows it to you." = "Consenti i messaggi a tempo solo se il contatto li consente a te.";
|
||||
|
||||
@@ -491,6 +496,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Allow sending disappearing messages." = "Permetti l'invio di messaggi a tempo.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow sharing" = "Consenti la condivisione";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages. (24 hours)" = "Permetti di eliminare irreversibilmente i messaggi inviati. (24 ore)";
|
||||
|
||||
@@ -587,6 +595,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Archive and upload" = "Archivia e carica";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Archive contacts to chat later." = "Archivia contatti per chattare più tardi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Archived contacts" = "Contatti archiviati";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Archiving database" = "Archiviazione del database";
|
||||
|
||||
@@ -662,6 +676,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Better messages" = "Messaggi migliorati";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Better networking" = "Rete migliorata";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Black" = "Nero";
|
||||
|
||||
@@ -689,12 +706,18 @@
|
||||
/* rcv group event chat item */
|
||||
"blocked %@" = "ha bloccato %@";
|
||||
|
||||
/* blocked chat item */
|
||||
/* marked deleted chat item preview text */
|
||||
"blocked by admin" = "bloccato dall'amministratore";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blocked by admin" = "Bloccato dall'amministratore";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blur for better privacy." = "Sfoca per una privacy maggiore.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blur media" = "Sfocatura file multimediali";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"bold" = "grassetto";
|
||||
|
||||
@@ -719,6 +742,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Per profilo di chat (predefinito) o [per connessione](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"call" = "chiama";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Call already ended!" = "Chiamata già terminata!";
|
||||
|
||||
@@ -734,15 +760,27 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Calls" = "Chiamate";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Calls prohibited!" = "Chiamate proibite!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Camera not available" = "Fotocamera non disponibile";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't call contact" = "Impossibile chiamare il contatto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't call member" = "Impossibile chiamare il membro";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't invite contact!" = "Impossibile invitare il contatto!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't invite contacts!" = "Impossibile invitare i contatti!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't message member" = "Impossibile inviare un messaggio al membro";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Annulla";
|
||||
|
||||
@@ -828,6 +866,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chat database deleted" = "Database della chat eliminato";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat database exported" = "Database della chat esportato";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat database imported" = "Database della chat importato";
|
||||
|
||||
@@ -840,6 +881,9 @@
|
||||
/* 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." = "La chat è ferma. Se hai già usato questo database su un altro dispositivo, dovresti trasferirlo prima di avviare la chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat list" = "Elenco delle chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat migrated!" = "Chat migrata!";
|
||||
|
||||
@@ -876,7 +920,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chunks uploaded" = "Blocchi inviati";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Clear" = "Svuota";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -891,6 +935,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Clear verification" = "Annulla la verifica";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Color chats with the new themes." = "Colora le chat con i nuovi temi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Color mode" = "Modalità di colore";
|
||||
|
||||
@@ -918,6 +965,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm" = "Conferma";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm contact deletion?" = "Confermare l'eliminazione del contatto?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm database upgrades" = "Conferma aggiornamenti database";
|
||||
|
||||
@@ -957,6 +1007,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"connect to SimpleX Chat developers." = "connettiti agli sviluppatori di SimpleX Chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect to your friends faster." = "Connettiti più velocemente ai tuoi amici.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect to yourself?" = "Connettersi a te stesso?";
|
||||
|
||||
@@ -1023,6 +1076,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting server… (error: %@)" = "Connessione al server… (errore: %@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting to contact, please wait or check later!" = "In collegamento con il contatto, attendi o controlla più tardi!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting to desktop" = "Connessione al desktop";
|
||||
|
||||
@@ -1032,6 +1088,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connection" = "Connessione";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection and servers status." = "Stato di connessione e server.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection error" = "Errore di connessione";
|
||||
|
||||
@@ -1041,6 +1100,9 @@
|
||||
/* chat list item title (it should not be shown */
|
||||
"connection established" = "connessione stabilita";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection notifications" = "Notifiche di connessione";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection request sent!" = "Richiesta di connessione inviata!";
|
||||
|
||||
@@ -1068,6 +1130,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact already exists" = "Il contatto esiste già";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact deleted!" = "Contatto eliminato!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"contact has e2e encryption" = "il contatto ha la crittografia e2e";
|
||||
|
||||
@@ -1081,7 +1146,7 @@
|
||||
"Contact is connected" = "Il contatto è connesso";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact is not connected yet!" = "Il contatto non è ancora connesso!";
|
||||
"Contact is deleted." = "Il contatto è stato eliminato.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact name" = "Nome del contatto";
|
||||
@@ -1089,6 +1154,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact preferences" = "Preferenze del contatto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact will be deleted - this cannot be undone!" = "Il contatto verrà eliminato - non è reversibile!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contacts" = "Contatti";
|
||||
|
||||
@@ -1098,6 +1166,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "Continua";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Conversation deleted!" = "Conversazione eliminata!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Copy" = "Copia";
|
||||
|
||||
@@ -1278,9 +1349,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "predefinito (sì)";
|
||||
|
||||
/* chat item action */
|
||||
/* chat item action
|
||||
swipe action */
|
||||
"Delete" = "Elimina";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete %lld messages of members?" = "Eliminare %lld messaggi dei membri?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete %lld messages?" = "Eliminare %lld messaggi?";
|
||||
|
||||
@@ -1318,10 +1393,7 @@
|
||||
"Delete contact" = "Elimina contatto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "Elimina contatto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact?\nThis cannot be undone!" = "Eliminare il contatto?\nNon è reversibile!";
|
||||
"Delete contact?" = "Eliminare il contatto?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Elimina database";
|
||||
@@ -1377,9 +1449,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete old database?" = "Eliminare il database vecchio?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection" = "Elimina connessione in attesa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection?" = "Eliminare la connessione in attesa?";
|
||||
|
||||
@@ -1389,9 +1458,15 @@
|
||||
/* server test step */
|
||||
"Delete queue" = "Elimina coda";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete up to 20 messages at once." = "Elimina fino a 20 messaggi contemporaneamente.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete user profile?" = "Eliminare il profilo utente?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete without notification" = "Elimina senza avvisare";
|
||||
|
||||
/* deleted chat item */
|
||||
"deleted" = "eliminato";
|
||||
|
||||
@@ -1434,9 +1509,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Desktop devices" = "Dispositivi desktop";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Destination server address of %@ is incompatible with forwarding server %@ settings." = "L'indirizzo del server di destinazione di %@ è incompatibile con le impostazioni del server di inoltro %@.";
|
||||
|
||||
/* snd error text */
|
||||
"Destination server error: %@" = "Errore del server di destinazione: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Destination server version of %@ is incompatible with forwarding server %@." = "La versione del server di destinazione di %@ è incompatibile con il server di inoltro %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Detailed statistics" = "Statistiche dettagliate";
|
||||
|
||||
@@ -1446,6 +1527,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Develop" = "Sviluppa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Developer options" = "Opzioni sviluppatore";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Developer tools" = "Strumenti di sviluppo";
|
||||
|
||||
@@ -1485,6 +1569,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"disabled" = "disattivato";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disabled" = "Disattivato";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disappearing message" = "Messaggio a tempo";
|
||||
|
||||
@@ -1632,6 +1719,9 @@
|
||||
/* enabled status */
|
||||
"enabled" = "attivato";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enabled" = "Attivato";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enabled for" = "Attivo per";
|
||||
|
||||
@@ -1773,6 +1863,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error changing setting" = "Errore nella modifica dell'impostazione";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error connecting to forwarding server %@. Please try later." = "Errore di connessione al server di inoltro %@. Riprova più tardi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating address" = "Errore nella creazione dell'indirizzo";
|
||||
|
||||
@@ -1803,9 +1896,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting connection" = "Errore nell'eliminazione della connessione";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting contact" = "Errore nell'eliminazione del contatto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting database" = "Errore nell'eliminazione del database";
|
||||
|
||||
@@ -1987,7 +2077,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Faster joining and more reliable messages." = "Ingresso più veloce e messaggi più affidabili.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Favorite" = "Preferito";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2086,6 +2176,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarded from" = "Inoltrato da";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Il server di inoltro %@ non è riuscito a connettersi al server di destinazione %@. Riprova più tardi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server address is incompatible with network settings: %@." = "L'indirizzo del server di inoltro è incompatibile con le impostazioni di rete: %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server version is incompatible with network settings: %@." = "La versione del server di inoltro è incompatibile con le impostazioni di rete: %@.";
|
||||
|
||||
/* snd error text */
|
||||
"Forwarding server: %@\nDestination server error: %@" = "Server di inoltro: %1$@\nErrore del server di destinazione: %2$@";
|
||||
|
||||
@@ -2437,6 +2536,9 @@
|
||||
/* group name */
|
||||
"invitation to group %@" = "invito al gruppo %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"invite" = "invita";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invite friends" = "Invita amici";
|
||||
|
||||
@@ -2482,6 +2584,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Può accadere quando:\n1. I messaggi sono scaduti sul client mittente dopo 2 giorni o sul server dopo 30 giorni.\n2. La decifrazione del messaggio è fallita, perché tu o il tuo contatto avete usato un backup del database vecchio.\n3. La connessione è stata compromessa.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"It protects your IP address and connections." = "Protegge il tuo indirizzo IP e le connessioni.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Sembra che tu sia già connesso tramite questo link. In caso contrario, c'è stato un errore (%@).";
|
||||
|
||||
@@ -2494,7 +2599,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Japanese interface" = "Interfaccia giapponese";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Join" = "Entra";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2524,6 +2629,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Keep" = "Tieni";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep conversation" = "Tieni la conversazione";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep the app open to use it from desktop" = "Tieni aperta l'app per usarla dal desktop";
|
||||
|
||||
@@ -2545,7 +2653,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Learn more" = "Maggiori informazioni";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Leave" = "Esci";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2635,6 +2743,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Max 30 seconds, received instantly." = "Max 30 secondi, ricevuto istantaneamente.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Media & file servers" = "Server di multimediali e file";
|
||||
|
||||
/* blur media */
|
||||
"Medium" = "Media";
|
||||
|
||||
/* member role */
|
||||
"member" = "membro";
|
||||
|
||||
@@ -2662,6 +2776,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Menus" = "Menu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"message" = "messaggio";
|
||||
|
||||
/* item status text */
|
||||
"Message delivery error" = "Errore di recapito del messaggio";
|
||||
|
||||
@@ -2699,10 +2816,7 @@
|
||||
"Message reception" = "Ricezione messaggi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message routing fallback" = "Ripiego instradamento messaggio";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message routing mode" = "Modalità instradamento messaggio";
|
||||
"Message servers" = "Server dei messaggi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message source remains private." = "La fonte del messaggio resta privata.";
|
||||
@@ -2813,12 +2927,15 @@
|
||||
"Multiple chat profiles" = "Profili di chat multipli";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"mute" = "silenzia";
|
||||
|
||||
/* swipe action */
|
||||
"Mute" = "Silenzia";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Muted when inactive!" = "Silenzioso quando inattivo!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Name" = "Nome";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2845,6 +2962,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New chat" = "Nuova chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New chat experience 🎉" = "Una nuova esperienza di chat 🎉";
|
||||
|
||||
/* notification */
|
||||
"New contact request" = "Nuova richiesta di contatto";
|
||||
|
||||
@@ -2863,6 +2983,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New in %@" = "Novità nella %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New media options" = "Nuove opzioni multimediali";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New member role" = "Nuovo ruolo del membro";
|
||||
|
||||
@@ -2932,6 +3055,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "Non compatibile!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Nothing selected" = "Nessuna selezione";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Notifications" = "Notifiche";
|
||||
|
||||
@@ -2977,10 +3103,10 @@
|
||||
"One-time invitation link" = "Link di invito una tantum";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be required for connection. Requires enabling VPN." = "Gli host Onion saranno necessari per la connessione. Richiede l'attivazione della VPN.";
|
||||
"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Gli host Onion saranno **necessari** per la connessione.\nRichiede l'attivazione della VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be used when available. Requires enabling VPN." = "Gli host Onion verranno usati quando disponibili. Richiede l'attivazione della VPN.";
|
||||
"Onion hosts will be used when available.\nRequires compatible VPN." = "Gli host Onion verranno usati quando disponibili.\nRichiede l'attivazione della VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will not be used." = "Gli host Onion non verranno usati.";
|
||||
@@ -2988,6 +3114,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Solo i dispositivi client archiviano profili utente, i contatti, i gruppi e i messaggi inviati con la **crittografia end-to-end a 2 livelli**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only delete conversation" = "Elimina solo la conversazione";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can change group preferences." = "Solo i proprietari del gruppo possono modificarne le preferenze.";
|
||||
|
||||
@@ -3144,6 +3273,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"PING interval" = "Intervallo PING";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Play from the chat list." = "Riproduci dall'elenco delle chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please ask your contact to enable calls." = "Chiedi al contatto di attivare le chiamate.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please ask your contact to enable sending voice messages." = "Chiedi al tuo contatto di attivare l'invio dei messaggi vocali.";
|
||||
|
||||
@@ -3324,10 +3459,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Rate the app" = "Valuta l'app";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reachable chat toolbar" = "Barra degli strumenti di chat accessibile";
|
||||
|
||||
/* chat item menu */
|
||||
"React…" = "Reagisci…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Read" = "Leggi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3429,7 +3567,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reduced battery usage" = "Consumo di batteria ridotto";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
/* reject incoming call via notification
|
||||
swipe action */
|
||||
"Reject" = "Rifiuta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3510,6 +3649,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reset" = "Ripristina";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reset all hints" = "Ripristina tutti i suggerimenti";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reset all statistics" = "Azzera tutte le statistiche";
|
||||
|
||||
@@ -3552,9 +3694,6 @@
|
||||
/* chat item action */
|
||||
"Reveal" = "Rivela";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revert" = "Ripristina";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revoke" = "Revoca";
|
||||
|
||||
@@ -3588,6 +3727,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Save and notify group members" = "Salva e avvisa i membri del gruppo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Save and reconnect" = "Salva e riconnetti";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Save and update group profile" = "Salva e aggiorna il profilo del gruppo";
|
||||
|
||||
@@ -3663,6 +3805,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Scan server QR code" = "Scansiona codice QR del server";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"search" = "cerca";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Search" = "Cerca";
|
||||
|
||||
@@ -3699,9 +3844,12 @@
|
||||
/* chat item text */
|
||||
"security code changed" = "codice di sicurezza modificato";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat item action */
|
||||
"Select" = "Seleziona";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected %lld" = "%lld selezionato";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Le preferenze della chat selezionata vietano questo messaggio.";
|
||||
|
||||
@@ -3729,9 +3877,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"send direct message" = "invia messaggio diretto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Invia messaggio diretto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "Invia messaggio diretto per connetterti";
|
||||
|
||||
@@ -3747,6 +3892,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send live message" = "Invia messaggio in diretta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send message to enable calls." = "Invia un messaggio per attivare le chiamate.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Invia messaggi direttamente quando l'indirizzo IP è protetto e il tuo server o quello di destinazione non supporta l'instradamento privato.";
|
||||
|
||||
@@ -3927,12 +4075,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Share address with contacts?" = "Condividere l'indirizzo con i contatti?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share from other apps." = "Condividi da altre app.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share link" = "Condividi link";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share this 1-time invite link" = "Condividi questo link di invito una tantum";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share to SimpleX" = "Condividi in SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Condividi con i contatti";
|
||||
|
||||
@@ -4026,12 +4180,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"SMP server" = "Server SMP";
|
||||
|
||||
/* blur media */
|
||||
"Soft" = "Leggera";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SMP servers" = "Server SMP";
|
||||
"Some file(s) were not exported:" = "Alcuni file non sono stati esportati:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Si sono verificati alcuni errori non gravi durante l'importazione: vedi la console della chat per i dettagli.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import:" = "Si sono verificati alcuni errori non fatali durante l'importazione:";
|
||||
|
||||
/* notification title */
|
||||
"Somebody" = "Qualcuno";
|
||||
|
||||
@@ -4098,6 +4258,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"strike" = "barrato";
|
||||
|
||||
/* blur media */
|
||||
"Strong" = "Forte";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Submit" = "Invia";
|
||||
|
||||
@@ -4144,7 +4307,7 @@
|
||||
"Tap to scan" = "Tocca per scansionare";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "Tocca per iniziare una chat";
|
||||
"TCP connection" = "Connessione TCP";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP connection timeout" = "Scadenza connessione TCP";
|
||||
@@ -4221,6 +4384,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The message will be marked as moderated for all members." = "Il messaggio sarà segnato come moderato per tutti i membri.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The messages will be deleted for all members." = "I messaggi verranno eliminati per tutti i membri.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The messages will be marked as moderated for all members." = "I messaggi verranno contrassegnati come moderati per tutti i membri.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The next generation of private messaging" = "La nuova generazione di messaggistica privata";
|
||||
|
||||
@@ -4332,9 +4501,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Per verificare la crittografia end-to-end con il tuo contatto, confrontate (o scansionate) il codice sui vostri dispositivi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle chat list:" = "Cambia elenco chat:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "Attiva/disattiva l'incognito quando ti colleghi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toolbar opacity" = "Opacità barra degli strumenti";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Total" = "Totale";
|
||||
|
||||
@@ -4383,7 +4558,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "Stato di migrazione imprevisto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unfav." = "Non pref.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4411,7 +4586,7 @@
|
||||
"Unknown error" = "Errore sconosciuto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unknown relays" = "relay sconosciuti";
|
||||
"unknown servers" = "relay sconosciuti";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown servers!" = "Server sconosciuti!";
|
||||
@@ -4438,12 +4613,15 @@
|
||||
"Unlock app" = "Sblocca l'app";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unmute" = "riattiva notifiche";
|
||||
|
||||
/* swipe action */
|
||||
"Unmute" = "Riattiva notifiche";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unprotected" = "non protetto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unread" = "Non letto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4452,9 +4630,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Update" = "Aggiorna";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update .onion hosts setting?" = "Aggiornare l'impostazione degli host .onion?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update database passphrase" = "Aggiorna la password del database";
|
||||
|
||||
@@ -4462,7 +4637,7 @@
|
||||
"Update network settings?" = "Aggiornare le impostazioni di rete?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update transport isolation mode?" = "Aggiornare la modalità di isolamento del trasporto?";
|
||||
"Update settings?" = "Aggiornare le impostazioni?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "ha aggiornato il profilo del gruppo";
|
||||
@@ -4473,9 +4648,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Updating settings will re-connect the client to all servers." = "L'aggiornamento delle impostazioni riconnetterà il client a tutti i server.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating this setting will re-connect the client to all servers." = "L'aggiornamento di questa impostazione riconnetterà il client a tutti i server.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "Aggiorna e apri chat";
|
||||
|
||||
@@ -4536,15 +4708,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Use the app while in the call." = "Usa l'app mentre sei in chiamata.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use the app with one hand." = "Usa l'app con una mano sola.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"User profile" = "Profilo utente";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"User selection" = "Selezione utente";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using .onion hosts requires compatible VPN provider." = "L'uso di host .onion richiede un fornitore di VPN compatibile.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using SimpleX Chat servers." = "Utilizzo dei server SimpleX Chat.";
|
||||
|
||||
@@ -4593,6 +4765,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Via secure quantum resistant protocol." = "Tramite protocollo sicuro resistente alla quantistica.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"video" = "video";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Video call" = "Videochiamata";
|
||||
|
||||
@@ -4737,9 +4912,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP server" = "Server XFTP";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP servers" = "Server XFTP";
|
||||
|
||||
/* pref value */
|
||||
"yes" = "sì";
|
||||
|
||||
@@ -4806,6 +4978,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can accept calls from lock screen, without device and app authentication." = "Puoi accettare chiamate dalla schermata di blocco, senza l'autenticazione del dispositivo e dell'app.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can change it in Appearance settings." = "Puoi cambiarlo nelle impostazioni dell'aspetto.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can create it later" = "Puoi crearlo più tardi";
|
||||
|
||||
@@ -4827,6 +5002,9 @@
|
||||
/* notification body */
|
||||
"You can now chat with %@" = "Ora puoi inviare messaggi a %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can send messages to %@ from Archived contacts." = "Puoi inviare messaggi a %@ dai contatti archiviati.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can set lock screen notification preview via settings." = "Puoi impostare l'anteprima della notifica nella schermata di blocco tramite le impostazioni.";
|
||||
|
||||
@@ -4842,6 +5020,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can start chat via app Settings / Database or by restarting the app" = "Puoi avviare la chat via Impostazioni / Database o riavviando l'app";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can still view conversation with %@ in the list of chats." = "Puoi ancora vedere la conversazione con %@ nell'elenco delle chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can turn on SimpleX Lock via Settings." = "Puoi attivare SimpleX Lock tramite le impostazioni.";
|
||||
|
||||
@@ -4878,9 +5059,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Hai già richiesto la connessione!\nRipetere la richiesta di connessione?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have no chats" = "Non hai chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Devi inserire la password ogni volta che si avvia l'app: non viene memorizzata sul dispositivo.";
|
||||
|
||||
@@ -4896,9 +5074,18 @@
|
||||
/* snd group event chat item */
|
||||
"you left" = "sei uscito/a";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You may migrate the exported database." = "Puoi migrare il database esportato.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You may save the exported archive." = "Puoi salvare l'archivio esportato.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "Devi usare la versione più recente del tuo database della chat SOLO su un dispositivo, altrimenti potresti non ricevere più i messaggi da alcuni contatti.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You need to allow your contact to call to be able to call them." = "Devi consentire le chiamate al tuo contatto per poterlo chiamare.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You need to allow your contact to send voice messages to be able to send them." = "Devi consentire al tuo contatto di inviare messaggi vocali per poterli inviare anche tu.";
|
||||
|
||||
@@ -4971,9 +5158,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "I tuoi profili di chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Il tuo contatto deve essere in linea per completare la connessione.\nPuoi annullare questa connessione e rimuovere il contatto (e riprovare più tardi con un link nuovo).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Il tuo contatto ha inviato un file più grande della dimensione massima attualmente supportata (%@).";
|
||||
|
||||
|
||||
@@ -329,7 +329,8 @@
|
||||
"above, then choose:" = "上で選んでください:";
|
||||
|
||||
/* accept contact request via notification
|
||||
accept incoming call via notification */
|
||||
accept incoming call via notification
|
||||
swipe action */
|
||||
"Accept" = "承諾";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -338,7 +339,8 @@
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "%@ からの連絡要求を受け入れますか?";
|
||||
|
||||
/* accept contact request via notification */
|
||||
/* accept contact request via notification
|
||||
swipe action */
|
||||
"Accept incognito" = "シークレットモードで承諾";
|
||||
|
||||
/* call status */
|
||||
@@ -714,7 +716,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Choose from library" = "ライブラリから選択";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Clear" = "消す";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -840,9 +842,6 @@
|
||||
/* notification */
|
||||
"Contact is connected" = "連絡先は接続中";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact is not connected yet!" = "連絡先がまだ繋がってません!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact name" = "連絡先の名前";
|
||||
|
||||
@@ -993,7 +992,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "デフォルト(はい)";
|
||||
|
||||
/* chat item action */
|
||||
/* chat item action
|
||||
swipe action */
|
||||
"Delete" = "削除";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1026,9 +1026,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact" = "連絡先を削除";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "連絡先を削除";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "データベースを削除";
|
||||
|
||||
@@ -1080,9 +1077,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete old database?" = "古いデータベースを削除しますか?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection" = "確認待ちの接続を削除";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection?" = "接続待ちの接続を削除しますか?";
|
||||
|
||||
@@ -1407,9 +1401,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting connection" = "接続の削除エラー";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting contact" = "連絡先の削除にエラー発生";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting database" = "データベースの削除にエラー発生";
|
||||
|
||||
@@ -1537,7 +1528,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Fast and no wait until the sender is online!" = "送信者がオンラインになるまでの待ち時間がなく、速い!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Favorite" = "お気に入り";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1921,7 +1912,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Japanese interface" = "日本語UI";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Join" = "参加";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1951,7 +1942,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Learn more" = "さらに詳しく";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Leave" = "脱退";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2125,13 +2116,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Multiple chat profiles" = "複数チャットのプロフィール";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Mute" = "ミュート";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Muted when inactive!" = "非アクティブ時はミュート!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Name" = "名前";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2263,10 +2254,10 @@
|
||||
"One-time invitation link" = "使い捨ての招待リンク";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be required for connection. Requires enabling VPN." = "接続にオニオンのホストが必要となります。VPN を有効にする必要があります。";
|
||||
"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "接続にオニオンのホストが必要となります。\nVPN を有効にする必要があります。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be used when available. Requires enabling VPN." = "オニオンのホストが利用可能時に使われます。VPN を有効にする必要があります。";
|
||||
"Onion hosts will be used when available.\nRequires compatible VPN." = "オニオンのホストが利用可能時に使われます。\nVPN を有効にする必要があります。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will not be used." = "オニオンのホストが使われません。";
|
||||
@@ -2493,7 +2484,7 @@
|
||||
/* chat item menu */
|
||||
"React…" = "反応する…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Read" = "読む";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2556,7 +2547,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reduced battery usage" = "電池使用量低減";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
/* reject incoming call via notification
|
||||
swipe action */
|
||||
"Reject" = "拒否";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2640,9 +2632,6 @@
|
||||
/* chat item action */
|
||||
"Reveal" = "開示する";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revert" = "元に戻す";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revoke" = "取り消す";
|
||||
|
||||
@@ -2745,7 +2734,7 @@
|
||||
/* chat item text */
|
||||
"security code changed" = "セキュリティコードが変更されました";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat item action */
|
||||
"Select" = "選択";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2766,9 +2755,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send a live message - it will update for the recipient(s) as you type it" = "ライブメッセージを送信 (入力しながら宛先の画面で更新される)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "ダイレクトメッセージを送信";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "ダイレクトメッセージを送信して接続する";
|
||||
|
||||
@@ -2940,9 +2926,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Small groups (max 20)" = "小グループ(最大20名)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SMP servers" = "SMPサーバ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import - you may see Chat console for more details." = "インポート中に致命的でないエラーが発生しました - 詳細はチャットコンソールを参照してください。";
|
||||
|
||||
@@ -3018,9 +3001,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to join incognito" = "タップしてシークレットモードで参加";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "タップして新しいチャットを始める";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP connection timeout" = "TCP接続タイムアウト";
|
||||
|
||||
@@ -3180,7 +3160,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "予期しない移行状態";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unfav." = "お気に入りを取り消す。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3219,36 +3199,27 @@
|
||||
/* authentication reason */
|
||||
"Unlock app" = "アプリのロック解除";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unmute" = "ミュート解除";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unread" = "未読";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update" = "更新";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update .onion hosts setting?" = ".onionのホスト設定を更新しますか?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update database passphrase" = "データベースのパスフレーズを更新";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update network settings?" = "ネットワーク設定を更新しますか?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update transport isolation mode?" = "トランスポート隔離モードを更新しますか?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "グループプロフィールを更新しました";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating settings will re-connect the client to all servers." = "設定を更新すると、全サーバにクライントの再接続が行われます。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating this setting will re-connect the client to all servers." = "設定を更新すると、全サーバにクライントの再接続が行われます。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "アップグレードしてチャットを開く";
|
||||
|
||||
@@ -3282,9 +3253,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"User profile" = "ユーザープロフィール";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using .onion hosts requires compatible VPN provider." = ".onionホストを使用するには、互換性のあるVPNプロバイダーが必要です。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using SimpleX Chat servers." = "SimpleX チャット サーバーを使用する。";
|
||||
|
||||
@@ -3399,9 +3367,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong passphrase!" = "パスフレーズが違います!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP servers" = "XFTPサーバ";
|
||||
|
||||
/* pref value */
|
||||
"yes" = "はい";
|
||||
|
||||
@@ -3492,9 +3457,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "確認できませんでした。 もう一度お試しください。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have no chats" = "あなたはチャットがありません";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "アプリ起動時にパスフレーズを入力しなければなりません。端末に保存されてません。";
|
||||
|
||||
@@ -3576,9 +3538,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "あなたのチャットプロフィール";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "接続を完了するには、連絡相手がオンラインになる必要があります。\nこの接続をキャンセルして、連絡先を削除をすることもできます (後でやり直すこともできます)。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "連絡先が現在サポートされている最大サイズ (%@) より大きいファイルを送信しました。";
|
||||
|
||||
|
||||
@@ -338,7 +338,8 @@
|
||||
"Accent" = "Accent";
|
||||
|
||||
/* accept contact request via notification
|
||||
accept incoming call via notification */
|
||||
accept incoming call via notification
|
||||
swipe action */
|
||||
"Accept" = "Accepteer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -347,7 +348,8 @@
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "Accepteer contactverzoek van %@?";
|
||||
|
||||
/* accept contact request via notification */
|
||||
/* accept contact request via notification
|
||||
swipe action */
|
||||
"Accept incognito" = "Accepteer incognito";
|
||||
|
||||
/* call status */
|
||||
@@ -470,6 +472,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Allow calls only if your contact allows them." = "Sta oproepen alleen toe als uw contact dit toestaat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow calls?" = "Oproepen toestaan?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow disappearing messages only if your contact allows it to you." = "Sta verdwijnende berichten alleen toe als uw contact dit toestaat.";
|
||||
|
||||
@@ -491,6 +496,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Allow sending disappearing messages." = "Toestaan dat verdwijnende berichten worden verzonden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow sharing" = "Delen toestaan";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages. (24 hours)" = "Sta toe om verzonden berichten onomkeerbaar te verwijderen. (24 uur)";
|
||||
|
||||
@@ -587,6 +595,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Archive and upload" = "Archiveren en uploaden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Archive contacts to chat later." = "Archiveer contacten om later te chatten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Archived contacts" = "Gearchiveerde contacten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Archiving database" = "Database archiveren";
|
||||
|
||||
@@ -662,6 +676,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Better messages" = "Betere berichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Better networking" = "Beter netwerk";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Black" = "Zwart";
|
||||
|
||||
@@ -689,12 +706,18 @@
|
||||
/* rcv group event chat item */
|
||||
"blocked %@" = "geblokkeerd %@";
|
||||
|
||||
/* blocked chat item */
|
||||
/* marked deleted chat item preview text */
|
||||
"blocked by admin" = "geblokkeerd door beheerder";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blocked by admin" = "Geblokkeerd door beheerder";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blur for better privacy." = "Vervagen voor betere privacy.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blur media" = "Vervaag media";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"bold" = "vetgedrukt";
|
||||
|
||||
@@ -719,6 +742,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Via chat profiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"call" = "bellen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Call already ended!" = "Oproep al beëindigd!";
|
||||
|
||||
@@ -734,15 +760,27 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Calls" = "Oproepen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Calls prohibited!" = "Bellen verboden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Camera not available" = "Camera niet beschikbaar";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't call contact" = "Kan contact niet bellen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't call member" = "Kan lid niet bellen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't invite contact!" = "Kan contact niet uitnodigen!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't invite contacts!" = "Kan geen contacten uitnodigen!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't message member" = "Kan geen bericht sturen naar lid";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Annuleren";
|
||||
|
||||
@@ -828,6 +866,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chat database deleted" = "Chat database verwijderd";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat database exported" = "Chat database geëxporteerd";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat database imported" = "Chat database geïmporteerd";
|
||||
|
||||
@@ -840,6 +881,9 @@
|
||||
/* 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." = "Chat is gestopt. Als je deze database al op een ander apparaat hebt gebruikt, moet je deze terugzetten voordat je met chatten begint.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat list" = "Chatlijst";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat migrated!" = "Chat gemigreerd!";
|
||||
|
||||
@@ -876,7 +920,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chunks uploaded" = "Stukken geüpload";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Clear" = "Wissen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -891,6 +935,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Clear verification" = "Verwijderd verificatie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Color chats with the new themes." = "Kleurchats met de nieuwe thema's.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Color mode" = "Kleur mode";
|
||||
|
||||
@@ -918,6 +965,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm" = "Bevestigen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm contact deletion?" = "Contact verwijderen bevestigen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm database upgrades" = "Bevestig database upgrades";
|
||||
|
||||
@@ -957,6 +1007,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"connect to SimpleX Chat developers." = "maak verbinding met SimpleX Chat-ontwikkelaars.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect to your friends faster." = "Maak sneller verbinding met je vrienden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect to yourself?" = "Verbinding maken met jezelf?";
|
||||
|
||||
@@ -1023,6 +1076,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting server… (error: %@)" = "Verbinden met server... (fout: %@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting to contact, please wait or check later!" = "Er wordt verbinding gemaakt met het contact. Even geduld of controleer het later!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting to desktop" = "Verbinding maken met desktop";
|
||||
|
||||
@@ -1032,6 +1088,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connection" = "Verbinding";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection and servers status." = "Verbindings- en serverstatus.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection error" = "Verbindingsfout";
|
||||
|
||||
@@ -1041,6 +1100,9 @@
|
||||
/* chat list item title (it should not be shown */
|
||||
"connection established" = "verbinding gemaakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection notifications" = "Verbindingsmeldingen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection request sent!" = "Verbindingsverzoek verzonden!";
|
||||
|
||||
@@ -1068,6 +1130,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact already exists" = "Contact bestaat al";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact deleted!" = "Contact verwijderd!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"contact has e2e encryption" = "contact heeft e2e-codering";
|
||||
|
||||
@@ -1081,7 +1146,7 @@
|
||||
"Contact is connected" = "Contact is verbonden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact is not connected yet!" = "Contact is nog niet verbonden!";
|
||||
"Contact is deleted." = "Contact is verwijderd.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact name" = "Contact naam";
|
||||
@@ -1089,6 +1154,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact preferences" = "Contact voorkeuren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact will be deleted - this cannot be undone!" = "Het contact wordt verwijderd. Dit kan niet ongedaan worden gemaakt!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contacts" = "Contacten";
|
||||
|
||||
@@ -1098,6 +1166,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "Doorgaan";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Conversation deleted!" = "Gesprek verwijderd!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Copy" = "Kopiëren";
|
||||
|
||||
@@ -1278,9 +1349,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "standaard (ja)";
|
||||
|
||||
/* chat item action */
|
||||
/* chat item action
|
||||
swipe action */
|
||||
"Delete" = "Verwijderen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete %lld messages of members?" = "%lld berichten van leden verwijderen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete %lld messages?" = "%lld berichten verwijderen?";
|
||||
|
||||
@@ -1318,10 +1393,7 @@
|
||||
"Delete contact" = "Verwijder contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "Verwijder contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact?\nThis cannot be undone!" = "Verwijder contact?\nDit kan niet ongedaan gemaakt worden!";
|
||||
"Delete contact?" = "Verwijder contact?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Database verwijderen";
|
||||
@@ -1377,9 +1449,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete old database?" = "Oude database verwijderen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection" = "Wachtende verbinding verwijderen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection?" = "Wachtende verbinding verwijderen?";
|
||||
|
||||
@@ -1389,9 +1458,15 @@
|
||||
/* server test step */
|
||||
"Delete queue" = "Wachtrij verwijderen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete up to 20 messages at once." = "Verwijder maximaal 20 berichten tegelijk.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete user profile?" = "Gebruikers profiel verwijderen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete without notification" = "Verwijderen zonder melding";
|
||||
|
||||
/* deleted chat item */
|
||||
"deleted" = "verwijderd";
|
||||
|
||||
@@ -1434,9 +1509,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Desktop devices" = "Desktop apparaten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Destination server address of %@ is incompatible with forwarding server %@ settings." = "Het bestemmingsserveradres van %@ is niet compatibel met de doorstuurserverinstellingen %@.";
|
||||
|
||||
/* snd error text */
|
||||
"Destination server error: %@" = "Bestemmingsserverfout: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Destination server version of %@ is incompatible with forwarding server %@." = "De versie van de bestemmingsserver %@ is niet compatibel met de doorstuurserver %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Detailed statistics" = "Gedetailleerde statistieken";
|
||||
|
||||
@@ -1446,6 +1527,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Develop" = "Ontwikkelen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Developer options" = "Ontwikkelaars opties";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Developer tools" = "Ontwikkel gereedschap";
|
||||
|
||||
@@ -1485,6 +1569,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"disabled" = "uitgeschakeld";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disabled" = "Uitgeschakeld";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disappearing message" = "Verdwijnend bericht";
|
||||
|
||||
@@ -1632,6 +1719,9 @@
|
||||
/* enabled status */
|
||||
"enabled" = "ingeschakeld";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enabled" = "Ingeschakeld";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enabled for" = "Ingeschakeld voor";
|
||||
|
||||
@@ -1773,6 +1863,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error changing setting" = "Fout bij wijzigen van instelling";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error connecting to forwarding server %@. Please try later." = "Fout bij het verbinden met doorstuurserver %@. Probeer het later opnieuw.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating address" = "Fout bij aanmaken van adres";
|
||||
|
||||
@@ -1803,9 +1896,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting connection" = "Fout bij verwijderen van verbinding";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting contact" = "Fout bij het verwijderen van contact";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting database" = "Fout bij het verwijderen van de database";
|
||||
|
||||
@@ -1987,7 +2077,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Faster joining and more reliable messages." = "Snellere deelname en betrouwbaardere berichten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Favorite" = "Favoriet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2086,6 +2176,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarded from" = "Doorgestuurd vanuit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server %@ failed to connect to destination server %@. Please try later." = "De doorstuurserver %@ kon geen verbinding maken met de bestemmingsserver %@. Probeer het later opnieuw.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server address is incompatible with network settings: %@." = "Het adres van de doorstuurserver is niet compatibel met de netwerkinstellingen: %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Forwarding server version is incompatible with network settings: %@." = "De doorstuurserverversie is niet compatibel met de netwerkinstellingen: %@.";
|
||||
|
||||
/* snd error text */
|
||||
"Forwarding server: %@\nDestination server error: %@" = "Doorstuurserver: %1$@\nBestemmingsserverfout: %2$@";
|
||||
|
||||
@@ -2437,6 +2536,9 @@
|
||||
/* group name */
|
||||
"invitation to group %@" = "uitnodiging voor groep %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"invite" = "uitnodiging";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invite friends" = "Nodig vrienden uit";
|
||||
|
||||
@@ -2482,6 +2584,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Het kan gebeuren wanneer:\n1. De berichten zijn na 2 dagen verlopen bij de verzendende client of na 30 dagen op de server.\n2. Decodering van het bericht is mislukt, omdat u of uw contact een oude database back-up heeft gebruikt.\n3. De verbinding is verbroken.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"It protects your IP address and connections." = "Het beschermt uw IP-adres en verbindingen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Het lijkt erop dat u al bent verbonden via deze link. Als dit niet het geval is, is er een fout opgetreden (%@).";
|
||||
|
||||
@@ -2494,7 +2599,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Japanese interface" = "Japanse interface";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Join" = "Word lid van";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2524,6 +2629,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Keep" = "Bewaar";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep conversation" = "Blijf in gesprek";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep the app open to use it from desktop" = "Houd de app geopend om deze vanaf de desktop te gebruiken";
|
||||
|
||||
@@ -2545,7 +2653,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Learn more" = "Kom meer te weten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Leave" = "Verlaten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2635,6 +2743,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Max 30 seconds, received instantly." = "Max 30 seconden, direct ontvangen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Media & file servers" = "Media- en bestandsservers";
|
||||
|
||||
/* blur media */
|
||||
"Medium" = "Medium";
|
||||
|
||||
/* member role */
|
||||
"member" = "lid";
|
||||
|
||||
@@ -2662,6 +2776,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Menus" = "Menu's";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"message" = "bericht";
|
||||
|
||||
/* item status text */
|
||||
"Message delivery error" = "Fout bij bezorging van bericht";
|
||||
|
||||
@@ -2699,10 +2816,7 @@
|
||||
"Message reception" = "Bericht ontvangst";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message routing fallback" = "Terugval op berichtroutering";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message routing mode" = "Berichtrouteringsmodus";
|
||||
"Message servers" = "Berichtservers";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message source remains private." = "Berichtbron blijft privé.";
|
||||
@@ -2768,7 +2882,7 @@
|
||||
"Migration error:" = "Migratiefout:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Migratie mislukt. Tik hieronder op **Overslaan** om de huidige database te blijven gebruiken. Meld het probleem aan de app-ontwikkelaars via chat of e-mail [chat@simplex.chat](mailto:chat@simplex.chat).";
|
||||
"Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Migratie mislukt. Tik hier onder op **Overslaan** om de huidige database te blijven gebruiken. Meld het probleem aan de app-ontwikkelaars via chat of e-mail [chat@simplex.chat](mailto:chat@simplex.chat).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migration is completed" = "Migratie is voltooid";
|
||||
@@ -2813,12 +2927,15 @@
|
||||
"Multiple chat profiles" = "Meerdere chat profielen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"mute" = "dempen";
|
||||
|
||||
/* swipe action */
|
||||
"Mute" = "Dempen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Muted when inactive!" = "Gedempt wanneer inactief!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Name" = "Naam";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2845,6 +2962,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New chat" = "Nieuw gesprek";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New chat experience 🎉" = "Nieuwe chatervaring 🎉";
|
||||
|
||||
/* notification */
|
||||
"New contact request" = "Nieuw contactverzoek";
|
||||
|
||||
@@ -2863,6 +2983,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New in %@" = "Nieuw in %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New media options" = "Nieuwe media-opties";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New member role" = "Nieuwe leden rol";
|
||||
|
||||
@@ -2932,6 +3055,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "Niet compatibel!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Nothing selected" = "Niets geselecteerd";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Notifications" = "Meldingen";
|
||||
|
||||
@@ -2977,10 +3103,10 @@
|
||||
"One-time invitation link" = "Eenmalige uitnodiging link";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be required for connection. Requires enabling VPN." = "Onion hosts zullen nodig zijn voor verbinding. Vereist het inschakelen van VPN.";
|
||||
"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Onion hosts zullen nodig zijn voor verbinding.\nVereist het inschakelen van VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be used when available. Requires enabling VPN." = "Onion hosts worden gebruikt indien beschikbaar. Vereist het inschakelen van VPN.";
|
||||
"Onion hosts will be used when available.\nRequires compatible VPN." = "Onion hosts worden gebruikt indien beschikbaar.\nVereist het inschakelen van VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will not be used." = "Onion hosts worden niet gebruikt.";
|
||||
@@ -2988,6 +3114,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Alleen client apparaten slaan gebruikers profielen, contacten, groepen en berichten op die zijn verzonden met **2-laags end-to-end-codering**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only delete conversation" = "Alleen conversatie verwijderen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only group owners can change group preferences." = "Alleen groep eigenaren kunnen groep voorkeuren wijzigen.";
|
||||
|
||||
@@ -3144,6 +3273,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"PING interval" = "PING interval";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Play from the chat list." = "Afspelen via de gesprekken lijst.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please ask your contact to enable calls." = "Vraag uw contactpersoon om oproepen in te schakelen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please ask your contact to enable sending voice messages." = "Vraag uw contact om het verzenden van spraak berichten in te schakelen.";
|
||||
|
||||
@@ -3324,10 +3459,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Rate the app" = "Beoordeel de app";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reachable chat toolbar" = "Toegankelijke chatwerkbalk";
|
||||
|
||||
/* chat item menu */
|
||||
"React…" = "Reageer…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Read" = "Lees";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3429,7 +3567,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reduced battery usage" = "Verminderd batterijgebruik";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
/* reject incoming call via notification
|
||||
swipe action */
|
||||
"Reject" = "Afwijzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3510,6 +3649,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reset" = "Resetten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reset all hints" = "Alle hints resetten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Reset all statistics" = "Reset alle statistieken";
|
||||
|
||||
@@ -3552,9 +3694,6 @@
|
||||
/* chat item action */
|
||||
"Reveal" = "Onthullen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revert" = "Terugdraaien";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revoke" = "Intrekken";
|
||||
|
||||
@@ -3588,6 +3727,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Save and notify group members" = "Opslaan en groep leden melden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Save and reconnect" = "Opslaan en opnieuw verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Save and update group profile" = "Groep profiel opslaan en bijwerken";
|
||||
|
||||
@@ -3663,6 +3805,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Scan server QR code" = "Scan server QR-code";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"search" = "zoekopdracht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Search" = "Zoeken";
|
||||
|
||||
@@ -3699,9 +3844,12 @@
|
||||
/* chat item text */
|
||||
"security code changed" = "beveiligingscode gewijzigd";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat item action */
|
||||
"Select" = "Selecteer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected %lld" = "%lld geselecteerd";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Geselecteerde chat voorkeuren verbieden dit bericht.";
|
||||
|
||||
@@ -3729,9 +3877,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"send direct message" = "stuur een direct bericht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Direct bericht sturen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "Stuur een direct bericht om verbinding te maken";
|
||||
|
||||
@@ -3747,6 +3892,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send live message" = "Stuur een livebericht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send message to enable calls." = "Stuur een bericht om oproepen mogelijk te maken.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Stuur berichten rechtstreeks als het IP-adres beschermd is en uw of bestemmingsserver geen privéroutering ondersteunt.";
|
||||
|
||||
@@ -3927,12 +4075,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Share address with contacts?" = "Adres delen met contacten?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share from other apps." = "Delen vanuit andere apps.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share link" = "Deel link";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share this 1-time invite link" = "Deel deze eenmalige uitnodigingslink";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share to SimpleX" = "Delen op SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Delen met contacten";
|
||||
|
||||
@@ -4026,12 +4180,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"SMP server" = "SMP server";
|
||||
|
||||
/* blur media */
|
||||
"Soft" = "Soft";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SMP servers" = "SMP servers";
|
||||
"Some file(s) were not exported:" = "Sommige bestanden zijn niet geëxporteerd:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Er zijn enkele niet-fatale fouten opgetreden tijdens het importeren - u kunt de Chat console raadplegen voor meer details.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import:" = "Er zijn enkele niet-fatale fouten opgetreden tijdens het importeren:";
|
||||
|
||||
/* notification title */
|
||||
"Somebody" = "Iemand";
|
||||
|
||||
@@ -4098,6 +4258,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"strike" = "staking";
|
||||
|
||||
/* blur media */
|
||||
"Strong" = "Krachtig";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Submit" = "Indienen";
|
||||
|
||||
@@ -4126,25 +4289,25 @@
|
||||
"Tap button " = "Tik op de knop ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to activate profile." = "Tik om profiel te activeren.";
|
||||
"Tap to activate profile." = "Tik hier om profiel te activeren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to Connect" = "Tik om verbinding te maken";
|
||||
"Tap to Connect" = "Tik hier om verbinding te maken";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to join" = "Tik om lid te worden";
|
||||
"Tap to join" = "Tik hier om lid te worden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to join incognito" = "Tik om incognito lid te worden";
|
||||
"Tap to join incognito" = "Tik hier om incognito lid te worden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to paste link" = "Tik om de link te plakken";
|
||||
"Tap to paste link" = "Tik hier om de link te plakken";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to scan" = "Tik om te scannen";
|
||||
"Tap to scan" = "Tik hier om te scannen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "Tik om een nieuw gesprek te starten";
|
||||
"TCP connection" = "TCP verbinding";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP connection timeout" = "Timeout van TCP-verbinding";
|
||||
@@ -4221,6 +4384,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The message will be marked as moderated for all members." = "Het bericht wordt gemarkeerd als gemodereerd voor alle leden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The messages will be deleted for all members." = "De berichten worden voor alle leden verwijderd.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The messages will be marked as moderated for all members." = "De berichten worden voor alle leden als gemodereerd gemarkeerd.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The next generation of private messaging" = "De volgende generatie privéberichten";
|
||||
|
||||
@@ -4332,9 +4501,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Vergelijk (of scan) de code op uw apparaten om end-to-end-codering met uw contact te verifiëren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle chat list:" = "Chatlijst wisselen:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "Schakel incognito in tijdens het verbinden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toolbar opacity" = "De transparantie van de werkbalk";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Total" = "Totaal";
|
||||
|
||||
@@ -4383,7 +4558,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "Onverwachte migratiestatus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unfav." = "Niet fav.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4411,7 +4586,7 @@
|
||||
"Unknown error" = "Onbekende fout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unknown relays" = "onbekende relays";
|
||||
"unknown servers" = "onbekende relays";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown servers!" = "Onbekende servers!";
|
||||
@@ -4438,12 +4613,15 @@
|
||||
"Unlock app" = "Ontgrendel app";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unmute" = "dempen opheffen";
|
||||
|
||||
/* swipe action */
|
||||
"Unmute" = "Dempen opheffen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unprotected" = "onbeschermd";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unread" = "Ongelezen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4452,9 +4630,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Update" = "Update";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update .onion hosts setting?" = ".onion hosts-instelling updaten?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update database passphrase" = "Database wachtwoord bijwerken";
|
||||
|
||||
@@ -4462,7 +4637,7 @@
|
||||
"Update network settings?" = "Netwerk instellingen bijwerken?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update transport isolation mode?" = "Transportisolatiemodus updaten?";
|
||||
"Update settings?" = "Instellingen actualiseren?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "bijgewerkt groep profiel";
|
||||
@@ -4473,9 +4648,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Updating settings will re-connect the client to all servers." = "Door de instellingen bij te werken, wordt de client opnieuw verbonden met alle servers.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating this setting will re-connect the client to all servers." = "Als u deze instelling bijwerkt, wordt de client opnieuw verbonden met alle servers.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "Upgrade en open chat";
|
||||
|
||||
@@ -4536,15 +4708,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Use the app while in the call." = "Gebruik de app tijdens het gesprek.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use the app with one hand." = "Gebruik de app met één hand.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"User profile" = "Gebruikers profiel";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"User selection" = "Gebruikersselectie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using .onion hosts requires compatible VPN provider." = "Het gebruik van .onion-hosts vereist een compatibele VPN-provider.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using SimpleX Chat servers." = "SimpleX Chat servers gebruiken.";
|
||||
|
||||
@@ -4593,6 +4765,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Via secure quantum resistant protocol." = "Via een beveiligd kwantumbestendig protocol.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"video" = "video";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Video call" = "video oproep";
|
||||
|
||||
@@ -4737,9 +4912,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP server" = "XFTP server";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP servers" = "XFTP servers";
|
||||
|
||||
/* pref value */
|
||||
"yes" = "Ja";
|
||||
|
||||
@@ -4806,6 +4978,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can accept calls from lock screen, without device and app authentication." = "U kunt oproepen van het vergrendelingsscherm accepteren, zonder apparaat- en app-verificatie.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can change it in Appearance settings." = "U kunt dit wijzigen in de instellingen onder uiterlijk.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can create it later" = "U kan het later maken";
|
||||
|
||||
@@ -4827,6 +5002,9 @@
|
||||
/* notification body */
|
||||
"You can now chat with %@" = "Je kunt nu berichten sturen naar %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can send messages to %@ from Archived contacts." = "U kunt berichten naar %@ sturen vanuit gearchiveerde contacten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can set lock screen notification preview via settings." = "U kunt een voorbeeld van een melding op het vergrendeld scherm instellen via instellingen.";
|
||||
|
||||
@@ -4842,6 +5020,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can start chat via app Settings / Database or by restarting the app" = "U kunt de chat starten via app Instellingen / Database of door de app opnieuw op te starten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can still view conversation with %@ in the list of chats." = "Je kunt het gesprek met %@ nog steeds bekijken in de lijst met chats.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can turn on SimpleX Lock via Settings." = "Je kunt SimpleX Vergrendeling aanzetten via Instellingen.";
|
||||
|
||||
@@ -4878,9 +5059,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Je hebt al verbinding aangevraagd!\nVerbindingsverzoek herhalen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have no chats" = "Je hebt geen gesprekken";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "U moet elke keer dat de app start het wachtwoord invoeren, deze wordt niet op het apparaat opgeslagen.";
|
||||
|
||||
@@ -4896,9 +5074,18 @@
|
||||
/* snd group event chat item */
|
||||
"you left" = "jij bent vertrokken";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You may migrate the exported database." = "U kunt de geëxporteerde database migreren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You may save the exported archive." = "U kunt het geëxporteerde archief opslaan.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "U mag ALLEEN de meest recente versie van uw chat database op één apparaat gebruiken, anders ontvangt u mogelijk geen berichten meer van sommige contacten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You need to allow your contact to call to be able to call them." = "U moet uw contactpersoon toestemming geven om te bellen, zodat hij/zij je kan bellen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You need to allow your contact to send voice messages to be able to send them." = "U moet uw contact toestemming geven om spraak berichten te verzenden om ze te kunnen verzenden.";
|
||||
|
||||
@@ -4971,9 +5158,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Uw chat profielen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Uw contact moet online zijn om de verbinding te voltooien.\nU kunt deze verbinding verbreken en het contact verwijderen en later proberen met een nieuwe link.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Uw contact heeft een bestand verzonden dat groter is dan de momenteel ondersteunde maximale grootte (%@).";
|
||||
|
||||
|
||||
@@ -338,7 +338,8 @@
|
||||
"Accent" = "Akcent";
|
||||
|
||||
/* accept contact request via notification
|
||||
accept incoming call via notification */
|
||||
accept incoming call via notification
|
||||
swipe action */
|
||||
"Accept" = "Akceptuj";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -347,7 +348,8 @@
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "Zaakceptuj prośbę o kontakt od %@?";
|
||||
|
||||
/* accept contact request via notification */
|
||||
/* accept contact request via notification
|
||||
swipe action */
|
||||
"Accept incognito" = "Akceptuj incognito";
|
||||
|
||||
/* call status */
|
||||
@@ -689,7 +691,7 @@
|
||||
/* rcv group event chat item */
|
||||
"blocked %@" = "zablokowany %@";
|
||||
|
||||
/* blocked chat item */
|
||||
/* marked deleted chat item preview text */
|
||||
"blocked by admin" = "zablokowany przez admina";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -876,7 +878,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chunks uploaded" = "Fragmenty przesłane";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Clear" = "Wyczyść";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1080,9 +1082,6 @@
|
||||
/* notification */
|
||||
"Contact is connected" = "Kontakt jest połączony";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact is not connected yet!" = "Kontakt nie jest jeszcze połączony!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact name" = "Nazwa kontaktu";
|
||||
|
||||
@@ -1278,7 +1277,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "domyślnie (tak)";
|
||||
|
||||
/* chat item action */
|
||||
/* chat item action
|
||||
swipe action */
|
||||
"Delete" = "Usuń";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1317,12 +1317,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact" = "Usuń kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "Usuń Kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact?\nThis cannot be undone!" = "Usunąć kontakt?\nTo nie może być cofnięte!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Usuń bazę danych";
|
||||
|
||||
@@ -1377,9 +1371,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete old database?" = "Usunąć starą bazę danych?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection" = "Usuń oczekujące połączenie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection?" = "Usunąć oczekujące połączenie?";
|
||||
|
||||
@@ -1803,9 +1794,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting connection" = "Błąd usuwania połączenia";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting contact" = "Błąd usuwania kontaktu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting database" = "Błąd usuwania bazy danych";
|
||||
|
||||
@@ -1987,7 +1975,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Faster joining and more reliable messages." = "Szybsze dołączenie i bardziej niezawodne wiadomości.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Favorite" = "Ulubione";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2494,7 +2482,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Japanese interface" = "Japoński interfejs";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Join" = "Dołącz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2545,7 +2533,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Learn more" = "Dowiedz się więcej";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Leave" = "Opuść";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2698,12 +2686,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Message reception" = "Odebranie wiadomości";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message routing fallback" = "Rezerwowe trasowania wiadomości";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message routing mode" = "Tryb trasowania wiadomości";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Message source remains private." = "Źródło wiadomości pozostaje prywatne.";
|
||||
|
||||
@@ -2812,13 +2794,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Multiple chat profiles" = "Wiele profili czatu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Mute" = "Wycisz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Muted when inactive!" = "Wyciszony, gdy jest nieaktywny!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Name" = "Nazwa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2977,10 +2959,10 @@
|
||||
"One-time invitation link" = "Jednorazowy link zaproszenia";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be required for connection. Requires enabling VPN." = "Hosty onion będą wymagane do połączenia. Wymaga włączenia VPN.";
|
||||
"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Hosty onion będą wymagane do połączenia.\nWymaga włączenia VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be used when available. Requires enabling VPN." = "Hosty onion będą używane, gdy będą dostępne. Wymaga włączenia VPN.";
|
||||
"Onion hosts will be used when available.\nRequires compatible VPN." = "Hosty onion będą używane, gdy będą dostępne.\nWymaga włączenia VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will not be used." = "Hosty onion nie będą używane.";
|
||||
@@ -3327,7 +3309,7 @@
|
||||
/* chat item menu */
|
||||
"React…" = "Reaguj…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Read" = "Czytaj";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3429,7 +3411,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reduced battery usage" = "Zmniejszone zużycie baterii";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
/* reject incoming call via notification
|
||||
swipe action */
|
||||
"Reject" = "Odrzuć";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3552,9 +3535,6 @@
|
||||
/* chat item action */
|
||||
"Reveal" = "Ujawnij";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revert" = "Przywrócić";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revoke" = "Odwołaj";
|
||||
|
||||
@@ -3699,7 +3679,7 @@
|
||||
/* chat item text */
|
||||
"security code changed" = "kod bezpieczeństwa zmieniony";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat item action */
|
||||
"Select" = "Wybierz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3729,9 +3709,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"send direct message" = "wyślij wiadomość bezpośrednią";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Wyślij wiadomość bezpośrednią";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "Wyślij wiadomość bezpośrednią aby połączyć";
|
||||
|
||||
@@ -4026,9 +4003,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"SMP server" = "Serwer SMP";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SMP servers" = "Serwery SMP";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Podczas importu wystąpiły niekrytyczne błędy - więcej szczegółów można znaleźć w konsoli czatu.";
|
||||
|
||||
@@ -4143,9 +4117,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to scan" = "Dotknij, aby zeskanować";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "Dotknij, aby rozpocząć nowy czat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP connection timeout" = "Limit czasu połączenia TCP";
|
||||
|
||||
@@ -4383,7 +4354,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "Nieoczekiwany stan migracji";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unfav." = "Nie ulub.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4411,7 +4382,7 @@
|
||||
"Unknown error" = "Nieznany błąd";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unknown relays" = "nieznane przekaźniki";
|
||||
"unknown servers" = "nieznane przekaźniki";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown servers!" = "Nieznane serwery!";
|
||||
@@ -4437,13 +4408,13 @@
|
||||
/* authentication reason */
|
||||
"Unlock app" = "Odblokuj aplikację";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unmute" = "Wyłącz wyciszenie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unprotected" = "niezabezpieczony";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unread" = "Nieprzeczytane";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4452,18 +4423,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Update" = "Aktualizuj";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update .onion hosts setting?" = "Zaktualizować ustawienie hostów .onion?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update database passphrase" = "Aktualizuj hasło do bazy danych";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update network settings?" = "Zaktualizować ustawienia sieci?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update transport isolation mode?" = "Zaktualizować tryb izolacji transportu?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "zaktualizowano profil grupy";
|
||||
|
||||
@@ -4473,9 +4438,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Updating settings will re-connect the client to all servers." = "Aktualizacja ustawień spowoduje ponowne połączenie klienta ze wszystkimi serwerami.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating this setting will re-connect the client to all servers." = "Aktualizacja tych ustawień spowoduje ponowne połączenie klienta ze wszystkimi serwerami.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "Zaktualizuj i otwórz czat";
|
||||
|
||||
@@ -4542,9 +4504,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"User selection" = "Wybór użytkownika";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using .onion hosts requires compatible VPN provider." = "Używanie hostów .onion wymaga kompatybilnego dostawcy VPN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using SimpleX Chat servers." = "Używanie serwerów SimpleX Chat.";
|
||||
|
||||
@@ -4737,9 +4696,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP server" = "Serwer XFTP";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP servers" = "Serwery XFTP";
|
||||
|
||||
/* pref value */
|
||||
"yes" = "tak";
|
||||
|
||||
@@ -4878,9 +4834,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Już prosiłeś o połączenie!\nPowtórzyć prośbę połączenia?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have no chats" = "Nie masz czatów";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Musisz wprowadzić hasło przy każdym uruchomieniu aplikacji - nie jest one przechowywane na urządzeniu.";
|
||||
|
||||
@@ -4971,9 +4924,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Twoje profile czatu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Twój kontakt musi być online, aby połączenie zostało zakończone.\nMożesz anulować to połączenie i usunąć kontakt (i spróbować później z nowym linkiem).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Twój kontakt wysłał plik, który jest większy niż obecnie obsługiwany maksymalny rozmiar (%@).";
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -257,13 +257,15 @@
|
||||
"above, then choose:" = "ด้านบน จากนั้นเลือก:";
|
||||
|
||||
/* accept contact request via notification
|
||||
accept incoming call via notification */
|
||||
accept incoming call via notification
|
||||
swipe action */
|
||||
"Accept" = "รับ";
|
||||
|
||||
/* notification body */
|
||||
"Accept contact request from %@?" = "รับการขอติดต่อจาก %@?";
|
||||
|
||||
/* accept contact request via notification */
|
||||
/* accept contact request via notification
|
||||
swipe action */
|
||||
"Accept incognito" = "ยอมรับโหมดไม่ระบุตัวตน";
|
||||
|
||||
/* call status */
|
||||
@@ -618,7 +620,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Choose from library" = "เลือกจากอัลบั้ม";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Clear" = "ลบ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -738,9 +740,6 @@
|
||||
/* notification */
|
||||
"Contact is connected" = "เชื่อมต่อกับผู้ติดต่อแล้ว";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact is not connected yet!" = "ผู้ติดต่อยังไม่ได้เชื่อมต่อ!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact name" = "ชื่อผู้ติดต่อ";
|
||||
|
||||
@@ -888,7 +887,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"default (yes)" = "ค่าเริ่มต้น (ใช่)";
|
||||
|
||||
/* chat item action */
|
||||
/* chat item action
|
||||
swipe action */
|
||||
"Delete" = "ลบ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -921,9 +921,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact" = "ลบผู้ติดต่อ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "ลบผู้ติดต่อ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "ลบฐานข้อมูล";
|
||||
|
||||
@@ -975,9 +972,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete old database?" = "ลบฐานข้อมูลเก่า?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection" = "ลบการเชื่อมต่อที่รอดำเนินการ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete pending connection?" = "ลบการเชื่อมต่อที่รอดำเนินการหรือไม่?";
|
||||
|
||||
@@ -1281,9 +1275,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting connection" = "เกิดข้อผิดพลาดในการลบการเชื่อมต่อ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting contact" = "เกิดข้อผิดพลาดในการลบผู้ติดต่อ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting database" = "เกิดข้อผิดพลาดในการลบฐานข้อมูล";
|
||||
|
||||
@@ -1411,7 +1402,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Fast and no wait until the sender is online!" = "รวดเร็วและไม่ต้องรอจนกว่าผู้ส่งจะออนไลน์!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Favorite" = "ที่ชอบ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1789,7 +1780,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Japanese interface" = "อินเทอร์เฟซภาษาญี่ปุ่น";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Join" = "เข้าร่วม";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1819,7 +1810,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Learn more" = "ศึกษาเพิ่มเติม";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Leave" = "ออกจาก";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1993,13 +1984,13 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Multiple chat profiles" = "โปรไฟล์การแชทหลายรายการ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Mute" = "ปิดเสียง";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Muted when inactive!" = "ปิดเสียงเมื่อไม่ได้ใช้งาน!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Name" = "ชื่อ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2125,10 +2116,10 @@
|
||||
"One-time invitation link" = "ลิงก์คำเชิญแบบใช้ครั้งเดียว";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be required for connection. Requires enabling VPN." = "จำเป็นต้องมีโฮสต์หัวหอมสำหรับการเชื่อมต่อ ต้องเปิดใช้งาน VPN สำหรับการเชื่อมต่อ";
|
||||
"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "จำเป็นต้องมีโฮสต์หัวหอมสำหรับการเชื่อมต่อ ต้องเปิดใช้งาน VPN สำหรับการเชื่อมต่อ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will be used when available. Requires enabling VPN." = "จำเป็นต้องมีโฮสต์หัวหอมสำหรับการเชื่อมต่อ ต้องเปิดใช้งาน VPN สำหรับการเชื่อมต่อ";
|
||||
"Onion hosts will be used when available.\nRequires compatible VPN." = "จำเป็นต้องมีโฮสต์หัวหอมสำหรับการเชื่อมต่อ ต้องเปิดใช้งาน VPN สำหรับการเชื่อมต่อ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Onion hosts will not be used." = "โฮสต์หัวหอมจะไม่ถูกใช้";
|
||||
@@ -2352,7 +2343,7 @@
|
||||
/* chat item menu */
|
||||
"React…" = "ตอบสนอง…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Read" = "อ่าน";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2415,7 +2406,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reduced battery usage" = "ลดการใช้แบตเตอรี่";
|
||||
|
||||
/* reject incoming call via notification */
|
||||
/* reject incoming call via notification
|
||||
swipe action */
|
||||
"Reject" = "ปฏิเสธ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2496,9 +2488,6 @@
|
||||
/* chat item action */
|
||||
"Reveal" = "เปิดเผย";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revert" = "เปลี่ยนกลับ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Revoke" = "ถอน";
|
||||
|
||||
@@ -2601,7 +2590,7 @@
|
||||
/* chat item text */
|
||||
"security code changed" = "เปลี่ยนรหัสความปลอดภัยแล้ว";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat item action */
|
||||
"Select" = "เลือก";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2625,9 +2614,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send delivery receipts to" = "ส่งใบเสร็จรับการจัดส่งข้อความไปที่";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "ส่งข้อความโดยตรง";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send disappearing message" = "ส่งข้อความแบบที่หายไป";
|
||||
|
||||
@@ -2802,9 +2788,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Skipped messages" = "ข้อความที่ข้ามไป";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SMP servers" = "เซิร์ฟเวอร์ SMP";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Some non-fatal errors occurred during import - you may see Chat console for more details." = "ข้อผิดพลาดที่ไม่ร้ายแรงบางอย่างเกิดขึ้นระหว่างการนำเข้า - คุณอาจดูรายละเอียดเพิ่มเติมได้ที่คอนโซล Chat";
|
||||
|
||||
@@ -2880,9 +2863,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to join incognito" = "แตะเพื่อเข้าร่วมโหมดไม่ระบุตัวตน";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "แตะเพื่อเริ่มแชทใหม่";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP connection timeout" = "หมดเวลาการเชื่อมต่อ TCP";
|
||||
|
||||
@@ -3039,7 +3019,7 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unexpected migration state" = "สถานะการย้ายข้อมูลที่ไม่คาดคิด";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unfav." = "เลิกชอบ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3078,36 +3058,27 @@
|
||||
/* authentication reason */
|
||||
"Unlock app" = "ปลดล็อคแอป";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unmute" = "เปิดเสียง";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* swipe action */
|
||||
"Unread" = "เปลี่ยนเป็นยังไม่ได้อ่าน";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update" = "อัปเดต";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update .onion hosts setting?" = "อัปเดตการตั้งค่าโฮสต์ .onion ไหม?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update database passphrase" = "อัปเดตรหัสผ่านของฐานข้อมูล";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update network settings?" = "อัปเดตการตั้งค่าเครือข่ายไหม?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update transport isolation mode?" = "อัปเดตโหมดการแยกการขนส่งไหม?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "อัปเดตโปรไฟล์กลุ่มแล้ว";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating settings will re-connect the client to all servers." = "การอัปเดตการตั้งค่าจะเชื่อมต่อไคลเอนต์กับเซิร์ฟเวอร์ทั้งหมดอีกครั้ง";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating this setting will re-connect the client to all servers." = "การอัปเดตการตั้งค่านี้จะเชื่อมต่อไคลเอนต์กับเซิร์ฟเวอร์ทั้งหมดอีกครั้ง";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Upgrade and open chat" = "อัปเกรดและเปิดการแชท";
|
||||
|
||||
@@ -3135,9 +3106,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"User profile" = "โปรไฟล์ผู้ใช้";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using .onion hosts requires compatible VPN provider." = "การใช้โฮสต์ .onion ต้องการผู้ให้บริการ VPN ที่เข้ากันได้";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Using SimpleX Chat servers." = "กำลังใช้เซิร์ฟเวอร์ SimpleX Chat อยู่";
|
||||
|
||||
@@ -3252,9 +3220,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong passphrase!" = "รหัสผ่านผิด!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"XFTP servers" = "เซิร์ฟเวอร์ XFTP";
|
||||
|
||||
/* pref value */
|
||||
"yes" = "ใช่";
|
||||
|
||||
@@ -3345,9 +3310,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "เราไม่สามารถตรวจสอบคุณได้ กรุณาลองอีกครั้ง.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have no chats" = "คุณไม่มีการแชท";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "คุณต้องใส่รหัสผ่านทุกครั้งที่เริ่มแอป - รหัสผ่านไม่ได้จัดเก็บไว้ในอุปกรณ์";
|
||||
|
||||
@@ -3426,9 +3388,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "โปรไฟล์แชทของคุณ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "ผู้ติดต่อของคุณจะต้องออนไลน์เพื่อให้การเชื่อมต่อเสร็จสมบูรณ์\nคุณสามารถยกเลิกการเชื่อมต่อนี้และลบผู้ติดต่อออก (และลองใหม่ในภายหลังด้วยลิงก์ใหม่)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "ผู้ติดต่อของคุณส่งไฟล์ที่ใหญ่กว่าขนาดสูงสุดที่รองรับในปัจจุบัน (%@)";
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user