mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35bf3eee6b | |||
| 23ebef8fb2 | |||
| 624f36d686 | |||
| 16aa3d0f18 | |||
| d31dedf132 | |||
| 559093d0de | |||
| 57e2f1a843 | |||
| 7441ed9892 | |||
| f6ee6338c4 | |||
| df1d47e662 | |||
| 5a42c0c1d2 | |||
| 240131a023 | |||
| e3ed7f68a2 | |||
| 205cc835bf | |||
| 8d6c1fc87a | |||
| 37e275c3ca | |||
| f7ee7c5a61 | |||
| f893ad15de | |||
| a0763b3a43 | |||
| 7e5aad371a | |||
| e6ba82c8ff | |||
| a8bdf1555f | |||
| d278b3dbcd | |||
| ac4b1e9406 | |||
| b04f159970 | |||
| 55331289d3 | |||
| b2b1519aea | |||
| e769abf14a | |||
| 9ca7031f82 | |||
| 7a418918d6 | |||
| 8f1302e1c6 | |||
| ee7c5d2aac | |||
| bf697c722a | |||
| f30dfa0be7 | |||
| 9c73ef9769 | |||
| e38db7fb44 | |||
| cb76c8079c | |||
| 8fbba16f53 | |||
| 5384e2826d | |||
| 0975079a93 | |||
| 74e2b7582e | |||
| 413a8f6b76 | |||
| 619cff91b2 | |||
| 2ee16f9398 | |||
| bfab2d9fb6 | |||
| 229ea80499 | |||
| 95de471754 | |||
| cbc86cd81e | |||
| 19cab39ee8 | |||
| 6fa3695ad6 | |||
| 6e6afdbd25 | |||
| 93e88c3953 | |||
| 676b533393 | |||
| e1fb0ac0b1 | |||
| 8bda64a5c1 | |||
| ab058d7222 | |||
| 4f9c53f561 | |||
| 5257c6f9ca | |||
| 6729b1fb4f | |||
| ac5351a752 | |||
| 2ff4619ca4 | |||
| 493ad14b39 | |||
| 7f08f87ee4 |
@@ -43,6 +43,21 @@ private func addTermItem(_ items: inout [TerminalItem], _ item: TerminalItem) {
|
||||
items.append(item)
|
||||
}
|
||||
|
||||
class ItemsModel: ObservableObject {
|
||||
static let shared = ItemsModel()
|
||||
private let publisher = ObservableObjectPublisher()
|
||||
private var bag = Set<AnyCancellable>()
|
||||
var reversedChatItems: [ChatItem] = [] {
|
||||
willSet { publisher.send() }
|
||||
}
|
||||
init() {
|
||||
publisher
|
||||
.throttle(for: 0.25, scheduler: DispatchQueue.main, latest: true)
|
||||
.sink { self.objectWillChange.send() }
|
||||
.store(in: &bag)
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatModel: ObservableObject {
|
||||
@Published var onboardingStage: OnboardingStage?
|
||||
@Published var setDeliveryReceipts = false
|
||||
@@ -69,7 +84,6 @@ final class ChatModel: ObservableObject {
|
||||
@Published var networkStatuses: Dictionary<String, NetworkStatus> = [:]
|
||||
// current chat
|
||||
@Published var chatId: String?
|
||||
@Published var reversedChatItems: [ChatItem] = []
|
||||
var chatItemStatuses: Dictionary<Int64, CIStatus> = [:]
|
||||
@Published var chatToTop: String?
|
||||
@Published var groupMembers: [GMember] = []
|
||||
@@ -117,6 +131,8 @@ final class ChatModel: ObservableObject {
|
||||
|
||||
static let shared = ChatModel()
|
||||
|
||||
let im = ItemsModel.shared
|
||||
|
||||
static var ok: Bool { ChatModel.shared.chatDbStatus == .ok }
|
||||
|
||||
let ntfEnableLocal = true
|
||||
@@ -317,18 +333,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.addChat(cInfo.id)
|
||||
} else {
|
||||
addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
|
||||
}
|
||||
@@ -343,7 +350,7 @@ final class ChatModel: ObservableObject {
|
||||
var res: Bool
|
||||
if let chat = getChat(cInfo.id) {
|
||||
if let pItem = chat.chatItems.last {
|
||||
if pItem.id == cItem.id || (chatId == cInfo.id && reversedChatItems.first(where: { $0.id == cItem.id }) == nil) {
|
||||
if pItem.id == cItem.id || (chatId == cInfo.id && im.reversedChatItems.first(where: { $0.id == cItem.id }) == nil) {
|
||||
chat.chatItems = [cItem]
|
||||
}
|
||||
} else {
|
||||
@@ -373,7 +380,7 @@ final class ChatModel: ObservableObject {
|
||||
if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus {
|
||||
ci.meta.itemStatus = status
|
||||
}
|
||||
reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0)
|
||||
im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -397,17 +404,17 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
|
||||
private func _updateChatItem(at i: Int, with cItem: ChatItem) {
|
||||
reversedChatItems[i] = cItem
|
||||
reversedChatItems[i].viewTimestamp = .now
|
||||
im.reversedChatItems[i] = cItem
|
||||
im.reversedChatItems[i].viewTimestamp = .now
|
||||
}
|
||||
|
||||
func getChatItemIndex(_ cItem: ChatItem) -> Int? {
|
||||
reversedChatItems.firstIndex(where: { $0.id == cItem.id })
|
||||
im.reversedChatItems.firstIndex(where: { $0.id == cItem.id })
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -419,7 +426,7 @@ final class ChatModel: ObservableObject {
|
||||
if chatId == cInfo.id {
|
||||
if let i = getChatItemIndex(cItem) {
|
||||
_ = withAnimation {
|
||||
self.reversedChatItems.remove(at: i)
|
||||
im.reversedChatItems.remove(at: i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,16 +434,16 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
|
||||
func nextChatItemData<T>(_ chatItemId: Int64, previous: Bool, map: @escaping (ChatItem) -> T?) -> T? {
|
||||
guard var i = reversedChatItems.firstIndex(where: { $0.id == chatItemId }) else { return nil }
|
||||
guard var i = im.reversedChatItems.firstIndex(where: { $0.id == chatItemId }) else { return nil }
|
||||
if previous {
|
||||
while i < reversedChatItems.count - 1 {
|
||||
while i < im.reversedChatItems.count - 1 {
|
||||
i += 1
|
||||
if let res = map(reversedChatItems[i]) { return res }
|
||||
if let res = map(im.reversedChatItems[i]) { return res }
|
||||
}
|
||||
} else {
|
||||
while i > 0 {
|
||||
i -= 1
|
||||
if let res = map(reversedChatItems[i]) { return res }
|
||||
if let res = map(im.reversedChatItems[i]) { return res }
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -467,7 +474,7 @@ final class ChatModel: ObservableObject {
|
||||
func addLiveDummy(_ chatInfo: ChatInfo) -> ChatItem {
|
||||
let cItem = ChatItem.liveDummy(chatInfo.chatType)
|
||||
withAnimation {
|
||||
reversedChatItems.insert(cItem, at: 0)
|
||||
im.reversedChatItems.insert(cItem, at: 0)
|
||||
}
|
||||
return cItem
|
||||
}
|
||||
@@ -475,15 +482,15 @@ final class ChatModel: ObservableObject {
|
||||
func removeLiveDummy(animated: Bool = true) {
|
||||
if hasLiveDummy {
|
||||
if animated {
|
||||
withAnimation { _ = reversedChatItems.removeFirst() }
|
||||
withAnimation { _ = im.reversedChatItems.removeFirst() }
|
||||
} else {
|
||||
_ = reversedChatItems.removeFirst()
|
||||
_ = im.reversedChatItems.removeFirst()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var hasLiveDummy: Bool {
|
||||
reversedChatItems.first?.isLiveDummy == true
|
||||
im.reversedChatItems.first?.isLiveDummy == true
|
||||
}
|
||||
|
||||
func markChatItemsRead(_ cInfo: ChatInfo) {
|
||||
@@ -500,7 +507,7 @@ final class ChatModel: ObservableObject {
|
||||
|
||||
private func markCurrentChatRead(fromIndex i: Int = 0) {
|
||||
var j = i
|
||||
while j < reversedChatItems.count {
|
||||
while j < im.reversedChatItems.count {
|
||||
markChatItemRead_(j)
|
||||
j += 1
|
||||
}
|
||||
@@ -514,7 +521,7 @@ final class ChatModel: ObservableObject {
|
||||
var unreadBelow = 0
|
||||
var j = i - 1
|
||||
while j >= 0 {
|
||||
if case .rcvNew = self.reversedChatItems[j].meta.itemStatus {
|
||||
if case .rcvNew = self.im.reversedChatItems[j].meta.itemStatus {
|
||||
unreadBelow += 1
|
||||
}
|
||||
j -= 1
|
||||
@@ -549,56 +556,114 @@ final class ChatModel: ObservableObject {
|
||||
// clear current chat
|
||||
if chatId == cInfo.id {
|
||||
chatItemStatuses = [:]
|
||||
reversedChatItems = []
|
||||
im.reversedChatItems = []
|
||||
}
|
||||
}
|
||||
|
||||
func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async {
|
||||
if chatId == cInfo.id,
|
||||
let itemIndex = getChatItemIndex(cItem),
|
||||
let chatIndex = getChatIndex(cInfo.id),
|
||||
reversedChatItems[itemIndex].isRcvNew {
|
||||
im.reversedChatItems[itemIndex].isRcvNew {
|
||||
await MainActor.run {
|
||||
withTransaction(Transaction()) {
|
||||
// update current chat
|
||||
markChatItemRead_(itemIndex)
|
||||
// update preview
|
||||
decreaseUnreadCounter(chatIndex)
|
||||
unreadCollector.changeUnreadCounter(cInfo.id, by: -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func markChatItemRead_(_ i: Int) {
|
||||
let meta = reversedChatItems[i].meta
|
||||
if case .rcvNew = meta.itemStatus {
|
||||
reversedChatItems[i].meta.itemStatus = .rcvRead
|
||||
reversedChatItems[i].viewTimestamp = .now
|
||||
if meta.itemLive != true, let ttl = meta.itemTimed?.ttl {
|
||||
reversedChatItems[i].meta.itemTimed?.deleteAt = .now + TimeInterval(ttl)
|
||||
private let unreadCollector = UnreadCollector()
|
||||
|
||||
class UnreadCollector {
|
||||
private let subject = PassthroughSubject<Void, Never>()
|
||||
private var bag = Set<AnyCancellable>()
|
||||
private var unreadCounts: [ChatId: Int] = [:]
|
||||
|
||||
init() {
|
||||
subject
|
||||
.debounce(for: 1, scheduler: DispatchQueue.main)
|
||||
.sink {
|
||||
let m = ChatModel.shared
|
||||
for (chatId, count) in self.unreadCounts {
|
||||
if let i = m.getChatIndex(chatId) {
|
||||
m.changeUnreadCounter(i, by: count)
|
||||
}
|
||||
}
|
||||
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>()
|
||||
|
||||
init() {
|
||||
subject
|
||||
.throttle(for: 2, scheduler: DispatchQueue.main, latest: true)
|
||||
.sink {
|
||||
let m = ChatModel.shared
|
||||
if m.chatId == nil {
|
||||
withAnimation {
|
||||
m.chats = m.chats.sorted(using: KeyPathComparator(\.popTs, order: .reverse))
|
||||
}
|
||||
} else {
|
||||
m.chats = m.chats.sorted(using: KeyPathComparator(\.popTs, order: .reverse))
|
||||
}
|
||||
}
|
||||
.store(in: &bag)
|
||||
}
|
||||
|
||||
func addChat(_ chatId: ChatId) {
|
||||
if let index = ChatModel.shared.getChatIndex(chatId) {
|
||||
ChatModel.shared.chats[index].popTs = CFAbsoluteTimeGetCurrent()
|
||||
subject.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decreaseUnreadCounter(_ chatIndex: Int) {
|
||||
chats[chatIndex].chatStats.unreadCount = chats[chatIndex].chatStats.unreadCount - 1
|
||||
decreaseUnreadCounter(user: currentUser!)
|
||||
private func markChatItemRead_(_ i: Int) {
|
||||
let meta = im.reversedChatItems[i].meta
|
||||
if case .rcvNew = meta.itemStatus {
|
||||
im.reversedChatItems[i].meta.itemStatus = .rcvRead
|
||||
im.reversedChatItems[i].viewTimestamp = .now
|
||||
if meta.itemLive != true, let ttl = meta.itemTimed?.ttl {
|
||||
im.reversedChatItems[i].meta.itemTimed?.deleteAt = .now + TimeInterval(ttl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -612,8 +677,8 @@ final class ChatModel: ObservableObject {
|
||||
var ns: [String] = []
|
||||
if let ciCategory = chatItem.mergeCategory,
|
||||
var i = getChatItemIndex(chatItem) {
|
||||
while i < reversedChatItems.count {
|
||||
let ci = reversedChatItems[i]
|
||||
while i < im.reversedChatItems.count {
|
||||
let ci = im.reversedChatItems[i]
|
||||
if ci.mergeCategory != ciCategory { break }
|
||||
if let m = ci.memberConnected {
|
||||
ns.append(m.displayName)
|
||||
@@ -628,7 +693,7 @@ final class ChatModel: ObservableObject {
|
||||
// returns the index of the passed item and the next item (it has smaller index)
|
||||
func getNextChatItem(_ ci: ChatItem) -> (Int?, ChatItem?) {
|
||||
if let i = getChatItemIndex(ci) {
|
||||
(i, i > 0 ? reversedChatItems[i - 1] : nil)
|
||||
(i, i > 0 ? im.reversedChatItems[i - 1] : nil)
|
||||
} else {
|
||||
(nil, nil)
|
||||
}
|
||||
@@ -638,10 +703,10 @@ final class ChatModel: ObservableObject {
|
||||
// and the previous visible item with another merge category
|
||||
func getPrevShownChatItem(_ ciIndex: Int?, _ ciCategory: CIMergeCategory?) -> (Int?, ChatItem?) {
|
||||
guard var i = ciIndex else { return (nil, nil) }
|
||||
let fst = reversedChatItems.count - 1
|
||||
let fst = im.reversedChatItems.count - 1
|
||||
while i < fst {
|
||||
i = i + 1
|
||||
let ci = reversedChatItems[i]
|
||||
let ci = im.reversedChatItems[i]
|
||||
if ciCategory == nil || ciCategory != ci.mergeCategory {
|
||||
return (i - 1, ci)
|
||||
}
|
||||
@@ -654,7 +719,7 @@ final class ChatModel: ObservableObject {
|
||||
var prevMember: GroupMember? = nil
|
||||
var memberIds: Set<Int64> = []
|
||||
for i in range {
|
||||
if case let .groupRcv(m) = reversedChatItems[i].chatDir {
|
||||
if case let .groupRcv(m) = im.reversedChatItems[i].chatDir {
|
||||
if prevMember == nil && m.groupMemberId != member.groupMemberId { prevMember = m }
|
||||
memberIds.insert(m.groupMemberId)
|
||||
}
|
||||
@@ -729,9 +794,9 @@ final class ChatModel: ObservableObject {
|
||||
var i = 0
|
||||
var totalBelow = 0
|
||||
var unreadBelow = 0
|
||||
while i < reversedChatItems.count - 1 && !itemsInView.contains(reversedChatItems[i].viewId) {
|
||||
while i < im.reversedChatItems.count - 1 && !itemsInView.contains(im.reversedChatItems[i].viewId) {
|
||||
totalBelow += 1
|
||||
if reversedChatItems[i].isRcvNew {
|
||||
if im.reversedChatItems[i].isRcvNew {
|
||||
unreadBelow += 1
|
||||
}
|
||||
i += 1
|
||||
@@ -740,12 +805,12 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
|
||||
func topItemInView(itemsInView: Set<String>) -> ChatItem? {
|
||||
let maxIx = reversedChatItems.count - 1
|
||||
let maxIx = im.reversedChatItems.count - 1
|
||||
var i = 0
|
||||
let inView = { itemsInView.contains(self.reversedChatItems[$0].viewId) }
|
||||
let inView = { itemsInView.contains(self.im.reversedChatItems[$0].viewId) }
|
||||
while i < maxIx && !inView(i) { i += 1 }
|
||||
while i < maxIx && inView(i) { i += 1 }
|
||||
return reversedChatItems[min(i - 1, maxIx)]
|
||||
return im.reversedChatItems[min(i - 1, maxIx)]
|
||||
}
|
||||
|
||||
func setContactNetworkStatus(_ contact: Contact, _ status: NetworkStatus) {
|
||||
@@ -783,6 +848,7 @@ final class Chat: ObservableObject, Identifiable, ChatLike {
|
||||
@Published var chatItems: [ChatItem]
|
||||
@Published var chatStats: ChatStats
|
||||
var created = Date.now
|
||||
var popTs: CFAbsoluteTime?
|
||||
|
||||
init(_ cData: ChatData) {
|
||||
self.chatInfo = cData.chatInfo
|
||||
@@ -829,24 +895,6 @@ final class Chat: ObservableObject, Identifiable, ChatLike {
|
||||
|
||||
var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } }
|
||||
|
||||
func groupFeatureEnabled(_ feature: GroupFeature) -> Bool {
|
||||
if case let .group(groupInfo) = self.chatInfo {
|
||||
let p = groupInfo.fullGroupPreferences
|
||||
return switch feature {
|
||||
case .timedMessages: p.timedMessages.on
|
||||
case .directMessages: p.directMessages.on(for: groupInfo.membership)
|
||||
case .fullDelete: p.fullDelete.on
|
||||
case .reactions: p.reactions.on
|
||||
case .voice: p.voice.on(for: groupInfo.membership)
|
||||
case .files: p.files.on(for: groupInfo.membership)
|
||||
case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership)
|
||||
case .history: p.history.on
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -225,6 +225,15 @@ func apiStartChat(ctrl: chat_ctrl? = nil) throws -> Bool {
|
||||
}
|
||||
}
|
||||
|
||||
func apiCheckChatRunning() throws -> Bool {
|
||||
let r = chatSendCmdSync(.checkChatRunning)
|
||||
switch r {
|
||||
case .chatRunning: return true
|
||||
case .chatStopped: return false
|
||||
default: throw r
|
||||
}
|
||||
}
|
||||
|
||||
func apiStopChat() async throws {
|
||||
let r = await chatSendCmd(.apiStopChat)
|
||||
switch r {
|
||||
@@ -270,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] {
|
||||
@@ -325,11 +336,18 @@ func loadChat(chat: Chat, search: String = "") {
|
||||
do {
|
||||
let cInfo = chat.chatInfo
|
||||
let m = ChatModel.shared
|
||||
let im = ItemsModel.shared
|
||||
m.chatItemStatuses = [:]
|
||||
m.reversedChatItems = []
|
||||
im.reversedChatItems = []
|
||||
let chat = try apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search)
|
||||
m.updateChatInfo(chat.chatInfo)
|
||||
m.reversedChatItems = chat.chatItems.reversed()
|
||||
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))")
|
||||
}
|
||||
@@ -421,15 +439,15 @@ func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, re
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode) async throws -> (ChatItem, ChatItem?) {
|
||||
let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemId: itemId, mode: mode), bgDelay: msgDelay)
|
||||
if case let .chatItemDeleted(_, deletedChatItem, toChatItem, _) = r { return (deletedChatItem.chatItem, toChatItem?.chatItem) }
|
||||
func apiDeleteChatItems(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] {
|
||||
let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemIds: itemIds, mode: mode), bgDelay: msgDelay)
|
||||
if case let .chatItemsDeleted(_, items, _) = r { return items }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiDeleteMemberChatItem(groupId: Int64, groupMemberId: Int64, itemId: Int64) async throws -> (ChatItem, ChatItem?) {
|
||||
let r = await chatSendCmd(.apiDeleteMemberChatItem(groupId: groupId, groupMemberId: groupMemberId, itemId: itemId), bgDelay: msgDelay)
|
||||
if case let .chatItemDeleted(_, deletedChatItem, toChatItem, _) = r { return (deletedChatItem.chatItem, toChatItem?.chatItem) }
|
||||
func apiDeleteMemberChatItems(groupId: Int64, itemIds: [Int64]) async throws -> [ChatItemDeletion] {
|
||||
let r = await chatSendCmd(.apiDeleteMemberChatItem(groupId: groupId, itemIds: itemIds), bgDelay: msgDelay)
|
||||
if case let .chatItemsDeleted(_, items, _) = r { return items }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -749,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(
|
||||
@@ -774,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 }
|
||||
@@ -1102,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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1439,15 +1513,16 @@ func startChat(refreshInvitations: Bool = true) throws {
|
||||
logger.debug("startChat")
|
||||
let m = ChatModel.shared
|
||||
try setNetworkConfig(getNetCfg())
|
||||
let justStarted = try apiStartChat()
|
||||
let chatRunning = try apiCheckChatRunning()
|
||||
m.users = try listUsers()
|
||||
if justStarted {
|
||||
if !chatRunning {
|
||||
try getUserChatData()
|
||||
NtfManager.shared.setNtfBadgeCount(m.totalUnreadCountForAllUsers())
|
||||
if (refreshInvitations) {
|
||||
try refreshCallInvitations()
|
||||
}
|
||||
(m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken()
|
||||
_ = try apiStartChat()
|
||||
// deviceToken is set when AppDelegate.application(didRegisterForRemoteNotificationsWithDeviceToken:) is called,
|
||||
// when it is called before startChat
|
||||
if let token = m.deviceToken {
|
||||
@@ -1700,6 +1775,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)
|
||||
@@ -1735,21 +1815,25 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
m.updateChatItem(r.chatInfo, r.chatReaction.chatItem)
|
||||
}
|
||||
}
|
||||
case let .chatItemDeleted(user, deletedChatItem, toChatItem, _):
|
||||
case let .chatItemsDeleted(user, items, _):
|
||||
if !active(user) {
|
||||
if toChatItem == nil && deletedChatItem.chatItem.isRcvNew && deletedChatItem.chatInfo.ntfsEnabled {
|
||||
await MainActor.run {
|
||||
m.decreaseUnreadCounter(user: user)
|
||||
for item in items {
|
||||
if item.toChatItem == nil && item.deletedChatItem.chatItem.isRcvNew && item.deletedChatItem.chatInfo.ntfsEnabled {
|
||||
await MainActor.run {
|
||||
m.decreaseUnreadCounter(user: user)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
if let toChatItem = toChatItem {
|
||||
_ = m.upsertChatItem(toChatItem.chatInfo, toChatItem.chatItem)
|
||||
} else {
|
||||
m.removeChatItem(deletedChatItem.chatInfo, deletedChatItem.chatItem)
|
||||
for item in items {
|
||||
if let toChatItem = item.toChatItem {
|
||||
_ = m.upsertChatItem(toChatItem.chatInfo, toChatItem.chatItem)
|
||||
} else {
|
||||
m.removeChatItem(item.deletedChatItem.chatInfo, item.deletedChatItem.chatItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .receivedGroupInvitation(user, groupInfo, _, _):
|
||||
|
||||
@@ -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 still send messages to \(contact.displayName) from the Deleted chats."),
|
||||
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: {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import SimpleXChat
|
||||
|
||||
struct CIChatFeatureView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@ObservedObject var im = ItemsModel.shared
|
||||
@ObservedObject var chat: Chat
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var chatItem: ChatItem
|
||||
@@ -53,8 +54,8 @@ struct CIChatFeatureView: View {
|
||||
var fs: [FeatureInfo] = []
|
||||
var icons: Set<String> = []
|
||||
if var i = m.getChatItemIndex(chatItem) {
|
||||
while i < m.reversedChatItems.count,
|
||||
let f = featureInfo(m.reversedChatItems[i]) {
|
||||
while i < im.reversedChatItems.count,
|
||||
let f = featureInfo(im.reversedChatItems[i]) {
|
||||
if !icons.contains(f.icon) {
|
||||
fs.insert(f, at: 0)
|
||||
icons.insert(f.icon)
|
||||
|
||||
@@ -49,7 +49,7 @@ struct FramedItemView: View {
|
||||
if let qi = chatItem.quotedItem {
|
||||
ciQuoteView(qi)
|
||||
.onTapGesture {
|
||||
if let ci = m.reversedChatItems.first(where: { $0.id == qi.itemId }) {
|
||||
if let ci = ItemsModel.shared.reversedChatItems.first(where: { $0.id == qi.itemId }) {
|
||||
withAnimation {
|
||||
scrollModel.scrollToItem(id: ci.id)
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ struct MarkedDeletedItemView: View {
|
||||
var blockedByAdmin = 0
|
||||
var deleted = 0
|
||||
var moderatedBy: Set<String> = []
|
||||
while i < m.reversedChatItems.count,
|
||||
let ci = .some(m.reversedChatItems[i]),
|
||||
while i < ItemsModel.shared.reversedChatItems.count,
|
||||
let ci = .some(ItemsModel.shared.reversedChatItems[i]),
|
||||
ci.mergeCategory == ciCategory,
|
||||
let itemDeleted = ci.meta.itemDeleted {
|
||||
switch itemDeleted {
|
||||
|
||||
@@ -21,7 +21,6 @@ struct ChatItemForwardingView: View {
|
||||
@State private var searchText: String = ""
|
||||
@FocusState private var searchFocused
|
||||
@State private var alert: SomeAlert?
|
||||
@State private var hasSimplexLink_: Bool?
|
||||
private let chatsToForwardTo = filterChatsToForwardTo(chats: ChatModel.shared.chats)
|
||||
|
||||
var body: some View {
|
||||
@@ -67,34 +66,6 @@ struct ChatItemForwardingView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func prohibitedByPref(_ chat: Chat) -> Bool {
|
||||
// preference checks should match checks in compose view
|
||||
let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
|
||||
let fileProhibited = (ci.content.msgContent?.isMediaOrFileAttachment ?? false) && !chat.groupFeatureEnabled(.files)
|
||||
let voiceProhibited = (ci.content.msgContent?.isVoice ?? false) && !chat.chatInfo.featureEnabled(.voice)
|
||||
return switch chat.chatInfo {
|
||||
case .direct: voiceProhibited
|
||||
case .group: simplexLinkProhibited || fileProhibited || voiceProhibited
|
||||
case .local: false
|
||||
case .contactRequest: false
|
||||
case .contactConnection: false
|
||||
case .invalidJSON: false
|
||||
}
|
||||
}
|
||||
|
||||
private var hasSimplexLink: Bool {
|
||||
if let hasSimplexLink_ { return hasSimplexLink_ }
|
||||
let r =
|
||||
if let mcText = ci.content.msgContent?.text,
|
||||
let parsedMsg = parseSimpleXMarkdown(mcText) {
|
||||
parsedMsgHasSimplexLink(parsedMsg)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
hasSimplexLink_ = r
|
||||
return r
|
||||
}
|
||||
|
||||
private func emptyList() -> some View {
|
||||
Text("No filtered chats")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
@@ -102,7 +73,11 @@ struct ChatItemForwardingView: View {
|
||||
}
|
||||
|
||||
@ViewBuilder private func forwardListChatView(_ chat: Chat) -> some View {
|
||||
let prohibited = prohibitedByPref(chat)
|
||||
let prohibited = chat.prohibitedByPref(
|
||||
hasSimplexLink: hasSimplexLink(ci.content.msgContent?.text),
|
||||
isMediaOrFileAttachment: ci.content.msgContent?.isMediaOrFileAttachment ?? false,
|
||||
isVoice: ci.content.msgContent?.isVoice ?? false
|
||||
)
|
||||
Button {
|
||||
if prohibited {
|
||||
alert = SomeAlert(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -15,6 +15,7 @@ private let memberImageSize: CGFloat = 34
|
||||
|
||||
struct ChatView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@ObservedObject var im = ItemsModel.shared
|
||||
@State var theme: AppTheme = buildTheme()
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@@ -42,6 +43,9 @@ struct ChatView: View {
|
||||
@State private var showGroupLinkSheet: Bool = false
|
||||
@State private var groupLink: String?
|
||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||
@State private var selectedChatItems: Set<Int64>? = nil
|
||||
@State private var showDeleteSelectedMessages: Bool = false
|
||||
@State private var allowToDeleteSelectedMessagesForAll: Bool = false
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
@@ -79,25 +83,58 @@ struct ChatView: View {
|
||||
floatingButtons(counts: floatingButtonModel.unreadChatItemCounts)
|
||||
}
|
||||
connectingText()
|
||||
ComposeView(
|
||||
chat: chat,
|
||||
composeState: $composeState,
|
||||
keyboardVisible: $keyboardVisible
|
||||
)
|
||||
.disabled(!cInfo.sendMsgEnabled)
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.navigationTitle(cInfo.chatViewName)
|
||||
.background(theme.colors.background)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.environmentObject(theme)
|
||||
.confirmationDialog(selectedChatItems?.count == 1 ? "Delete message?" : "Delete \((selectedChatItems?.count ?? 0)) messages?", isPresented: $showDeleteSelectedMessages, titleVisibility: .visible) {
|
||||
Button("Delete for me", role: .destructive) {
|
||||
if let selected = selectedChatItems {
|
||||
deleteMessages(chat, selected.sorted(), .cidmInternal, moderate: false, deletedSelectedMessages) }
|
||||
}
|
||||
if allowToDeleteSelectedMessagesForAll {
|
||||
Button(broadcastDeleteButtonText(chat), role: .destructive) {
|
||||
if let selected = selectedChatItems {
|
||||
allowToDeleteSelectedMessagesForAll = false
|
||||
deleteMessages(chat, selected.sorted(), .cidmBroadcast, moderate: false, deletedSelectedMessages)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
loadChat(chat: chat)
|
||||
initChatView()
|
||||
selectedChatItems = nil
|
||||
}
|
||||
.onChange(of: chatModel.chatId) { cId in
|
||||
showChatInfoSheet = false
|
||||
stopAudioPlayer()
|
||||
if let cId {
|
||||
selectedChatItems = nil
|
||||
if let c = chatModel.getChat(cId) {
|
||||
chat = c
|
||||
}
|
||||
@@ -110,7 +147,7 @@ struct ChatView: View {
|
||||
.onChange(of: revealedChatItem) { _ in
|
||||
NotificationCenter.postReverseListNeedsLayout()
|
||||
}
|
||||
.onChange(of: chatModel.reversedChatItems) { reversedChatItems in
|
||||
.onChange(of: im.reversedChatItems) { reversedChatItems in
|
||||
if reversedChatItems.count <= loadItemsPerPage && filtered(reversedChatItems).count < 10 {
|
||||
loadChatItems(chat.chatInfo)
|
||||
}
|
||||
@@ -124,7 +161,7 @@ struct ChatView: View {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
|
||||
if chatModel.chatId == nil {
|
||||
chatModel.chatItemStatuses = [:]
|
||||
chatModel.reversedChatItems = []
|
||||
ItemsModel.shared.reversedChatItems = []
|
||||
chatModel.groupMembers = []
|
||||
chatModel.groupMembersIndexes.removeAll()
|
||||
chatModel.membersLoaded = false
|
||||
@@ -137,35 +174,23 @@ struct ChatView: View {
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .principal) {
|
||||
if case let .direct(contact) = cInfo {
|
||||
if selectedChatItems != nil {
|
||||
SelectedItemsTopToolbar(selectedChatItems: $selectedChatItems)
|
||||
} 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) {
|
||||
ChatInfoView(
|
||||
chat: chat,
|
||||
contact: contact,
|
||||
localAlias: chat.chatInfo.localAlias,
|
||||
onSearch: { focusSearch() }
|
||||
)
|
||||
}
|
||||
} else if case let .group(groupInfo) = cInfo {
|
||||
Button {
|
||||
@@ -183,7 +208,8 @@ struct ChatView: View {
|
||||
chat.chatInfo = .group(groupInfo: gInfo)
|
||||
chat.created = Date.now
|
||||
}
|
||||
)
|
||||
),
|
||||
onSearch: { focusSearch() }
|
||||
)
|
||||
}
|
||||
} else if case .local = cInfo {
|
||||
@@ -191,66 +217,76 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
switch cInfo {
|
||||
case let .direct(contact):
|
||||
HStack {
|
||||
let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser
|
||||
if callsPrefEnabled {
|
||||
if chatModel.activeCall == nil {
|
||||
callButton(contact, .audio, imageName: "phone")
|
||||
.disabled(!contact.ready || !contact.active)
|
||||
} else if let call = chatModel.activeCall, call.contact.id == cInfo.id {
|
||||
endCallButton(call)
|
||||
}
|
||||
if selectedChatItems != nil {
|
||||
Button {
|
||||
withAnimation {
|
||||
selectedChatItems = nil
|
||||
}
|
||||
Menu {
|
||||
if callsPrefEnabled && chatModel.activeCall == nil {
|
||||
Button {
|
||||
CallController.shared.startCall(contact, .video)
|
||||
} label: {
|
||||
Label("Video call", systemImage: "video")
|
||||
} label: {
|
||||
Text("Cancel")
|
||||
}
|
||||
} else {
|
||||
switch cInfo {
|
||||
case let .direct(contact):
|
||||
HStack {
|
||||
let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser
|
||||
if callsPrefEnabled {
|
||||
if chatModel.activeCall == nil {
|
||||
callButton(contact, .audio, imageName: "phone")
|
||||
.disabled(!contact.ready || !contact.active)
|
||||
} else if let call = chatModel.activeCall, call.contact.id == cInfo.id {
|
||||
endCallButton(call)
|
||||
}
|
||||
.disabled(!contact.ready || !contact.active)
|
||||
}
|
||||
searchButton()
|
||||
ToggleNtfsButton(chat: chat)
|
||||
.disabled(!contact.ready || !contact.active)
|
||||
} label: {
|
||||
Image(systemName: "ellipsis")
|
||||
}
|
||||
}
|
||||
case let .group(groupInfo):
|
||||
HStack {
|
||||
if groupInfo.canAddMembers {
|
||||
if (chat.chatInfo.incognito) {
|
||||
groupLinkButton()
|
||||
.appSheet(isPresented: $showGroupLinkSheet) {
|
||||
GroupLinkView(
|
||||
groupId: groupInfo.groupId,
|
||||
groupLink: $groupLink,
|
||||
groupLinkMemberRole: $groupLinkMemberRole,
|
||||
showTitle: true,
|
||||
creatingGroup: false
|
||||
)
|
||||
}
|
||||
} else {
|
||||
addMembersButton()
|
||||
.appSheet(isPresented: $showAddMembersSheet) {
|
||||
AddGroupMembersView(chat: chat, groupInfo: groupInfo)
|
||||
Menu {
|
||||
if callsPrefEnabled && chatModel.activeCall == nil {
|
||||
Button {
|
||||
CallController.shared.startCall(contact, .video)
|
||||
} label: {
|
||||
Label("Video call", systemImage: "video")
|
||||
}
|
||||
.disabled(!contact.ready || !contact.active)
|
||||
}
|
||||
searchButton()
|
||||
ToggleNtfsButton(chat: chat)
|
||||
.disabled(!contact.ready || !contact.active)
|
||||
} label: {
|
||||
Image(systemName: "ellipsis")
|
||||
}
|
||||
}
|
||||
Menu {
|
||||
searchButton()
|
||||
ToggleNtfsButton(chat: chat)
|
||||
} label: {
|
||||
Image(systemName: "ellipsis")
|
||||
case let .group(groupInfo):
|
||||
HStack {
|
||||
if groupInfo.canAddMembers {
|
||||
if (chat.chatInfo.incognito) {
|
||||
groupLinkButton()
|
||||
.appSheet(isPresented: $showGroupLinkSheet) {
|
||||
GroupLinkView(
|
||||
groupId: groupInfo.groupId,
|
||||
groupLink: $groupLink,
|
||||
groupLinkMemberRole: $groupLinkMemberRole,
|
||||
showTitle: true,
|
||||
creatingGroup: false
|
||||
)
|
||||
}
|
||||
} else {
|
||||
addMembersButton()
|
||||
.appSheet(isPresented: $showAddMembersSheet) {
|
||||
AddGroupMembersView(chat: chat, groupInfo: groupInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
Menu {
|
||||
searchButton()
|
||||
ToggleNtfsButton(chat: chat)
|
||||
} label: {
|
||||
Image(systemName: "ellipsis")
|
||||
}
|
||||
}
|
||||
case .local:
|
||||
searchButton()
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
case .local:
|
||||
searchButton()
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,7 +363,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
|
||||
@@ -339,7 +375,7 @@ struct ChatView: View {
|
||||
|
||||
private func chatItemsList() -> some View {
|
||||
let cInfo = chat.chatInfo
|
||||
let mergedItems = filtered(chatModel.reversedChatItems)
|
||||
let mergedItems = filtered(im.reversedChatItems)
|
||||
return GeometryReader { g in
|
||||
ReverseList(items: mergedItems, scrollState: $scrollModel.state) { ci in
|
||||
let voiceNoFrame = voiceWithoutFrame(ci)
|
||||
@@ -372,7 +408,7 @@ struct ChatView: View {
|
||||
loadChat(chat: c)
|
||||
}
|
||||
}
|
||||
.onChange(of: chatModel.reversedChatItems) { _ in
|
||||
.onChange(of: im.reversedChatItems) { _ in
|
||||
floatingButtonModel.chatItemsChanged()
|
||||
}
|
||||
}
|
||||
@@ -407,7 +443,7 @@ struct ChatView: View {
|
||||
init() {
|
||||
unreadChatItemCounts = UnreadChatItemCounts(
|
||||
isNearBottom: true,
|
||||
unreadBelow: .zero
|
||||
unreadBelow: 0
|
||||
)
|
||||
events
|
||||
.receive(on: DispatchQueue.global(qos: .background))
|
||||
@@ -515,14 +551,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 {
|
||||
@@ -552,6 +592,33 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func showModerateSelectedMessagesAlert(_ groupInfo: GroupInfo) {
|
||||
guard let count = selectedChatItems?.count, count > 0 else { return }
|
||||
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
title: Text(count == 1 ? "Delete member message?" : "Delete \(count) messages of members?"),
|
||||
message: Text(
|
||||
groupInfo.fullGroupPreferences.fullDelete.on
|
||||
? (count == 1 ? "The message will be deleted for all members." : "The messages will be deleted for all members.")
|
||||
: (count == 1 ? "The message will be marked as moderated for all members." : "The messages will be marked as moderated for all members.")
|
||||
),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
if let selected = selectedChatItems {
|
||||
deleteMessages(chat, selected.sorted(), .cidmBroadcast, moderate: true, deletedSelectedMessages)
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
))
|
||||
}
|
||||
|
||||
private func deletedSelectedMessages() async {
|
||||
await MainActor.run {
|
||||
withAnimation {
|
||||
selectedChatItems = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadChatItems(_ cInfo: ChatInfo) {
|
||||
Task {
|
||||
if loadingItems || firstPage { return }
|
||||
@@ -562,7 +629,7 @@ struct ChatView: View {
|
||||
// Load additional items until the page is +50 large after merging
|
||||
while chatItemsAvailable && filtered(reversedPage).count < loadItemsPerPage {
|
||||
let pagination: ChatPagination =
|
||||
if let lastItem = reversedPage.last ?? chatModel.reversedChatItems.last {
|
||||
if let lastItem = reversedPage.last ?? im.reversedChatItems.last {
|
||||
.before(chatItemId: lastItem.id, count: loadItemsPerPage)
|
||||
} else {
|
||||
.last(count: loadItemsPerPage)
|
||||
@@ -580,7 +647,7 @@ struct ChatView: View {
|
||||
if reversedPage.count == 0 {
|
||||
firstPage = true
|
||||
} else {
|
||||
chatModel.reversedChatItems.append(contentsOf: reversedPage)
|
||||
im.reversedChatItems.append(contentsOf: reversedPage)
|
||||
}
|
||||
loadingItems = false
|
||||
}
|
||||
@@ -603,7 +670,8 @@ struct ChatView: View {
|
||||
maxWidth: maxWidth,
|
||||
composeState: $composeState,
|
||||
selectedMember: $selectedMember,
|
||||
revealedChatItem: $revealedChatItem
|
||||
revealedChatItem: $revealedChatItem,
|
||||
selectedChatItems: $selectedChatItems
|
||||
)
|
||||
}
|
||||
|
||||
@@ -625,6 +693,8 @@ struct ChatView: View {
|
||||
@State private var chatItemInfo: ChatItemInfo?
|
||||
@State private var showForwardingSheet: Bool = false
|
||||
|
||||
@Binding var selectedChatItems: Set<Int64>?
|
||||
|
||||
@State private var allowMenu: Bool = true
|
||||
|
||||
var revealed: Bool { chatItem == revealedChatItem }
|
||||
@@ -634,15 +704,36 @@ struct ChatView: View {
|
||||
let ciCategory = chatItem.mergeCategory
|
||||
let (prevHidden, prevItem) = m.getPrevShownChatItem(currIndex, ciCategory)
|
||||
let range = itemsRange(currIndex, prevHidden)
|
||||
let im = ItemsModel.shared
|
||||
Group {
|
||||
if revealed, let range = range {
|
||||
let items = Array(zip(Array(range), m.reversedChatItems[range]))
|
||||
let items = Array(zip(Array(range), im.reversedChatItems[range]))
|
||||
ForEach(items, id: \.1.viewId) { (i, ci) in
|
||||
let prev = i == prevHidden ? prevItem : m.reversedChatItems[i + 1]
|
||||
let prev = i == prevHidden ? prevItem : im.reversedChatItems[i + 1]
|
||||
chatItemView(ci, nil, prev)
|
||||
.overlay {
|
||||
if let selected = selectedChatItems, ci.canBeDeletedForSelf {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
let checked = selected.contains(ci.id)
|
||||
selectUnselectChatItem(select: !checked, ci)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chatItemView(chatItem, range, prevItem)
|
||||
.overlay {
|
||||
if let selected = selectedChatItems, chatItem.canBeDeletedForSelf {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
let checked = selected.contains(chatItem.id)
|
||||
selectUnselectChatItem(select: !checked, chatItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
@@ -663,9 +754,10 @@ struct ChatView: View {
|
||||
}
|
||||
|
||||
private func unreadItems(_ range: ClosedRange<Int>) -> [ChatItem]? {
|
||||
let im = ItemsModel.shared
|
||||
let items = range.compactMap { i in
|
||||
if i >= 0 && i < m.reversedChatItems.count {
|
||||
let ci = m.reversedChatItems[i]
|
||||
if i >= 0 && i < im.reversedChatItems.count {
|
||||
let ci = im.reversedChatItems[i]
|
||||
return if ci.isRcvNew { ci } else { nil }
|
||||
} else {
|
||||
return nil
|
||||
@@ -686,11 +778,11 @@ struct ChatView: View {
|
||||
if case let .groupRcv(member) = ci.chatDir,
|
||||
case let .group(groupInfo) = chat.chatInfo {
|
||||
let (prevMember, memCount): (GroupMember?, Int) =
|
||||
if let range = range {
|
||||
m.getPrevHiddenMember(member, range)
|
||||
} else {
|
||||
(nil, 1)
|
||||
}
|
||||
if let range = range {
|
||||
m.getPrevHiddenMember(member, range)
|
||||
} else {
|
||||
(nil, 1)
|
||||
}
|
||||
if prevItem == nil || showMemberImage(member, prevItem) || prevMember != nil {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
if ci.content.showMemberName {
|
||||
@@ -703,41 +795,64 @@ struct ChatView: View {
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(2)
|
||||
.padding(.leading, memberImageSize + 14)
|
||||
.padding(.leading, memberImageSize + 14 + (selectedChatItems != nil && ci.canBeDeletedForSelf ? 12 + 24 : 0))
|
||||
.padding(.top, 7)
|
||||
}
|
||||
HStack(alignment: .top, spacing: 8) {
|
||||
ProfileImage(imageStr: member.memberProfile.image, size: memberImageSize, backgroundColor: theme.colors.background)
|
||||
.onTapGesture {
|
||||
if m.membersLoaded {
|
||||
selectedMember = m.getGroupMember(member.groupMemberId)
|
||||
} else {
|
||||
Task {
|
||||
await m.loadGroupMembers(groupInfo) {
|
||||
selectedMember = m.getGroupMember(member.groupMemberId)
|
||||
HStack(alignment: .center, spacing: 0) {
|
||||
if selectedChatItems != nil && ci.canBeDeletedForSelf {
|
||||
SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems)
|
||||
.padding(.trailing, 12)
|
||||
}
|
||||
HStack(alignment: .top, spacing: 8) {
|
||||
MemberProfileImage(member, size: memberImageSize, backgroundColor: theme.colors.background)
|
||||
.onTapGesture {
|
||||
if m.membersLoaded {
|
||||
selectedMember = m.getGroupMember(member.groupMemberId)
|
||||
} else {
|
||||
Task {
|
||||
await m.loadGroupMembers(groupInfo) {
|
||||
selectedMember = m.getGroupMember(member.groupMemberId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.appSheet(item: $selectedMember) { member in
|
||||
GroupMemberInfoView(groupInfo: groupInfo, groupMember: member, navigation: true)
|
||||
}
|
||||
chatItemWithMenu(ci, range, maxWidth)
|
||||
.appSheet(item: $selectedMember) { member in
|
||||
GroupMemberInfoView(groupInfo: groupInfo, groupMember: member, navigation: true)
|
||||
}
|
||||
chatItemWithMenu(ci, range, maxWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.padding(.trailing)
|
||||
.padding(.leading, 12)
|
||||
} else {
|
||||
chatItemWithMenu(ci, range, maxWidth)
|
||||
.padding(.bottom, 5)
|
||||
.padding(.trailing)
|
||||
.padding(.leading, memberImageSize + 8 + 12)
|
||||
HStack(alignment: .center, spacing: 0) {
|
||||
if selectedChatItems != nil && ci.canBeDeletedForSelf {
|
||||
SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems)
|
||||
.padding(.leading, 12)
|
||||
}
|
||||
chatItemWithMenu(ci, range, maxWidth)
|
||||
.padding(.trailing)
|
||||
.padding(.leading, memberImageSize + 8 + 12)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
} else {
|
||||
chatItemWithMenu(ci, range, maxWidth)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 5)
|
||||
HStack(alignment: .center, spacing: 0) {
|
||||
if selectedChatItems != nil && ci.canBeDeletedForSelf {
|
||||
if chat.chatInfo.chatType == .group {
|
||||
SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems)
|
||||
.padding(.leading, 12)
|
||||
} else {
|
||||
SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems)
|
||||
.padding(.leading)
|
||||
}
|
||||
}
|
||||
chatItemWithMenu(ci, range, maxWidth)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,17 +887,17 @@ struct ChatView: View {
|
||||
}
|
||||
.confirmationDialog("Delete message?", isPresented: $showDeleteMessage, titleVisibility: .visible) {
|
||||
Button("Delete for me", role: .destructive) {
|
||||
deleteMessage(.cidmInternal)
|
||||
deleteMessage(.cidmInternal, moderate: false)
|
||||
}
|
||||
if let di = deletingItem, di.meta.deletable && !di.localNote {
|
||||
Button(broadcastDeleteButtonText, role: .destructive) {
|
||||
deleteMessage(.cidmBroadcast)
|
||||
Button(broadcastDeleteButtonText(chat), role: .destructive) {
|
||||
deleteMessage(.cidmBroadcast, moderate: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog(deleteMessagesTitle, isPresented: $showDeleteMessages, titleVisibility: .visible) {
|
||||
Button("Delete for me", role: .destructive) {
|
||||
deleteMessages()
|
||||
deleteMessages(chat, deletingItems, moderate: false)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: maxWidth, maxHeight: .infinity, alignment: alignment)
|
||||
@@ -891,7 +1006,7 @@ struct ChatView: View {
|
||||
if !live || !ci.meta.isLive {
|
||||
deleteButton(ci)
|
||||
}
|
||||
if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo) {
|
||||
if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo), ci.chatDir != .groupSnd {
|
||||
moderateButton(ci, groupInfo)
|
||||
}
|
||||
} else if ci.meta.itemDeleted != nil {
|
||||
@@ -915,6 +1030,10 @@ struct ChatView: View {
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
if selectedChatItems == nil && ci.canBeDeletedForSelf {
|
||||
Divider()
|
||||
selectButton(ci)
|
||||
}
|
||||
}
|
||||
|
||||
var replyButton: Button<some View> {
|
||||
@@ -970,7 +1089,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)
|
||||
}
|
||||
@@ -1087,6 +1206,21 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func selectButton(_ ci: ChatItem) -> Button<some View> {
|
||||
Button {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
withAnimation {
|
||||
selectUnselectChatItem(select: true, ci)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label(
|
||||
NSLocalizedString("Select", comment: "chat item action"),
|
||||
systemImage: "checkmark.circle"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func viewInfoButton(_ ci: ChatItem) -> Button<some View> {
|
||||
Button {
|
||||
Task {
|
||||
@@ -1156,7 +1290,7 @@ struct ChatView: View {
|
||||
if let range = itemsRange(currIndex, prevHidden) {
|
||||
var itemIds: [Int64] = []
|
||||
for i in range {
|
||||
itemIds.append(m.reversedChatItems[i].id)
|
||||
itemIds.append(ItemsModel.shared.reversedChatItems[i].id)
|
||||
}
|
||||
showDeleteMessages = true
|
||||
deletingItems = itemIds
|
||||
@@ -1197,7 +1331,7 @@ struct ChatView: View {
|
||||
),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
deletingItem = ci
|
||||
deleteMessage(.cidmBroadcast)
|
||||
deleteMessage(.cidmBroadcast, moderate: true)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
))
|
||||
@@ -1248,72 +1382,66 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var broadcastDeleteButtonText: LocalizedStringKey {
|
||||
chat.chatInfo.featureEnabled(.fullDelete) ? "Delete for everyone" : "Mark deleted for everyone"
|
||||
}
|
||||
|
||||
var deleteMessagesTitle: LocalizedStringKey {
|
||||
let n = deletingItems.count
|
||||
return n == 1 ? "Delete message?" : "Delete \(n) messages?"
|
||||
}
|
||||
|
||||
private func deleteMessages() {
|
||||
let itemIds = deletingItems
|
||||
if itemIds.count > 0 {
|
||||
let chatInfo = chat.chatInfo
|
||||
Task {
|
||||
var deletedItems: [ChatItem] = []
|
||||
for itemId in itemIds {
|
||||
do {
|
||||
let (di, _) = try await apiDeleteChatItem(
|
||||
type: chatInfo.chatType,
|
||||
id: chatInfo.apiId,
|
||||
itemId: itemId,
|
||||
mode: .cidmInternal
|
||||
)
|
||||
deletedItems.append(di)
|
||||
} catch {
|
||||
logger.error("ChatView.deleteMessage error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
await MainActor.run {
|
||||
for di in deletedItems {
|
||||
m.removeChatItem(chatInfo, di)
|
||||
}
|
||||
private func selectUnselectChatItem(select: Bool, _ ci: ChatItem) {
|
||||
selectedChatItems = selectedChatItems ?? []
|
||||
var itemIds: [Int64] = []
|
||||
if !revealed,
|
||||
let currIndex = m.getChatItemIndex(ci),
|
||||
let ciCategory = ci.mergeCategory {
|
||||
let (prevHidden, _) = m.getPrevShownChatItem(currIndex, ciCategory)
|
||||
if let range = itemsRange(currIndex, prevHidden) {
|
||||
for i in range {
|
||||
itemIds.append(ItemsModel.shared.reversedChatItems[i].id)
|
||||
}
|
||||
} else {
|
||||
itemIds.append(ci.id)
|
||||
}
|
||||
} else {
|
||||
itemIds.append(ci.id)
|
||||
}
|
||||
if select {
|
||||
if let sel = selectedChatItems {
|
||||
selectedChatItems = sel.union(itemIds)
|
||||
}
|
||||
} else {
|
||||
itemIds.forEach { selectedChatItems?.remove($0) }
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteMessage(_ mode: CIDeleteMode) {
|
||||
private func deleteMessage(_ mode: CIDeleteMode, moderate: Bool) {
|
||||
logger.debug("ChatView deleteMessage")
|
||||
Task {
|
||||
logger.debug("ChatView deleteMessage: in Task")
|
||||
do {
|
||||
if let di = deletingItem {
|
||||
var deletedItem: ChatItem
|
||||
var toItem: ChatItem?
|
||||
if case .cidmBroadcast = mode,
|
||||
let (groupInfo, groupMember) = di.memberToModerate(chat.chatInfo) {
|
||||
(deletedItem, toItem) = try await apiDeleteMemberChatItem(
|
||||
let r = if case .cidmBroadcast = mode,
|
||||
moderate,
|
||||
let (groupInfo, _) = di.memberToModerate(chat.chatInfo) {
|
||||
try await apiDeleteMemberChatItems(
|
||||
groupId: groupInfo.apiId,
|
||||
groupMemberId: groupMember.groupMemberId,
|
||||
itemId: di.id
|
||||
itemIds: [di.id]
|
||||
)
|
||||
} else {
|
||||
(deletedItem, toItem) = try await apiDeleteChatItem(
|
||||
try await apiDeleteChatItems(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
itemId: di.id,
|
||||
itemIds: [di.id],
|
||||
mode: mode
|
||||
)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
deletingItem = nil
|
||||
if let toItem = toItem {
|
||||
_ = m.upsertChatItem(chat.chatInfo, toItem)
|
||||
} else {
|
||||
m.removeChatItem(chat.chatInfo, deletedItem)
|
||||
if let itemDeletion = r.first {
|
||||
await MainActor.run {
|
||||
deletingItem = nil
|
||||
if let toItem = itemDeletion.toChatItem {
|
||||
_ = m.upsertChatItem(chat.chatInfo, toItem.chatItem)
|
||||
} else {
|
||||
m.removeChatItem(chat.chatInfo, itemDeletion.deletedChatItem.chatItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1322,6 +1450,68 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct SelectedChatItem: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var ciId: Int64
|
||||
@Binding var selectedChatItems: Set<Int64>?
|
||||
@State var checked: Bool = false
|
||||
var body: some View {
|
||||
Image(systemName: checked ? "checkmark.circle.fill" : "circle")
|
||||
.resizable()
|
||||
.foregroundColor(checked ? theme.colors.primary : Color(uiColor: .tertiaryLabel))
|
||||
.frame(width: 24, height: 24)
|
||||
.onAppear {
|
||||
checked = selectedChatItems?.contains(ciId) == true
|
||||
}
|
||||
.onChange(of: selectedChatItems) { selected in
|
||||
checked = selected?.contains(ciId) == true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func broadcastDeleteButtonText(_ chat: Chat) -> LocalizedStringKey {
|
||||
chat.chatInfo.featureEnabled(.fullDelete) ? "Delete for everyone" : "Mark deleted for everyone"
|
||||
}
|
||||
|
||||
private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDeleteMode = .cidmInternal, moderate: Bool, _ onSuccess: @escaping () async -> Void = {}) {
|
||||
let itemIds = deletingItems
|
||||
if itemIds.count > 0 {
|
||||
let chatInfo = chat.chatInfo
|
||||
Task {
|
||||
do {
|
||||
let deletedItems = if case .cidmBroadcast = mode,
|
||||
moderate,
|
||||
case .group = chat.chatInfo {
|
||||
try await apiDeleteMemberChatItems(
|
||||
groupId: chatInfo.apiId,
|
||||
itemIds: itemIds
|
||||
)
|
||||
} else {
|
||||
try await apiDeleteChatItems(
|
||||
type: chatInfo.chatType,
|
||||
id: chatInfo.apiId,
|
||||
itemIds: itemIds,
|
||||
mode: mode
|
||||
)
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
for di in deletedItems {
|
||||
if let toItem = di.toChatItem {
|
||||
_ = ChatModel.shared.upsertChatItem(chat.chatInfo, toItem.chatItem)
|
||||
} else {
|
||||
ChatModel.shared.removeChatItem(chatInfo, di.deletedChatItem.chatItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
await onSuccess()
|
||||
} catch {
|
||||
logger.error("ChatView.deleteMessages error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1399,7 +1589,7 @@ struct ChatView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chatModel = ChatModel()
|
||||
chatModel.chatId = "@1"
|
||||
chatModel.reversedChatItems = [
|
||||
ItemsModel.shared.reversedChatItems = [
|
||||
ChatItem.getSample(1, .directSnd, .now, "hello"),
|
||||
ChatItem.getSample(2, .directRcv, .now, "hi"),
|
||||
ChatItem.getSample(3, .directRcv, .now, "hi there"),
|
||||
|
||||
@@ -1120,10 +1120,6 @@ struct ComposeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func parsedMsgHasSimplexLink(_ parsedMsg: [FormattedText]) -> Bool {
|
||||
parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false })
|
||||
}
|
||||
|
||||
struct ComposeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
|
||||
|
||||
@@ -257,6 +257,9 @@ struct SendMessageView: View {
|
||||
var body: some View {
|
||||
Button(action: {}) {
|
||||
Image(systemName: "mic.fill")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundColor(theme.colors.primary)
|
||||
}
|
||||
.disabled(disabled)
|
||||
@@ -310,6 +313,9 @@ struct SendMessageView: View {
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "mic")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
.disabled(composeState.inProgress)
|
||||
|
||||
@@ -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?"),
|
||||
|
||||
@@ -33,7 +33,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 +45,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) {
|
||||
@@ -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> {
|
||||
@@ -153,7 +153,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 +168,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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// SelectableChatItemToolbars.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Stanislav Dmitrenko on 30.07.2024.
|
||||
// Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct SelectedItemsTopToolbar: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Binding var selectedChatItems: Set<Int64>?
|
||||
|
||||
var body: some View {
|
||||
let count = selectedChatItems?.count ?? 0
|
||||
return Text(count == 0 ? "Nothing selected" : "Selected \(count)").font(.headline)
|
||||
.foregroundColor(theme.colors.onBackground)
|
||||
.frame(width: 220)
|
||||
}
|
||||
}
|
||||
|
||||
struct SelectedItemsBottomToolbar: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
let chatItems: [ChatItem]
|
||||
@Binding var selectedChatItems: Set<Int64>?
|
||||
var chatInfo: ChatInfo
|
||||
// Bool - delete for everyone is possible
|
||||
var deleteItems: (Bool) -> Void
|
||||
var moderateItems: () -> Void
|
||||
//var shareItems: () -> Void
|
||||
@State var deleteEnabled: Bool = false
|
||||
@State var deleteForEveryoneEnabled: Bool = false
|
||||
|
||||
@State var canModerate: Bool = false
|
||||
@State var moderateEnabled: Bool = false
|
||||
|
||||
@State var allButtonsDisabled = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
Divider()
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Button {
|
||||
deleteItems(deleteForEveryoneEnabled)
|
||||
} label: {
|
||||
Image(systemName: "trash")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.foregroundColor(!deleteEnabled || allButtonsDisabled ? theme.colors.secondary: .red)
|
||||
}
|
||||
.disabled(!deleteEnabled || allButtonsDisabled)
|
||||
|
||||
Spacer()
|
||||
Button {
|
||||
moderateItems()
|
||||
} label: {
|
||||
Image(systemName: "flag")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.foregroundColor(!moderateEnabled || allButtonsDisabled ? theme.colors.secondary : .red)
|
||||
}
|
||||
.disabled(!moderateEnabled || allButtonsDisabled)
|
||||
.opacity(canModerate ? 1 : 0)
|
||||
|
||||
|
||||
Spacer()
|
||||
Button {
|
||||
//shareItems()
|
||||
} label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.foregroundColor(allButtonsDisabled ? theme.colors.secondary : theme.colors.primary)
|
||||
}
|
||||
.disabled(allButtonsDisabled)
|
||||
.opacity(0)
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.padding([.leading, .trailing], 12)
|
||||
}
|
||||
.onAppear {
|
||||
recheckItems(chatInfo, chatItems, selectedChatItems)
|
||||
}
|
||||
.onChange(of: chatInfo) { info in
|
||||
recheckItems(info, chatItems, selectedChatItems)
|
||||
}
|
||||
.onChange(of: chatItems) { items in
|
||||
recheckItems(chatInfo, items, selectedChatItems)
|
||||
}
|
||||
.onChange(of: selectedChatItems) { selected in
|
||||
recheckItems(chatInfo, chatItems, selected)
|
||||
}
|
||||
.frame(height: 55.5)
|
||||
.background(.thinMaterial)
|
||||
}
|
||||
|
||||
private func recheckItems(_ chatInfo: ChatInfo, _ chatItems: [ChatItem], _ selectedItems: Set<Int64>?) {
|
||||
let count = selectedItems?.count ?? 0
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func possibleToModerate(_ chatInfo: ChatInfo) -> Bool {
|
||||
return switch chatInfo {
|
||||
case let .group(groupInfo):
|
||||
groupInfo.membership.memberRole >= .admin
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:")
|
||||
}
|
||||
|
||||
|
||||
@@ -9,45 +9,55 @@
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
let dynamicSizes: [
|
||||
DynamicTypeSize: (
|
||||
rowHeight: CGFloat,
|
||||
profileImageSize: CGFloat,
|
||||
mediaSize: CGFloat,
|
||||
chatInfoSize: CGFloat,
|
||||
unreadCorner: CGFloat,
|
||||
unreadPadding: CGFloat
|
||||
)
|
||||
] = [
|
||||
.xSmall: (68, 55, 33, 18, 9, 3),
|
||||
.small: (72, 57, 34, 18, 9, 3),
|
||||
.medium: (76, 60, 36, 18, 10, 4),
|
||||
.large: (80, 63, 38, 20, 10, 4),
|
||||
.xLarge: (88, 67, 41, 20, 10, 4),
|
||||
.xxLarge: (94, 71, 44, 22, 11, 4),
|
||||
.xxxLarge: (104, 75, 48, 24, 12, 5),
|
||||
.accessibility1: (104, 75, 48, 24, 12, 5),
|
||||
.accessibility2: (114, 75, 48, 24, 12, 5),
|
||||
.accessibility3: (124, 75, 48, 24, 12, 5),
|
||||
.accessibility4: (134, 75, 48, 24, 12, 5),
|
||||
.accessibility5: (144, 75, 48, 24, 12, 5)
|
||||
typealias DynamicSizes = (
|
||||
rowHeight: CGFloat,
|
||||
profileImageSize: CGFloat,
|
||||
mediaSize: CGFloat,
|
||||
incognitoSize: CGFloat,
|
||||
chatInfoSize: CGFloat,
|
||||
unreadCorner: CGFloat,
|
||||
unreadPadding: CGFloat
|
||||
)
|
||||
|
||||
private let dynamicSizes: [DynamicTypeSize: DynamicSizes] = [
|
||||
.xSmall: (68, 55, 33, 22, 18, 9, 3),
|
||||
.small: (72, 57, 34, 22, 18, 9, 3),
|
||||
.medium: (76, 60, 36, 22, 18, 10, 4),
|
||||
.large: (80, 63, 38, 24, 20, 10, 4),
|
||||
.xLarge: (88, 67, 41, 24, 20, 10, 4),
|
||||
.xxLarge: (100, 71, 44, 27, 22, 11, 4),
|
||||
.xxxLarge: (110, 75, 48, 30, 24, 12, 5),
|
||||
.accessibility1: (110, 75, 48, 30, 24, 12, 5),
|
||||
.accessibility2: (114, 75, 48, 30, 24, 12, 5),
|
||||
.accessibility3: (124, 75, 48, 30, 24, 12, 5),
|
||||
.accessibility4: (134, 75, 48, 30, 24, 12, 5),
|
||||
.accessibility5: (144, 75, 48, 30, 24, 12, 5)
|
||||
]
|
||||
|
||||
private let defaultDynamicSizes: DynamicSizes = dynamicSizes[.large]!
|
||||
|
||||
func dynamicSize(_ font: DynamicTypeSize) -> DynamicSizes {
|
||||
dynamicSizes[font] ?? defaultDynamicSizes
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -76,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)
|
||||
}
|
||||
@@ -104,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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,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 {
|
||||
@@ -234,7 +244,7 @@ struct ChatListNavLink: View {
|
||||
await MainActor.run { inProgress = false }
|
||||
}
|
||||
} label: {
|
||||
Label("Join", systemImage: chat.chatInfo.incognito ? "theatermasks" : "ipad.and.arrow.forward")
|
||||
SwipeLabel("Join", systemImage: chat.chatInfo.incognito ? "theatermasks" : "ipad.and.arrow.forward", inverted: oneHandUI)
|
||||
}
|
||||
.tint(chat.chatInfo.incognito ? .indigo : theme.colors.primary)
|
||||
}
|
||||
@@ -244,14 +254,14 @@ struct ChatListNavLink: View {
|
||||
Button {
|
||||
Task { await markChatRead(chat) }
|
||||
} label: {
|
||||
Label("Read", systemImage: "checkmark")
|
||||
SwipeLabel("Read", systemImage: "checkmark", inverted: oneHandUI)
|
||||
}
|
||||
.tint(theme.colors.primary)
|
||||
} else {
|
||||
Button {
|
||||
Task { await markChatUnread(chat) }
|
||||
} label: {
|
||||
Label("Unread", systemImage: "circlebadge.fill")
|
||||
SwipeLabel("Unread", systemImage: "circlebadge.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(theme.colors.primary)
|
||||
}
|
||||
@@ -263,24 +273,36 @@ struct ChatListNavLink: View {
|
||||
Button {
|
||||
toggleChatFavorite(chat, favorite: false)
|
||||
} label: {
|
||||
Label("Unfav.", systemImage: "star.slash")
|
||||
SwipeLabel("Unfav.", systemImage: "star.slash.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.green)
|
||||
} else {
|
||||
Button {
|
||||
toggleChatFavorite(chat, favorite: true)
|
||||
} label: {
|
||||
Label("Favorite", systemImage: "star.fill")
|
||||
SwipeLabel("Favorite", 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("Mute", systemImage: "speaker.slash.fill", inverted: oneHandUI)
|
||||
} else {
|
||||
SwipeLabel("Unmute", systemImage: "speaker.wave.2.fill", inverted: oneHandUI)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func clearChatButton() -> some View {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(clearChatAlert())
|
||||
} label: {
|
||||
Label("Clear", systemImage: "gobackward")
|
||||
SwipeLabel("Clear", systemImage: "gobackward", inverted: oneHandUI)
|
||||
}
|
||||
.tint(Color.orange)
|
||||
}
|
||||
@@ -289,7 +311,7 @@ struct ChatListNavLink: View {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(clearNoteFolderAlert())
|
||||
} label: {
|
||||
Label("Clear", systemImage: "gobackward")
|
||||
SwipeLabel("Clear", systemImage: "gobackward", inverted: oneHandUI)
|
||||
}
|
||||
.tint(Color.orange)
|
||||
}
|
||||
@@ -298,7 +320,7 @@ struct ChatListNavLink: View {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(leaveGroupAlert(groupInfo))
|
||||
} label: {
|
||||
Label("Leave", systemImage: "rectangle.portrait.and.arrow.right")
|
||||
SwipeLabel("Leave", systemImage: "rectangle.portrait.and.arrow.right.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(Color.yellow)
|
||||
}
|
||||
@@ -307,7 +329,7 @@ struct ChatListNavLink: View {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(deleteGroupAlert(groupInfo))
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
deleteLabel
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
@@ -317,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("Accept", systemImage: "checkmark", inverted: oneHandUI) }
|
||||
.tint(theme.colors.primary)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) }
|
||||
} label: {
|
||||
Label("Accept incognito", systemImage: "theatermasks")
|
||||
SwipeLabel("Accept incognito", systemImage: "theatermasks.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequest))
|
||||
} label: {
|
||||
Label("Reject", systemImage: "multiply")
|
||||
SwipeLabel("Reject", systemImage: "multiply.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
@@ -349,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("Name", systemImage: "pencil", inverted: oneHandUI)
|
||||
}
|
||||
.tint(theme.colors.primary)
|
||||
}
|
||||
@@ -375,6 +397,10 @@ struct ChatListNavLink: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var deleteLabel: some View {
|
||||
SwipeLabel("Delete", systemImage: "trash.fill", inverted: oneHandUI)
|
||||
}
|
||||
|
||||
private func deleteGroupAlert(_ groupInfo: GroupInfo) -> Alert {
|
||||
Alert(
|
||||
title: Text("Delete group?"),
|
||||
@@ -423,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"),
|
||||
@@ -452,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)
|
||||
@@ -490,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?"),
|
||||
@@ -526,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
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
import SwiftUIIntrospect
|
||||
|
||||
private weak var collectionView: UICollectionView?
|
||||
|
||||
struct ChatListView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@@ -18,11 +21,15 @@ 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 isSearchExpanded = true
|
||||
@State private var contentOffsetObservation: NSKeyValueObservation?
|
||||
|
||||
@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
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
@@ -33,21 +40,14 @@ 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 }
|
||||
),
|
||||
destination: chatView
|
||||
) {
|
||||
VStack {
|
||||
if chatModel.chats.isEmpty {
|
||||
onboardingButtons()
|
||||
}
|
||||
chatListView
|
||||
}
|
||||
}
|
||||
) { chatListView }
|
||||
if userPickerVisible {
|
||||
Rectangle().fill(.white.opacity(0.001)).onTapGesture {
|
||||
withAnimation {
|
||||
@@ -67,9 +67,13 @@ struct ChatListView: View {
|
||||
}
|
||||
|
||||
private var chatListView: some View {
|
||||
VStack {
|
||||
withToolbar {
|
||||
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,79 +91,151 @@ 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)
|
||||
}
|
||||
.safeAreaInset(edge: .top) {
|
||||
if oneHandUI {
|
||||
Divider().background(Material.thin)
|
||||
} else {
|
||||
searchBar
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
if oneHandUI { searchBar }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Used to always return full height search bar height
|
||||
/// to avoid changing safe are insets during scrolling
|
||||
private static var searchBarHeight: Double = .zero
|
||||
|
||||
@ViewBuilder
|
||||
var searchBar: some View {
|
||||
let isVisible = isSearchExpanded || searchFocussed
|
||||
VStack(spacing: 0) {
|
||||
if oneHandUI { Divider() }
|
||||
ChatListSearchBar(
|
||||
searchMode: $searchMode,
|
||||
searchFocussed: $searchFocussed,
|
||||
searchText: $searchText,
|
||||
searchShowingSimplexLink: $searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
|
||||
)
|
||||
.padding(8)
|
||||
.overlay {
|
||||
GeometryReader { proxy in
|
||||
Self.searchBarHeight = proxy.size.height
|
||||
return Color.clear
|
||||
}
|
||||
.onTapGesture {
|
||||
if chatModel.users.filter({ u in u.user.activeUser || !u.user.hidden }).count > 1 {
|
||||
withAnimation {
|
||||
userPickerVisible.toggle()
|
||||
}
|
||||
}
|
||||
.frame(height: isVisible ? Self.searchBarHeight : 0)
|
||||
.opacity(isVisible ? 1 : 0)
|
||||
if !oneHandUI { Divider() }
|
||||
}
|
||||
.background(Material.thin)
|
||||
.frame(height: Self.searchBarHeight, alignment: oneHandUI ? .bottom : .top)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func withToolbar(content: () -> some View) -> some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
content()
|
||||
.toolbarBackground(.hidden, for: .navigationBar)
|
||||
.toolbarBackground(.hidden, for: .bottomBar)
|
||||
.toolbar {
|
||||
if oneHandUI {
|
||||
bottomToolbar
|
||||
} else {
|
||||
showSettings = true
|
||||
topToolbar
|
||||
}
|
||||
}
|
||||
} else {
|
||||
content().toolbar { topToolbar }
|
||||
}
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder
|
||||
var topToolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .topBarLeading) { leadingToolbarItem }
|
||||
ToolbarItem(placement: .principal) { principalToolbarItem }
|
||||
ToolbarItem(placement: .topBarTrailing) { trailingToolbarItem }
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder
|
||||
var bottomToolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .bottomBar) {
|
||||
HStack {
|
||||
leadingToolbarItem
|
||||
principalToolbarItem
|
||||
trailingToolbarItem
|
||||
}
|
||||
ToolbarItem(placement: .principal) {
|
||||
HStack(spacing: 4) {
|
||||
Text("Chats")
|
||||
.font(.headline)
|
||||
SubsStatusIndicator()
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
switch chatModel.chatRunning {
|
||||
case .some(true): NewChatMenuButton(newChatMenuOption: $newChatMenuOption)
|
||||
case .some(false): chatStoppedIcon()
|
||||
case .none: EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
@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 principalToolbarItem: some View {
|
||||
HStack(spacing: 4) {
|
||||
Text("Chats").font(.headline)
|
||||
SubsStatusIndicator()
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
|
||||
@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 {
|
||||
List {
|
||||
if !chatModel.chats.isEmpty {
|
||||
ChatListSearchBar(
|
||||
searchMode: $searchMode,
|
||||
searchFocussed: $searchFocussed,
|
||||
searchText: $searchText,
|
||||
searchShowingSimplexLink: $searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
|
||||
)
|
||||
List {
|
||||
if !oneHandUICardShown {
|
||||
OneHandUICard()
|
||||
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowBackground(Color.clear)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
ForEach(cs, id: \.viewId) { chat in
|
||||
ChatListNavLink(chat: chat)
|
||||
.padding(.trailing, -16)
|
||||
.disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id))
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
.offset(x: -8)
|
||||
}
|
||||
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 {
|
||||
.introspect(.list, on: .iOS(.v16, .v17, .v18)) { setObservations(for: $0) }
|
||||
.listStyle(.plain)
|
||||
.onChange(of: chatModel.chatId) { chId in
|
||||
if chId == nil, let chatId = chatModel.chatToTop {
|
||||
chatModel.chatToTop = nil
|
||||
chatModel.popChat(chatId)
|
||||
}
|
||||
@@ -168,8 +244,40 @@ struct ChatListView: View {
|
||||
.onChange(of: chatModel.currentUser?.userId) { _ in
|
||||
stopAudioPlayer()
|
||||
}
|
||||
// .onAppear {
|
||||
// oneHandUICardShown = false
|
||||
// }
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setObservations(for cv: UICollectionView) {
|
||||
if collectionView != cv {
|
||||
collectionView = cv
|
||||
var scrollDistance: CGFloat = 0
|
||||
contentOffsetObservation?.invalidate()
|
||||
contentOffsetObservation = cv.observe(
|
||||
\.contentOffset,
|
||||
options: [.new, .old]
|
||||
) { (cv, change) in
|
||||
if let newOffset = change.newValue?.y,
|
||||
let oldOffset = change.oldValue?.y {
|
||||
let bottomOffset = cv.contentSize.height - cv.visibleSize.height - newOffset + cv.safeAreaInsets.bottom
|
||||
// Show/Hide search bar when scrolled for more than `MAX` amount
|
||||
if newOffset > .zero,
|
||||
bottomOffset > 0 {
|
||||
let MAX: CGFloat = 64
|
||||
scrollDistance = min(max(scrollDistance + oldOffset - newOffset, -MAX), +MAX)
|
||||
if (isSearchExpanded && scrollDistance == -MAX) ||
|
||||
(!isSearchExpanded && scrollDistance == +MAX) {
|
||||
withAnimation(.easeOut(duration: 0.15)) { isSearchExpanded.toggle() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,42 +288,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 +305,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)
|
||||
@@ -300,8 +376,9 @@ struct SubsStatusIndicator: View {
|
||||
.onDisappear {
|
||||
stopTimer()
|
||||
}
|
||||
.sheet(isPresented: $showServersSummary) {
|
||||
.appSheet(isPresented: $showServersSummary) {
|
||||
ServersSummaryView()
|
||||
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,39 +418,36 @@ struct ChatListSearchBar: View {
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
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")
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.background(Color(.tertiarySystemFill))
|
||||
.cornerRadius(10.0)
|
||||
|
||||
if searchFocussed {
|
||||
Text("Cancel")
|
||||
.foregroundColor(theme.colors.primary)
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
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")
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
searchFocussed = false
|
||||
}
|
||||
} else if m.chats.count > 0 {
|
||||
toggleFilterButton()
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.background(Color(.tertiarySystemFill))
|
||||
.cornerRadius(10.0)
|
||||
|
||||
if searchFocussed {
|
||||
Text("Cancel")
|
||||
.foregroundColor(theme.colors.primary)
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
searchFocussed = false
|
||||
}
|
||||
} else if m.chats.count > 0 {
|
||||
toggleFilterButton()
|
||||
}
|
||||
}
|
||||
.onChange(of: searchFocussed) { sf in
|
||||
withAnimation { searchMode = sf }
|
||||
|
||||
@@ -22,14 +22,14 @@ struct ChatPreviewView: View {
|
||||
|
||||
@AppStorage(DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) private var showChatPreviews = true
|
||||
|
||||
var dynamicMediaSize: CGFloat { dynamicSizes[userFont]?.mediaSize ?? 36 }
|
||||
var dynamicChatInfoSize: CGFloat { dynamicSizes[userFont]?.chatInfoSize ?? 18 }
|
||||
var dynamicMediaSize: CGFloat { dynamicSize(userFont).mediaSize }
|
||||
var dynamicChatInfoSize: CGFloat { dynamicSize(userFont).chatInfoSize }
|
||||
|
||||
var body: some View {
|
||||
let cItem = chat.chatItems.last
|
||||
return HStack(spacing: 8) {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
ChatInfoImage(chat: chat, size: dynamicSizes[userFont]?.profileImageSize ?? 63)
|
||||
ChatInfoImage(chat: chat, size: dynamicSize(userFont).profileImageSize)
|
||||
chatPreviewImageOverlayIcon()
|
||||
.padding([.bottom, .trailing], 1)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ struct ChatPreviewView: View {
|
||||
checkActiveContentPreview(chat, ci, mc)
|
||||
}
|
||||
chatStatusImage()
|
||||
.padding(.top, 26)
|
||||
.padding(.top, dynamicChatInfoSize * 1.44)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -198,10 +198,10 @@ struct ChatPreviewView: View {
|
||||
unreadCountText(s.unreadCount)
|
||||
.font(userFont <= .xxxLarge ? .caption : .caption2)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, dynamicSizes[userFont]?.unreadPadding ?? 4)
|
||||
.padding(.horizontal, dynamicSize(userFont).unreadPadding)
|
||||
.frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize)
|
||||
.background(chat.chatInfo.ntfsEnabled || chat.chatInfo.chatType == .local ? theme.colors.primary : theme.colors.secondary)
|
||||
.cornerRadius(dynamicSizes[userFont]?.unreadCorner ?? 10)
|
||||
.cornerRadius(dynamicSize(userFont).unreadCorner)
|
||||
} else if !chat.chatInfo.ntfsEnabled && chat.chatInfo.chatType != .local {
|
||||
Image(systemName: "speaker.slash.fill")
|
||||
.resizable()
|
||||
@@ -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 {
|
||||
@@ -372,41 +372,42 @@ struct ChatPreviewView: View {
|
||||
}
|
||||
|
||||
@ViewBuilder private func chatStatusImage() -> some View {
|
||||
let size = dynamicSize(userFont).incognitoSize
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact):
|
||||
if contact.active && contact.activeConn != nil {
|
||||
switch (chatModel.contactNetworkStatus(contact)) {
|
||||
case .connected: incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary)
|
||||
case .connected: incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
|
||||
case .error:
|
||||
Image(systemName: "exclamationmark.circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 17, height: 17)
|
||||
.frame(width: dynamicChatInfoSize, height: dynamicChatInfoSize)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
default:
|
||||
ProgressView()
|
||||
}
|
||||
} else {
|
||||
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary)
|
||||
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
|
||||
}
|
||||
case .group:
|
||||
if progressByTimeout {
|
||||
ProgressView()
|
||||
} else {
|
||||
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary)
|
||||
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
|
||||
}
|
||||
default:
|
||||
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary)
|
||||
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func incognitoIcon(_ incognito: Bool, _ secondaryColor: Color) -> some View {
|
||||
@ViewBuilder func incognitoIcon(_ incognito: Bool, _ secondaryColor: Color, size: CGFloat) -> some View {
|
||||
if incognito {
|
||||
Image(systemName: "theatermasks")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 22, height: 22)
|
||||
.frame(width: size, height: size)
|
||||
.foregroundColor(secondaryColor)
|
||||
} else {
|
||||
EmptyView()
|
||||
|
||||
@@ -13,6 +13,7 @@ struct ContactConnectionView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@ObservedObject var chat: Chat
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
|
||||
@State private var localAlias = ""
|
||||
@FocusState private var aliasTextFieldFocused: Bool
|
||||
@State private var showContactConnectionInfo = false
|
||||
@@ -62,7 +63,7 @@ struct ContactConnectionView: View {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
Text(contactConnection.description)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
incognitoIcon(contactConnection.incognito, theme.colors.secondary)
|
||||
incognitoIcon(contactConnection.incognito, theme.colors.secondary, size: dynamicSize(userFont).incognitoSize)
|
||||
.padding(.top, 26)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ struct ContactRequestView: View {
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 8) {
|
||||
ChatInfoImage(chat: chat, size: dynamicSizes[userFont]?.profileImageSize ?? 63)
|
||||
ChatInfoImage(chat: chat, size: dynamicSize(userFont).profileImageSize)
|
||||
.padding(.leading, 4)
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(alignment: .top) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
let defaultProfileImageCorner = 22.5
|
||||
|
||||
struct ProfileImage: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var imageStr: String? = nil
|
||||
@@ -18,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)
|
||||
@@ -34,26 +33,6 @@ struct ProfileImage: View {
|
||||
}
|
||||
}
|
||||
|
||||
private let squareToCircleRatio = 0.935
|
||||
|
||||
private let radiusFactor = (1 - squareToCircleRatio) / 50
|
||||
|
||||
@ViewBuilder func clipProfileImage(_ img: Image, size: CGFloat, radius: Double) -> some View {
|
||||
let v = img.resizable()
|
||||
if radius >= 50 {
|
||||
v.frame(width: size, height: size).clipShape(Circle())
|
||||
} else if radius <= 0 {
|
||||
let sz = size * squareToCircleRatio
|
||||
v.frame(width: sz, height: sz).padding((size - sz) / 2)
|
||||
} else {
|
||||
let sz = size * (squareToCircleRatio + radius * radiusFactor)
|
||||
v.frame(width: sz, height: sz)
|
||||
.clipShape(RoundedRectangle(cornerRadius: sz * radius / 100, style: .continuous))
|
||||
.padding((size - sz) / 2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Color {
|
||||
func asAnotherColorFromSecondary(_ theme: AppTheme) -> Color {
|
||||
return self
|
||||
|
||||
@@ -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 = NSLocalizedString(text, comment: "swipe action")
|
||||
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
|
||||
|
||||
@@ -64,7 +64,7 @@ struct LocalAuthView: View {
|
||||
deleteAppDatabaseAndFiles()
|
||||
// Clear sensitive data on screen just in case app fails to hide its views while new database is created
|
||||
m.chatId = nil
|
||||
m.reversedChatItems = []
|
||||
ItemsModel.shared.reversedChatItems = []
|
||||
m.chats = []
|
||||
m.users = []
|
||||
_ = kcAppPassword.set(password)
|
||||
|
||||
@@ -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,454 @@
|
||||
//
|
||||
|
||||
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?
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
viewBody()
|
||||
.navigationTitle("New Chat")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.navigationBarHidden(searchMode)
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func viewBody() -> 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 {
|
||||
NewChatView(selection: .invite, parentAlert: $alert)
|
||||
.navigationTitle("New chat")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
Label("Add contact", systemImage: "link.badge.plus")
|
||||
}
|
||||
NavigationLink {
|
||||
NewChatView(selection: .connect, showQRCodeScanner: true, parentAlert: $alert)
|
||||
.navigationTitle("New chat")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
Label("Scan / Paste link", systemImage: "qrcode")
|
||||
}
|
||||
NavigationLink {
|
||||
AddGroupView()
|
||||
.navigationTitle("Create secret group")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
Label("Create group", systemImage: "person.2")
|
||||
}
|
||||
}
|
||||
|
||||
if (!filterContactTypes(chats: chatModel.chats, contactTypes: [.chatDeleted]).isEmpty) {
|
||||
Section {
|
||||
NavigationLink {
|
||||
DeletedChats()
|
||||
} label: {
|
||||
newChatActionButton("archivebox", color: theme.colors.secondary) { Text("Deleted chats") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContactsList(
|
||||
baseContactTypes: $baseContactTypes,
|
||||
searchMode: $searchMode,
|
||||
searchText: $searchText,
|
||||
header: "Your Contacts",
|
||||
searchFocussed: $searchFocussed,
|
||||
searchShowingSimplexLink: $searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink,
|
||||
showDeletedChatIcon: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func newChatActionButton<Content : View>(_ icon: String, color: Color/* = .secondary*/, content: @escaping () -> Content) -> some View {
|
||||
ZStack(alignment: .leading) {
|
||||
Image(systemName: icon).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(theme.colors.isLight ? theme.colors.background : theme.colors.secondaryVariant)
|
||||
.cornerRadius(10.0)
|
||||
|
||||
if searchFocussed {
|
||||
Text("Cancel")
|
||||
.foregroundColor(theme.colors.primary)
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
searchFocussed = false
|
||||
}
|
||||
} else if m.chats.count > 0 {
|
||||
toggleFilterButton()
|
||||
}
|
||||
}
|
||||
.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("Deleted chats")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -76,11 +83,12 @@ extension AppSettings {
|
||||
c.androidCallOnLockScreen = AppSettingsLockScreenCalls(rawValue: def.string(forKey: ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN)!)
|
||||
c.iosCallKitEnabled = callKitEnabledGroupDefault.get()
|
||||
c.iosCallKitCallsInRecents = def.bool(forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS)
|
||||
c.uiProfileImageCornerRadius = def.float(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS)
|
||||
c.uiProfileImageCornerRadius = def.double(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS)
|
||||
c.uiColorScheme = currentThemeDefault.get()
|
||||
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,7 @@ 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
|
||||
|
||||
@State var themeUserDestination: (Int64, ThemeModeOverrides?)? = {
|
||||
if let currentUser = ChatModel.shared.currentUser, let uiThemes = currentUser.uiThemes, uiThemes.preferredMode(!CurrentColors.colors.isLight) != nil {
|
||||
@@ -62,6 +63,10 @@ struct AppearanceSettings: View {
|
||||
}
|
||||
}
|
||||
|
||||
Section("Chat list") {
|
||||
Toggle("Reachable chat toolbar", isOn: $oneHandUI)
|
||||
}
|
||||
|
||||
Section {
|
||||
ThemeDestinationPicker(themeUserDestination: $themeUserDestination, themeUserDest: themeUserDestination?.0, customizeThemeIsOpen: $customizeThemeIsOpen)
|
||||
|
||||
@@ -143,7 +148,8 @@ struct AppearanceSettings: View {
|
||||
Text("Themes")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
.onChange(of: profileImageCornerRadius) { _ in
|
||||
.onChange(of: profileImageCornerRadius) { cornerRadius in
|
||||
profileImageCornerRadiusGroupDefault.set(cornerRadius)
|
||||
saveThemeToDatabase(nil)
|
||||
}
|
||||
.onChange(of: colorMode) { mode in
|
||||
|
||||
@@ -33,9 +33,6 @@ 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("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) {
|
||||
Toggle("Show developer options", isOn: $developerTools)
|
||||
}
|
||||
@@ -45,6 +42,16 @@ 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ struct PrivacySettings: View {
|
||||
Section {
|
||||
settingsRow("network", color: theme.colors.secondary) {
|
||||
Toggle("Send link previews", isOn: $useLinkPreviews)
|
||||
.onChange(of: useLinkPreviews) { linkPreviews in
|
||||
privacyLinkPreviewsGroupDefault.set(linkPreviews)
|
||||
}
|
||||
}
|
||||
settingsRow("message", color: theme.colors.secondary) {
|
||||
Toggle("Show last messages", isOn: $showChatPreviews)
|
||||
@@ -365,6 +368,7 @@ struct SimplexLockView: View {
|
||||
@State private var selfDestruct: Bool = UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT)
|
||||
@State private var currentSelfDestruct: Bool = UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT)
|
||||
@AppStorage(DEFAULT_LA_SELF_DESTRUCT_DISPLAY_NAME) private var selfDestructDisplayName = ""
|
||||
@AppStorage(GROUP_DEFAULT_ALLOW_SHARE_EXTENSION, store: groupDefaults) private var allowShareExtension = false
|
||||
@State private var performLAToggleReset = false
|
||||
@State private var performLAModeReset = false
|
||||
@State private var performLASelfDestructReset = false
|
||||
@@ -436,6 +440,12 @@ struct SimplexLockView: View {
|
||||
}
|
||||
}
|
||||
|
||||
if performLA {
|
||||
Section("Share to SimpleX") {
|
||||
Toggle("Allow sharing", isOn: $allowShareExtension)
|
||||
}
|
||||
}
|
||||
|
||||
if performLA && laMode == .passcode {
|
||||
Section(header: Text("Self-destruct passcode").foregroundColor(theme.colors.secondary)) {
|
||||
Toggle(isOn: $selfDestruct) {
|
||||
@@ -460,6 +470,7 @@ struct SimplexLockView: View {
|
||||
}
|
||||
}
|
||||
.onChange(of: performLA) { performLAToggle in
|
||||
performLAGroupDefault.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()
|
||||
|
||||
@@ -18,7 +18,7 @@ let appBuild = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as?
|
||||
|
||||
let DEFAULT_SHOW_LA_NOTICE = "showLocalAuthenticationNotice"
|
||||
let DEFAULT_LA_NOTICE_SHOWN = "localAuthenticationNoticeShown"
|
||||
let DEFAULT_PERFORM_LA = "performLocalAuthentication"
|
||||
let DEFAULT_PERFORM_LA = "performLocalAuthentication" // deprecated, moved to app group
|
||||
let DEFAULT_LA_MODE = "localAuthenticationMode"
|
||||
let DEFAULT_LA_LOCK_DELAY = "localAuthenticationLockDelay"
|
||||
let DEFAULT_LA_SELF_DESTRUCT = "localAuthenticationSelfDestruct"
|
||||
@@ -28,7 +28,7 @@ let DEFAULT_WEBRTC_POLICY_RELAY = "webrtcPolicyRelay"
|
||||
let DEFAULT_WEBRTC_ICE_SERVERS = "webrtcICEServers"
|
||||
let DEFAULT_CALL_KIT_CALLS_IN_RECENTS = "callKitCallsInRecents"
|
||||
let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" // unused. Use GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES instead
|
||||
let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews"
|
||||
let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" // deprecated, moved to app group
|
||||
let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode"
|
||||
let DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS = "privacyShowChatPreviews"
|
||||
let DEFAULT_PRIVACY_SAVE_LAST_DRAFT = "privacySaveLastDraft"
|
||||
@@ -47,6 +47,7 @@ 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_CONNECT_VIA_LINK_TAB = "connectViaLinkTab"
|
||||
let DEFAULT_LIVE_MESSAGE_ALERT_SHOWN = "liveMessageAlertShown"
|
||||
let DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE = "showHiddenProfilesNotice"
|
||||
@@ -61,6 +62,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 +97,7 @@ 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_CONNECT_VIA_LINK_TAB: ConnectViaLinkTab.scan.rawValue,
|
||||
DEFAULT_LIVE_MESSAGE_ALERT_SHOWN: false,
|
||||
DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE: true,
|
||||
@@ -104,6 +108,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,
|
||||
@@ -158,6 +164,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,6 +174,9 @@ 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))
|
||||
privacyLinkPreviewsGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS))
|
||||
profileImageCornerRadiusGroupDefault.set(UserDefaults.standard.double(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS))
|
||||
}
|
||||
|
||||
public class StringDefault {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -772,6 +772,10 @@
|
||||
<target>Разреши изпращането на изчезващи съобщения.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow sharing" xml:space="preserve">
|
||||
<source>Allow sharing</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Позволи необратимо изтриване на изпратените съобщения. (24 часа)</target>
|
||||
@@ -1058,6 +1062,10 @@
|
||||
<target>Блокиран от админ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blur media" xml:space="preserve">
|
||||
<source>Blur media</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
|
||||
<source>Both you and your contact can add message reactions.</source>
|
||||
<target>И вие, и вашият контакт можете да добавяте реакции към съобщението.</target>
|
||||
@@ -1226,6 +1234,10 @@
|
||||
<target>Базата данни на чата е изтрита</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database exported" xml:space="preserve">
|
||||
<source>Chat database exported</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database imported" xml:space="preserve">
|
||||
<source>Chat database imported</source>
|
||||
<target>Базата данни на чат е импортирана</target>
|
||||
@@ -1509,6 +1521,10 @@ This is your own one-time link!</source>
|
||||
<target>Грешка при свързване (AUTH)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection notifications" xml:space="preserve">
|
||||
<source>Connection notifications</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection request sent!" xml:space="preserve">
|
||||
<source>Connection request sent!</source>
|
||||
<target>Заявката за връзка е изпратена!</target>
|
||||
@@ -1849,6 +1865,10 @@ This is your own one-time link!</source>
|
||||
<target>Изтрий</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
|
||||
<source>Delete %lld messages of members?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages?" xml:space="preserve">
|
||||
<source>Delete %lld messages?</source>
|
||||
<target>Изтриване на %lld съобщения?</target>
|
||||
@@ -2089,10 +2109,18 @@ This cannot be undone!</source>
|
||||
<target>Настолни устройства</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server address of %@ is incompatible with forwarding server %@ settings." xml:space="preserve">
|
||||
<source>Destination server address of %@ is incompatible with forwarding server %@ settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server error: %@" xml:space="preserve">
|
||||
<source>Destination server error: %@</source>
|
||||
<note>snd error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server version of %@ is incompatible with forwarding server %@." xml:space="preserve">
|
||||
<source>Destination server version of %@ is incompatible with forwarding server %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Detailed statistics" xml:space="preserve">
|
||||
<source>Detailed statistics</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -2156,6 +2184,10 @@ This cannot be undone!</source>
|
||||
<target>Деактивиране за всички</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disabled" xml:space="preserve">
|
||||
<source>Disabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing message" xml:space="preserve">
|
||||
<source>Disappearing message</source>
|
||||
<target>Изчезващо съобщение</target>
|
||||
@@ -2376,6 +2408,10 @@ This cannot be undone!</source>
|
||||
<target>Активирай kод за достъп за самоунищожение</target>
|
||||
<note>set passcode view</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled" xml:space="preserve">
|
||||
<source>Enabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled for" xml:space="preserve">
|
||||
<source>Enabled for</source>
|
||||
<target>Активирано за</target>
|
||||
@@ -2546,6 +2582,10 @@ This cannot be undone!</source>
|
||||
<target>Грешка при промяна на настройката</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
|
||||
<source>Error connecting to forwarding server %@. Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating address" xml:space="preserve">
|
||||
<source>Error creating address</source>
|
||||
<target>Грешка при създаване на адрес</target>
|
||||
@@ -3040,6 +3080,18 @@ This cannot be undone!</source>
|
||||
<target>Препратено от</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server %@ failed to connect to destination server %@. Please try later." xml:space="preserve">
|
||||
<source>Forwarding server %@ failed to connect to destination server %@. Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server address is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server version is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server: %@ Destination server error: %@" xml:space="preserve">
|
||||
<source>Forwarding server: %1$@
|
||||
Destination server error: %2$@</source>
|
||||
@@ -3851,6 +3903,14 @@ This is your link for group %@!</source>
|
||||
<target>Макс. 30 секунди, получено незабавно.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Media & file servers" xml:space="preserve">
|
||||
<source>Media & file servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Medium" xml:space="preserve">
|
||||
<source>Medium</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member" xml:space="preserve">
|
||||
<source>Member</source>
|
||||
<target>Член</target>
|
||||
@@ -3929,12 +3989,8 @@ This is your link for group %@!</source>
|
||||
<source>Message reception</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing fallback" xml:space="preserve">
|
||||
<source>Message routing fallback</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing mode" xml:space="preserve">
|
||||
<source>Message routing mode</source>
|
||||
<trans-unit id="Message servers" xml:space="preserve">
|
||||
<source>Message servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message source remains private." xml:space="preserve">
|
||||
@@ -4260,6 +4316,10 @@ This is your link for group %@!</source>
|
||||
<target>Несъвместим!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nothing selected" xml:space="preserve">
|
||||
<source>Nothing selected</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Известия</target>
|
||||
@@ -4287,7 +4347,7 @@ This is your link for group %@!</source>
|
||||
<trans-unit id="Off" xml:space="preserve">
|
||||
<source>Off</source>
|
||||
<target>Изключено</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
@@ -4309,14 +4369,18 @@ This is your link for group %@!</source>
|
||||
<target>Линк за еднократна покана</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
|
||||
<target>За свързване ще са необходими Onion хостове. Изисква се активиране на VPN.</target>
|
||||
<trans-unit id="Onion hosts will be **required** for connection. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be **required** for connection.
|
||||
Requires compatible VPN.</source>
|
||||
<target>За свързване ще са **необходими** Onion хостове.
|
||||
Изисква се активиране на VPN.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
|
||||
<target>Ще се използват Onion хостове, когато са налични. Изисква се активиране на VPN.</target>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available.
|
||||
Requires compatible VPN.</source>
|
||||
<target>Ще се използват Onion хостове, когато са налични.
|
||||
Изисква се активиране на VPN.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
|
||||
@@ -4638,10 +4702,6 @@ Error: %@</source>
|
||||
<target>Моля, съхранявайте паролата на сигурно място, НЯМА да можете да я промените, ако я загубите.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please try later." xml:space="preserve">
|
||||
<source>Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Polish interface" xml:space="preserve">
|
||||
<source>Polish interface</source>
|
||||
<target>Полски интерфейс</target>
|
||||
@@ -5179,11 +5239,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Покажи</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revert" xml:space="preserve">
|
||||
<source>Revert</source>
|
||||
<target>Отмени промените</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revoke" xml:space="preserve">
|
||||
<source>Revoke</source>
|
||||
<target>Отзови</target>
|
||||
@@ -5213,11 +5268,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>SMP server</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SMP servers" xml:space="preserve">
|
||||
<source>SMP servers</source>
|
||||
<target>SMP сървъри</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safely receive files" xml:space="preserve">
|
||||
<source>Safely receive files</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5247,6 +5297,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Запази и уведоми членовете на групата</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and reconnect" xml:space="preserve">
|
||||
<source>Save and reconnect</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and update group profile" xml:space="preserve">
|
||||
<source>Save and update group profile</source>
|
||||
<target>Запази и актуализирай профила на групата</target>
|
||||
@@ -5401,6 +5455,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<trans-unit id="Select" xml:space="preserve">
|
||||
<source>Select</source>
|
||||
<target>Избери</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected %lld" xml:space="preserve">
|
||||
<source>Selected %lld</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
@@ -5639,10 +5697,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>Server version is incompatible with network settings.</source>
|
||||
<note>srv error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with your app: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5754,6 +5808,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Сподели този еднократен линк за връзка</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share to SimpleX" xml:space="preserve">
|
||||
<source>Share to SimpleX</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share with contacts" xml:space="preserve">
|
||||
<source>Share with contacts</source>
|
||||
<target>Сподели с контактите</target>
|
||||
@@ -5899,11 +5957,23 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Малки групи (максимум 20)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Soft" xml:space="preserve">
|
||||
<source>Soft</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
|
||||
<source>Some file(s) were not exported:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
|
||||
<target>Някои не-фатални грешки са възникнали по време на импортиране - може да видите конзолата за повече подробности.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Somebody" xml:space="preserve">
|
||||
<source>Somebody</source>
|
||||
<target>Някой</target>
|
||||
@@ -5997,6 +6067,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Спиране на чата</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Strong" xml:space="preserve">
|
||||
<source>Strong</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Submit" xml:space="preserve">
|
||||
<source>Submit</source>
|
||||
<target>Изпрати</target>
|
||||
@@ -6029,6 +6103,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Системна идентификация</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection" xml:space="preserve">
|
||||
<source>TCP connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
||||
<source>TCP connection timeout</source>
|
||||
<target>Времето на изчакване за установяване на TCP връзка</target>
|
||||
@@ -6199,6 +6277,14 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>Съобщението ще бъде маркирано като модерирано за всички членове.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
|
||||
<source>The messages will be deleted for all members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be marked as moderated for all members." xml:space="preserve">
|
||||
<source>The messages will be marked as moderated for all members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The next generation of private messaging" xml:space="preserve">
|
||||
<source>The next generation of private messaging</source>
|
||||
<target>Ново поколение поверителни съобщения</target>
|
||||
@@ -6551,11 +6637,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Актуализация</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
|
||||
<source>Update .onion hosts setting?</source>
|
||||
<target>Актуализиране на настройката за .onion хостове?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update database passphrase" xml:space="preserve">
|
||||
<source>Update database passphrase</source>
|
||||
<target>Актуализирай паролата на базата данни</target>
|
||||
@@ -6566,9 +6647,8 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Актуализиране на мрежовите настройки?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
|
||||
<source>Update transport isolation mode?</source>
|
||||
<target>Актуализиране на режима на изолация на транспорта?</target>
|
||||
<trans-unit id="Update settings?" xml:space="preserve">
|
||||
<source>Update settings?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
|
||||
@@ -6576,11 +6656,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Актуализирането на настройките ще свърже отново клиента към всички сървъри.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
|
||||
<source>Updating this setting will re-connect the client to all servers.</source>
|
||||
<target>Актуализирането на тази настройка ще свърже повторно клиента към всички сървъри.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Upgrade and open chat" xml:space="preserve">
|
||||
<source>Upgrade and open chat</source>
|
||||
<target>Актуализирай и отвори чата</target>
|
||||
@@ -6685,11 +6760,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<source>User selection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
|
||||
<source>Using .onion hosts requires compatible VPN provider.</source>
|
||||
<target>Използването на .onion хостове изисква съвместим VPN доставчик.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
|
||||
<source>Using SimpleX Chat servers.</source>
|
||||
<target>Използват се сървърите на SimpleX Chat.</target>
|
||||
@@ -6943,11 +7013,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<source>XFTP server</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="XFTP servers" xml:space="preserve">
|
||||
<source>XFTP servers</source>
|
||||
<target>XFTP сървъри</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You" xml:space="preserve">
|
||||
<source>You</source>
|
||||
<target>Вие</target>
|
||||
@@ -7161,6 +7226,14 @@ Repeat connection request?</source>
|
||||
<target>Вие се присъединихте към тази група. Свързване с поканващия член на групата.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may migrate the exported database." xml:space="preserve">
|
||||
<source>You may migrate the exported database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may save the exported archive." xml:space="preserve">
|
||||
<source>You may save the exported archive.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="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." xml:space="preserve">
|
||||
<source>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.</source>
|
||||
<target>Трябва да използвате най-новата версия на вашата чат база данни САМО на едно устройство, в противен случай може да спрете да получавате съобщения от някои контакти.</target>
|
||||
@@ -8131,8 +8204,8 @@ last received msg: %2$@</source>
|
||||
<target>неизвестен</target>
|
||||
<note>connection info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown relays" xml:space="preserve">
|
||||
<source>unknown relays</source>
|
||||
<trans-unit id="unknown servers" xml:space="preserve">
|
||||
<source>unknown servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown status" xml:space="preserve">
|
||||
@@ -8354,4 +8427,178 @@ last received msg: %2$@</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="bg" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<note>Bundle display name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<note>Bundle name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
|
||||
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
|
||||
<note>Copyright (human-readable)</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="bg" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
<source>%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App is locked!" xml:space="preserve">
|
||||
<source>App is locked!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cancel" xml:space="preserve">
|
||||
<source>Cancel</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
|
||||
<source>Cannot access keychain to save database password</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot forward message" xml:space="preserve">
|
||||
<source>Cannot forward message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Comment" xml:space="preserve">
|
||||
<source>Comment</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
|
||||
<source>Currently maximum supported file size is %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database downgrade required" xml:space="preserve">
|
||||
<source>Database downgrade required</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database encrypted!" xml:space="preserve">
|
||||
<source>Database encrypted!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database error" xml:space="preserve">
|
||||
<source>Database error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is different from saved in the keychain." xml:space="preserve">
|
||||
<source>Database passphrase is different from saved in the keychain.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
|
||||
<source>Database passphrase is required to open chat.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database upgrade required" xml:space="preserve">
|
||||
<source>Database upgrade required</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing file" xml:space="preserve">
|
||||
<source>Error preparing file</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing message" xml:space="preserve">
|
||||
<source>Error preparing message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error: %@" xml:space="preserve">
|
||||
<source>Error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="File error" xml:space="preserve">
|
||||
<source>File error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible database version" xml:space="preserve">
|
||||
<source>Incompatible database version</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
|
||||
<source>Invalid migration confirmation</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keychain error" xml:space="preserve">
|
||||
<source>Keychain error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Large file!" xml:space="preserve">
|
||||
<source>Large file!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No active profile" xml:space="preserve">
|
||||
<source>No active profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
|
||||
<source>Open the app to downgrade the database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
|
||||
<source>Open the app to upgrade the database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Passphrase" xml:space="preserve">
|
||||
<source>Passphrase</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
|
||||
<source>Please create a profile in the SimpleX app</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
<source>Selected chat preferences prohibit this message.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
|
||||
<source>Sending a message takes longer than expected.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending message…" xml:space="preserve">
|
||||
<source>Sending message…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share" xml:space="preserve">
|
||||
<source>Share</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Slow network?" xml:space="preserve">
|
||||
<source>Slow network?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unknown database error: %@" xml:space="preserve">
|
||||
<source>Unknown database error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported format" xml:space="preserve">
|
||||
<source>Unsupported format</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wait" xml:space="preserve">
|
||||
<source>Wait</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can allow sharing in Privacy & Security / SimpleX Lock settings." xml:space="preserve">
|
||||
<source>You can allow sharing in Privacy & Security / SimpleX Lock settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
+3
@@ -1,6 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
@@ -749,6 +749,10 @@
|
||||
<target>Povolit odesílání mizících zpráv.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow sharing" xml:space="preserve">
|
||||
<source>Allow sharing</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Povolit nevratné smazání odeslaných zpráv. (24 hodin)</target>
|
||||
@@ -1019,6 +1023,10 @@
|
||||
<source>Blocked by admin</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blur media" xml:space="preserve">
|
||||
<source>Blur media</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
|
||||
<source>Both you and your contact can add message reactions.</source>
|
||||
<target>Vy i váš kontakt můžete přidávat reakce na zprávy.</target>
|
||||
@@ -1184,6 +1192,10 @@
|
||||
<target>Databáze chatu odstraněna</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database exported" xml:space="preserve">
|
||||
<source>Chat database exported</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database imported" xml:space="preserve">
|
||||
<source>Chat database imported</source>
|
||||
<target>Importovaná databáze chatu</target>
|
||||
@@ -1448,6 +1460,10 @@ This is your own one-time link!</source>
|
||||
<target>Chyba spojení (AUTH)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection notifications" xml:space="preserve">
|
||||
<source>Connection notifications</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection request sent!" xml:space="preserve">
|
||||
<source>Connection request sent!</source>
|
||||
<target>Požadavek na připojení byl odeslán!</target>
|
||||
@@ -1779,6 +1795,10 @@ This is your own one-time link!</source>
|
||||
<target>Smazat</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
|
||||
<source>Delete %lld messages of members?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages?" xml:space="preserve">
|
||||
<source>Delete %lld messages?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -2011,10 +2031,18 @@ This cannot be undone!</source>
|
||||
<source>Desktop devices</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server address of %@ is incompatible with forwarding server %@ settings." xml:space="preserve">
|
||||
<source>Destination server address of %@ is incompatible with forwarding server %@ settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server error: %@" xml:space="preserve">
|
||||
<source>Destination server error: %@</source>
|
||||
<note>snd error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server version of %@ is incompatible with forwarding server %@." xml:space="preserve">
|
||||
<source>Destination server version of %@ is incompatible with forwarding server %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Detailed statistics" xml:space="preserve">
|
||||
<source>Detailed statistics</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -2078,6 +2106,10 @@ This cannot be undone!</source>
|
||||
<target>Vypnout pro všechny</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disabled" xml:space="preserve">
|
||||
<source>Disabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing message" xml:space="preserve">
|
||||
<source>Disappearing message</source>
|
||||
<target>Mizící zpráva</target>
|
||||
@@ -2289,6 +2321,10 @@ This cannot be undone!</source>
|
||||
<target>Povolit sebedestrukční heslo</target>
|
||||
<note>set passcode view</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled" xml:space="preserve">
|
||||
<source>Enabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled for" xml:space="preserve">
|
||||
<source>Enabled for</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -2451,6 +2487,10 @@ This cannot be undone!</source>
|
||||
<target>Chyba změny nastavení</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
|
||||
<source>Error connecting to forwarding server %@. Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating address" xml:space="preserve">
|
||||
<source>Error creating address</source>
|
||||
<target>Chyba při vytváření adresy</target>
|
||||
@@ -2928,6 +2968,18 @@ This cannot be undone!</source>
|
||||
<source>Forwarded from</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server %@ failed to connect to destination server %@. Please try later." xml:space="preserve">
|
||||
<source>Forwarding server %@ failed to connect to destination server %@. Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server address is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server version is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server: %@ Destination server error: %@" xml:space="preserve">
|
||||
<source>Forwarding server: %1$@
|
||||
Destination server error: %2$@</source>
|
||||
@@ -3708,6 +3760,14 @@ This is your link for group %@!</source>
|
||||
<target>Max 30 vteřin, přijato okamžitě.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Media & file servers" xml:space="preserve">
|
||||
<source>Media & file servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Medium" xml:space="preserve">
|
||||
<source>Medium</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member" xml:space="preserve">
|
||||
<source>Member</source>
|
||||
<target>Člen</target>
|
||||
@@ -3786,12 +3846,8 @@ This is your link for group %@!</source>
|
||||
<source>Message reception</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing fallback" xml:space="preserve">
|
||||
<source>Message routing fallback</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing mode" xml:space="preserve">
|
||||
<source>Message routing mode</source>
|
||||
<trans-unit id="Message servers" xml:space="preserve">
|
||||
<source>Message servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message source remains private." xml:space="preserve">
|
||||
@@ -4099,6 +4155,10 @@ This is your link for group %@!</source>
|
||||
<source>Not compatible!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nothing selected" xml:space="preserve">
|
||||
<source>Nothing selected</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Oznámení</target>
|
||||
@@ -4125,7 +4185,7 @@ This is your link for group %@!</source>
|
||||
<trans-unit id="Off" xml:space="preserve">
|
||||
<source>Off</source>
|
||||
<target>Vypnout</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
@@ -4147,14 +4207,18 @@ This is your link for group %@!</source>
|
||||
<target>Jednorázový zvací odkaz</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
|
||||
<target>Pro připojení budou vyžadováni Onion hostitelé. Vyžaduje povolení sítě VPN.</target>
|
||||
<trans-unit id="Onion hosts will be **required** for connection. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be **required** for connection.
|
||||
Requires compatible VPN.</source>
|
||||
<target>Pro připojení budou vyžadováni Onion hostitelé.
|
||||
Vyžaduje povolení sítě VPN.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
|
||||
<target>Onion hostitelé budou použiti, pokud jsou k dispozici. Vyžaduje povolení sítě VPN.</target>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available.
|
||||
Requires compatible VPN.</source>
|
||||
<target>Onion hostitelé budou použiti, pokud jsou k dispozici.
|
||||
Vyžaduje povolení sítě VPN.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
|
||||
@@ -4460,10 +4524,6 @@ Error: %@</source>
|
||||
<target>Heslo uložte bezpečně, v případě jeho ztráty jej NEBUDE možné změnit.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please try later." xml:space="preserve">
|
||||
<source>Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Polish interface" xml:space="preserve">
|
||||
<source>Polish interface</source>
|
||||
<target>Polské rozhraní</target>
|
||||
@@ -4985,11 +5045,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Odhalit</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revert" xml:space="preserve">
|
||||
<source>Revert</source>
|
||||
<target>Vrátit</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revoke" xml:space="preserve">
|
||||
<source>Revoke</source>
|
||||
<target>Odvolat</target>
|
||||
@@ -5019,11 +5074,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>SMP server</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SMP servers" xml:space="preserve">
|
||||
<source>SMP servers</source>
|
||||
<target>SMP servery</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safely receive files" xml:space="preserve">
|
||||
<source>Safely receive files</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5052,6 +5102,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Uložit a upozornit členy skupiny</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and reconnect" xml:space="preserve">
|
||||
<source>Save and reconnect</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and update group profile" xml:space="preserve">
|
||||
<source>Save and update group profile</source>
|
||||
<target>Uložit a aktualizovat profil skupiny</target>
|
||||
@@ -5200,6 +5254,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<trans-unit id="Select" xml:space="preserve">
|
||||
<source>Select</source>
|
||||
<target>Vybrat</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected %lld" xml:space="preserve">
|
||||
<source>Selected %lld</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
@@ -5437,10 +5495,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>Server version is incompatible with network settings.</source>
|
||||
<note>srv error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with your app: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5548,6 +5602,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>Share this 1-time invite link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share to SimpleX" xml:space="preserve">
|
||||
<source>Share to SimpleX</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share with contacts" xml:space="preserve">
|
||||
<source>Share with contacts</source>
|
||||
<target>Sdílet s kontakty</target>
|
||||
@@ -5690,11 +5748,23 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Malé skupiny (max. 20)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Soft" xml:space="preserve">
|
||||
<source>Soft</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
|
||||
<source>Some file(s) were not exported:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
|
||||
<target>Během importu došlo k nezávažným chybám - podrobnosti naleznete v chat konzoli.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Somebody" xml:space="preserve">
|
||||
<source>Somebody</source>
|
||||
<target>Někdo</target>
|
||||
@@ -5784,6 +5854,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>Stopping chat</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Strong" xml:space="preserve">
|
||||
<source>Strong</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Submit" xml:space="preserve">
|
||||
<source>Submit</source>
|
||||
<target>Odeslat</target>
|
||||
@@ -5816,6 +5890,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Ověření systému</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection" xml:space="preserve">
|
||||
<source>TCP connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
||||
<source>TCP connection timeout</source>
|
||||
<target>Časový limit připojení TCP</target>
|
||||
@@ -5982,6 +6060,14 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován
|
||||
<target>Zpráva bude pro všechny členy označena jako moderovaná.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
|
||||
<source>The messages will be deleted for all members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be marked as moderated for all members." xml:space="preserve">
|
||||
<source>The messages will be marked as moderated for all members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The next generation of private messaging" xml:space="preserve">
|
||||
<source>The next generation of private messaging</source>
|
||||
<target>Nová generace soukromých zpráv</target>
|
||||
@@ -6317,11 +6403,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
|
||||
<target>Aktualizovat</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
|
||||
<source>Update .onion hosts setting?</source>
|
||||
<target>Aktualizovat nastavení hostitelů .onion?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update database passphrase" xml:space="preserve">
|
||||
<source>Update database passphrase</source>
|
||||
<target>Aktualizovat přístupovou frázi databáze</target>
|
||||
@@ -6332,9 +6413,8 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
|
||||
<target>Aktualizovat nastavení sítě?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
|
||||
<source>Update transport isolation mode?</source>
|
||||
<target>Aktualizovat režim dopravní izolace?</target>
|
||||
<trans-unit id="Update settings?" xml:space="preserve">
|
||||
<source>Update settings?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
|
||||
@@ -6342,11 +6422,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
|
||||
<target>Aktualizací nastavení se klient znovu připojí ke všem serverům.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
|
||||
<source>Updating this setting will re-connect the client to all servers.</source>
|
||||
<target>Aktualizace tohoto nastavení znovu připojí klienta ke všem serverům.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Upgrade and open chat" xml:space="preserve">
|
||||
<source>Upgrade and open chat</source>
|
||||
<target>Zvýšit a otevřít chat</target>
|
||||
@@ -6446,11 +6521,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
|
||||
<source>User selection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
|
||||
<source>Using .onion hosts requires compatible VPN provider.</source>
|
||||
<target>Použití hostitelů .onion vyžaduje kompatibilního poskytovatele VPN.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
|
||||
<source>Using SimpleX Chat servers.</source>
|
||||
<target>Používat servery SimpleX Chat.</target>
|
||||
@@ -6687,11 +6757,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
|
||||
<source>XFTP server</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="XFTP servers" xml:space="preserve">
|
||||
<source>XFTP servers</source>
|
||||
<target>XFTP servery</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You" xml:space="preserve">
|
||||
<source>You</source>
|
||||
<target>Vy</target>
|
||||
@@ -6890,6 +6955,14 @@ Repeat connection request?</source>
|
||||
<target>Připojili jste se k této skupině. Připojení k pozvání člena skupiny.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may migrate the exported database." xml:space="preserve">
|
||||
<source>You may migrate the exported database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may save the exported archive." xml:space="preserve">
|
||||
<source>You may save the exported archive.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="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." xml:space="preserve">
|
||||
<source>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.</source>
|
||||
<target>Nejnovější verzi databáze chatu musíte používat POUZE v jednom zařízení, jinak se může stát, že přestanete přijímat zprávy od některých kontaktů.</target>
|
||||
@@ -7835,8 +7908,8 @@ last received msg: %2$@</source>
|
||||
<target>neznámý</target>
|
||||
<note>connection info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown relays" xml:space="preserve">
|
||||
<source>unknown relays</source>
|
||||
<trans-unit id="unknown servers" xml:space="preserve">
|
||||
<source>unknown servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown status" xml:space="preserve">
|
||||
@@ -8051,4 +8124,178 @@ last received msg: %2$@</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="cs" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<note>Bundle display name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<note>Bundle name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
|
||||
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
|
||||
<note>Copyright (human-readable)</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="cs" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
<source>%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App is locked!" xml:space="preserve">
|
||||
<source>App is locked!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cancel" xml:space="preserve">
|
||||
<source>Cancel</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
|
||||
<source>Cannot access keychain to save database password</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot forward message" xml:space="preserve">
|
||||
<source>Cannot forward message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Comment" xml:space="preserve">
|
||||
<source>Comment</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
|
||||
<source>Currently maximum supported file size is %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database downgrade required" xml:space="preserve">
|
||||
<source>Database downgrade required</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database encrypted!" xml:space="preserve">
|
||||
<source>Database encrypted!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database error" xml:space="preserve">
|
||||
<source>Database error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is different from saved in the keychain." xml:space="preserve">
|
||||
<source>Database passphrase is different from saved in the keychain.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
|
||||
<source>Database passphrase is required to open chat.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database upgrade required" xml:space="preserve">
|
||||
<source>Database upgrade required</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing file" xml:space="preserve">
|
||||
<source>Error preparing file</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing message" xml:space="preserve">
|
||||
<source>Error preparing message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error: %@" xml:space="preserve">
|
||||
<source>Error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="File error" xml:space="preserve">
|
||||
<source>File error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible database version" xml:space="preserve">
|
||||
<source>Incompatible database version</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
|
||||
<source>Invalid migration confirmation</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keychain error" xml:space="preserve">
|
||||
<source>Keychain error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Large file!" xml:space="preserve">
|
||||
<source>Large file!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No active profile" xml:space="preserve">
|
||||
<source>No active profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
|
||||
<source>Open the app to downgrade the database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
|
||||
<source>Open the app to upgrade the database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Passphrase" xml:space="preserve">
|
||||
<source>Passphrase</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
|
||||
<source>Please create a profile in the SimpleX app</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
<source>Selected chat preferences prohibit this message.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
|
||||
<source>Sending a message takes longer than expected.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending message…" xml:space="preserve">
|
||||
<source>Sending message…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share" xml:space="preserve">
|
||||
<source>Share</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Slow network?" xml:space="preserve">
|
||||
<source>Slow network?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unknown database error: %@" xml:space="preserve">
|
||||
<source>Unknown database error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported format" xml:space="preserve">
|
||||
<source>Unsupported format</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wait" xml:space="preserve">
|
||||
<source>Wait</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can allow sharing in Privacy & Security / SimpleX Lock settings." xml:space="preserve">
|
||||
<source>You can allow sharing in Privacy & Security / SimpleX Lock settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
+3
@@ -1,6 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
@@ -590,6 +590,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Active connections" xml:space="preserve">
|
||||
<source>Active connections</source>
|
||||
<target>Aktive Verbindungen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." xml:space="preserve">
|
||||
@@ -684,7 +685,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="All chats and messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>All chats and messages will be deleted - this cannot be undone!</source>
|
||||
<target>Alle Chats und Nachrichten werden gelöscht. Dies kann nicht rückgängig gemacht werden!</target>
|
||||
<target>Es werden alle Chats und Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All data is erased when it is entered." xml:space="preserve">
|
||||
@@ -694,7 +695,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="All data is private to your device." xml:space="preserve">
|
||||
<source>All data is private to your device.</source>
|
||||
<target>Alle Daten sind auf Ihrem Gerät geschützt.</target>
|
||||
<target>Alle Daten werden nur auf Ihrem Gerät gespeichert.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All group members will remain connected." xml:space="preserve">
|
||||
@@ -709,7 +710,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." xml:space="preserve">
|
||||
<source>All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you.</source>
|
||||
<target>Alle Nachrichten werden gelöscht . Dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht.</target>
|
||||
<target>Es werden alle Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
|
||||
@@ -782,6 +783,11 @@
|
||||
<target>Das Senden von verschwindenden Nachrichten erlauben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow sharing" xml:space="preserve">
|
||||
<source>Allow sharing</source>
|
||||
<target>Teilen erlauben</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Unwiederbringliches löschen von gesendeten Nachrichten erlauben. (24 Stunden)</target>
|
||||
@@ -1072,6 +1078,11 @@
|
||||
<target>wurde vom Administrator blockiert</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blur media" xml:space="preserve">
|
||||
<source>Blur media</source>
|
||||
<target>Medium unscharf machen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
|
||||
<source>Both you and your contact can add message reactions.</source>
|
||||
<target>Sowohl Sie, als auch Ihr Kontakt können Reaktionen auf Nachrichten geben.</target>
|
||||
@@ -1243,6 +1254,10 @@
|
||||
<target>Chat-Datenbank gelöscht</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database exported" xml:space="preserve">
|
||||
<source>Chat database exported</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database imported" xml:space="preserve">
|
||||
<source>Chat database imported</source>
|
||||
<target>Chat-Datenbank importiert</target>
|
||||
@@ -1375,6 +1390,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Configured %@ servers" xml:space="preserve">
|
||||
<source>Configured %@ servers</source>
|
||||
<target>Konfigurierte %@ Server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Confirm" xml:space="preserve">
|
||||
@@ -1536,6 +1552,11 @@ Das ist Ihr eigener Einmal-Link!</target>
|
||||
<target>Verbindungsfehler (AUTH)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection notifications" xml:space="preserve">
|
||||
<source>Connection notifications</source>
|
||||
<target>Verbindungsbenachrichtigungen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection request sent!" xml:space="preserve">
|
||||
<source>Connection request sent!</source>
|
||||
<target>Verbindungsanfrage wurde gesendet!</target>
|
||||
@@ -1884,6 +1905,11 @@ Das ist Ihr eigener Einmal-Link!</target>
|
||||
<target>Löschen</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
|
||||
<source>Delete %lld messages of members?</source>
|
||||
<target>%lld Nachrichten der Mitglieder löschen?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages?" xml:space="preserve">
|
||||
<source>Delete %lld messages?</source>
|
||||
<target>%lld Nachrichten löschen?</target>
|
||||
@@ -2126,11 +2152,21 @@ Dies kann nicht rückgängig gemacht werden!</target>
|
||||
<target>Desktop-Geräte</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server address of %@ is incompatible with forwarding server %@ settings." xml:space="preserve">
|
||||
<source>Destination server address of %@ is incompatible with forwarding server %@ settings.</source>
|
||||
<target>Adresse des Zielservers von %@ ist nicht kompatibel mit den Einstellungen des Weiterleitungsservers %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server error: %@" xml:space="preserve">
|
||||
<source>Destination server error: %@</source>
|
||||
<target>Zielserver-Fehler: %@</target>
|
||||
<note>snd error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server version of %@ is incompatible with forwarding server %@." xml:space="preserve">
|
||||
<source>Destination server version of %@ is incompatible with forwarding server %@.</source>
|
||||
<target>Die Version des Zielservers %@ ist nicht kompatibel mit dem Weiterleitungsserver %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Detailed statistics" xml:space="preserve">
|
||||
<source>Detailed statistics</source>
|
||||
<target>Detaillierte Statistiken</target>
|
||||
@@ -2196,6 +2232,11 @@ Dies kann nicht rückgängig gemacht werden!</target>
|
||||
<target>Für Alle deaktivieren</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disabled" xml:space="preserve">
|
||||
<source>Disabled</source>
|
||||
<target>Deaktiviert</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing message" xml:space="preserve">
|
||||
<source>Disappearing message</source>
|
||||
<target>Verschwindende Nachricht</target>
|
||||
@@ -2421,6 +2462,11 @@ Dies kann nicht rückgängig gemacht werden!</target>
|
||||
<target>Selbstzerstörungs-Zugangscode aktivieren</target>
|
||||
<note>set passcode view</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled" xml:space="preserve">
|
||||
<source>Enabled</source>
|
||||
<target>Aktiviert</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled for" xml:space="preserve">
|
||||
<source>Enabled for</source>
|
||||
<target>Aktiviert für</target>
|
||||
@@ -2591,6 +2637,11 @@ Dies kann nicht rückgängig gemacht werden!</target>
|
||||
<target>Fehler beim Ändern der Einstellung</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
|
||||
<source>Error connecting to forwarding server %@. Please try later.</source>
|
||||
<target>Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es später erneut.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating address" xml:space="preserve">
|
||||
<source>Error creating address</source>
|
||||
<target>Fehler beim Erstellen der Adresse</target>
|
||||
@@ -3097,6 +3148,21 @@ Dies kann nicht rückgängig gemacht werden!</target>
|
||||
<target>Weitergeleitet aus</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server %@ failed to connect to destination server %@. Please try later." xml:space="preserve">
|
||||
<source>Forwarding server %@ failed to connect to destination server %@. Please try later.</source>
|
||||
<target>Weiterleitungsserver %@ konnte sich nicht mit dem Zielserver %@ verbinden. Bitte versuchen Sie es später erneut.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server address is incompatible with network settings: %@.</source>
|
||||
<target>Adresse des Weiterleitungsservers ist nicht kompatibel mit den Netzwerkeinstellungen: %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server version is incompatible with network settings: %@.</source>
|
||||
<target>Version des Weiterleitungsservers ist nicht kompatibel mit den Netzwerkeinstellungen: %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server: %@ Destination server error: %@" xml:space="preserve">
|
||||
<source>Forwarding server: %1$@
|
||||
Destination server error: %2$@</source>
|
||||
@@ -3523,12 +3589,12 @@ Fehler: %2$@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible database version" xml:space="preserve">
|
||||
<source>Incompatible database version</source>
|
||||
<target>Inkompatible Datenbank-Version</target>
|
||||
<target>Datenbank-Version nicht kompatibel</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible version" xml:space="preserve">
|
||||
<source>Incompatible version</source>
|
||||
<target>Inkompatible Version</target>
|
||||
<target>Version nicht kompatibel</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incorrect passcode" xml:space="preserve">
|
||||
@@ -3916,6 +3982,15 @@ Das ist Ihr Link für die Gruppe %@!</target>
|
||||
<target>Max. 30 Sekunden, sofort erhalten.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Media & file servers" xml:space="preserve">
|
||||
<source>Media & file servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Medium" xml:space="preserve">
|
||||
<source>Medium</source>
|
||||
<target>Medium</target>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member" xml:space="preserve">
|
||||
<source>Member</source>
|
||||
<target>Mitglied</target>
|
||||
@@ -3998,16 +4073,11 @@ Das ist Ihr Link für die Gruppe %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message reception" xml:space="preserve">
|
||||
<source>Message reception</source>
|
||||
<target>Nachrichtenempfang</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing fallback" xml:space="preserve">
|
||||
<source>Message routing fallback</source>
|
||||
<target>Fallback für das Nachrichten-Routing</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing mode" xml:space="preserve">
|
||||
<source>Message routing mode</source>
|
||||
<target>Modus für das Nachrichten-Routing</target>
|
||||
<trans-unit id="Message servers" xml:space="preserve">
|
||||
<source>Message servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message source remains private." xml:space="preserve">
|
||||
@@ -4340,6 +4410,11 @@ Das ist Ihr Link für die Gruppe %@!</target>
|
||||
<target>Nicht kompatibel!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nothing selected" xml:space="preserve">
|
||||
<source>Nothing selected</source>
|
||||
<target>Nichts ausgewählt</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Benachrichtigungen</target>
|
||||
@@ -4367,7 +4442,7 @@ Das ist Ihr Link für die Gruppe %@!</target>
|
||||
<trans-unit id="Off" xml:space="preserve">
|
||||
<source>Off</source>
|
||||
<target>Aus</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
@@ -4389,14 +4464,18 @@ Das ist Ihr Link für die Gruppe %@!</target>
|
||||
<target>Einmal-Einladungslink</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
|
||||
<target>Für diese Verbindung werden Onion-Hosts benötigt. Dies erfordert die Aktivierung eines VPNs.</target>
|
||||
<trans-unit id="Onion hosts will be **required** for connection. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be **required** for connection.
|
||||
Requires compatible VPN.</source>
|
||||
<target>Für diese Verbindung werden Onion-Hosts benötigt.
|
||||
Dies erfordert die Aktivierung eines VPNs.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
|
||||
<target>Wenn Onion-Hosts verfügbar sind, werden sie verwendet. Dies erfordert die Aktivierung eines VPNs.</target>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available.
|
||||
Requires compatible VPN.</source>
|
||||
<target>Wenn Onion-Hosts verfügbar sind, werden sie verwendet.
|
||||
Dies erfordert die Aktivierung eines VPNs.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
|
||||
@@ -4551,6 +4630,7 @@ Das ist Ihr Link für die Gruppe %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Other %@ servers" xml:space="preserve">
|
||||
<source>Other %@ servers</source>
|
||||
<target>Andere %@ Server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
@@ -4722,10 +4802,6 @@ Fehler: %@</target>
|
||||
<target>Bitte bewahren Sie das Passwort sicher auf, Sie können es NICHT mehr ändern, wenn Sie es vergessen haben oder verlieren.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please try later." xml:space="preserve">
|
||||
<source>Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Polish interface" xml:space="preserve">
|
||||
<source>Polish interface</source>
|
||||
<target>Polnische Bedienoberfläche</target>
|
||||
@@ -4798,6 +4874,7 @@ Fehler: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Private routing error" xml:space="preserve">
|
||||
<source>Private routing error</source>
|
||||
<target>Fehler beim privaten Routing</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile and server connections" xml:space="preserve">
|
||||
@@ -4919,7 +4996,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Proxied" xml:space="preserve">
|
||||
<source>Proxied</source>
|
||||
<target>Proxy</target>
|
||||
<target>Proxied</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Proxied servers" xml:space="preserve">
|
||||
@@ -5287,11 +5364,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
<target>Aufdecken</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revert" xml:space="preserve">
|
||||
<source>Revert</source>
|
||||
<target>Zurückkehren</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revoke" xml:space="preserve">
|
||||
<source>Revoke</source>
|
||||
<target>Widerrufen</target>
|
||||
@@ -5322,11 +5394,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
<target>SMP-Server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SMP servers" xml:space="preserve">
|
||||
<source>SMP servers</source>
|
||||
<target>SMP-Server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safely receive files" xml:space="preserve">
|
||||
<source>Safely receive files</source>
|
||||
<target>Dateien sicher empfangen</target>
|
||||
@@ -5357,6 +5424,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
<target>Speichern und Gruppenmitglieder benachrichtigen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and reconnect" xml:space="preserve">
|
||||
<source>Save and reconnect</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and update group profile" xml:space="preserve">
|
||||
<source>Save and update group profile</source>
|
||||
<target>Gruppen-Profil sichern und aktualisieren</target>
|
||||
@@ -5515,6 +5586,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
<trans-unit id="Select" xml:space="preserve">
|
||||
<source>Select</source>
|
||||
<target>Auswählen</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected %lld" xml:space="preserve">
|
||||
<source>Selected %lld</source>
|
||||
<target>%lld ausgewählt</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
@@ -5734,11 +5810,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server address is incompatible with network settings." xml:space="preserve">
|
||||
<source>Server address is incompatible with network settings.</source>
|
||||
<target>Die Server-Adresse ist nicht mit den Netzwerk-Einstellungen kompatibel.</target>
|
||||
<target>Die Server-Adresse ist nicht mit den Netzwerkeinstellungen kompatibel.</target>
|
||||
<note>srv error text.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server address is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Server address is incompatible with network settings: %@.</source>
|
||||
<target>Die Server-Adresse ist nicht mit den Netzwerkeinstellungen kompatibel: %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server requires authorization to create queues, check password" xml:space="preserve">
|
||||
@@ -5763,15 +5840,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with network settings." xml:space="preserve">
|
||||
<source>Server version is incompatible with network settings.</source>
|
||||
<target>Die Server-Version ist nicht mit den Netzwerk-Einstellungen kompatibel.</target>
|
||||
<target>Die Server-Version ist nicht mit den Netzwerkeinstellungen kompatibel.</target>
|
||||
<note>srv error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with your app: %@.</source>
|
||||
<target>Die Server-Version ist nicht mit Ihrer App kompatibel: %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Servers" xml:space="preserve">
|
||||
@@ -5884,6 +5958,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
<target>Teilen Sie diesen Einmal-Einladungslink</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share to SimpleX" xml:space="preserve">
|
||||
<source>Share to SimpleX</source>
|
||||
<target>Mit SimpleX teilen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share with contacts" xml:space="preserve">
|
||||
<source>Share with contacts</source>
|
||||
<target>Mit Kontakten teilen</target>
|
||||
@@ -5916,6 +5995,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show percentage" xml:space="preserve">
|
||||
<source>Show percentage</source>
|
||||
<target>Prozentualen Anteil anzeigen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show preview" xml:space="preserve">
|
||||
@@ -6033,11 +6113,24 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
<target>Kleine Gruppen (max. 20)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Soft" xml:space="preserve">
|
||||
<source>Soft</source>
|
||||
<target>Weich</target>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
|
||||
<source>Some file(s) were not exported:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
|
||||
<target>Während des Imports sind einige nicht schwerwiegende Fehler aufgetreten - in der Chat-Konsole finden Sie weitere Einzelheiten.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Somebody" xml:space="preserve">
|
||||
<source>Somebody</source>
|
||||
<target>Jemand</target>
|
||||
@@ -6133,6 +6226,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
<target>Chat wird beendet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Strong" xml:space="preserve">
|
||||
<source>Strong</source>
|
||||
<target>Hart</target>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Submit" xml:space="preserve">
|
||||
<source>Submit</source>
|
||||
<target>Bestätigen</target>
|
||||
@@ -6168,6 +6266,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
<target>System-Authentifizierung</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection" xml:space="preserve">
|
||||
<source>TCP connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
||||
<source>TCP connection timeout</source>
|
||||
<target>Timeout der TCP-Verbindung</target>
|
||||
@@ -6340,6 +6442,16 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro
|
||||
<target>Diese Nachricht wird für alle Mitglieder als moderiert gekennzeichnet.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
|
||||
<source>The messages will be deleted for all members.</source>
|
||||
<target>Die Nachrichten werden für alle Mitglieder gelöscht werden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be marked as moderated for all members." xml:space="preserve">
|
||||
<source>The messages will be marked as moderated for all members.</source>
|
||||
<target>Die Nachrichten werden für alle Mitglieder als moderiert gekennzeichnet werden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The next generation of private messaging" xml:space="preserve">
|
||||
<source>The next generation of private messaging</source>
|
||||
<target>Die nächste Generation von privatem Messaging</target>
|
||||
@@ -6392,12 +6504,12 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro
|
||||
</trans-unit>
|
||||
<trans-unit id="This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." xml:space="preserve">
|
||||
<source>This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain.</source>
|
||||
<target>Diese Aktion kann nicht rückgängig gemacht werden! Alle empfangenen und gesendeten Dateien und Medien werden gelöscht. Bilder mit niedriger Auflösung bleiben erhalten.</target>
|
||||
<target>Diese Aktion kann nicht rückgängig gemacht werden! Es werden alle empfangenen und gesendeten Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." xml:space="preserve">
|
||||
<source>This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes.</source>
|
||||
<target>Diese Aktion kann nicht rückgängig gemacht werden! Alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, werden gelöscht. Dieser Vorgang kann mehrere Minuten dauern.</target>
|
||||
<target>Diese Aktion kann nicht rückgängig gemacht werden! Es werden alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, gelöscht. Dieser Vorgang kann mehrere Minuten dauern.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." xml:space="preserve">
|
||||
@@ -6699,11 +6811,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
<target>Aktualisieren</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
|
||||
<source>Update .onion hosts setting?</source>
|
||||
<target>Einstellung für .onion-Hosts aktualisieren?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update database passphrase" xml:space="preserve">
|
||||
<source>Update database passphrase</source>
|
||||
<target>Datenbank-Passwort aktualisieren</target>
|
||||
@@ -6714,9 +6821,8 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
<target>Netzwerkeinstellungen aktualisieren?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
|
||||
<source>Update transport isolation mode?</source>
|
||||
<target>Transport-Isolations-Modus aktualisieren?</target>
|
||||
<trans-unit id="Update settings?" xml:space="preserve">
|
||||
<source>Update settings?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
|
||||
@@ -6724,11 +6830,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
<target>Die Aktualisierung der Einstellungen wird den Client wieder mit allen Servern verbinden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
|
||||
<source>Updating this setting will re-connect the client to all servers.</source>
|
||||
<target>Die Aktualisierung dieser Einstellung wird den Client wieder mit allen Servern verbinden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Upgrade and open chat" xml:space="preserve">
|
||||
<source>Upgrade and open chat</source>
|
||||
<target>Aktualisieren und den Chat öffnen</target>
|
||||
@@ -6839,11 +6940,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
<target>Benutzer-Auswahl</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
|
||||
<source>Using .onion hosts requires compatible VPN provider.</source>
|
||||
<target>Für die Nutzung von .onion-Hosts sind kompatible VPN-Anbieter erforderlich.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
|
||||
<source>Using SimpleX Chat servers.</source>
|
||||
<target>Verwendung von SimpleX-Chat-Servern.</target>
|
||||
@@ -7104,11 +7200,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
<target>XFTP-Server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="XFTP servers" xml:space="preserve">
|
||||
<source>XFTP servers</source>
|
||||
<target>XFTP-Server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You" xml:space="preserve">
|
||||
<source>You</source>
|
||||
<target>Profil</target>
|
||||
@@ -7323,6 +7414,14 @@ Verbindungsanfrage wiederholen?</target>
|
||||
<target>Sie sind dieser Gruppe beigetreten. Sie werden mit dem einladenden Gruppenmitglied verbunden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may migrate the exported database." xml:space="preserve">
|
||||
<source>You may migrate the exported database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may save the exported archive." xml:space="preserve">
|
||||
<source>You may save the exported archive.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="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." xml:space="preserve">
|
||||
<source>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.</source>
|
||||
<target>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.</target>
|
||||
@@ -8303,8 +8402,8 @@ Zuletzt empfangene Nachricht: %2$@</target>
|
||||
<target>Unbekannt</target>
|
||||
<note>connection info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown relays" xml:space="preserve">
|
||||
<source>unknown relays</source>
|
||||
<trans-unit id="unknown servers" xml:space="preserve">
|
||||
<source>unknown servers</source>
|
||||
<target>Unbekannte Relais</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -8529,4 +8628,214 @@ Zuletzt empfangene Nachricht: %2$@</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="de" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<target>SimpleX SE</target>
|
||||
<note>Bundle display name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<target>SimpleX SE</target>
|
||||
<note>Bundle name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
|
||||
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
|
||||
<target>Copyright © 2024 SimpleX Chat. Alle Rechte vorbehalten.</target>
|
||||
<note>Copyright (human-readable)</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="de" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
<source>%@</source>
|
||||
<target>%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App is locked!" xml:space="preserve">
|
||||
<source>App is locked!</source>
|
||||
<target>Die App ist gesperrt!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cancel" xml:space="preserve">
|
||||
<source>Cancel</source>
|
||||
<target>Abbrechen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
|
||||
<source>Cannot access keychain to save database password</source>
|
||||
<target>Es ist nicht möglich, auf den Schlüsselbund zuzugreifen, um das Datenbankpasswort zu speichern</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot forward message" xml:space="preserve">
|
||||
<source>Cannot forward message</source>
|
||||
<target>Nachricht kann nicht weitergeleitet werden</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Comment" xml:space="preserve">
|
||||
<source>Comment</source>
|
||||
<target>Kommentieren</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
|
||||
<source>Currently maximum supported file size is %@.</source>
|
||||
<target>Die maximale erlaubte Dateigröße beträgt aktuell %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database downgrade required" xml:space="preserve">
|
||||
<source>Database downgrade required</source>
|
||||
<target>Datenbank-Herabstufung erforderlich</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database encrypted!" xml:space="preserve">
|
||||
<source>Database encrypted!</source>
|
||||
<target>Datenbank verschlüsselt!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database error" xml:space="preserve">
|
||||
<source>Database error</source>
|
||||
<target>Datenbankfehler</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is different from saved in the keychain." xml:space="preserve">
|
||||
<source>Database passphrase is different from saved in the keychain.</source>
|
||||
<target>Das Datenbank-Passwort unterscheidet sich vom im Schlüsselbund gespeicherten.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
|
||||
<source>Database passphrase is required to open chat.</source>
|
||||
<target>Ein Datenbank-Passwort ist erforderlich, um den Chat zu öffnen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database upgrade required" xml:space="preserve">
|
||||
<source>Database upgrade required</source>
|
||||
<target>Datenbank-Aktualisierung erforderlich</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing file" xml:space="preserve">
|
||||
<source>Error preparing file</source>
|
||||
<target>Fehler beim Vorbereiten der Datei</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing message" xml:space="preserve">
|
||||
<source>Error preparing message</source>
|
||||
<target>Fehler beim Vorbereiten der Nachricht</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error: %@" xml:space="preserve">
|
||||
<source>Error: %@</source>
|
||||
<target>Fehler: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="File error" xml:space="preserve">
|
||||
<source>File error</source>
|
||||
<target>Dateifehler</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible database version" xml:space="preserve">
|
||||
<source>Incompatible database version</source>
|
||||
<target>Datenbank-Version nicht kompatibel</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
|
||||
<source>Invalid migration confirmation</source>
|
||||
<target>Migrations-Bestätigung ungültig</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keychain error" xml:space="preserve">
|
||||
<source>Keychain error</source>
|
||||
<target>Schlüsselbund-Fehler</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Large file!" xml:space="preserve">
|
||||
<source>Large file!</source>
|
||||
<target>Große Datei!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No active profile" xml:space="preserve">
|
||||
<source>No active profile</source>
|
||||
<target>Kein aktives Profil</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
<target>OK</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
|
||||
<source>Open the app to downgrade the database.</source>
|
||||
<target>Öffne die App, um die Datenbank herabzustufen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
|
||||
<source>Open the app to upgrade the database.</source>
|
||||
<target>Öffne die App, um die Datenbank zu aktualisieren.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Passphrase" xml:space="preserve">
|
||||
<source>Passphrase</source>
|
||||
<target>Passwort</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
|
||||
<source>Please create a profile in the SimpleX app</source>
|
||||
<target>Bitte erstelle ein Profil in der SimpleX-App</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
<source>Selected chat preferences prohibit this message.</source>
|
||||
<target>Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
|
||||
<source>Sending a message takes longer than expected.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending message…" xml:space="preserve">
|
||||
<source>Sending message…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share" xml:space="preserve">
|
||||
<source>Share</source>
|
||||
<target>Teilen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Slow network?" xml:space="preserve">
|
||||
<source>Slow network?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unknown database error: %@" xml:space="preserve">
|
||||
<source>Unknown database error: %@</source>
|
||||
<target>Unbekannter Datenbankfehler: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported format" xml:space="preserve">
|
||||
<source>Unsupported format</source>
|
||||
<target>Nicht unterstütztes Format</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wait" xml:space="preserve">
|
||||
<source>Wait</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<target>Falsches Datenbank-Passwort</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can allow sharing in Privacy & Security / SimpleX Lock settings." xml:space="preserve">
|
||||
<source>You can allow sharing in Privacy & Security / SimpleX Lock settings.</source>
|
||||
<target>Du kannst das Teilen in den Einstellungen zu Datenschutz & Sicherheit bzw. SimpleX-Sperre erlauben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
+3
@@ -1,6 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
@@ -783,6 +783,11 @@
|
||||
<target>Allow sending disappearing messages.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow sharing" xml:space="preserve">
|
||||
<source>Allow sharing</source>
|
||||
<target>Allow sharing</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Allow to irreversibly delete sent messages. (24 hours)</target>
|
||||
@@ -1073,6 +1078,11 @@
|
||||
<target>Blocked by admin</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blur media" xml:space="preserve">
|
||||
<source>Blur media</source>
|
||||
<target>Blur media</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
|
||||
<source>Both you and your contact can add message reactions.</source>
|
||||
<target>Both you and your contact can add message reactions.</target>
|
||||
@@ -1244,6 +1254,11 @@
|
||||
<target>Chat database deleted</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database exported" xml:space="preserve">
|
||||
<source>Chat database exported</source>
|
||||
<target>Chat database exported</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database imported" xml:space="preserve">
|
||||
<source>Chat database imported</source>
|
||||
<target>Chat database imported</target>
|
||||
@@ -1538,6 +1553,11 @@ This is your own one-time link!</target>
|
||||
<target>Connection error (AUTH)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection notifications" xml:space="preserve">
|
||||
<source>Connection notifications</source>
|
||||
<target>Connection notifications</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection request sent!" xml:space="preserve">
|
||||
<source>Connection request sent!</source>
|
||||
<target>Connection request sent!</target>
|
||||
@@ -1886,6 +1906,11 @@ This is your own one-time link!</target>
|
||||
<target>Delete</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
|
||||
<source>Delete %lld messages of members?</source>
|
||||
<target>Delete %lld messages of members?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages?" xml:space="preserve">
|
||||
<source>Delete %lld messages?</source>
|
||||
<target>Delete %lld messages?</target>
|
||||
@@ -2128,11 +2153,21 @@ This cannot be undone!</target>
|
||||
<target>Desktop devices</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server address of %@ is incompatible with forwarding server %@ settings." xml:space="preserve">
|
||||
<source>Destination server address of %@ is incompatible with forwarding server %@ settings.</source>
|
||||
<target>Destination server address of %@ is incompatible with forwarding server %@ settings.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server error: %@" xml:space="preserve">
|
||||
<source>Destination server error: %@</source>
|
||||
<target>Destination server error: %@</target>
|
||||
<note>snd error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server version of %@ is incompatible with forwarding server %@." xml:space="preserve">
|
||||
<source>Destination server version of %@ is incompatible with forwarding server %@.</source>
|
||||
<target>Destination server version of %@ is incompatible with forwarding server %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Detailed statistics" xml:space="preserve">
|
||||
<source>Detailed statistics</source>
|
||||
<target>Detailed statistics</target>
|
||||
@@ -2198,6 +2233,11 @@ This cannot be undone!</target>
|
||||
<target>Disable for all</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disabled" xml:space="preserve">
|
||||
<source>Disabled</source>
|
||||
<target>Disabled</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing message" xml:space="preserve">
|
||||
<source>Disappearing message</source>
|
||||
<target>Disappearing message</target>
|
||||
@@ -2423,6 +2463,11 @@ This cannot be undone!</target>
|
||||
<target>Enable self-destruct passcode</target>
|
||||
<note>set passcode view</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled" xml:space="preserve">
|
||||
<source>Enabled</source>
|
||||
<target>Enabled</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled for" xml:space="preserve">
|
||||
<source>Enabled for</source>
|
||||
<target>Enabled for</target>
|
||||
@@ -2593,6 +2638,11 @@ This cannot be undone!</target>
|
||||
<target>Error changing setting</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
|
||||
<source>Error connecting to forwarding server %@. Please try later.</source>
|
||||
<target>Error connecting to forwarding server %@. Please try later.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating address" xml:space="preserve">
|
||||
<source>Error creating address</source>
|
||||
<target>Error creating address</target>
|
||||
@@ -3099,6 +3149,21 @@ This cannot be undone!</target>
|
||||
<target>Forwarded from</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server %@ failed to connect to destination server %@. Please try later." xml:space="preserve">
|
||||
<source>Forwarding server %@ failed to connect to destination server %@. Please try later.</source>
|
||||
<target>Forwarding server %@ failed to connect to destination server %@. Please try later.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server address is incompatible with network settings: %@.</source>
|
||||
<target>Forwarding server address is incompatible with network settings: %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server version is incompatible with network settings: %@.</source>
|
||||
<target>Forwarding server version is incompatible with network settings: %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server: %@ Destination server error: %@" xml:space="preserve">
|
||||
<source>Forwarding server: %1$@
|
||||
Destination server error: %2$@</source>
|
||||
@@ -3918,6 +3983,16 @@ This is your link for group %@!</target>
|
||||
<target>Max 30 seconds, received instantly.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Media & file servers" xml:space="preserve">
|
||||
<source>Media & file servers</source>
|
||||
<target>Media & file servers</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Medium" xml:space="preserve">
|
||||
<source>Medium</source>
|
||||
<target>Medium</target>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member" xml:space="preserve">
|
||||
<source>Member</source>
|
||||
<target>Member</target>
|
||||
@@ -4003,14 +4078,9 @@ This is your link for group %@!</target>
|
||||
<target>Message reception</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing fallback" xml:space="preserve">
|
||||
<source>Message routing fallback</source>
|
||||
<target>Message routing fallback</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing mode" xml:space="preserve">
|
||||
<source>Message routing mode</source>
|
||||
<target>Message routing mode</target>
|
||||
<trans-unit id="Message servers" xml:space="preserve">
|
||||
<source>Message servers</source>
|
||||
<target>Message servers</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message source remains private." xml:space="preserve">
|
||||
@@ -4343,6 +4413,11 @@ This is your link for group %@!</target>
|
||||
<target>Not compatible!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nothing selected" xml:space="preserve">
|
||||
<source>Nothing selected</source>
|
||||
<target>Nothing selected</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Notifications</target>
|
||||
@@ -4370,7 +4445,7 @@ This is your link for group %@!</target>
|
||||
<trans-unit id="Off" xml:space="preserve">
|
||||
<source>Off</source>
|
||||
<target>Off</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
@@ -4392,14 +4467,18 @@ This is your link for group %@!</target>
|
||||
<target>One-time invitation link</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
|
||||
<target>Onion hosts will be required for connection. Requires enabling VPN.</target>
|
||||
<trans-unit id="Onion hosts will be **required** for connection. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be **required** for connection.
|
||||
Requires compatible VPN.</source>
|
||||
<target>Onion hosts will be **required** for connection.
|
||||
Requires compatible VPN.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
|
||||
<target>Onion hosts will be used when available. Requires enabling VPN.</target>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available.
|
||||
Requires compatible VPN.</source>
|
||||
<target>Onion hosts will be used when available.
|
||||
Requires compatible VPN.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
|
||||
@@ -4726,11 +4805,6 @@ Error: %@</target>
|
||||
<target>Please store passphrase securely, you will NOT be able to change it if you lose it.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please try later." xml:space="preserve">
|
||||
<source>Please try later.</source>
|
||||
<target>Please try later.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Polish interface" xml:space="preserve">
|
||||
<source>Polish interface</source>
|
||||
<target>Polish interface</target>
|
||||
@@ -5293,11 +5367,6 @@ Enable in *Network & servers* settings.</target>
|
||||
<target>Reveal</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revert" xml:space="preserve">
|
||||
<source>Revert</source>
|
||||
<target>Revert</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revoke" xml:space="preserve">
|
||||
<source>Revoke</source>
|
||||
<target>Revoke</target>
|
||||
@@ -5328,11 +5397,6 @@ Enable in *Network & servers* settings.</target>
|
||||
<target>SMP server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SMP servers" xml:space="preserve">
|
||||
<source>SMP servers</source>
|
||||
<target>SMP servers</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safely receive files" xml:space="preserve">
|
||||
<source>Safely receive files</source>
|
||||
<target>Safely receive files</target>
|
||||
@@ -5363,6 +5427,11 @@ Enable in *Network & servers* settings.</target>
|
||||
<target>Save and notify group members</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and reconnect" xml:space="preserve">
|
||||
<source>Save and reconnect</source>
|
||||
<target>Save and reconnect</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and update group profile" xml:space="preserve">
|
||||
<source>Save and update group profile</source>
|
||||
<target>Save and update group profile</target>
|
||||
@@ -5521,6 +5590,11 @@ Enable in *Network & servers* settings.</target>
|
||||
<trans-unit id="Select" xml:space="preserve">
|
||||
<source>Select</source>
|
||||
<target>Select</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected %lld" xml:space="preserve">
|
||||
<source>Selected %lld</source>
|
||||
<target>Selected %lld</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
@@ -5773,11 +5847,6 @@ Enable in *Network & servers* settings.</target>
|
||||
<target>Server version is incompatible with network settings.</target>
|
||||
<note>srv error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with network settings: %@.</source>
|
||||
<target>Server version is incompatible with network settings: %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with your app: %@.</source>
|
||||
<target>Server version is incompatible with your app: %@.</target>
|
||||
@@ -5893,6 +5962,11 @@ Enable in *Network & servers* settings.</target>
|
||||
<target>Share this 1-time invite link</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share to SimpleX" xml:space="preserve">
|
||||
<source>Share to SimpleX</source>
|
||||
<target>Share to SimpleX</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share with contacts" xml:space="preserve">
|
||||
<source>Share with contacts</source>
|
||||
<target>Share with contacts</target>
|
||||
@@ -6043,11 +6117,26 @@ Enable in *Network & servers* settings.</target>
|
||||
<target>Small groups (max 20)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Soft" xml:space="preserve">
|
||||
<source>Soft</source>
|
||||
<target>Soft</target>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
|
||||
<source>Some file(s) were not exported:</source>
|
||||
<target>Some file(s) were not exported:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
|
||||
<target>Some non-fatal errors occurred during import - you may see Chat console for more details.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import:</source>
|
||||
<target>Some non-fatal errors occurred during import:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Somebody" xml:space="preserve">
|
||||
<source>Somebody</source>
|
||||
<target>Somebody</target>
|
||||
@@ -6143,6 +6232,11 @@ Enable in *Network & servers* settings.</target>
|
||||
<target>Stopping chat</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Strong" xml:space="preserve">
|
||||
<source>Strong</source>
|
||||
<target>Strong</target>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Submit" xml:space="preserve">
|
||||
<source>Submit</source>
|
||||
<target>Submit</target>
|
||||
@@ -6178,6 +6272,11 @@ Enable in *Network & servers* settings.</target>
|
||||
<target>System authentication</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection" xml:space="preserve">
|
||||
<source>TCP connection</source>
|
||||
<target>TCP connection</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
||||
<source>TCP connection timeout</source>
|
||||
<target>TCP connection timeout</target>
|
||||
@@ -6350,6 +6449,16 @@ It can happen because of some bug or when the connection is compromised.</target
|
||||
<target>The message will be marked as moderated for all members.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
|
||||
<source>The messages will be deleted for all members.</source>
|
||||
<target>The messages will be deleted for all members.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be marked as moderated for all members." xml:space="preserve">
|
||||
<source>The messages will be marked as moderated for all members.</source>
|
||||
<target>The messages will be marked as moderated for all members.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The next generation of private messaging" xml:space="preserve">
|
||||
<source>The next generation of private messaging</source>
|
||||
<target>The next generation of private messaging</target>
|
||||
@@ -6709,11 +6818,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Update</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
|
||||
<source>Update .onion hosts setting?</source>
|
||||
<target>Update .onion hosts setting?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update database passphrase" xml:space="preserve">
|
||||
<source>Update database passphrase</source>
|
||||
<target>Update database passphrase</target>
|
||||
@@ -6724,9 +6828,9 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Update network settings?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
|
||||
<source>Update transport isolation mode?</source>
|
||||
<target>Update transport isolation mode?</target>
|
||||
<trans-unit id="Update settings?" xml:space="preserve">
|
||||
<source>Update settings?</source>
|
||||
<target>Update settings?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
|
||||
@@ -6734,11 +6838,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Updating settings will re-connect the client to all servers.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
|
||||
<source>Updating this setting will re-connect the client to all servers.</source>
|
||||
<target>Updating this setting will re-connect the client to all servers.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Upgrade and open chat" xml:space="preserve">
|
||||
<source>Upgrade and open chat</source>
|
||||
<target>Upgrade and open chat</target>
|
||||
@@ -6849,11 +6948,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>User selection</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
|
||||
<source>Using .onion hosts requires compatible VPN provider.</source>
|
||||
<target>Using .onion hosts requires compatible VPN provider.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
|
||||
<source>Using SimpleX Chat servers.</source>
|
||||
<target>Using SimpleX Chat servers.</target>
|
||||
@@ -7114,11 +7208,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>XFTP server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="XFTP servers" xml:space="preserve">
|
||||
<source>XFTP servers</source>
|
||||
<target>XFTP servers</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You" xml:space="preserve">
|
||||
<source>You</source>
|
||||
<target>You</target>
|
||||
@@ -7333,6 +7422,16 @@ Repeat connection request?</target>
|
||||
<target>You joined this group. Connecting to inviting group member.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may migrate the exported database." xml:space="preserve">
|
||||
<source>You may migrate the exported database.</source>
|
||||
<target>You may migrate the exported database.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may save the exported archive." xml:space="preserve">
|
||||
<source>You may save the exported archive.</source>
|
||||
<target>You may save the exported archive.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="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." xml:space="preserve">
|
||||
<source>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.</source>
|
||||
<target>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.</target>
|
||||
@@ -8313,9 +8412,9 @@ last received msg: %2$@</target>
|
||||
<target>unknown</target>
|
||||
<note>connection info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown relays" xml:space="preserve">
|
||||
<source>unknown relays</source>
|
||||
<target>unknown relays</target>
|
||||
<trans-unit id="unknown servers" xml:space="preserve">
|
||||
<source>unknown servers</source>
|
||||
<target>unknown servers</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown status" xml:space="preserve">
|
||||
@@ -8539,4 +8638,218 @@ last received msg: %2$@</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<target>SimpleX SE</target>
|
||||
<note>Bundle display name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<target>SimpleX SE</target>
|
||||
<note>Bundle name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
|
||||
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
|
||||
<target>Copyright © 2024 SimpleX Chat. All rights reserved.</target>
|
||||
<note>Copyright (human-readable)</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="en" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
<source>%@</source>
|
||||
<target>%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App is locked!" xml:space="preserve">
|
||||
<source>App is locked!</source>
|
||||
<target>App is locked!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cancel" xml:space="preserve">
|
||||
<source>Cancel</source>
|
||||
<target>Cancel</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
|
||||
<source>Cannot access keychain to save database password</source>
|
||||
<target>Cannot access keychain to save database password</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot forward message" xml:space="preserve">
|
||||
<source>Cannot forward message</source>
|
||||
<target>Cannot forward message</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Comment" xml:space="preserve">
|
||||
<source>Comment</source>
|
||||
<target>Comment</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
|
||||
<source>Currently maximum supported file size is %@.</source>
|
||||
<target>Currently maximum supported file size is %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database downgrade required" xml:space="preserve">
|
||||
<source>Database downgrade required</source>
|
||||
<target>Database downgrade required</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database encrypted!" xml:space="preserve">
|
||||
<source>Database encrypted!</source>
|
||||
<target>Database encrypted!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database error" xml:space="preserve">
|
||||
<source>Database error</source>
|
||||
<target>Database error</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is different from saved in the keychain." xml:space="preserve">
|
||||
<source>Database passphrase is different from saved in the keychain.</source>
|
||||
<target>Database passphrase is different from saved in the keychain.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
|
||||
<source>Database passphrase is required to open chat.</source>
|
||||
<target>Database passphrase is required to open chat.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database upgrade required" xml:space="preserve">
|
||||
<source>Database upgrade required</source>
|
||||
<target>Database upgrade required</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing file" xml:space="preserve">
|
||||
<source>Error preparing file</source>
|
||||
<target>Error preparing file</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing message" xml:space="preserve">
|
||||
<source>Error preparing message</source>
|
||||
<target>Error preparing message</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error: %@" xml:space="preserve">
|
||||
<source>Error: %@</source>
|
||||
<target>Error: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="File error" xml:space="preserve">
|
||||
<source>File error</source>
|
||||
<target>File error</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible database version" xml:space="preserve">
|
||||
<source>Incompatible database version</source>
|
||||
<target>Incompatible database version</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
|
||||
<source>Invalid migration confirmation</source>
|
||||
<target>Invalid migration confirmation</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keychain error" xml:space="preserve">
|
||||
<source>Keychain error</source>
|
||||
<target>Keychain error</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Large file!" xml:space="preserve">
|
||||
<source>Large file!</source>
|
||||
<target>Large file!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No active profile" xml:space="preserve">
|
||||
<source>No active profile</source>
|
||||
<target>No active profile</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
<target>Ok</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
|
||||
<source>Open the app to downgrade the database.</source>
|
||||
<target>Open the app to downgrade the database.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
|
||||
<source>Open the app to upgrade the database.</source>
|
||||
<target>Open the app to upgrade the database.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Passphrase" xml:space="preserve">
|
||||
<source>Passphrase</source>
|
||||
<target>Passphrase</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
|
||||
<source>Please create a profile in the SimpleX app</source>
|
||||
<target>Please create a profile in the SimpleX app</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
<source>Selected chat preferences prohibit this message.</source>
|
||||
<target>Selected chat preferences prohibit this message.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
|
||||
<source>Sending a message takes longer than expected.</source>
|
||||
<target>Sending a message takes longer than expected.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending message…" xml:space="preserve">
|
||||
<source>Sending message…</source>
|
||||
<target>Sending message…</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share" xml:space="preserve">
|
||||
<source>Share</source>
|
||||
<target>Share</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Slow network?" xml:space="preserve">
|
||||
<source>Slow network?</source>
|
||||
<target>Slow network?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unknown database error: %@" xml:space="preserve">
|
||||
<source>Unknown database error: %@</source>
|
||||
<target>Unknown database error: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported format" xml:space="preserve">
|
||||
<source>Unsupported format</source>
|
||||
<target>Unsupported format</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wait" xml:space="preserve">
|
||||
<source>Wait</source>
|
||||
<target>Wait</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<target>Wrong database passphrase</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can allow sharing in Privacy & Security / SimpleX Lock settings." xml:space="preserve">
|
||||
<source>You can allow sharing in Privacy & Security / SimpleX Lock settings.</source>
|
||||
<target>You can allow sharing in Privacy & Security / SimpleX Lock settings.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
+3
@@ -1,6 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
File diff suppressed because it is too large
Load Diff
+3
@@ -1,6 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
@@ -744,6 +744,10 @@
|
||||
<target>Salli katoavien viestien lähettäminen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow sharing" xml:space="preserve">
|
||||
<source>Allow sharing</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Salli lähetettyjen viestien peruuttamaton poistaminen. (24 tuntia)</target>
|
||||
@@ -1013,6 +1017,10 @@
|
||||
<source>Blocked by admin</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blur media" xml:space="preserve">
|
||||
<source>Blur media</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
|
||||
<source>Both you and your contact can add message reactions.</source>
|
||||
<target>Sekä sinä että kontaktisi voivat käyttää viestireaktioita.</target>
|
||||
@@ -1177,6 +1185,10 @@
|
||||
<target>Chat-tietokanta poistettu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database exported" xml:space="preserve">
|
||||
<source>Chat database exported</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database imported" xml:space="preserve">
|
||||
<source>Chat database imported</source>
|
||||
<target>Chat-tietokanta tuotu</target>
|
||||
@@ -1441,6 +1453,10 @@ This is your own one-time link!</source>
|
||||
<target>Yhteysvirhe (AUTH)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection notifications" xml:space="preserve">
|
||||
<source>Connection notifications</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection request sent!" xml:space="preserve">
|
||||
<source>Connection request sent!</source>
|
||||
<target>Yhteyspyyntö lähetetty!</target>
|
||||
@@ -1772,6 +1788,10 @@ This is your own one-time link!</source>
|
||||
<target>Poista</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
|
||||
<source>Delete %lld messages of members?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages?" xml:space="preserve">
|
||||
<source>Delete %lld messages?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -2004,10 +2024,18 @@ This cannot be undone!</source>
|
||||
<source>Desktop devices</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server address of %@ is incompatible with forwarding server %@ settings." xml:space="preserve">
|
||||
<source>Destination server address of %@ is incompatible with forwarding server %@ settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server error: %@" xml:space="preserve">
|
||||
<source>Destination server error: %@</source>
|
||||
<note>snd error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server version of %@ is incompatible with forwarding server %@." xml:space="preserve">
|
||||
<source>Destination server version of %@ is incompatible with forwarding server %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Detailed statistics" xml:space="preserve">
|
||||
<source>Detailed statistics</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -2071,6 +2099,10 @@ This cannot be undone!</source>
|
||||
<target>Poista käytöstä kaikilta</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disabled" xml:space="preserve">
|
||||
<source>Disabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing message" xml:space="preserve">
|
||||
<source>Disappearing message</source>
|
||||
<target>Tuhoutuva viesti</target>
|
||||
@@ -2282,6 +2314,10 @@ This cannot be undone!</source>
|
||||
<target>Ota itsetuhoava pääsykoodi käyttöön</target>
|
||||
<note>set passcode view</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled" xml:space="preserve">
|
||||
<source>Enabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled for" xml:space="preserve">
|
||||
<source>Enabled for</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -2443,6 +2479,10 @@ This cannot be undone!</source>
|
||||
<target>Virhe asetuksen muuttamisessa</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
|
||||
<source>Error connecting to forwarding server %@. Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating address" xml:space="preserve">
|
||||
<source>Error creating address</source>
|
||||
<target>Virhe osoitteen luomisessa</target>
|
||||
@@ -2918,6 +2958,18 @@ This cannot be undone!</source>
|
||||
<source>Forwarded from</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server %@ failed to connect to destination server %@. Please try later." xml:space="preserve">
|
||||
<source>Forwarding server %@ failed to connect to destination server %@. Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server address is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server version is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server: %@ Destination server error: %@" xml:space="preserve">
|
||||
<source>Forwarding server: %1$@
|
||||
Destination server error: %2$@</source>
|
||||
@@ -3698,6 +3750,14 @@ This is your link for group %@!</source>
|
||||
<target>Enintään 30 sekuntia, vastaanotetaan välittömästi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Media & file servers" xml:space="preserve">
|
||||
<source>Media & file servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Medium" xml:space="preserve">
|
||||
<source>Medium</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member" xml:space="preserve">
|
||||
<source>Member</source>
|
||||
<target>Jäsen</target>
|
||||
@@ -3776,12 +3836,8 @@ This is your link for group %@!</source>
|
||||
<source>Message reception</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing fallback" xml:space="preserve">
|
||||
<source>Message routing fallback</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing mode" xml:space="preserve">
|
||||
<source>Message routing mode</source>
|
||||
<trans-unit id="Message servers" xml:space="preserve">
|
||||
<source>Message servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message source remains private." xml:space="preserve">
|
||||
@@ -4088,6 +4144,10 @@ This is your link for group %@!</source>
|
||||
<source>Not compatible!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nothing selected" xml:space="preserve">
|
||||
<source>Nothing selected</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Ilmoitukset</target>
|
||||
@@ -4114,7 +4174,7 @@ This is your link for group %@!</source>
|
||||
<trans-unit id="Off" xml:space="preserve">
|
||||
<source>Off</source>
|
||||
<target>Pois</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
@@ -4136,14 +4196,18 @@ This is your link for group %@!</source>
|
||||
<target>Kertakutsulinkki</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
|
||||
<target>Yhteyden muodostamiseen tarvitaan Onion-isäntiä. Edellyttää VPN:n sallimista.</target>
|
||||
<trans-unit id="Onion hosts will be **required** for connection. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be **required** for connection.
|
||||
Requires compatible VPN.</source>
|
||||
<target>Yhteyden muodostamiseen tarvitaan Onion-isäntiä.
|
||||
Edellyttää VPN:n sallimista.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
|
||||
<target>Onion-isäntiä käytetään, kun niitä on saatavilla. Edellyttää VPN:n sallimista.</target>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available.
|
||||
Requires compatible VPN.</source>
|
||||
<target>Onion-isäntiä käytetään, kun niitä on saatavilla.
|
||||
Edellyttää VPN:n sallimista.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
|
||||
@@ -4448,10 +4512,6 @@ Error: %@</source>
|
||||
<target>Säilytä tunnuslause turvallisesti, ET voi muuttaa sitä, jos kadotat sen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please try later." xml:space="preserve">
|
||||
<source>Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Polish interface" xml:space="preserve">
|
||||
<source>Polish interface</source>
|
||||
<target>Puolalainen käyttöliittymä</target>
|
||||
@@ -4973,11 +5033,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Paljasta</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revert" xml:space="preserve">
|
||||
<source>Revert</source>
|
||||
<target>Palauta</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revoke" xml:space="preserve">
|
||||
<source>Revoke</source>
|
||||
<target>Peruuta</target>
|
||||
@@ -5007,11 +5062,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>SMP server</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SMP servers" xml:space="preserve">
|
||||
<source>SMP servers</source>
|
||||
<target>SMP-palvelimet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safely receive files" xml:space="preserve">
|
||||
<source>Safely receive files</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5040,6 +5090,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Tallenna ja ilmoita ryhmän jäsenille</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and reconnect" xml:space="preserve">
|
||||
<source>Save and reconnect</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and update group profile" xml:space="preserve">
|
||||
<source>Save and update group profile</source>
|
||||
<target>Tallenna ja päivitä ryhmäprofiili</target>
|
||||
@@ -5188,6 +5242,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<trans-unit id="Select" xml:space="preserve">
|
||||
<source>Select</source>
|
||||
<target>Valitse</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected %lld" xml:space="preserve">
|
||||
<source>Selected %lld</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
@@ -5424,10 +5482,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>Server version is incompatible with network settings.</source>
|
||||
<note>srv error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with your app: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5535,6 +5589,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>Share this 1-time invite link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share to SimpleX" xml:space="preserve">
|
||||
<source>Share to SimpleX</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share with contacts" xml:space="preserve">
|
||||
<source>Share with contacts</source>
|
||||
<target>Jaa kontaktien kanssa</target>
|
||||
@@ -5676,11 +5734,23 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Pienryhmät (max 20)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Soft" xml:space="preserve">
|
||||
<source>Soft</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
|
||||
<source>Some file(s) were not exported:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
|
||||
<target>Tuonnin aikana tapahtui joitakin ei-vakavia virheitä – saatat nähdä Chat-konsolissa lisätietoja.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Somebody" xml:space="preserve">
|
||||
<source>Somebody</source>
|
||||
<target>Joku</target>
|
||||
@@ -5770,6 +5840,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>Stopping chat</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Strong" xml:space="preserve">
|
||||
<source>Strong</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Submit" xml:space="preserve">
|
||||
<source>Submit</source>
|
||||
<target>Lähetä</target>
|
||||
@@ -5802,6 +5876,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>Järjestelmän todennus</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection" xml:space="preserve">
|
||||
<source>TCP connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
||||
<source>TCP connection timeout</source>
|
||||
<target>TCP-yhteyden aikakatkaisu</target>
|
||||
@@ -5968,6 +6046,14 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.</t
|
||||
<target>Viesti merkitään moderoiduksi kaikille jäsenille.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
|
||||
<source>The messages will be deleted for all members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be marked as moderated for all members." xml:space="preserve">
|
||||
<source>The messages will be marked as moderated for all members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The next generation of private messaging" xml:space="preserve">
|
||||
<source>The next generation of private messaging</source>
|
||||
<target>Seuraavan sukupolven yksityisviestit</target>
|
||||
@@ -6302,11 +6388,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Päivitä</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
|
||||
<source>Update .onion hosts setting?</source>
|
||||
<target>Päivitä .onion-isäntien asetus?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update database passphrase" xml:space="preserve">
|
||||
<source>Update database passphrase</source>
|
||||
<target>Päivitä tietokannan tunnuslause</target>
|
||||
@@ -6317,9 +6398,8 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Päivitä verkkoasetukset?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
|
||||
<source>Update transport isolation mode?</source>
|
||||
<target>Päivitä kuljetuksen eristystila?</target>
|
||||
<trans-unit id="Update settings?" xml:space="preserve">
|
||||
<source>Update settings?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
|
||||
@@ -6327,11 +6407,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Asetusten päivittäminen yhdistää asiakkaan uudelleen kaikkiin palvelimiin.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
|
||||
<source>Updating this setting will re-connect the client to all servers.</source>
|
||||
<target>Tämän asetuksen päivittäminen yhdistää asiakkaan uudelleen kaikkiin palvelimiin.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Upgrade and open chat" xml:space="preserve">
|
||||
<source>Upgrade and open chat</source>
|
||||
<target>Päivitä ja avaa keskustelu</target>
|
||||
@@ -6431,11 +6506,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<source>User selection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
|
||||
<source>Using .onion hosts requires compatible VPN provider.</source>
|
||||
<target>.onion-isäntien käyttäminen vaatii yhteensopivan VPN-palveluntarjoajan.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
|
||||
<source>Using SimpleX Chat servers.</source>
|
||||
<target>Käyttää SimpleX Chat -palvelimia.</target>
|
||||
@@ -6672,11 +6742,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<source>XFTP server</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="XFTP servers" xml:space="preserve">
|
||||
<source>XFTP servers</source>
|
||||
<target>XFTP-palvelimet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You" xml:space="preserve">
|
||||
<source>You</source>
|
||||
<target>Sinä</target>
|
||||
@@ -6875,6 +6940,14 @@ Repeat connection request?</source>
|
||||
<target>Liityit tähän ryhmään. Muodostetaan yhteyttä ryhmän jäsenten kutsumiseksi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may migrate the exported database." xml:space="preserve">
|
||||
<source>You may migrate the exported database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may save the exported archive." xml:space="preserve">
|
||||
<source>You may save the exported archive.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="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." xml:space="preserve">
|
||||
<source>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.</source>
|
||||
<target>Sinun tulee käyttää keskustelujen-tietokannan uusinta versiota AINOSTAAN yhdessä laitteessa, muuten saatat lakata vastaanottamasta viestejä joiltakin kontakteilta.</target>
|
||||
@@ -7819,8 +7892,8 @@ last received msg: %2$@</source>
|
||||
<target>tuntematon</target>
|
||||
<note>connection info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown relays" xml:space="preserve">
|
||||
<source>unknown relays</source>
|
||||
<trans-unit id="unknown servers" xml:space="preserve">
|
||||
<source>unknown servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown status" xml:space="preserve">
|
||||
@@ -8035,4 +8108,178 @@ last received msg: %2$@</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="fi" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<note>Bundle display name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<note>Bundle name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
|
||||
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
|
||||
<note>Copyright (human-readable)</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="fi" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
<source>%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App is locked!" xml:space="preserve">
|
||||
<source>App is locked!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cancel" xml:space="preserve">
|
||||
<source>Cancel</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
|
||||
<source>Cannot access keychain to save database password</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot forward message" xml:space="preserve">
|
||||
<source>Cannot forward message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Comment" xml:space="preserve">
|
||||
<source>Comment</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
|
||||
<source>Currently maximum supported file size is %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database downgrade required" xml:space="preserve">
|
||||
<source>Database downgrade required</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database encrypted!" xml:space="preserve">
|
||||
<source>Database encrypted!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database error" xml:space="preserve">
|
||||
<source>Database error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is different from saved in the keychain." xml:space="preserve">
|
||||
<source>Database passphrase is different from saved in the keychain.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
|
||||
<source>Database passphrase is required to open chat.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database upgrade required" xml:space="preserve">
|
||||
<source>Database upgrade required</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing file" xml:space="preserve">
|
||||
<source>Error preparing file</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing message" xml:space="preserve">
|
||||
<source>Error preparing message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error: %@" xml:space="preserve">
|
||||
<source>Error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="File error" xml:space="preserve">
|
||||
<source>File error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible database version" xml:space="preserve">
|
||||
<source>Incompatible database version</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
|
||||
<source>Invalid migration confirmation</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keychain error" xml:space="preserve">
|
||||
<source>Keychain error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Large file!" xml:space="preserve">
|
||||
<source>Large file!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No active profile" xml:space="preserve">
|
||||
<source>No active profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
|
||||
<source>Open the app to downgrade the database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
|
||||
<source>Open the app to upgrade the database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Passphrase" xml:space="preserve">
|
||||
<source>Passphrase</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
|
||||
<source>Please create a profile in the SimpleX app</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
<source>Selected chat preferences prohibit this message.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
|
||||
<source>Sending a message takes longer than expected.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending message…" xml:space="preserve">
|
||||
<source>Sending message…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share" xml:space="preserve">
|
||||
<source>Share</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Slow network?" xml:space="preserve">
|
||||
<source>Slow network?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unknown database error: %@" xml:space="preserve">
|
||||
<source>Unknown database error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported format" xml:space="preserve">
|
||||
<source>Unsupported format</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wait" xml:space="preserve">
|
||||
<source>Wait</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can allow sharing in Privacy & Security / SimpleX Lock settings." xml:space="preserve">
|
||||
<source>You can allow sharing in Privacy & Security / SimpleX Lock settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
+3
@@ -1,6 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
File diff suppressed because it is too large
Load Diff
+3
@@ -1,6 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
@@ -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
+3
@@ -1,6 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
@@ -590,6 +590,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Active connections" xml:space="preserve">
|
||||
<source>Active connections</source>
|
||||
<target>Connessioni attive</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." xml:space="preserve">
|
||||
@@ -782,6 +783,11 @@
|
||||
<target>Permetti l'invio di messaggi a tempo.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow sharing" xml:space="preserve">
|
||||
<source>Allow sharing</source>
|
||||
<target>Consenti la condivisione</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Permetti di eliminare irreversibilmente i messaggi inviati. (24 ore)</target>
|
||||
@@ -1072,6 +1078,11 @@
|
||||
<target>Bloccato dall'amministratore</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blur media" xml:space="preserve">
|
||||
<source>Blur media</source>
|
||||
<target>Sfocatura file multimediali</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
|
||||
<source>Both you and your contact can add message reactions.</source>
|
||||
<target>Sia tu che il tuo contatto potete aggiungere reazioni ai messaggi.</target>
|
||||
@@ -1243,6 +1254,10 @@
|
||||
<target>Database della chat eliminato</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database exported" xml:space="preserve">
|
||||
<source>Chat database exported</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database imported" xml:space="preserve">
|
||||
<source>Chat database imported</source>
|
||||
<target>Database della chat importato</target>
|
||||
@@ -1375,6 +1390,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Configured %@ servers" xml:space="preserve">
|
||||
<source>Configured %@ servers</source>
|
||||
<target>Configurati %@ server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Confirm" xml:space="preserve">
|
||||
@@ -1536,6 +1552,11 @@ Questo è il tuo link una tantum!</target>
|
||||
<target>Errore di connessione (AUTH)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection notifications" xml:space="preserve">
|
||||
<source>Connection notifications</source>
|
||||
<target>Notifiche di connessione</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection request sent!" xml:space="preserve">
|
||||
<source>Connection request sent!</source>
|
||||
<target>Richiesta di connessione inviata!</target>
|
||||
@@ -1884,6 +1905,11 @@ Questo è il tuo link una tantum!</target>
|
||||
<target>Elimina</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
|
||||
<source>Delete %lld messages of members?</source>
|
||||
<target>Eliminare %lld messaggi dei membri?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages?" xml:space="preserve">
|
||||
<source>Delete %lld messages?</source>
|
||||
<target>Eliminare %lld messaggi?</target>
|
||||
@@ -2126,11 +2152,21 @@ Non è reversibile!</target>
|
||||
<target>Dispositivi desktop</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server address of %@ is incompatible with forwarding server %@ settings." xml:space="preserve">
|
||||
<source>Destination server address of %@ is incompatible with forwarding server %@ settings.</source>
|
||||
<target>L'indirizzo del server di destinazione di %@ è incompatibile con le impostazioni del server di inoltro %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server error: %@" xml:space="preserve">
|
||||
<source>Destination server error: %@</source>
|
||||
<target>Errore del server di destinazione: %@</target>
|
||||
<note>snd error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server version of %@ is incompatible with forwarding server %@." xml:space="preserve">
|
||||
<source>Destination server version of %@ is incompatible with forwarding server %@.</source>
|
||||
<target>La versione del server di destinazione di %@ è incompatibile con il server di inoltro %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Detailed statistics" xml:space="preserve">
|
||||
<source>Detailed statistics</source>
|
||||
<target>Statistiche dettagliate</target>
|
||||
@@ -2196,6 +2232,11 @@ Non è reversibile!</target>
|
||||
<target>Disattiva per tutti</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disabled" xml:space="preserve">
|
||||
<source>Disabled</source>
|
||||
<target>Disattivato</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing message" xml:space="preserve">
|
||||
<source>Disappearing message</source>
|
||||
<target>Messaggio a tempo</target>
|
||||
@@ -2421,6 +2462,11 @@ Non è reversibile!</target>
|
||||
<target>Attiva il codice di autodistruzione</target>
|
||||
<note>set passcode view</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled" xml:space="preserve">
|
||||
<source>Enabled</source>
|
||||
<target>Attivato</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled for" xml:space="preserve">
|
||||
<source>Enabled for</source>
|
||||
<target>Attivo per</target>
|
||||
@@ -2591,6 +2637,11 @@ Non è reversibile!</target>
|
||||
<target>Errore nella modifica dell'impostazione</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
|
||||
<source>Error connecting to forwarding server %@. Please try later.</source>
|
||||
<target>Errore di connessione al server di inoltro %@. Riprova più tardi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating address" xml:space="preserve">
|
||||
<source>Error creating address</source>
|
||||
<target>Errore nella creazione dell'indirizzo</target>
|
||||
@@ -3097,6 +3148,21 @@ Non è reversibile!</target>
|
||||
<target>Inoltrato da</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server %@ failed to connect to destination server %@. Please try later." xml:space="preserve">
|
||||
<source>Forwarding server %@ failed to connect to destination server %@. Please try later.</source>
|
||||
<target>Il server di inoltro %@ non è riuscito a connettersi al server di destinazione %@. Riprova più tardi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server address is incompatible with network settings: %@.</source>
|
||||
<target>L'indirizzo del server di inoltro è incompatibile con le impostazioni di rete: %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server version is incompatible with network settings: %@.</source>
|
||||
<target>La versione del server di inoltro è incompatibile con le impostazioni di rete: %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server: %@ Destination server error: %@" xml:space="preserve">
|
||||
<source>Forwarding server: %1$@
|
||||
Destination server error: %2$@</source>
|
||||
@@ -3916,6 +3982,15 @@ Questo è il tuo link per il gruppo %@!</target>
|
||||
<target>Max 30 secondi, ricevuto istantaneamente.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Media & file servers" xml:space="preserve">
|
||||
<source>Media & file servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Medium" xml:space="preserve">
|
||||
<source>Medium</source>
|
||||
<target>Media</target>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member" xml:space="preserve">
|
||||
<source>Member</source>
|
||||
<target>Membro</target>
|
||||
@@ -3998,16 +4073,11 @@ Questo è il tuo link per il gruppo %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message reception" xml:space="preserve">
|
||||
<source>Message reception</source>
|
||||
<target>Ricezione messaggi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing fallback" xml:space="preserve">
|
||||
<source>Message routing fallback</source>
|
||||
<target>Ripiego instradamento messaggio</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing mode" xml:space="preserve">
|
||||
<source>Message routing mode</source>
|
||||
<target>Modalità instradamento messaggio</target>
|
||||
<trans-unit id="Message servers" xml:space="preserve">
|
||||
<source>Message servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message source remains private." xml:space="preserve">
|
||||
@@ -4340,6 +4410,11 @@ Questo è il tuo link per il gruppo %@!</target>
|
||||
<target>Non compatibile!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nothing selected" xml:space="preserve">
|
||||
<source>Nothing selected</source>
|
||||
<target>Nessuna selezione</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Notifiche</target>
|
||||
@@ -4367,7 +4442,7 @@ Questo è il tuo link per il gruppo %@!</target>
|
||||
<trans-unit id="Off" xml:space="preserve">
|
||||
<source>Off</source>
|
||||
<target>Off</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
@@ -4389,14 +4464,18 @@ Questo è il tuo link per il gruppo %@!</target>
|
||||
<target>Link di invito una tantum</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
|
||||
<target>Gli host Onion saranno necessari per la connessione. Richiede l'attivazione della VPN.</target>
|
||||
<trans-unit id="Onion hosts will be **required** for connection. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be **required** for connection.
|
||||
Requires compatible VPN.</source>
|
||||
<target>Gli host Onion saranno **necessari** per la connessione.
|
||||
Richiede l'attivazione della VPN.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
|
||||
<target>Gli host Onion verranno usati quando disponibili. Richiede l'attivazione della VPN.</target>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available.
|
||||
Requires compatible VPN.</source>
|
||||
<target>Gli host Onion verranno usati quando disponibili.
|
||||
Richiede l'attivazione della VPN.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
|
||||
@@ -4551,6 +4630,7 @@ Questo è il tuo link per il gruppo %@!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Other %@ servers" xml:space="preserve">
|
||||
<source>Other %@ servers</source>
|
||||
<target>Altri %@ server</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
@@ -4722,10 +4802,6 @@ Errore: %@</target>
|
||||
<target>Conserva la password in modo sicuro, NON potrai cambiarla se la perdi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please try later." xml:space="preserve">
|
||||
<source>Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Polish interface" xml:space="preserve">
|
||||
<source>Polish interface</source>
|
||||
<target>Interfaccia polacca</target>
|
||||
@@ -4798,6 +4874,7 @@ Errore: %@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Private routing error" xml:space="preserve">
|
||||
<source>Private routing error</source>
|
||||
<target>Errore di instradamento privato</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile and server connections" xml:space="preserve">
|
||||
@@ -5287,11 +5364,6 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
<target>Rivela</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revert" xml:space="preserve">
|
||||
<source>Revert</source>
|
||||
<target>Ripristina</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revoke" xml:space="preserve">
|
||||
<source>Revoke</source>
|
||||
<target>Revoca</target>
|
||||
@@ -5322,11 +5394,6 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
<target>Server SMP</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SMP servers" xml:space="preserve">
|
||||
<source>SMP servers</source>
|
||||
<target>Server SMP</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safely receive files" xml:space="preserve">
|
||||
<source>Safely receive files</source>
|
||||
<target>Ricevi i file in sicurezza</target>
|
||||
@@ -5357,6 +5424,10 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
<target>Salva e avvisa i membri del gruppo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and reconnect" xml:space="preserve">
|
||||
<source>Save and reconnect</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and update group profile" xml:space="preserve">
|
||||
<source>Save and update group profile</source>
|
||||
<target>Salva e aggiorna il profilo del gruppo</target>
|
||||
@@ -5515,6 +5586,11 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
<trans-unit id="Select" xml:space="preserve">
|
||||
<source>Select</source>
|
||||
<target>Seleziona</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected %lld" xml:space="preserve">
|
||||
<source>Selected %lld</source>
|
||||
<target>%lld selezionato</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
@@ -5739,6 +5815,7 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server address is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Server address is incompatible with network settings: %@.</source>
|
||||
<target>L'indirizzo del server è incompatibile con le impostazioni di rete: %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server requires authorization to create queues, check password" xml:space="preserve">
|
||||
@@ -5766,12 +5843,9 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
<target>La versione del server non è compatibile con le impostazioni di rete.</target>
|
||||
<note>srv error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with your app: %@.</source>
|
||||
<target>La versione del server è incompatibile con la tua app: %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Servers" xml:space="preserve">
|
||||
@@ -5884,6 +5958,11 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
<target>Condividi questo link di invito una tantum</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share to SimpleX" xml:space="preserve">
|
||||
<source>Share to SimpleX</source>
|
||||
<target>Condividi in SimpleX</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share with contacts" xml:space="preserve">
|
||||
<source>Share with contacts</source>
|
||||
<target>Condividi con i contatti</target>
|
||||
@@ -5916,6 +5995,7 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show percentage" xml:space="preserve">
|
||||
<source>Show percentage</source>
|
||||
<target>Mostra percentuale</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show preview" xml:space="preserve">
|
||||
@@ -6033,11 +6113,24 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
<target>Piccoli gruppi (max 20)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Soft" xml:space="preserve">
|
||||
<source>Soft</source>
|
||||
<target>Leggera</target>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
|
||||
<source>Some file(s) were not exported:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
|
||||
<target>Si sono verificati alcuni errori non gravi durante l'importazione: vedi la console della chat per i dettagli.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Somebody" xml:space="preserve">
|
||||
<source>Somebody</source>
|
||||
<target>Qualcuno</target>
|
||||
@@ -6133,6 +6226,11 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
<target>Arresto della chat</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Strong" xml:space="preserve">
|
||||
<source>Strong</source>
|
||||
<target>Forte</target>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Submit" xml:space="preserve">
|
||||
<source>Submit</source>
|
||||
<target>Invia</target>
|
||||
@@ -6168,6 +6266,10 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
<target>Autenticazione di sistema</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection" xml:space="preserve">
|
||||
<source>TCP connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
||||
<source>TCP connection timeout</source>
|
||||
<target>Scadenza connessione TCP</target>
|
||||
@@ -6340,6 +6442,16 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.</ta
|
||||
<target>Il messaggio sarà segnato come moderato per tutti i membri.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
|
||||
<source>The messages will be deleted for all members.</source>
|
||||
<target>I messaggi verranno eliminati per tutti i membri.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be marked as moderated for all members." xml:space="preserve">
|
||||
<source>The messages will be marked as moderated for all members.</source>
|
||||
<target>I messaggi verranno contrassegnati come moderati per tutti i membri.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The next generation of private messaging" xml:space="preserve">
|
||||
<source>The next generation of private messaging</source>
|
||||
<target>La nuova generazione di messaggistica privata</target>
|
||||
@@ -6699,11 +6811,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
|
||||
<target>Aggiorna</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
|
||||
<source>Update .onion hosts setting?</source>
|
||||
<target>Aggiornare l'impostazione degli host .onion?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update database passphrase" xml:space="preserve">
|
||||
<source>Update database passphrase</source>
|
||||
<target>Aggiorna la password del database</target>
|
||||
@@ -6714,9 +6821,8 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
|
||||
<target>Aggiornare le impostazioni di rete?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
|
||||
<source>Update transport isolation mode?</source>
|
||||
<target>Aggiornare la modalità di isolamento del trasporto?</target>
|
||||
<trans-unit id="Update settings?" xml:space="preserve">
|
||||
<source>Update settings?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
|
||||
@@ -6724,11 +6830,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
|
||||
<target>L'aggiornamento delle impostazioni riconnetterà il client a tutti i server.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
|
||||
<source>Updating this setting will re-connect the client to all servers.</source>
|
||||
<target>L'aggiornamento di questa impostazione riconnetterà il client a tutti i server.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Upgrade and open chat" xml:space="preserve">
|
||||
<source>Upgrade and open chat</source>
|
||||
<target>Aggiorna e apri chat</target>
|
||||
@@ -6839,11 +6940,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
|
||||
<target>Selezione utente</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
|
||||
<source>Using .onion hosts requires compatible VPN provider.</source>
|
||||
<target>L'uso di host .onion richiede un fornitore di VPN compatibile.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
|
||||
<source>Using SimpleX Chat servers.</source>
|
||||
<target>Utilizzo dei server SimpleX Chat.</target>
|
||||
@@ -7104,11 +7200,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
|
||||
<target>Server XFTP</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="XFTP servers" xml:space="preserve">
|
||||
<source>XFTP servers</source>
|
||||
<target>Server XFTP</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You" xml:space="preserve">
|
||||
<source>You</source>
|
||||
<target>Tu</target>
|
||||
@@ -7323,6 +7414,14 @@ Ripetere la richiesta di connessione?</target>
|
||||
<target>Sei entrato/a in questo gruppo. Connessione al membro del gruppo invitante.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may migrate the exported database." xml:space="preserve">
|
||||
<source>You may migrate the exported database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may save the exported archive." xml:space="preserve">
|
||||
<source>You may save the exported archive.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="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." xml:space="preserve">
|
||||
<source>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.</source>
|
||||
<target>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.</target>
|
||||
@@ -8303,8 +8402,8 @@ ultimo msg ricevuto: %2$@</target>
|
||||
<target>sconosciuto</target>
|
||||
<note>connection info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown relays" xml:space="preserve">
|
||||
<source>unknown relays</source>
|
||||
<trans-unit id="unknown servers" xml:space="preserve">
|
||||
<source>unknown servers</source>
|
||||
<target>relay sconosciuti</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -8529,4 +8628,214 @@ ultimo msg ricevuto: %2$@</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="it" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<target>SimpleX SE</target>
|
||||
<note>Bundle display name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<target>SimpleX SE</target>
|
||||
<note>Bundle name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
|
||||
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
|
||||
<target>Copyright © 2024 SimpleX Chat. Tutti i diritti riservati.</target>
|
||||
<note>Copyright (human-readable)</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="it" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
<source>%@</source>
|
||||
<target>%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App is locked!" xml:space="preserve">
|
||||
<source>App is locked!</source>
|
||||
<target>L'app è bloccata!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cancel" xml:space="preserve">
|
||||
<source>Cancel</source>
|
||||
<target>Annulla</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
|
||||
<source>Cannot access keychain to save database password</source>
|
||||
<target>Impossibile accedere al portachiavi per salvare la password del database</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot forward message" xml:space="preserve">
|
||||
<source>Cannot forward message</source>
|
||||
<target>Impossibile inoltrare il messaggio</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Comment" xml:space="preserve">
|
||||
<source>Comment</source>
|
||||
<target>Commento</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
|
||||
<source>Currently maximum supported file size is %@.</source>
|
||||
<target>Attualmente la dimensione massima supportata è di %@.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database downgrade required" xml:space="preserve">
|
||||
<source>Database downgrade required</source>
|
||||
<target>Downgrade del database necessario</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database encrypted!" xml:space="preserve">
|
||||
<source>Database encrypted!</source>
|
||||
<target>Database crittografato!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database error" xml:space="preserve">
|
||||
<source>Database error</source>
|
||||
<target>Errore del database</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is different from saved in the keychain." xml:space="preserve">
|
||||
<source>Database passphrase is different from saved in the keychain.</source>
|
||||
<target>La password del database è diversa da quella salvata nel portachiavi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
|
||||
<source>Database passphrase is required to open chat.</source>
|
||||
<target>La password del database è necessaria per aprire la chat.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database upgrade required" xml:space="preserve">
|
||||
<source>Database upgrade required</source>
|
||||
<target>Aggiornamento del database necessario</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing file" xml:space="preserve">
|
||||
<source>Error preparing file</source>
|
||||
<target>Errore nella preparazione del file</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing message" xml:space="preserve">
|
||||
<source>Error preparing message</source>
|
||||
<target>Errore nella preparazione del messaggio</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error: %@" xml:space="preserve">
|
||||
<source>Error: %@</source>
|
||||
<target>Errore: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="File error" xml:space="preserve">
|
||||
<source>File error</source>
|
||||
<target>Errore del file</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible database version" xml:space="preserve">
|
||||
<source>Incompatible database version</source>
|
||||
<target>Versione del database incompatibile</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
|
||||
<source>Invalid migration confirmation</source>
|
||||
<target>Conferma di migrazione non valida</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keychain error" xml:space="preserve">
|
||||
<source>Keychain error</source>
|
||||
<target>Errore del portachiavi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Large file!" xml:space="preserve">
|
||||
<source>Large file!</source>
|
||||
<target>File grande!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No active profile" xml:space="preserve">
|
||||
<source>No active profile</source>
|
||||
<target>Nessun profilo attivo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
<target>Ok</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
|
||||
<source>Open the app to downgrade the database.</source>
|
||||
<target>Apri l'app per eseguire il downgrade del database.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
|
||||
<source>Open the app to upgrade the database.</source>
|
||||
<target>Apri l'app per aggiornare il database.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Passphrase" xml:space="preserve">
|
||||
<source>Passphrase</source>
|
||||
<target>Password</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
|
||||
<source>Please create a profile in the SimpleX app</source>
|
||||
<target>Crea un profilo nell'app SimpleX</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
<source>Selected chat preferences prohibit this message.</source>
|
||||
<target>Le preferenze della chat selezionata vietano questo messaggio.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
|
||||
<source>Sending a message takes longer than expected.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending message…" xml:space="preserve">
|
||||
<source>Sending message…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share" xml:space="preserve">
|
||||
<source>Share</source>
|
||||
<target>Condividi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Slow network?" xml:space="preserve">
|
||||
<source>Slow network?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unknown database error: %@" xml:space="preserve">
|
||||
<source>Unknown database error: %@</source>
|
||||
<target>Errore del database sconosciuto: %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported format" xml:space="preserve">
|
||||
<source>Unsupported format</source>
|
||||
<target>Formato non supportato</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wait" xml:space="preserve">
|
||||
<source>Wait</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<target>Password del database sbagliata</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can allow sharing in Privacy & Security / SimpleX Lock settings." xml:space="preserve">
|
||||
<source>You can allow sharing in Privacy & Security / SimpleX Lock settings.</source>
|
||||
<target>Puoi consentire la condivisione in Privacy e sicurezza / impostazioni di SimpleX Lock.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
+3
@@ -1,6 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
@@ -762,6 +762,10 @@
|
||||
<target>消えるメッセージの送信を許可する。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow sharing" xml:space="preserve">
|
||||
<source>Allow sharing</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>送信済みメッセージの永久削除を許可する。(24時間)</target>
|
||||
@@ -1036,6 +1040,10 @@
|
||||
<source>Blocked by admin</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blur media" xml:space="preserve">
|
||||
<source>Blur media</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
|
||||
<source>Both you and your contact can add message reactions.</source>
|
||||
<target>自分も相手もメッセージへのリアクションを追加できます。</target>
|
||||
@@ -1201,6 +1209,10 @@
|
||||
<target>チャットのデータベースが削除されました</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database exported" xml:space="preserve">
|
||||
<source>Chat database exported</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat database imported" xml:space="preserve">
|
||||
<source>Chat database imported</source>
|
||||
<target>チャットのデータベースが読み込まれました</target>
|
||||
@@ -1465,6 +1477,10 @@ This is your own one-time link!</source>
|
||||
<target>接続エラー (AUTH)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection notifications" xml:space="preserve">
|
||||
<source>Connection notifications</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection request sent!" xml:space="preserve">
|
||||
<source>Connection request sent!</source>
|
||||
<target>接続リクエストを送信しました!</target>
|
||||
@@ -1796,6 +1812,10 @@ This is your own one-time link!</source>
|
||||
<target>削除</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages of members?" xml:space="preserve">
|
||||
<source>Delete %lld messages of members?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages?" xml:space="preserve">
|
||||
<source>Delete %lld messages?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -2028,10 +2048,18 @@ This cannot be undone!</source>
|
||||
<source>Desktop devices</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server address of %@ is incompatible with forwarding server %@ settings." xml:space="preserve">
|
||||
<source>Destination server address of %@ is incompatible with forwarding server %@ settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server error: %@" xml:space="preserve">
|
||||
<source>Destination server error: %@</source>
|
||||
<note>snd error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Destination server version of %@ is incompatible with forwarding server %@." xml:space="preserve">
|
||||
<source>Destination server version of %@ is incompatible with forwarding server %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Detailed statistics" xml:space="preserve">
|
||||
<source>Detailed statistics</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -2095,6 +2123,10 @@ This cannot be undone!</source>
|
||||
<target>すべて無効</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disabled" xml:space="preserve">
|
||||
<source>Disabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disappearing message" xml:space="preserve">
|
||||
<source>Disappearing message</source>
|
||||
<target>消えるメッセージ</target>
|
||||
@@ -2306,6 +2338,10 @@ This cannot be undone!</source>
|
||||
<target>自己破壊パスコードを有効にする</target>
|
||||
<note>set passcode view</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled" xml:space="preserve">
|
||||
<source>Enabled</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enabled for" xml:space="preserve">
|
||||
<source>Enabled for</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -2468,6 +2504,10 @@ This cannot be undone!</source>
|
||||
<target>設定変更にエラー発生</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error connecting to forwarding server %@. Please try later." xml:space="preserve">
|
||||
<source>Error connecting to forwarding server %@. Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating address" xml:space="preserve">
|
||||
<source>Error creating address</source>
|
||||
<target>アドレス作成にエラー発生</target>
|
||||
@@ -2943,6 +2983,18 @@ This cannot be undone!</source>
|
||||
<source>Forwarded from</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server %@ failed to connect to destination server %@. Please try later." xml:space="preserve">
|
||||
<source>Forwarding server %@ failed to connect to destination server %@. Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server address is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server address is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Forwarding server version is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarding server: %@ Destination server error: %@" xml:space="preserve">
|
||||
<source>Forwarding server: %1$@
|
||||
Destination server error: %2$@</source>
|
||||
@@ -3723,6 +3775,14 @@ This is your link for group %@!</source>
|
||||
<target>最大 30 秒で即時受信します。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Media & file servers" xml:space="preserve">
|
||||
<source>Media & file servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Medium" xml:space="preserve">
|
||||
<source>Medium</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member" xml:space="preserve">
|
||||
<source>Member</source>
|
||||
<target>メンバー</target>
|
||||
@@ -3800,12 +3860,8 @@ This is your link for group %@!</source>
|
||||
<source>Message reception</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing fallback" xml:space="preserve">
|
||||
<source>Message routing fallback</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message routing mode" xml:space="preserve">
|
||||
<source>Message routing mode</source>
|
||||
<trans-unit id="Message servers" xml:space="preserve">
|
||||
<source>Message servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Message source remains private." xml:space="preserve">
|
||||
@@ -4113,6 +4169,10 @@ This is your link for group %@!</source>
|
||||
<source>Not compatible!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nothing selected" xml:space="preserve">
|
||||
<source>Nothing selected</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>通知</target>
|
||||
@@ -4139,7 +4199,7 @@ This is your link for group %@!</source>
|
||||
<trans-unit id="Off" xml:space="preserve">
|
||||
<source>Off</source>
|
||||
<target>オフ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
@@ -4161,14 +4221,18 @@ This is your link for group %@!</source>
|
||||
<target>使い捨ての招待リンク</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be required for connection. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be required for connection. Requires enabling VPN.</source>
|
||||
<target>接続にオニオンのホストが必要となります。VPN を有効にする必要があります。</target>
|
||||
<trans-unit id="Onion hosts will be **required** for connection. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be **required** for connection.
|
||||
Requires compatible VPN.</source>
|
||||
<target>接続にオニオンのホストが必要となります。
|
||||
VPN を有効にする必要があります。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires enabling VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available. Requires enabling VPN.</source>
|
||||
<target>オニオンのホストが利用可能時に使われます。VPN を有効にする必要があります。</target>
|
||||
<trans-unit id="Onion hosts will be used when available. Requires compatible VPN." xml:space="preserve">
|
||||
<source>Onion hosts will be used when available.
|
||||
Requires compatible VPN.</source>
|
||||
<target>オニオンのホストが利用可能時に使われます。
|
||||
VPN を有効にする必要があります。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Onion hosts will not be used." xml:space="preserve">
|
||||
@@ -4474,10 +4538,6 @@ Error: %@</source>
|
||||
<target>パスフレーズを失くさないように保管してください。失くすと変更できなくなります。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please try later." xml:space="preserve">
|
||||
<source>Please try later.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Polish interface" xml:space="preserve">
|
||||
<source>Polish interface</source>
|
||||
<target>ポーランド語UI</target>
|
||||
@@ -4998,11 +5058,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>開示する</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revert" xml:space="preserve">
|
||||
<source>Revert</source>
|
||||
<target>元に戻す</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Revoke" xml:space="preserve">
|
||||
<source>Revoke</source>
|
||||
<target>取り消す</target>
|
||||
@@ -5032,11 +5087,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>SMP server</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="SMP servers" xml:space="preserve">
|
||||
<source>SMP servers</source>
|
||||
<target>SMPサーバ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safely receive files" xml:space="preserve">
|
||||
<source>Safely receive files</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5065,6 +5115,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>保存して、グループのメンバーにに知らせる</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and reconnect" xml:space="preserve">
|
||||
<source>Save and reconnect</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save and update group profile" xml:space="preserve">
|
||||
<source>Save and update group profile</source>
|
||||
<target>グループプロファイルの保存と更新</target>
|
||||
@@ -5213,6 +5267,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<trans-unit id="Select" xml:space="preserve">
|
||||
<source>Select</source>
|
||||
<target>選択</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected %lld" xml:space="preserve">
|
||||
<source>Selected %lld</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
@@ -5442,10 +5500,6 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>Server version is incompatible with network settings.</source>
|
||||
<note>srv error text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with network settings: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with network settings: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Server version is incompatible with your app: %@." xml:space="preserve">
|
||||
<source>Server version is incompatible with your app: %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5553,6 +5607,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>Share this 1-time invite link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share to SimpleX" xml:space="preserve">
|
||||
<source>Share to SimpleX</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share with contacts" xml:space="preserve">
|
||||
<source>Share with contacts</source>
|
||||
<target>連絡先と共有する</target>
|
||||
@@ -5695,11 +5753,23 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>小グループ(最大20名)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Soft" xml:space="preserve">
|
||||
<source>Soft</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some file(s) were not exported:" xml:space="preserve">
|
||||
<source>Some file(s) were not exported:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import - you may see Chat console for more details." xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import - you may see Chat console for more details.</source>
|
||||
<target>インポート中に致命的でないエラーが発生しました - 詳細はチャットコンソールを参照してください。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Some non-fatal errors occurred during import:" xml:space="preserve">
|
||||
<source>Some non-fatal errors occurred during import:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Somebody" xml:space="preserve">
|
||||
<source>Somebody</source>
|
||||
<target>誰か</target>
|
||||
@@ -5789,6 +5859,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<source>Stopping chat</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Strong" xml:space="preserve">
|
||||
<source>Strong</source>
|
||||
<note>blur media</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Submit" xml:space="preserve">
|
||||
<source>Submit</source>
|
||||
<target>送信</target>
|
||||
@@ -5821,6 +5895,10 @@ Enable in *Network & servers* settings.</source>
|
||||
<target>システム認証</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection" xml:space="preserve">
|
||||
<source>TCP connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
||||
<source>TCP connection timeout</source>
|
||||
<target>TCP接続タイムアウト</target>
|
||||
@@ -5987,6 +6065,14 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>メッセージは、すべてのメンバーに対してモデレートされたものとして表示されます。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be deleted for all members." xml:space="preserve">
|
||||
<source>The messages will be deleted for all members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The messages will be marked as moderated for all members." xml:space="preserve">
|
||||
<source>The messages will be marked as moderated for all members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The next generation of private messaging" xml:space="preserve">
|
||||
<source>The next generation of private messaging</source>
|
||||
<target>次世代のプライバシー・メッセンジャー</target>
|
||||
@@ -6320,11 +6406,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>更新</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update .onion hosts setting?" xml:space="preserve">
|
||||
<source>Update .onion hosts setting?</source>
|
||||
<target>.onionのホスト設定を更新しますか?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update database passphrase" xml:space="preserve">
|
||||
<source>Update database passphrase</source>
|
||||
<target>データベースのパスフレーズを更新</target>
|
||||
@@ -6335,9 +6416,8 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>ネットワーク設定を更新しますか?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update transport isolation mode?" xml:space="preserve">
|
||||
<source>Update transport isolation mode?</source>
|
||||
<target>トランスポート隔離モードを更新しますか?</target>
|
||||
<trans-unit id="Update settings?" xml:space="preserve">
|
||||
<source>Update settings?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating settings will re-connect the client to all servers." xml:space="preserve">
|
||||
@@ -6345,11 +6425,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>設定を更新すると、全サーバにクライントの再接続が行われます。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Updating this setting will re-connect the client to all servers." xml:space="preserve">
|
||||
<source>Updating this setting will re-connect the client to all servers.</source>
|
||||
<target>設定を更新すると、全サーバにクライントの再接続が行われます。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Upgrade and open chat" xml:space="preserve">
|
||||
<source>Upgrade and open chat</source>
|
||||
<target>アップグレードしてチャットを開く</target>
|
||||
@@ -6449,11 +6524,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<source>User selection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using .onion hosts requires compatible VPN provider." xml:space="preserve">
|
||||
<source>Using .onion hosts requires compatible VPN provider.</source>
|
||||
<target>.onionホストを使用するには、互換性のあるVPNプロバイダーが必要です。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Using SimpleX Chat servers." xml:space="preserve">
|
||||
<source>Using SimpleX Chat servers.</source>
|
||||
<target>SimpleX チャット サーバーを使用する。</target>
|
||||
@@ -6690,11 +6760,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<source>XFTP server</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="XFTP servers" xml:space="preserve">
|
||||
<source>XFTP servers</source>
|
||||
<target>XFTPサーバ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You" xml:space="preserve">
|
||||
<source>You</source>
|
||||
<target>あなた</target>
|
||||
@@ -6893,6 +6958,14 @@ Repeat connection request?</source>
|
||||
<target>グループに参加しました。招待をくれたメンバーに接続してます。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may migrate the exported database." xml:space="preserve">
|
||||
<source>You may migrate the exported database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You may save the exported archive." xml:space="preserve">
|
||||
<source>You may save the exported archive.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="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." xml:space="preserve">
|
||||
<source>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.</source>
|
||||
<target>あなたの最新データベースを1つの端末にしか使わなければ、一部の連絡先からメッセージが届きかねます。</target>
|
||||
@@ -7837,8 +7910,8 @@ last received msg: %2$@</source>
|
||||
<target>不明</target>
|
||||
<note>connection info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown relays" xml:space="preserve">
|
||||
<source>unknown relays</source>
|
||||
<trans-unit id="unknown servers" xml:space="preserve">
|
||||
<source>unknown servers</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown status" xml:space="preserve">
|
||||
@@ -8053,4 +8126,178 @@ last received msg: %2$@</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/InfoPlist.strings" source-language="en" target-language="ja" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<note>Bundle display name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||
<source>SimpleX SE</source>
|
||||
<note>Bundle name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
|
||||
<source>Copyright © 2024 SimpleX Chat. All rights reserved.</source>
|
||||
<note>Copyright (human-readable)</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="SimpleX SE/en.lproj/Localizable.strings" source-language="en" target-language="ja" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
<source>%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="App is locked!" xml:space="preserve">
|
||||
<source>App is locked!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cancel" xml:space="preserve">
|
||||
<source>Cancel</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot access keychain to save database password" xml:space="preserve">
|
||||
<source>Cannot access keychain to save database password</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cannot forward message" xml:space="preserve">
|
||||
<source>Cannot forward message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Comment" xml:space="preserve">
|
||||
<source>Comment</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Currently maximum supported file size is %@." xml:space="preserve">
|
||||
<source>Currently maximum supported file size is %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database downgrade required" xml:space="preserve">
|
||||
<source>Database downgrade required</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database encrypted!" xml:space="preserve">
|
||||
<source>Database encrypted!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database error" xml:space="preserve">
|
||||
<source>Database error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is different from saved in the keychain." xml:space="preserve">
|
||||
<source>Database passphrase is different from saved in the keychain.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database passphrase is required to open chat." xml:space="preserve">
|
||||
<source>Database passphrase is required to open chat.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database upgrade required" xml:space="preserve">
|
||||
<source>Database upgrade required</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing file" xml:space="preserve">
|
||||
<source>Error preparing file</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error preparing message" xml:space="preserve">
|
||||
<source>Error preparing message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error: %@" xml:space="preserve">
|
||||
<source>Error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="File error" xml:space="preserve">
|
||||
<source>File error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible database version" xml:space="preserve">
|
||||
<source>Incompatible database version</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid migration confirmation" xml:space="preserve">
|
||||
<source>Invalid migration confirmation</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keychain error" xml:space="preserve">
|
||||
<source>Keychain error</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Large file!" xml:space="preserve">
|
||||
<source>Large file!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No active profile" xml:space="preserve">
|
||||
<source>No active profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to downgrade the database." xml:space="preserve">
|
||||
<source>Open the app to downgrade the database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open the app to upgrade the database." xml:space="preserve">
|
||||
<source>Open the app to upgrade the database.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Passphrase" xml:space="preserve">
|
||||
<source>Passphrase</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please create a profile in the SimpleX app" xml:space="preserve">
|
||||
<source>Please create a profile in the SimpleX app</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Selected chat preferences prohibit this message." xml:space="preserve">
|
||||
<source>Selected chat preferences prohibit this message.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending a message takes longer than expected." xml:space="preserve">
|
||||
<source>Sending a message takes longer than expected.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sending message…" xml:space="preserve">
|
||||
<source>Sending message…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share" xml:space="preserve">
|
||||
<source>Share</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Slow network?" xml:space="preserve">
|
||||
<source>Slow network?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unknown database error: %@" xml:space="preserve">
|
||||
<source>Unknown database error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported format" xml:space="preserve">
|
||||
<source>Unsupported format</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wait" xml:space="preserve">
|
||||
<source>Wait</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can allow sharing in Privacy & Security / SimpleX Lock settings." xml:space="preserve">
|
||||
<source>You can allow sharing in Privacy & Security / SimpleX Lock settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
+3
@@ -1,6 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
SimpleX
|
||||
|
||||
Created by EP on 30/07/2024.
|
||||
Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
*/
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user