From bd04775cbf094f29968d6198784d0cc2227235a0 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 3 May 2021 15:13:18 +1000 Subject: [PATCH 01/57] Implement open group invitations UI --- Session.xcodeproj/project.pbxproj | 8 ++ .../OpenGroupInvitationView.swift | 78 +++++++++++++++++++ .../Message Cells/VisibleMessageCell.swift | 8 +- .../File Server/FileServerAPIV2.swift | 2 + SessionMessagingKit/Jobs/Job.swift | 2 +- .../ConfigurationMessage.swift | 10 +-- .../ExpirationTimerUpdate.swift | 2 +- .../Signal/TSIncomingMessage+Conversion.swift | 4 +- .../Messages/Signal/TSIncomingMessage.h | 4 +- .../Messages/Signal/TSIncomingMessage.m | 6 +- .../Messages/Signal/TSInfoMessage.m | 4 +- .../Messages/Signal/TSMessage.h | 6 +- .../Messages/Signal/TSMessage.m | 4 + .../Signal/TSOutgoingMessage+Conversion.swift | 4 +- .../Messages/Signal/TSOutgoingMessage.h | 4 +- .../Messages/Signal/TSOutgoingMessage.m | 14 +++- .../VisibleMessage+LinkPreview.swift | 4 +- .../VisibleMessage+OpenGroupInvitation.swift | 43 ++++++++++ .../VisibleMessage+Profile.swift | 4 +- .../VisibleMessage+Quote.swift | 6 +- .../Visible Messages/VisibleMessage.swift | 15 ++-- .../Sending & Receiving/MessageReceiver.swift | 32 ++++---- .../Sending & Receiving/MessageSender.swift | 6 +- .../Pollers/ClosedGroupPoller.swift | 2 +- .../Pollers/OpenGroupPoller.swift | 2 +- .../Sending & Receiving/Pollers/Poller.swift | 2 +- 26 files changed, 221 insertions(+), 55 deletions(-) create mode 100644 Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift create mode 100644 SessionMessagingKit/Messages/Visible Messages/VisibleMessage+OpenGroupInvitation.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 687fe8f63..6cc48e38b 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -268,6 +268,8 @@ B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D84EA225DF745A005A043E /* LinkPreviewState.swift */; }; B8D84ECF25E3108A005A043E /* ExpandingAttachmentsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D84ECE25E3108A005A043E /* ExpandingAttachmentsButton.swift */; }; + B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */; }; + B8EB20F02640F7F000773E52 /* OpenGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */; }; B8F5F52925EC4F8A003BF8D4 /* BlockListUIUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F5F52825EC4F8A003BF8D4 /* BlockListUIUtils.m */; }; B8F5F54E25EC50A5003BF8D4 /* BlockListUIUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = B8F5F52725EC4F6A003BF8D4 /* BlockListUIUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; B8F5F56525EC8453003BF8D4 /* Notification+Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F5F56425EC8453003BF8D4 /* Notification+Contacts.swift */; }; @@ -1252,6 +1254,8 @@ B8D8F1BC25661C6F0092EF10 /* Storage+OnionRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+OnionRequests.swift"; sourceTree = ""; }; B8D8F1EF256621180092EF10 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "MessageSender+Convenience.swift"; path = "../../SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift"; sourceTree = ""; }; B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; + B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = ""; }; + B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupInvitationView.swift; sourceTree = ""; }; B8F5F52725EC4F6A003BF8D4 /* BlockListUIUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BlockListUIUtils.h; sourceTree = ""; }; B8F5F52825EC4F8A003BF8D4 /* BlockListUIUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BlockListUIUtils.m; sourceTree = ""; }; B8F5F56425EC8453003BF8D4 /* Notification+Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Contacts.swift"; sourceTree = ""; }; @@ -2110,6 +2114,7 @@ B8569AE225CBB19A00DBA3DB /* DocumentView.swift */, B849789525D4A2F500D0D0B3 /* LinkPreviewView.swift */, B8D84EA225DF745A005A043E /* LinkPreviewState.swift */, + B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */, ); path = "Content Views"; sourceTree = ""; @@ -2380,6 +2385,7 @@ C3C2A75E2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift */, C3C2A7672553A3D900C340D1 /* VisibleMessage+Contact.swift */, C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */, + B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */, ); path = "Visible Messages"; sourceTree = ""; @@ -4732,6 +4738,7 @@ C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */, B8566C63256F55930045A0B9 /* OWSLinkPreview+Conversion.swift in Sources */, C32C5B3F256DC1DF003C73A2 /* TSQuotedMessage+Conversion.swift in Sources */, + B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */, C32C5D19256DD493003C73A2 /* OWSLinkPreview.swift in Sources */, C32C5CF0256DD3E4003C73A2 /* Storage+Shared.swift in Sources */, @@ -4971,6 +4978,7 @@ 45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */, C302093E25DCBF08001F572D /* MentionSelectionView.swift in Sources */, C328251F25CA3A900062D0A7 /* QuoteView.swift in Sources */, + B8EB20F02640F7F000773E52 /* OpenGroupInvitationView.swift in Sources */, B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */, C328254025CA55880062D0A7 /* ContextMenuVC.swift in Sources */, 3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */, diff --git a/Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift b/Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift new file mode 100644 index 000000000..30b5d56fa --- /dev/null +++ b/Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift @@ -0,0 +1,78 @@ + +final class OpenGroupInvitationView : UIView { + private let name: String + private let rawURL: String + private let textColor: UIColor + + private lazy var url: String = { + if let range = rawURL.range(of: "?public_key=") { + return String(rawURL[.. Profile? { + notImplemented() + } + + public func toProto() -> SNProtoDataMessage? { + notImplemented() + } + + // MARK: Description + public override var description: String { + """ + OpenGroupInvitation( + name: \(name ?? "null"), + url: \(url ?? "null") + ) + """ + } + } +} diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift index 71bec53c3..d9e82f7e5 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift @@ -62,8 +62,8 @@ public extension VisibleMessage { public override var description: String { """ Profile( - displayName: \(displayName ?? "null") - profileKey: \(profileKey?.description ?? "null") + displayName: \(displayName ?? "null"), + profileKey: \(profileKey?.description ?? "null"), profilePictureURL: \(profilePictureURL ?? "null") ) """ diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Quote.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Quote.swift index 2f818fdcd..2a0f9bf93 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Quote.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Quote.swift @@ -88,9 +88,9 @@ public extension VisibleMessage { public override var description: String { """ Quote( - timestamp: \(timestamp?.description ?? "null") - publicKey: \(publicKey ?? "null") - text: \(text ?? "null") + timestamp: \(timestamp?.description ?? "null"), + publicKey: \(publicKey ?? "null"), + text: \(text ?? "null"), attachmentID: \(attachmentID ?? "null") ) """ diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift index 9e6e4e020..a8d856382 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift @@ -12,6 +12,7 @@ public final class VisibleMessage : Message { @objc public var linkPreview: LinkPreview? @objc public var contact: Contact? @objc public var profile: Profile? + @objc public var openGroupInvitation: OpenGroupInvitation? public override var isSelfSendValid: Bool { true } @@ -22,6 +23,7 @@ public final class VisibleMessage : Message { public override var isValid: Bool { guard super.isValid else { return false } if !attachmentIDs.isEmpty { return true } + if openGroupInvitation != nil { return true } if let text = text?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty { return true } return false } @@ -36,6 +38,7 @@ public final class VisibleMessage : Message { if let linkPreview = coder.decodeObject(forKey: "linkPreview") as! LinkPreview? { self.linkPreview = linkPreview } // TODO: Contact if let profile = coder.decodeObject(forKey: "profile") as! Profile? { self.profile = profile } + if let openGroupInvitation = coder.decodeObject(forKey: "openGroupInvitation") as! OpenGroupInvitation? { self.openGroupInvitation = openGroupInvitation } } public override func encode(with coder: NSCoder) { @@ -47,6 +50,7 @@ public final class VisibleMessage : Message { coder.encode(linkPreview, forKey: "linkPreview") // TODO: Contact coder.encode(profile, forKey: "profile") + coder.encode(openGroupInvitation, forKey: "openGroupInvitation") } // MARK: Proto Conversion @@ -128,12 +132,13 @@ public final class VisibleMessage : Message { public override var description: String { """ VisibleMessage( - text: \(text ?? "null") - attachmentIDs: \(attachmentIDs) - quote: \(quote?.description ?? "null") - linkPreview: \(linkPreview?.description ?? "null") - contact: \(contact?.description ?? "null") + text: \(text ?? "null"), + attachmentIDs: \(attachmentIDs), + quote: \(quote?.description ?? "null"), + linkPreview: \(linkPreview?.description ?? "null"), + contact: \(contact?.description ?? "null"), profile: \(profile?.description ?? "null") + "openGroupInvitation": \(openGroupInvitation?.description ?? "null") ) """ } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 3547681f3..cf724fe9f 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -92,22 +92,22 @@ public enum MessageReceiver { } groupPublicKey = envelope.source try decrypt() - -// do { -// try decrypt() -// } catch { -// do { -// let now = Date() -// // Don't spam encryption key pair requests -// let shouldRequestEncryptionKeyPair = given(lastEncryptionKeyPairRequest[groupPublicKey!]) { now.timeIntervalSince($0) > 30 } ?? true -// if shouldRequestEncryptionKeyPair { -// try MessageSender.requestEncryptionKeyPair(for: groupPublicKey!, using: transaction as! YapDatabaseReadWriteTransaction) -// lastEncryptionKeyPairRequest[groupPublicKey!] = now -// } -// } -// throw error // Throw the * decryption * error and not the error generated by requestEncryptionKeyPair (if it generated one) -// } - + /* + do { + try decrypt() + } catch { + do { + let now = Date() + // Don't spam encryption key pair requests + let shouldRequestEncryptionKeyPair = given(lastEncryptionKeyPairRequest[groupPublicKey!]) { now.timeIntervalSince($0) > 30 } ?? true + if shouldRequestEncryptionKeyPair { + try MessageSender.requestEncryptionKeyPair(for: groupPublicKey!, using: transaction as! YapDatabaseReadWriteTransaction) + lastEncryptionKeyPairRequest[groupPublicKey!] = now + } + } + throw error // Throw the * decryption * error and not the error generated by requestEncryptionKeyPair (if it generated one) + } + */ default: throw Error.unknownEnvelopeType } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index fe737b2ef..eb7be10e8 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -9,7 +9,6 @@ public final class MessageSender : NSObject { public enum Error : LocalizedError { case invalidMessage case protoConversionFailed - case proofOfWorkCalculationFailed case noUserX25519KeyPair case noUserED25519KeyPair case signingFailed @@ -22,7 +21,7 @@ public final class MessageSender : NSObject { internal var isRetryable: Bool { switch self { - case .invalidMessage, .protoConversionFailed, .proofOfWorkCalculationFailed, .invalidClosedGroupUpdate, .signingFailed, .encryptionFailed: return false + case .invalidMessage, .protoConversionFailed, .invalidClosedGroupUpdate, .signingFailed, .encryptionFailed: return false default: return true } } @@ -31,7 +30,6 @@ public final class MessageSender : NSObject { switch self { case .invalidMessage: return "Invalid message." case .protoConversionFailed: return "Couldn't convert message to proto." - case .proofOfWorkCalculationFailed: return "Proof of work calculation failed." case .noUserX25519KeyPair: return "Couldn't find user X25519 key pair." case .noUserED25519KeyPair: return "Couldn't find user ED25519 key pair." case .signingFailed: return "Couldn't sign message." @@ -48,8 +46,6 @@ public final class MessageSender : NSObject { // MARK: Initialization private override init() { } - public static let shared = MessageSender() // FIXME: Remove once requestSenderKey is static - // MARK: Preparation public static func prep(_ signalAttachments: [SignalAttachment], for message: VisibleMessage, using transaction: YapDatabaseReadWriteTransaction) { guard let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) else { diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 89283dd03..111d57152 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -7,7 +7,7 @@ public final class ClosedGroupPoller : NSObject { private var timer: Timer? // MARK: Settings - private static let pollInterval: TimeInterval = 2 + private static let pollInterval: TimeInterval = 3 // MARK: Error private enum Error : LocalizedError { diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 2a1c5286e..4dfc780a0 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -18,7 +18,7 @@ public final class OpenGroupPoller : NSObject { } // MARK: Settings - private let pollForNewMessagesInterval: TimeInterval = 4 + private let pollForNewMessagesInterval: TimeInterval = 8 private let pollForDeletedMessagesInterval: TimeInterval = 30 private let pollForModeratorsInterval: TimeInterval = 10 * 60 diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 6c338c0c4..85da45a78 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -9,7 +9,7 @@ public final class Poller : NSObject { private var pollCount = 0 // MARK: Settings - private static let pollInterval: TimeInterval = 1 + private static let pollInterval: TimeInterval = 1.5 private static let retryInterval: TimeInterval = 0.25 /// After polling a given snode this many times we always switch to a new one. /// From 2f1e2a141b8386b315dc75cef69831187367e11b Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 4 May 2021 14:39:06 +1000 Subject: [PATCH 02/57] Localize new string --- .../Message Cells/Content Views/OpenGroupInvitationView.swift | 2 +- Session/Meta/Translations/de.lproj/Localizable.strings | 1 + Session/Meta/Translations/en.lproj/Localizable.strings | 1 + Session/Meta/Translations/es.lproj/Localizable.strings | 1 + Session/Meta/Translations/fa.lproj/Localizable.strings | 1 + Session/Meta/Translations/fr.lproj/Localizable.strings | 1 + Session/Meta/Translations/id-ID.lproj/Localizable.strings | 1 + Session/Meta/Translations/it.lproj/Localizable.strings | 1 + Session/Meta/Translations/ja.lproj/Localizable.strings | 1 + Session/Meta/Translations/nl.lproj/Localizable.strings | 1 + Session/Meta/Translations/pl.lproj/Localizable.strings | 1 + Session/Meta/Translations/pt_BR.lproj/Localizable.strings | 1 + Session/Meta/Translations/ru.lproj/Localizable.strings | 1 + Session/Meta/Translations/sk.lproj/Localizable.strings | 1 + Session/Meta/Translations/vi-VN.lproj/Localizable.strings | 1 + Session/Meta/Translations/zh_CN.lproj/Localizable.strings | 1 + 16 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift b/Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift index 30b5d56fa..bd328fe8d 100644 --- a/Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift +++ b/Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift @@ -43,7 +43,7 @@ final class OpenGroupInvitationView : UIView { // Subtitle let subtitleLabel = UILabel() subtitleLabel.lineBreakMode = .byTruncatingTail - subtitleLabel.text = "Open group invitation" + subtitleLabel.text = NSLocalizedString("view_open_group_invitation_description", comment: "") subtitleLabel.textColor = textColor subtitleLabel.font = .systemFont(ofSize: Values.smallFontSize) // URL diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index a0c21f35b..90619c0ba 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index fca861682..0ab7860e8 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -525,3 +525,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 621d7d823..6120375ca 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 0b0f2c494..0d8831ad8 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "اشتراک گذاری با Session"; "vc_share_loading_message" = "آماده سازی پیوست‌ها..."; "vc_share_sending_message" = "در حال ارسال..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 70cc8473c..c24ef9ab0 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 8c78ed5ae..8ef2eaf81 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index 01b23c43b..299b21db9 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index de430e75b..711286551 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index f01ac9cd2..5bfdc6e91 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -525,3 +525,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index ddbb14eb9..385877294 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 1889fdb28..63f82387c 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Compartilhar no Session"; "vc_share_loading_message" = "Preparando anexos..."; "vc_share_sending_message" = "Enviando..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 2254fb24f..3e427faf3 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index c60d66f62..e9c40e002 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index e532ab9a3..1263c8277 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index 75d160738..59f52a88f 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -523,3 +523,4 @@ "vc_share_title" = "Share to Session"; "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; From 5aab5346ddc92dc6e68b59c0a20650d11e3c75e8 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 7 May 2021 13:39:39 +1000 Subject: [PATCH 03/57] Add invite button to conversation settings --- .../OWSConversationSettingsViewController.m | 19 +++++++++++++++++++ .../Translations/en.lproj/Localizable.strings | 1 + 2 files changed, 20 insertions(+) diff --git a/Session/Conversations/Settings/OWSConversationSettingsViewController.m b/Session/Conversations/Settings/OWSConversationSettingsViewController.m index 016da8c74..c44f7d904 100644 --- a/Session/Conversations/Settings/OWSConversationSettingsViewController.m +++ b/Session/Conversations/Settings/OWSConversationSettingsViewController.m @@ -312,6 +312,20 @@ CGFloat kIconViewLength = 24; actionBlock:^{ [weakSelf showMediaGallery]; }]]; + + if (self.isOpenGroup) { + [mainSection addItem:[OWSTableItem + itemWithCustomCellBlock:^{ + return [weakSelf + disclosureCellWithName:NSLocalizedString(@"vc_conversation_settings_invite_button_title", "") + iconName:@"ic_plus_24" + accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME( + OWSConversationSettingsViewController, @"invite")]; + } + actionBlock:^{ + [weakSelf inviteUsersToOpenGroup]; + }]]; + } [mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{ @@ -1089,6 +1103,11 @@ CGFloat kIconViewLength = 24; UIPasteboard.generalPasteboard.string = ((TSContactThread *)self.thread).contactSessionID; } +- (void)inviteUsersToOpenGroup +{ + +} + - (void)showMediaGallery { OWSLogDebug(@""); diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 0ab7860e8..2a835e2e1 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -526,3 +526,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; From b3f45455e5df29cf0e9724463c071b4ee9fdce9e Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 7 May 2021 13:50:32 +1000 Subject: [PATCH 04/57] Hook up user selection screen --- .../Settings/OWSConversationSettingsViewController.m | 7 ++++++- Session/Meta/Translations/de.lproj/Localizable.strings | 1 + Session/Meta/Translations/es.lproj/Localizable.strings | 1 + Session/Meta/Translations/fa.lproj/Localizable.strings | 1 + Session/Meta/Translations/fr.lproj/Localizable.strings | 1 + Session/Meta/Translations/id-ID.lproj/Localizable.strings | 1 + Session/Meta/Translations/it.lproj/Localizable.strings | 1 + Session/Meta/Translations/ja.lproj/Localizable.strings | 1 + Session/Meta/Translations/nl.lproj/Localizable.strings | 1 + Session/Meta/Translations/pl.lproj/Localizable.strings | 1 + Session/Meta/Translations/pt_BR.lproj/Localizable.strings | 1 + Session/Meta/Translations/ru.lproj/Localizable.strings | 1 + Session/Meta/Translations/sk.lproj/Localizable.strings | 1 + Session/Meta/Translations/vi-VN.lproj/Localizable.strings | 1 + Session/Meta/Translations/zh_CN.lproj/Localizable.strings | 1 + Session/Shared/UserSelectionVC.swift | 4 +++- 16 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Session/Conversations/Settings/OWSConversationSettingsViewController.m b/Session/Conversations/Settings/OWSConversationSettingsViewController.m index c44f7d904..47bfde914 100644 --- a/Session/Conversations/Settings/OWSConversationSettingsViewController.m +++ b/Session/Conversations/Settings/OWSConversationSettingsViewController.m @@ -1105,7 +1105,12 @@ CGFloat kIconViewLength = 24; - (void)inviteUsersToOpenGroup { - + SNUserSelectionVC *userSelectionVC = [[SNUserSelectionVC alloc] initWithTitle:@"vc_conversation_settings_invite_button_title" + excluding:[NSSet new] + completion:^(NSSet *selectedUsers) { + + }]; + [self.navigationController pushViewController:userSelectionVC animated:YES]; } - (void)showMediaGallery diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 90619c0ba..d1e31458e 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 6120375ca..f7b0dc486 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 0d8831ad8..d3bb68b22 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "آماده سازی پیوست‌ها..."; "vc_share_sending_message" = "در حال ارسال..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index c24ef9ab0..19c64afab 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 8ef2eaf81..2d8a1e992 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index 299b21db9..1c84802bb 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 711286551..03cd9f569 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 5bfdc6e91..6ee5b7833 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -526,3 +526,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 385877294..921f70388 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 63f82387c..f3f5d87b6 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparando anexos..."; "vc_share_sending_message" = "Enviando..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 3e427faf3..59d15e9e2 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index e9c40e002..836a7d21a 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 1263c8277..53cad3499 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index 59f52a88f..cd0acace1 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -524,3 +524,4 @@ "vc_share_loading_message" = "Preparing attachments..."; "vc_share_sending_message" = "Sending..."; "view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; diff --git a/Session/Shared/UserSelectionVC.swift b/Session/Shared/UserSelectionVC.swift index b39b8c66a..b62104da3 100644 --- a/Session/Shared/UserSelectionVC.swift +++ b/Session/Shared/UserSelectionVC.swift @@ -1,4 +1,5 @@ +@objc(SNUserSelectionVC) final class UserSelectionVC : BaseVC, UITableViewDataSource, UITableViewDelegate { private let navBarTitle: String private let usersToExclude: Set @@ -25,7 +26,8 @@ final class UserSelectionVC : BaseVC, UITableViewDataSource, UITableViewDelegate }() // MARK: Lifecycle - @objc init(with title: String, excluding usersToExclude: Set, completion: @escaping (Set) -> Void) { + @objc(initWithTitle:excluding:completion:) + init(with title: String, excluding usersToExclude: Set, completion: @escaping (Set) -> Void) { self.navBarTitle = title self.usersToExclude = usersToExclude self.completion = completion From e2715d1ee9c0e39048c076e1ee10e9e87a950067 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 7 May 2021 14:08:52 +1000 Subject: [PATCH 05/57] Implement sending logic --- .../OWSConversationSettingsViewController.m | 20 +++++++++++++++++-- .../VisibleMessage+OpenGroupInvitation.swift | 3 ++- .../Open Groups/V2/OpenGroupV2.swift | 4 ++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Session/Conversations/Settings/OWSConversationSettingsViewController.m b/Session/Conversations/Settings/OWSConversationSettingsViewController.m index 47bfde914..ae8ba7aa0 100644 --- a/Session/Conversations/Settings/OWSConversationSettingsViewController.m +++ b/Session/Conversations/Settings/OWSConversationSettingsViewController.m @@ -1105,10 +1105,26 @@ CGFloat kIconViewLength = 24; - (void)inviteUsersToOpenGroup { - SNUserSelectionVC *userSelectionVC = [[SNUserSelectionVC alloc] initWithTitle:@"vc_conversation_settings_invite_button_title" + NSString *threadID = self.thread.uniqueId; + SNOpenGroupV2 *openGroup = [LKStorage.shared getV2OpenGroupForThreadID:threadID]; + NSString *url = [NSString stringWithFormat:@"%@/%@?public_key=%@", openGroup.server, openGroup.room, openGroup.publicKey]; + SNUserSelectionVC *userSelectionVC = [[SNUserSelectionVC alloc] initWithTitle:NSLocalizedString(@"vc_conversation_settings_invite_button_title", @"") excluding:[NSSet new] completion:^(NSSet *selectedUsers) { - + for (NSString *user in selectedUsers) { + SNVisibleMessage *message = [SNVisibleMessage new]; + message.sentTimestamp = [NSDate millisecondTimestamp]; + message.openGroupInvitation = [[SNOpenGroupInvitation alloc] initWithName:openGroup.name url:url]; + TSOutgoingMessage *tsMessage = [TSOutgoingMessage from:message associatedWith:self.thread]; + [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [tsMessage saveWithTransaction:transaction]; + }]; + [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + TSContactThread *thread = [TSContactThread getThreadWithContactSessionID:user transaction:transaction]; + [SNMessageSender send:message inThread:thread usingTransaction:transaction]; + [self.navigationController popViewControllerAnimated:YES]; + }]; + } }]; [self.navigationController pushViewController:userSelectionVC animated:YES]; } diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+OpenGroupInvitation.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+OpenGroupInvitation.swift index 6c3ef7628..97295578a 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+OpenGroupInvitation.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+OpenGroupInvitation.swift @@ -7,7 +7,8 @@ public extension VisibleMessage { public var name: String? public var url: String? - internal init(name: String, url: String) { + @objc + public init(name: String, url: String) { self.name = name self.url = url } diff --git a/SessionMessagingKit/Open Groups/V2/OpenGroupV2.swift b/SessionMessagingKit/Open Groups/V2/OpenGroupV2.swift index bd62c31f7..504920b76 100644 --- a/SessionMessagingKit/Open Groups/V2/OpenGroupV2.swift +++ b/SessionMessagingKit/Open Groups/V2/OpenGroupV2.swift @@ -4,8 +4,8 @@ public final class OpenGroupV2 : NSObject, NSCoding { // NSObject/NSCoding confo @objc public let server: String @objc public let room: String public let id: String - public let name: String - public let publicKey: String + @objc public let name: String + @objc public let publicKey: String /// The ID with which the image can be retrieved from the server. public let imageID: String? From 0153e1cefd7bc1b8280d7b4e25d7b081bbd66b27 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 7 May 2021 14:46:53 +1000 Subject: [PATCH 06/57] Update protos --- .../Protos/Generated/SNProto.swift | 112 ++++++++++++++++++ .../Protos/Generated/SessionProtos.pb.swift | 74 ++++++++++++ .../Protos/SessionProtos.proto | 7 ++ 3 files changed, 193 insertions(+) diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index d5ea4e38e..da1d9dc85 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -1294,6 +1294,118 @@ extension SNProtoDataMessageLokiProfile.SNProtoDataMessageLokiProfileBuilder { #endif +// MARK: - SNProtoDataMessageOpenGroupInvitation + +@objc public class SNProtoDataMessageOpenGroupInvitation: NSObject { + + // MARK: - SNProtoDataMessageOpenGroupInvitationBuilder + + @objc public class func builder(url: String, name: String) -> SNProtoDataMessageOpenGroupInvitationBuilder { + return SNProtoDataMessageOpenGroupInvitationBuilder(url: url, name: name) + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> SNProtoDataMessageOpenGroupInvitationBuilder { + let builder = SNProtoDataMessageOpenGroupInvitationBuilder(url: url, name: name) + return builder + } + + @objc public class SNProtoDataMessageOpenGroupInvitationBuilder: NSObject { + + private var proto = SessionProtos_DataMessage.OpenGroupInvitation() + + @objc fileprivate override init() {} + + @objc fileprivate init(url: String, name: String) { + super.init() + + setUrl(url) + setName(name) + } + + @objc public func setUrl(_ valueParam: String) { + proto.url = valueParam + } + + @objc public func setName(_ valueParam: String) { + proto.name = valueParam + } + + @objc public func build() throws -> SNProtoDataMessageOpenGroupInvitation { + return try SNProtoDataMessageOpenGroupInvitation.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try SNProtoDataMessageOpenGroupInvitation.parseProto(proto).serializedData() + } + } + + fileprivate let proto: SessionProtos_DataMessage.OpenGroupInvitation + + @objc public let url: String + + @objc public let name: String + + private init(proto: SessionProtos_DataMessage.OpenGroupInvitation, + url: String, + name: String) { + self.proto = proto + self.url = url + self.name = name + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> SNProtoDataMessageOpenGroupInvitation { + let proto = try SessionProtos_DataMessage.OpenGroupInvitation(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.OpenGroupInvitation) throws -> SNProtoDataMessageOpenGroupInvitation { + guard proto.hasURL else { + throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: url") + } + let url = proto.url + + guard proto.hasName else { + throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: name") + } + let name = proto.name + + // MARK: - Begin Validation Logic for SNProtoDataMessageOpenGroupInvitation - + + // MARK: - End Validation Logic for SNProtoDataMessageOpenGroupInvitation - + + let result = SNProtoDataMessageOpenGroupInvitation(proto: proto, + url: url, + name: name) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension SNProtoDataMessageOpenGroupInvitation { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension SNProtoDataMessageOpenGroupInvitation.SNProtoDataMessageOpenGroupInvitationBuilder { + @objc public func buildIgnoringErrors() -> SNProtoDataMessageOpenGroupInvitation? { + return try! self.build() + } +} + +#endif + // MARK: - SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper @objc public class SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper: NSObject { diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 46674b662..4f5b5cfe0 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -670,6 +670,39 @@ struct SessionProtos_DataMessage { fileprivate var _profilePicture: String? = nil } + struct OpenGroupInvitation { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// @required + var url: String { + get {return _url ?? String()} + set {_url = newValue} + } + /// Returns true if `url` has been explicitly set. + var hasURL: Bool {return self._url != nil} + /// Clears the value of `url`. Subsequent reads from it will return its default value. + mutating func clearURL() {self._url = nil} + + /// @required + var name: String { + get {return _name ?? String()} + set {_name = newValue} + } + /// Returns true if `name` has been explicitly set. + var hasName: Bool {return self._name != nil} + /// Clears the value of `name`. Subsequent reads from it will return its default value. + mutating func clearName() {self._name = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _url: String? = nil + fileprivate var _name: String? = nil + } + struct ClosedGroupControlMessage { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -2058,6 +2091,47 @@ extension SessionProtos_DataMessage.LokiProfile: SwiftProtobuf.Message, SwiftPro } } +extension SessionProtos_DataMessage.OpenGroupInvitation: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = SessionProtos_DataMessage.protoMessageName + ".OpenGroupInvitation" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "url"), + 3: .same(proto: "name"), + ] + + public var isInitialized: Bool { + if self._url == nil {return false} + if self._name == nil {return false} + return true + } + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularStringField(value: &self._url) + case 3: try decoder.decodeSingularStringField(value: &self._name) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if let v = self._url { + try visitor.visitSingularStringField(value: v, fieldNumber: 1) + } + if let v = self._name { + try visitor.visitSingularStringField(value: v, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SessionProtos_DataMessage.OpenGroupInvitation, rhs: SessionProtos_DataMessage.OpenGroupInvitation) -> Bool { + if lhs._url != rhs._url {return false} + if lhs._name != rhs._name {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension SessionProtos_DataMessage.ClosedGroupControlMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = SessionProtos_DataMessage.protoMessageName + ".ClosedGroupControlMessage" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index b0b3b45d3..2e22da754 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -101,6 +101,13 @@ message DataMessage { optional string profilePicture = 2; } + message OpenGroupInvitation { + // @required + required string url = 1; + // @required + required string name = 3; + } + message ClosedGroupControlMessage { enum Type { From f3fef3425940333f630dcb0dee95d624c0306dda Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 7 May 2021 14:53:28 +1000 Subject: [PATCH 07/57] Implement proto conversion --- .../VisibleMessage+OpenGroupInvitation.swift | 20 +++++++++++++++---- .../Visible Messages/VisibleMessage.swift | 4 ++++ .../Protos/Generated/SNProto.swift | 17 ++++++++++++++++ .../Protos/Generated/SessionProtos.pb.swift | 18 +++++++++++++++++ .../Protos/SessionProtos.proto | 1 + 5 files changed, 56 insertions(+), 4 deletions(-) diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+OpenGroupInvitation.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+OpenGroupInvitation.swift index 97295578a..678eeb04a 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+OpenGroupInvitation.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+OpenGroupInvitation.swift @@ -23,12 +23,24 @@ public extension VisibleMessage { coder.encode(url, forKey: "url") } - public static func fromProto(_ proto: SNProtoDataMessage) -> Profile? { - notImplemented() + public static func fromProto(_ proto: SNProtoDataMessageOpenGroupInvitation) -> OpenGroupInvitation? { + let url = proto.url + let name = proto.name + return OpenGroupInvitation(name: name, url: url) } - public func toProto() -> SNProtoDataMessage? { - notImplemented() + public func toProto() -> SNProtoDataMessageOpenGroupInvitation? { + guard let url = url, let name = name else { + SNLog("Couldn't construct open group invitation proto from: \(self).") + return nil + } + let openGroupInvitationProto = SNProtoDataMessageOpenGroupInvitation.builder(url: url, name: name) + do { + return try openGroupInvitationProto.build() + } catch { + SNLog("Couldn't construct open group invitation proto from: \(self).") + return nil + } } // MARK: Description diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift index a8d856382..49e70e2c8 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift @@ -63,6 +63,8 @@ public final class VisibleMessage : Message { if let linkPreviewProto = dataMessage.preview.first, let linkPreview = LinkPreview.fromProto(linkPreviewProto) { result.linkPreview = linkPreview } // TODO: Contact if let profile = Profile.fromProto(dataMessage) { result.profile = profile } + if let openGroupInvitationProto = dataMessage.openGroupInvitation, + let openGroupInvitation = OpenGroupInvitation.fromProto(openGroupInvitationProto) { result.openGroupInvitation = openGroupInvitation } result.syncTarget = dataMessage.syncTarget return result } @@ -99,6 +101,8 @@ public final class VisibleMessage : Message { let attachmentProtos = attachments.compactMap { $0.buildProto() } dataMessage.setAttachments(attachmentProtos) // TODO: Contact + // Open group invitation + if let openGroupInvitation = openGroupInvitation, let openGroupInvitationProto = openGroupInvitation.toProto() { dataMessage.setOpenGroupInvitation(openGroupInvitationProto) } // Expiration timer // TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation // if it receives a message without the current expiration timer value attached to it... diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index da1d9dc85..ea256a14d 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -1808,6 +1808,9 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr if let _value = profile { builder.setProfile(_value) } + if let _value = openGroupInvitation { + builder.setOpenGroupInvitation(_value) + } if let _value = closedGroupControlMessage { builder.setClosedGroupControlMessage(_value) } @@ -1875,6 +1878,10 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr proto.profile = valueParam.proto } + @objc public func setOpenGroupInvitation(_ valueParam: SNProtoDataMessageOpenGroupInvitation) { + proto.openGroupInvitation = valueParam.proto + } + @objc public func setClosedGroupControlMessage(_ valueParam: SNProtoDataMessageClosedGroupControlMessage) { proto.closedGroupControlMessage = valueParam.proto } @@ -1904,6 +1911,8 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr @objc public let profile: SNProtoDataMessageLokiProfile? + @objc public let openGroupInvitation: SNProtoDataMessageOpenGroupInvitation? + @objc public let closedGroupControlMessage: SNProtoDataMessageClosedGroupControlMessage? @objc public var body: String? { @@ -1963,6 +1972,7 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr quote: SNProtoDataMessageQuote?, preview: [SNProtoDataMessagePreview], profile: SNProtoDataMessageLokiProfile?, + openGroupInvitation: SNProtoDataMessageOpenGroupInvitation?, closedGroupControlMessage: SNProtoDataMessageClosedGroupControlMessage?) { self.proto = proto self.attachments = attachments @@ -1970,6 +1980,7 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr self.quote = quote self.preview = preview self.profile = profile + self.openGroupInvitation = openGroupInvitation self.closedGroupControlMessage = closedGroupControlMessage } @@ -2005,6 +2016,11 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr profile = try SNProtoDataMessageLokiProfile.parseProto(proto.profile) } + var openGroupInvitation: SNProtoDataMessageOpenGroupInvitation? = nil + if proto.hasOpenGroupInvitation { + openGroupInvitation = try SNProtoDataMessageOpenGroupInvitation.parseProto(proto.openGroupInvitation) + } + var closedGroupControlMessage: SNProtoDataMessageClosedGroupControlMessage? = nil if proto.hasClosedGroupControlMessage { closedGroupControlMessage = try SNProtoDataMessageClosedGroupControlMessage.parseProto(proto.closedGroupControlMessage) @@ -2020,6 +2036,7 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr quote: quote, preview: preview, profile: profile, + openGroupInvitation: openGroupInvitation, closedGroupControlMessage: closedGroupControlMessage) return result } diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 4f5b5cfe0..d9e4a94cf 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -440,6 +440,15 @@ struct SessionProtos_DataMessage { /// Clears the value of `profile`. Subsequent reads from it will return its default value. mutating func clearProfile() {_uniqueStorage()._profile = nil} + var openGroupInvitation: SessionProtos_DataMessage.OpenGroupInvitation { + get {return _storage._openGroupInvitation ?? SessionProtos_DataMessage.OpenGroupInvitation()} + set {_uniqueStorage()._openGroupInvitation = newValue} + } + /// Returns true if `openGroupInvitation` has been explicitly set. + var hasOpenGroupInvitation: Bool {return _storage._openGroupInvitation != nil} + /// Clears the value of `openGroupInvitation`. Subsequent reads from it will return its default value. + mutating func clearOpenGroupInvitation() {_uniqueStorage()._openGroupInvitation = nil} + var closedGroupControlMessage: SessionProtos_DataMessage.ClosedGroupControlMessage { get {return _storage._closedGroupControlMessage ?? SessionProtos_DataMessage.ClosedGroupControlMessage()} set {_uniqueStorage()._closedGroupControlMessage = newValue} @@ -1666,6 +1675,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa 8: .same(proto: "quote"), 10: .same(proto: "preview"), 101: .same(proto: "profile"), + 102: .same(proto: "openGroupInvitation"), 104: .same(proto: "closedGroupControlMessage"), 105: .same(proto: "syncTarget"), ] @@ -1681,6 +1691,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa var _quote: SessionProtos_DataMessage.Quote? = nil var _preview: [SessionProtos_DataMessage.Preview] = [] var _profile: SessionProtos_DataMessage.LokiProfile? = nil + var _openGroupInvitation: SessionProtos_DataMessage.OpenGroupInvitation? = nil var _closedGroupControlMessage: SessionProtos_DataMessage.ClosedGroupControlMessage? = nil var _syncTarget: String? = nil @@ -1699,6 +1710,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa _quote = source._quote _preview = source._preview _profile = source._profile + _openGroupInvitation = source._openGroupInvitation _closedGroupControlMessage = source._closedGroupControlMessage _syncTarget = source._syncTarget } @@ -1717,6 +1729,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa if let v = _storage._group, !v.isInitialized {return false} if let v = _storage._quote, !v.isInitialized {return false} if !SwiftProtobuf.Internal.areAllInitialized(_storage._preview) {return false} + if let v = _storage._openGroupInvitation, !v.isInitialized {return false} if let v = _storage._closedGroupControlMessage, !v.isInitialized {return false} return true } @@ -1737,6 +1750,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa case 8: try decoder.decodeSingularMessageField(value: &_storage._quote) case 10: try decoder.decodeRepeatedMessageField(value: &_storage._preview) case 101: try decoder.decodeSingularMessageField(value: &_storage._profile) + case 102: try decoder.decodeSingularMessageField(value: &_storage._openGroupInvitation) case 104: try decoder.decodeSingularMessageField(value: &_storage._closedGroupControlMessage) case 105: try decoder.decodeSingularStringField(value: &_storage._syncTarget) default: break @@ -1777,6 +1791,9 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa if let v = _storage._profile { try visitor.visitSingularMessageField(value: v, fieldNumber: 101) } + if let v = _storage._openGroupInvitation { + try visitor.visitSingularMessageField(value: v, fieldNumber: 102) + } if let v = _storage._closedGroupControlMessage { try visitor.visitSingularMessageField(value: v, fieldNumber: 104) } @@ -1802,6 +1819,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa if _storage._quote != rhs_storage._quote {return false} if _storage._preview != rhs_storage._preview {return false} if _storage._profile != rhs_storage._profile {return false} + if _storage._openGroupInvitation != rhs_storage._openGroupInvitation {return false} if _storage._closedGroupControlMessage != rhs_storage._closedGroupControlMessage {return false} if _storage._syncTarget != rhs_storage._syncTarget {return false} return true diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 2e22da754..bdb101bcc 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -147,6 +147,7 @@ message DataMessage { optional Quote quote = 8; repeated Preview preview = 10; optional LokiProfile profile = 101; + optional OpenGroupInvitation openGroupInvitation = 102; optional ClosedGroupControlMessage closedGroupControlMessage = 104; optional string syncTarget = 105; } From 216c4a92e524875048acc7ae063a70e96bfa6936 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 7 May 2021 15:06:20 +1000 Subject: [PATCH 08/57] Debug --- .../Settings/OWSConversationSettingsViewController.m | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Session/Conversations/Settings/OWSConversationSettingsViewController.m b/Session/Conversations/Settings/OWSConversationSettingsViewController.m index ae8ba7aa0..2adb62bed 100644 --- a/Session/Conversations/Settings/OWSConversationSettingsViewController.m +++ b/Session/Conversations/Settings/OWSConversationSettingsViewController.m @@ -1115,14 +1115,13 @@ CGFloat kIconViewLength = 24; SNVisibleMessage *message = [SNVisibleMessage new]; message.sentTimestamp = [NSDate millisecondTimestamp]; message.openGroupInvitation = [[SNOpenGroupInvitation alloc] initWithName:openGroup.name url:url]; - TSOutgoingMessage *tsMessage = [TSOutgoingMessage from:message associatedWith:self.thread]; + TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactSessionID:user]; + TSOutgoingMessage *tsMessage = [TSOutgoingMessage from:message associatedWith:thread]; [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [tsMessage saveWithTransaction:transaction]; }]; [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSContactThread *thread = [TSContactThread getThreadWithContactSessionID:user transaction:transaction]; [SNMessageSender send:message inThread:thread usingTransaction:transaction]; - [self.navigationController popViewControllerAnimated:YES]; }]; } }]; From e4d1925436c4fe7d4a5e39f2f3b69dbceefd1787 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 7 May 2021 15:18:57 +1000 Subject: [PATCH 09/57] Show confirmation dialog --- Session.xcodeproj/project.pbxproj | 4 ++ .../ConversationVC+Interaction.swift | 11 +++ .../Views & Modals/JoinOpenGroupModal.swift | 68 +++++++++++++++++++ .../Messages/Signal/TSMessage.m | 2 + 4 files changed, 85 insertions(+) create mode 100644 Session/Conversations/Views & Modals/JoinOpenGroupModal.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6cc48e38b..dc09384c8 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -202,6 +202,7 @@ B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E7134E251C867C009649BB /* Sodium+Conversion.swift */; }; B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; }; B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; }; + B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */; }; B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */; }; B879D449247E1BE300DB3608 /* PathVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B879D448247E1BE300DB3608 /* PathVC.swift */; }; B87EF17126367CF800124B3C /* FileServerAPIV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87EF17026367CF800124B3C /* FileServerAPIV2.swift */; }; @@ -1198,6 +1199,7 @@ B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = ""; }; B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = ""; }; B87588582644CA9D000E60D0 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinOpenGroupModal.swift; sourceTree = ""; }; B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = ""; }; B879D448247E1BE300DB3608 /* PathVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathVC.swift; sourceTree = ""; }; B879D44A247E1D9200DB3608 /* PathStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathStatusView.swift; sourceTree = ""; }; @@ -2139,6 +2141,7 @@ B8F5F72225F1B4CA003BF8D4 /* DownloadAttachmentModal.swift */, C3A76A8C25DB83F90074CB90 /* PermissionMissingModal.swift */, B821494525D4D6FF009C0F2A /* URLModal.swift */, + B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */, B821494E25D4E163009C0F2A /* BodyTextView.swift */, B82149B725D60393009C0F2A /* BlockedModal.swift */, C374EEE125DA26740073A857 /* LinkPreviewModal.swift */, @@ -4927,6 +4930,7 @@ B85357C323A1BD1200AAF6CD /* SeedVC.swift in Sources */, 45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */, B8D84ECF25E3108A005A043E /* ExpandingAttachmentsButton.swift in Sources */, + B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */, B8CCF6432397711F0091D419 /* SettingsVC.swift in Sources */, C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */, 3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */, diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 1dbdfea19..61d2c37bf 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -447,6 +447,9 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc // Scroll to the source of the reply guard let indexPath = viewModel.ensureLoadWindowContainsQuotedReply(reply) else { return } messagesTableView.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.middle, animated: true) + } else if let message = viewItem.interaction as? TSIncomingMessage, let name = message.openGroupInvitationName, + let url = message.openGroupInvitationURL { + joinOpenGroup(name: name, url: url) } default: break } @@ -555,6 +558,14 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc present(urlModal, animated: true, completion: nil) } + func joinOpenGroup(name: String, url: String) { + // Open groups can be unsafe, so always ask the user whether they want to join one + let joinOpenGroupModal = JoinOpenGroupModal(name: name, url: url) + joinOpenGroupModal.modalPresentationStyle = .overFullScreen + joinOpenGroupModal.modalTransitionStyle = .crossDissolve + present(joinOpenGroupModal, animated: true, completion: nil) + } + func handleReplyButtonTapped(for viewItem: ConversationViewItem) { reply(viewItem) } diff --git a/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift b/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift new file mode 100644 index 000000000..58bd69644 --- /dev/null +++ b/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift @@ -0,0 +1,68 @@ + +final class JoinOpenGroupModal : Modal { + private let name: String + private let url: String + + // MARK: Lifecycle + init(name: String, url: String) { + self.name = name + self.url = url + super.init(nibName: nil, bundle: nil) + } + + override init(nibName: String?, bundle: Bundle?) { + preconditionFailure("Use init(name:url:) instead.") + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init(name:url:) instead.") + } + + override func populateContentView() { + // Title + let titleLabel = UILabel() + titleLabel.textColor = Colors.text + titleLabel.font = .boldSystemFont(ofSize: Values.largeFontSize) + titleLabel.text = "Join \(name)?" + titleLabel.textAlignment = .center + // Message + let messageLabel = UILabel() + messageLabel.textColor = Colors.text + messageLabel.font = .systemFont(ofSize: Values.smallFontSize) + let message = "Are you sure you want to join the \(name) open group?"; + let attributedMessage = NSMutableAttributedString(string: message) + attributedMessage.addAttributes([ .font : UIFont.boldSystemFont(ofSize: Values.smallFontSize) ], range: (message as NSString).range(of: name)) + messageLabel.attributedText = attributedMessage + messageLabel.numberOfLines = 0 + messageLabel.lineBreakMode = .byWordWrapping + messageLabel.textAlignment = .center + // Join button + let joinButton = UIButton() + joinButton.set(.height, to: Values.mediumButtonHeight) + joinButton.layer.cornerRadius = Modal.buttonCornerRadius + joinButton.backgroundColor = Colors.buttonBackground + joinButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) + joinButton.setTitleColor(Colors.text, for: UIControl.State.normal) + joinButton.setTitle("Join", for: UIControl.State.normal) + joinButton.addTarget(self, action: #selector(openURL), for: UIControl.Event.touchUpInside) + // Button stack view + let buttonStackView = UIStackView(arrangedSubviews: [ cancelButton, joinButton ]) + buttonStackView.axis = .horizontal + buttonStackView.spacing = Values.mediumSpacing + buttonStackView.distribution = .fillEqually + // Main stack view + let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, messageLabel, buttonStackView ]) + mainStackView.axis = .vertical + mainStackView.spacing = Values.largeSpacing + contentView.addSubview(mainStackView) + mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing) + mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) + contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing) + contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing) + } + + // MARK: Interaction + @objc private func openURL() { + + } +} diff --git a/SessionMessagingKit/Messages/Signal/TSMessage.m b/SessionMessagingKit/Messages/Signal/TSMessage.m index 8ea5f61c2..5b0634fe8 100644 --- a/SessionMessagingKit/Messages/Signal/TSMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSMessage.m @@ -346,6 +346,8 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024; return bodyDescription; } else if (attachmentDescription.length > 0) { return attachmentDescription; + } else if (self.openGroupInvitationName != nil) { + return @"😎 Open group invitation"; } else { // TODO: We should do better here. return @""; From 539b13d18922a2248f2777277576485fd873d98b Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 7 May 2021 15:22:48 +1000 Subject: [PATCH 10/57] fix inconsistent voice message durations --- .../Sending & Receiving/Attachments/TSAttachmentStream.m | 1 + 1 file changed, 1 insertion(+) diff --git a/SessionMessagingKit/Sending & Receiving/Attachments/TSAttachmentStream.m b/SessionMessagingKit/Sending & Receiving/Attachments/TSAttachmentStream.m index 8b3d94a30..11daf4eeb 100644 --- a/SessionMessagingKit/Sending & Receiving/Attachments/TSAttachmentStream.m +++ b/SessionMessagingKit/Sending & Receiving/Attachments/TSAttachmentStream.m @@ -523,6 +523,7 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail); // Ignore "invalid audio file" errors. return 0.f; } + [audioPlayer prepareToPlay]; if (!error) { return (CGFloat)[audioPlayer duration]; } else { From 4eff61595a7d3d92f8ba2365199424c2ae8fd7ad Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 7 May 2021 15:29:54 +1000 Subject: [PATCH 11/57] Implement open group joining business logic --- .../Views & Modals/JoinOpenGroupModal.swift | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift b/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift index 58bd69644..0269bb943 100644 --- a/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift +++ b/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift @@ -44,7 +44,7 @@ final class JoinOpenGroupModal : Modal { joinButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) joinButton.setTitleColor(Colors.text, for: UIControl.State.normal) joinButton.setTitle("Join", for: UIControl.State.normal) - joinButton.addTarget(self, action: #selector(openURL), for: UIControl.Event.touchUpInside) + joinButton.addTarget(self, action: #selector(joinOpenGroup), for: UIControl.Event.touchUpInside) // Button stack view let buttonStackView = UIStackView(arrangedSubviews: [ cancelButton, joinButton ]) buttonStackView.axis = .horizontal @@ -62,7 +62,24 @@ final class JoinOpenGroupModal : Modal { } // MARK: Interaction - @objc private func openURL() { - + @objc private func joinOpenGroup() { + guard let (room, server, publicKey) = OpenGroupManagerV2.parseV2OpenGroup(from: url), Features.useV2OpenGroups else { + let alert = UIAlertController(title: "Couldn't Join", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)) + return presentingViewController!.present(alert, animated: true, completion: nil) + } + presentingViewController!.dismiss(animated: true, completion: nil) + Storage.shared.write { [presentingViewController = self.presentingViewController!] transaction in + OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction) + .done(on: DispatchQueue.main) { _ in + let appDelegate = UIApplication.shared.delegate as! AppDelegate + appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) + } + .catch(on: DispatchQueue.main) { error in + let alert = UIAlertController(title: "Couldn't Join", message: error.localizedDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)) + presentingViewController.present(alert, animated: true, completion: nil) + } + } } } From 00300afecb20385e97fef846b8f2e0a4bbc5d53d Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 7 May 2021 15:47:40 +1000 Subject: [PATCH 12/57] Swap out sent open group invitation icon --- .../Content Views/OpenGroupInvitationView.swift | 7 +++++-- .../Conversations/Message Cells/VisibleMessageCell.swift | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift b/Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift index bd328fe8d..7a1065044 100644 --- a/Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift +++ b/Session/Conversations/Message Cells/Content Views/OpenGroupInvitationView.swift @@ -3,6 +3,7 @@ final class OpenGroupInvitationView : UIView { private let name: String private let rawURL: String private let textColor: UIColor + private let isOutgoing: Bool private lazy var url: String = { if let range = rawURL.range(of: "?public_key=") { @@ -17,10 +18,11 @@ final class OpenGroupInvitationView : UIView { private static let iconImageViewSize: CGFloat = 48 // MARK: Lifecycle - init(name: String, url: String, textColor: UIColor) { + init(name: String, url: String, textColor: UIColor, isOutgoing: Bool) { self.name = name self.rawURL = url self.textColor = textColor + self.isOutgoing = isOutgoing super.init(frame: CGRect.zero) setUpViewHierarchy() } @@ -58,7 +60,8 @@ final class OpenGroupInvitationView : UIView { labelStackView.axis = .vertical // Icon let iconSize = OpenGroupInvitationView.iconSize - let icon = UIImage(named: "Plus")?.withTint(.white)?.resizedImage(to: CGSize(width: iconSize, height: iconSize)) + let iconName = isOutgoing ? "Globe" : "Plus" + let icon = UIImage(named: iconName)?.withTint(.white)?.resizedImage(to: CGSize(width: iconSize, height: iconSize)) let iconImageViewSize = OpenGroupInvitationView.iconImageViewSize let iconImageView = UIImageView(image: icon) iconImageView.contentMode = .center diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 064065f84..d6c1e2977 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -321,7 +321,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { snContentView.addSubview(linkPreviewView) linkPreviewView.pin(to: snContentView) } else if let openGroupInvitationName = message.openGroupInvitationName, let openGroupInvitationURL = message.openGroupInvitationURL { - let openGroupInvitationView = OpenGroupInvitationView(name: openGroupInvitationName, url: openGroupInvitationURL, textColor: bodyLabelTextColor) + let openGroupInvitationView = OpenGroupInvitationView(name: openGroupInvitationName, url: openGroupInvitationURL, textColor: bodyLabelTextColor, isOutgoing: isOutgoing) snContentView.addSubview(openGroupInvitationView) openGroupInvitationView.pin(to: snContentView) } else { From 428a61f9f93b1079120c7478985b98018bb70b6b Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 7 May 2021 15:49:06 +1000 Subject: [PATCH 13/57] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index dc09384c8..7212d2ca3 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5135,7 +5135,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 238; + CURRENT_PROJECT_VERSION = 239; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5204,7 +5204,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 238; + CURRENT_PROJECT_VERSION = 239; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5265,7 +5265,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 238; + CURRENT_PROJECT_VERSION = 239; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5335,7 +5335,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 238; + CURRENT_PROJECT_VERSION = 239; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6220,7 +6220,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 238; + CURRENT_PROJECT_VERSION = 239; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6288,7 +6288,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 238; + CURRENT_PROJECT_VERSION = 239; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From b694777a71575994b7c916ba580fd88265044d9b Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 12 May 2021 09:13:53 +1000 Subject: [PATCH 14/57] Fix snode pool updating bug --- SessionSnodeKit/SnodeAPI.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 50f3f7351..977028c44 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -184,10 +184,12 @@ public final class SnodeAPI : NSObject { } let snodePoolPromises: [Promise>] = snodes.map { snode in return attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) { + // Don't specify a limit in the request. Service nodes return a shuffled + // list of nodes so if we specify a limit the 3 responses we get might have + // very little overlap. let parameters: JSON = [ "endpoint" : "get_service_nodes", "params" : [ - "limit" : 256, "active_only" : true, "fields" : [ "public_ip" : true, "storage_port" : true, "pubkey_ed25519" : true, "pubkey_x25519" : true @@ -214,7 +216,9 @@ public final class SnodeAPI : NSObject { var result: Set = results[0] results.forEach { result = result.union($0) } if result.count > 24 { // We want the snodes to agree on at least this many snodes - return result + // Limit the snode pool size to 256 so that we don't go too long without + // refreshing it + return (result.count > 256) ? result[..<256] : result } else { throw Error.inconsistentSnodePools } From ce90620a2310b29adb867eb4eaa0767348633e16 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 12 May 2021 09:14:23 +1000 Subject: [PATCH 15/57] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 7212d2ca3..6afcd65f9 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5135,7 +5135,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 239; + CURRENT_PROJECT_VERSION = 240; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5204,7 +5204,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 239; + CURRENT_PROJECT_VERSION = 240; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5265,7 +5265,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 239; + CURRENT_PROJECT_VERSION = 240; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5335,7 +5335,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 239; + CURRENT_PROJECT_VERSION = 240; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6220,7 +6220,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 239; + CURRENT_PROJECT_VERSION = 240; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6288,7 +6288,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 239; + CURRENT_PROJECT_VERSION = 240; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From 5c699d6bb77fc4edad4eeab97c8fde49bd7f9d8b Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 12 May 2021 09:23:19 +1000 Subject: [PATCH 16/57] Fix inverted operation --- SessionSnodeKit/SnodeAPI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 977028c44..f35e8ee1c 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -214,7 +214,7 @@ public final class SnodeAPI : NSObject { } let promise = when(fulfilled: snodePoolPromises).map2 { results -> Set in var result: Set = results[0] - results.forEach { result = result.union($0) } + results.forEach { result = result.intersection($0) } if result.count > 24 { // We want the snodes to agree on at least this many snodes // Limit the snode pool size to 256 so that we don't go too long without // refreshing it From 1b68c109eeff9d2a444bbdb4b8f1a58cd2ceabeb Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 12 May 2021 10:33:02 +1000 Subject: [PATCH 17/57] Fix build --- SessionSnodeKit/SnodeAPI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index f35e8ee1c..dabc398ac 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -218,7 +218,7 @@ public final class SnodeAPI : NSObject { if result.count > 24 { // We want the snodes to agree on at least this many snodes // Limit the snode pool size to 256 so that we don't go too long without // refreshing it - return (result.count > 256) ? result[..<256] : result + return (result.count > 256) ? Set([Snode](result)[0..<256]) : result } else { throw Error.inconsistentSnodePools } From 17688a66e674733ba65db56dfbf7d6a526d3541c Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 12 May 2021 10:33:29 +1000 Subject: [PATCH 18/57] Make closed group polling variable rate --- Session/Home/HomeVC.swift | 2 +- Session/Meta/AppDelegate.h | 2 - Session/Meta/AppDelegate.m | 18 +-- Session/Meta/AppDelegate.swift | 9 ++ .../MessageReceiver+Handling.swift | 3 + .../MessageSender+ClosedGroups.swift | 3 + .../Pollers/ClosedGroupPoller.swift | 145 ++++++++++++------ .../Pollers/OpenGroupPoller.swift | 2 +- 8 files changed, 114 insertions(+), 70 deletions(-) diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 151cc0e0b..c9418248d 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -142,7 +142,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv if OWSIdentityManager.shared().identityKeyPair() != nil { let appDelegate = UIApplication.shared.delegate as! AppDelegate appDelegate.startPollerIfNeeded() - appDelegate.startClosedGroupPollerIfNeeded() + appDelegate.startClosedGroupPoller() appDelegate.startOpenGroupPollersIfNeeded() // Do this only if we created a new Session ID, or if we already received the initial configuration message if UserDefaults.standard[.hasSyncedInitialConfiguration] { diff --git a/Session/Meta/AppDelegate.h b/Session/Meta/AppDelegate.h index ac88f3f32..76cf25ce5 100644 --- a/Session/Meta/AppDelegate.h +++ b/Session/Meta/AppDelegate.h @@ -10,8 +10,6 @@ extern NSString *const AppDelegateStoryboardMain; - (void)startPollerIfNeeded; - (void)stopPoller; -- (void)startClosedGroupPollerIfNeeded; -- (void)stopClosedGroupPoller; - (void)startOpenGroupPollersIfNeeded; - (void)stopOpenGroupPollers; diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index d8193af9b..83ca336d8 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -48,7 +48,6 @@ static NSTimeInterval launchStartedAt; @property (nonatomic) BOOL areVersionMigrationsComplete; @property (nonatomic) BOOL didAppLaunchFail; @property (nonatomic) LKPoller *poller; -@property (nonatomic) LKClosedGroupPoller *closedGroupPoller; @end @@ -414,7 +413,7 @@ static NSTimeInterval launchStartedAt; [[SNSnodeAPI getSnodePool] retainUntilComplete]; [self startPollerIfNeeded]; - [self startClosedGroupPollerIfNeeded]; + [self startClosedGroupPoller]; [self startOpenGroupPollersIfNeeded]; if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) { @@ -564,7 +563,7 @@ static NSTimeInterval launchStartedAt; [self.readReceiptManager setAreReadReceiptsEnabled:YES]; [self startPollerIfNeeded]; - [self startClosedGroupPollerIfNeeded]; + [self startClosedGroupPoller]; [self startOpenGroupPollersIfNeeded]; } } @@ -727,19 +726,6 @@ static NSTimeInterval launchStartedAt; - (void)stopPoller { [self.poller stop]; } -- (void)startClosedGroupPollerIfNeeded -{ - if (self.closedGroupPoller == nil) { - NSString *userPublicKey = [SNGeneralUtilities getUserPublicKey]; - if (userPublicKey != nil) { - self.closedGroupPoller = [[LKClosedGroupPoller alloc] init]; - } - } - [self.closedGroupPoller startIfNeeded]; -} - -- (void)stopClosedGroupPoller { [self.closedGroupPoller stop]; } - - (void)startOpenGroupPollersIfNeeded { [SNOpenGroupManager.shared startPolling]; diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index b2b65d728..56e4100b2 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -31,4 +31,13 @@ extension AppDelegate { } return promise } + + @objc func startClosedGroupPoller() { + guard OWSIdentityManager.shared().identityKeyPair() != nil else { return } + ClosedGroupPoller.shared.start() + } + + @objc func stopClosedGroupPoller() { + ClosedGroupPoller.shared.stop() + } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 4bf04e96f..c37be5df7 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -408,6 +408,8 @@ extension MessageReceiver { Storage.shared.addClosedGroupEncryptionKeyPair(encryptionKeyPair, for: groupPublicKey, using: transaction) // Store the formation timestamp Storage.shared.setClosedGroupFormationTimestamp(to: messageSentTimestamp, for: groupPublicKey, using: transaction) + // Start polling + ClosedGroupPoller.shared.startPolling(for: groupPublicKey) // Notify the PN server let _ = PushNotificationAPI.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey()) } @@ -539,6 +541,7 @@ extension MessageReceiver { if wasCurrentUserRemoved { Storage.shared.removeClosedGroupPublicKey(groupPublicKey, using: transaction) Storage.shared.removeAllClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction) + ClosedGroupPoller.shared.stopPolling(for: groupPublicKey) let _ = PushNotificationAPI.performOperation(.unsubscribe, for: groupPublicKey, publicKey: userPublicKey) } let storage = SNMessagingKitConfiguration.shared.storage diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift index 2fac0e5ea..c570b7278 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift @@ -43,6 +43,8 @@ extension MessageSender { // Notify the user let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .groupCreated) infoMessage.save(with: transaction) + // Start polling + ClosedGroupPoller.shared.startPolling(for: groupPublicKey) // Return return when(fulfilled: promises).map2 { thread } } @@ -272,6 +274,7 @@ extension MessageSender { // Remove the group from the database and unsubscribe from PNs Storage.shared.removeAllClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction) Storage.shared.removeClosedGroupPublicKey(groupPublicKey, using: transaction) + ClosedGroupPoller.shared.stopPolling(for: groupPublicKey) let _ = PushNotificationAPI.performOperation(.unsubscribe, for: groupPublicKey, publicKey: userPublicKey) } }.map { _ in } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 111d57152..b066cd300 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -3,11 +3,12 @@ import PromiseKit @objc(LKClosedGroupPoller) public final class ClosedGroupPoller : NSObject { - private var isPolling = false - private var timer: Timer? + private var isPolling: [String:Bool] = [:] + private var timers: [String:Timer] = [:] // MARK: Settings - private static let pollInterval: TimeInterval = 3 + private static let minPollInterval: Double = 4 + private static let maxPollInterval: Double = 2 * 60 // MARK: Error private enum Error : LocalizedError { @@ -22,65 +23,109 @@ public final class ClosedGroupPoller : NSObject { } } + // MARK: Initialization + public static let shared = ClosedGroupPoller() + + private override init() { } + // MARK: Public API - @objc public func startIfNeeded() { + @objc public func start() { #if DEBUG assert(Thread.current.isMainThread) // Timers don't do well on background queues #endif - guard !isPolling else { return } - isPolling = true - timer = Timer.scheduledTimer(withTimeInterval: ClosedGroupPoller.pollInterval, repeats: true) { [weak self] _ in - let _ = self?.poll() - } + let storage = SNMessagingKitConfiguration.shared.storage + let allGroupPublicKeys = storage.getUserClosedGroupPublicKeys() + allGroupPublicKeys.forEach { startPolling(for: $0) } } - public func pollOnce() -> [Promise] { - guard !isPolling else { return [] } - isPolling = true - return poll() + public func startPolling(for groupPublicKey: String) { + guard !isPolling(for: groupPublicKey) else { return } + setUpPolling(for: groupPublicKey) + isPolling[groupPublicKey] = true } @objc public func stop() { - isPolling = false - timer?.invalidate() + let storage = SNMessagingKitConfiguration.shared.storage + let allGroupPublicKeys = storage.getUserClosedGroupPublicKeys() + allGroupPublicKeys.forEach { stopPolling(for: $0) } + } + + public func stopPolling(for groupPublicKey: String) { + timers[groupPublicKey]?.invalidate() + isPolling[groupPublicKey] = false } // MARK: Private API - private func poll() -> [Promise] { - guard isPolling else { return [] } - let publicKeys = Storage.shared.getUserClosedGroupPublicKeys() - return publicKeys.map { publicKey in - let promise = SnodeAPI.getSwarm(for: publicKey).then2 { [weak self] swarm -> Promise<[JSON]> in - // randomElement() uses the system's default random generator, which is cryptographically secure - guard let snode = swarm.randomElement() else { return Promise(error: Error.insufficientSnodes) } - guard let self = self, self.isPolling else { return Promise(error: Error.pollingCanceled) } - return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).map2 { - SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: publicKey) - } - } - promise.done2 { [weak self] messages in - guard let self = self, self.isPolling else { return } - if !messages.isEmpty { - SNLog("Received \(messages.count) new message(s) in closed group with public key: \(publicKey).") - } - messages.forEach { json in - guard let envelope = SNProtoEnvelope.from(json) else { return } - do { - let data = try envelope.serializedData() - let job = MessageReceiveJob(data: data, isBackgroundPoll: false) - SNMessagingKitConfiguration.shared.storage.write { transaction in - SessionMessagingKit.JobQueue.shared.add(job, using: transaction) - } - } catch { - SNLog("Failed to deserialize envelope due to error: \(error).") - } - } - } - promise.catch2 { error in - SNLog("Polling failed for closed group with public key: \(publicKey) due to error: \(error).") - } - promise.retainUntilComplete() - return promise.map { _ in } + private func setUpPolling(for groupPublicKey: String) { + poll(groupPublicKey).done2 { [weak self] _ in + self?.pollRecursively(groupPublicKey) + }.catch2 { [weak self] error in + // The error is logged in poll(_:isBackgroundPoll:) + self?.pollRecursively(groupPublicKey) } } + + private func pollRecursively(_ groupPublicKey: String) { + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) + guard isPolling(for: groupPublicKey), + let thread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID)) else { return } + // Get the received date of the last message in the thread. If we don't have any messages yet, pick some + // reasonable fake time interval to use instead. + let lastMessageDate = + (thread.numberOfInteractions() > 0) ? thread.lastInteraction.receivedAtDate() : Date().addingTimeInterval(-5 * 60) + let timeSinceLastMessage = Date().timeIntervalSince(lastMessageDate) + let minPollInterval = ClosedGroupPoller.minPollInterval + let limit: Double = 12 * 60 * 60 + let a = (ClosedGroupPoller.maxPollInterval - minPollInterval) / limit + let nextPollInterval = a * min(timeSinceLastMessage, limit) + minPollInterval + SNLog("Next poll interval for closed group with public key: \(groupPublicKey) is \(nextPollInterval) s.") + timers[groupPublicKey] = Timer.scheduledTimer(withTimeInterval: nextPollInterval, repeats: false) { [weak self] timer in + timer.invalidate() + self?.poll(groupPublicKey).done2 { _ in + self?.pollRecursively(groupPublicKey) + }.catch2 { error in + // The error is logged in poll(_:) + self?.pollRecursively(groupPublicKey) + } + } + } + + private func poll(_ groupPublicKey: String) -> Promise { + guard isPolling(for: groupPublicKey) else { return Promise.value(()) } + let promise = SnodeAPI.getSwarm(for: groupPublicKey).then2 { [weak self] swarm -> Promise<[JSON]> in + // randomElement() uses the system's default random generator, which is cryptographically secure + guard let snode = swarm.randomElement() else { return Promise(error: Error.insufficientSnodes) } + guard let self = self, self.isPolling(for: groupPublicKey) else { return Promise(error: Error.pollingCanceled) } + return SnodeAPI.getRawMessages(from: snode, associatedWith: groupPublicKey).map2 { + SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: groupPublicKey) + } + } + promise.done2 { [weak self] rawMessages in + guard let self = self, self.isPolling(for: groupPublicKey) else { return } + if !rawMessages.isEmpty { + SNLog("Received \(rawMessages.count) new message(s) in closed group with public key: \(groupPublicKey).") + } + rawMessages.forEach { json in + guard let envelope = SNProtoEnvelope.from(json) else { return } + do { + let data = try envelope.serializedData() + let job = MessageReceiveJob(data: data, isBackgroundPoll: false) + SNMessagingKitConfiguration.shared.storage.write { transaction in + SessionMessagingKit.JobQueue.shared.add(job, using: transaction) + } + } catch { + SNLog("Failed to deserialize envelope due to error: \(error).") + } + } + } + promise.catch2 { error in + SNLog("Polling failed for closed group with public key: \(groupPublicKey) due to error: \(error).") + } + return promise.map { _ in } + } + + // MARK: Convenience + private func isPolling(for groupPublicKey: String) -> Bool { + return isPolling[groupPublicKey] ?? false + } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 4dfc780a0..ff8dc35f7 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -18,7 +18,7 @@ public final class OpenGroupPoller : NSObject { } // MARK: Settings - private let pollForNewMessagesInterval: TimeInterval = 8 + private let pollForNewMessagesInterval: TimeInterval = 20 private let pollForDeletedMessagesInterval: TimeInterval = 30 private let pollForModeratorsInterval: TimeInterval = 10 * 60 From 73eeff3ea1a5758c13b6d41e0ec97d44c8da51ac Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 12 May 2021 10:40:42 +1000 Subject: [PATCH 19/57] Debug --- .../Pollers/ClosedGroupPoller.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index b066cd300..2765b541a 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -58,10 +58,14 @@ public final class ClosedGroupPoller : NSObject { // MARK: Private API private func setUpPolling(for groupPublicKey: String) { poll(groupPublicKey).done2 { [weak self] _ in - self?.pollRecursively(groupPublicKey) + DispatchQueue.main.async { // Timers don't do well on background queues + self?.pollRecursively(groupPublicKey) + } }.catch2 { [weak self] error in - // The error is logged in poll(_:isBackgroundPoll:) - self?.pollRecursively(groupPublicKey) + // The error is logged in poll(_:) + DispatchQueue.main.async { // Timers don't do well on background queues + self?.pollRecursively(groupPublicKey) + } } } @@ -82,10 +86,14 @@ public final class ClosedGroupPoller : NSObject { timers[groupPublicKey] = Timer.scheduledTimer(withTimeInterval: nextPollInterval, repeats: false) { [weak self] timer in timer.invalidate() self?.poll(groupPublicKey).done2 { _ in - self?.pollRecursively(groupPublicKey) + DispatchQueue.main.async { // Timers don't do well on background queues + self?.pollRecursively(groupPublicKey) + } }.catch2 { error in // The error is logged in poll(_:) - self?.pollRecursively(groupPublicKey) + DispatchQueue.main.async { // Timers don't do well on background queues + self?.pollRecursively(groupPublicKey) + } } } } From 0bff796ab1eef04597f04e35ac70168c23f4e62c Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 13 May 2021 13:24:48 +1000 Subject: [PATCH 20/57] Fix slight misalignment --- Session/Open Groups/OpenGroupSuggestionGrid.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Open Groups/OpenGroupSuggestionGrid.swift b/Session/Open Groups/OpenGroupSuggestionGrid.swift index 3bc78f7a1..b62af71fc 100644 --- a/Session/Open Groups/OpenGroupSuggestionGrid.swift +++ b/Session/Open Groups/OpenGroupSuggestionGrid.swift @@ -155,7 +155,7 @@ extension OpenGroupSuggestionGrid { stackView.spacing = Values.smallSpacing snContentView.addSubview(stackView) stackView.center(.vertical, in: snContentView) - stackView.pin(.leading, to: .leading, of: snContentView, withInset: Values.smallSpacing) + stackView.pin(.leading, to: .leading, of: snContentView, withInset: 4) snContentView.trailingAnchor.constraint(greaterThanOrEqualTo: stackView.trailingAnchor, constant: Values.smallSpacing).isActive = true snContentView.pin(to: self, withInset: Cell.contentViewInset) } From 3d35face94e33646551eed67af713cd748694594 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 13 May 2021 14:33:44 +1000 Subject: [PATCH 21/57] Fix crash --- SignalUtilitiesKit/To Do/OWSProfileManager.m | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/SignalUtilitiesKit/To Do/OWSProfileManager.m b/SignalUtilitiesKit/To Do/OWSProfileManager.m index ddc3f3c54..a451670c3 100644 --- a/SignalUtilitiesKit/To Do/OWSProfileManager.m +++ b/SignalUtilitiesKit/To Do/OWSProfileManager.m @@ -362,14 +362,10 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); NSData *encryptedAvatarData = [self encryptProfileData:avatarData profileKey:newProfileKey]; OWSAssertDebug(encryptedAvatarData.length > 0); - AnyPromise *promise; - if (SNFeatures.useV2FileServer) { - promise = [SNFileServerAPIV2 upload:encryptedAvatarData]; - } else { - promise = [SNFileServerAPI uploadProfilePicture:encryptedAvatarData]; - } + AnyPromise *promise = [SNFileServerAPIV2 upload:encryptedAvatarData]; - [promise.thenOn(dispatch_get_main_queue(), ^(NSString *downloadURL) { + [promise.thenOn(dispatch_get_main_queue(), ^(uint64_t fileID) { + NSString *downloadURL = [NSString stringWithFormat:@"%@/files/%llu", SNFileServerAPIV2.server, fileID]; [NSUserDefaults.standardUserDefaults setObject:[NSDate new] forKey:@"lastProfilePictureUpload"]; [self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{ successBlock(downloadURL); From 40ef02ac00c419193ae6b8ff1c73c775a1ea93f3 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 13 May 2021 14:38:42 +1000 Subject: [PATCH 22/57] Update translations --- .../Translations/es.lproj/Localizable.strings | 89 +-- .../Translations/fr.lproj/Localizable.strings | 77 +-- .../Translations/nl.lproj/Localizable.strings | 539 +++++++++--------- .../pt_BR.lproj/Localizable.strings | 5 +- 4 files changed, 356 insertions(+), 354 deletions(-) diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index f7b0dc486..b85a2405d 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Message format for the 'new app version available' alert. Embeds: {{The latest app version number}} */ "APP_UPDATE_NAG_ALERT_MESSAGE_FORMAT" = "La versión %@ está disponible en la App Store."; /* Title for the 'new app version available' alert. */ -"APP_UPDATE_NAG_ALERT_TITLE" = "Hay disponible una nueva versión de Session"; +"APP_UPDATE_NAG_ALERT_TITLE" = "Hay una nueva versión de Session disponible"; /* Label for the 'update' button in the 'new app version available' alert. */ "APP_UPDATE_NAG_ALERT_UPDATE_BUTTON" = "Actualizar"; /* No comment provided by engineer. */ @@ -97,11 +97,11 @@ /* Alert body */ "CONFIRM_LEAVE_GROUP_DESCRIPTION" = "No podrás enviar o recibir más mensajes en este grupo."; /* Alert title */ -"CONFIRM_LEAVE_GROUP_TITLE" = "¿De verdad deseas abandonar el grupo?"; +"CONFIRM_LEAVE_GROUP_TITLE" = "¿De verdad quieres abandonar el grupo?"; /* Message for the 'conversation delete confirmation' alert. */ "CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE" = "Este paso no se puede deshacer."; /* Title for the 'conversation delete confirmation' alert. */ -"CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE" = "¿Eliminar chat?"; +"CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE" = "¿Eliminar conversación?"; /* keyboard toolbar label when no messages match the search string */ "CONVERSATION_SEARCH_NO_RESULTS" = "Sin resultados"; /* keyboard toolbar label when exactly 1 message matches the search string */ @@ -137,7 +137,7 @@ /* Title for the 'crop/scale image' dialog. */ "CROP_SCALE_IMAGE_VIEW_TITLE" = "Editar foto"; /* Subtitle shown while the app is updating its database. */ -"DATABASE_VIEW_OVERLAY_SUBTITLE" = "Puede tomar unos minutos."; +"DATABASE_VIEW_OVERLAY_SUBTITLE" = "Esto puede tomar unos minutos."; /* Title shown while the app is updating its database. */ "DATABASE_VIEW_OVERLAY_TITLE" = "Optimizando base de datos"; /* Format string for a relative time, expressed as a certain number of hours in the past. Embeds {{The number of hours}}. */ @@ -181,7 +181,7 @@ /* No comment provided by engineer. */ "GROUP_MEMBER_LEFT" = "%@ ha abandonado el grupo."; /* No comment provided by engineer. */ -"GROUP_MEMBER_REMOVED" = " %@ was removed from the group. "; +"GROUP_MEMBER_REMOVED" = " Fue eliminado del grupo. "; /* No comment provided by engineer. */ "GROUP_MEMBERS_REMOVED" = " %@ were removed from the group. "; /* No comment provided by engineer. */ @@ -191,7 +191,7 @@ /* No comment provided by engineer. */ "GROUP_YOU_LEFT" = "Has abandonado el grupo."; /* No comment provided by engineer. */ -"YOU_WERE_REMOVED" = " You were removed from the group. "; +"YOU_WERE_REMOVED" = " Has sido eliminado del grupo. "; /* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */ "IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "No se pueden compartir más de %@ objetos."; /* alert title */ @@ -337,7 +337,7 @@ /* Setting for enabling & disabling link previews. */ "SETTINGS_LINK_PREVIEWS" = "Enviar previsualizaciones"; /* Footer for setting for enabling & disabling link previews. */ -"SETTINGS_LINK_PREVIEWS_FOOTER" = "Previews are supported for Imgur, Instagram, Pinterest, Reddit, and YouTube links."; +"SETTINGS_LINK_PREVIEWS_FOOTER" = "Las previsualizaciones están disponibles para enlaces hacia Imgur, Instagram, Pinterest, Reddit y YouTube."; /* Header for setting for enabling & disabling link previews. */ "SETTINGS_LINK_PREVIEWS_HEADER" = "Previsualizar enlaces"; /* table section header */ @@ -446,8 +446,8 @@ "vc_path_title" = "Ruta"; "vc_path_explanation" = "Session oculta tu dirección IP haciendo rebotar tus mensajes a través de los Nodos de servicio de la red descentralizada de Session. Estos son los países por los que tu conexión está siendo rebotada actualmente."; "vc_path_device_row_title" = "Tú"; -"vc_path_guard_node_row_title" = "Entry Node"; -"vc_path_service_node_row_title" = "Service Node"; +"vc_path_guard_node_row_title" = "Nodo de entrada"; +"vc_path_service_node_row_title" = "Nodo de Servicio"; "vc_path_destination_row_title" = "Destino"; "vc_path_learn_more_button_title" = "Saber Más"; "vc_create_private_chat_title" = "Nueva Session"; @@ -490,38 +490,39 @@ "vc_qr_code_view_scan_qr_code_explanation" = "Escanea el código QR de una persona para comenzar una conversación con ella"; "vc_view_my_qr_code_explanation" = "Este es tu código QR. Otros usuarios pueden escanearlo para empezar una Session contigo."; // MARK: - Not Yet Translated -"fast_mode_explanation" = "You’ll be notified of new messages reliably and immediately using Apple’s notification servers."; -"slow_mode_explanation" = "Session will occasionally check for new messages in the background."; -"vc_pn_mode_title" = "Message Notifications"; -"vc_notification_settings_notification_mode_title" = "Use Fast Mode"; -"vc_link_device_recovery_phrase_tab_title" = "Recovery Phrase"; -"vc_link_device_scan_qr_code_explanation" = "Navigate to Settings → Recovery Phrase on your other device to show your QR code."; -"vc_enter_recovery_phrase_title" = "Recovery Phrase"; -"vc_enter_recovery_phrase_explanation" = "To link your device, enter the recovery phrase that was given to you when you signed up."; -"vc_enter_public_key_text_field_hint" = "Enter Session ID or ONS name"; -"vc_home_title" = "Messages"; -"admin_group_leave_warning" = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."; -"vc_join_open_group_suggestions_title" = "Or join one of these..."; -"vc_settings_invite_a_friend_button_title" = "Invite a Friend"; -"vc_settings_help_us_translate_button_title" = "Help us Translate Session"; -"copied" = "Copied"; -"vc_conversation_settings_copy_session_id_button_title" = "Copy Session ID"; -"vc_conversation_input_prompt" = "Message"; -"vc_conversation_voice_message_cancel_message" = "Slide to Cancel"; -"modal_download_attachment_title" = "Trust %@?"; -"modal_download_attachment_explanation" = "Are you sure you want to download media sent by %@?"; -"modal_download_button_title" = "Download"; -"modal_open_url_title" = "Open URL?"; -"modal_open_url_explanation" = "Are you sure you want to open %@?"; -"modal_open_url_button_title" = "Open"; -"modal_blocked_title" = "Unblock %@?"; -"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; -"modal_blocked_button_title" = "Unblock"; -"modal_link_previews_title" = "Enable Link Previews?"; -"modal_link_previews_explanation" = "Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session's settings."; -"modal_link_previews_button_title" = "Enable"; -"vc_share_title" = "Share to Session"; -"vc_share_loading_message" = "Preparing attachments..."; -"vc_share_sending_message" = "Sending..."; -"view_open_group_invitation_description" = "Open group invitation"; -"vc_conversation_settings_invite_button_title" = "Add Members"; +"fast_mode_explanation" = "Se le notificará de los nuevos mensajes de forma fiable e inmediata usando los servidores de notificaciones de Apple."; +"fast_mode" = "Fast Mode"; +"slow_mode_explanation" = "Session comprobará ocasionalmente si hay nuevos mensajes en segundo plano."; +"slow_mode" = "Modo lento"; +"vc_pn_mode_title" = "Notificaciones de mensaje"; +"vc_notification_settings_notification_mode_title" = "Usar modo rápido"; +"vc_link_device_recovery_phrase_tab_title" = "Frase de recuperación"; +"vc_link_device_scan_qr_code_explanation" = "Navega a Ajustes → Frase de recuperación en tu otro dispositivo para mostrar tu código QR."; +"vc_enter_recovery_phrase_title" = "Frase de recuperación"; +"vc_enter_recovery_phrase_explanation" = "Para vincular tú dispositivo, introduce la frase de recuperación que se le dio cuando se registró."; +"vc_enter_public_key_text_field_hint" = "Introduzca el ID de Session o el nombre ONS"; +"vc_home_title" = "Mensajes"; +"admin_group_leave_warning" = "Debido a que eres el creador de este grupo, se eliminará para todos. Esto no se puede deshacer."; +"vc_join_open_group_suggestions_title" = "O únase a uno de estos..."; +"vc_settings_invite_a_friend_button_title" = "Invita a un amigo"; +"vc_settings_help_us_translate_button_title" = "Ayúdanos a traducir Session"; +"copied" = "Copiado"; +"vc_conversation_settings_copy_session_id_button_title" = "Copiar ID de Session"; +"vc_conversation_input_prompt" = "Mensaje"; +"vc_conversation_voice_message_cancel_message" = "Desliza para cancelar"; +"modal_download_attachment_title" = "¿Confiar en %@?"; +"modal_download_attachment_explanation" = "¿Estás seguro de que quieres descargar este archivo?"; +"modal_download_button_title" = "Descargar"; +"modal_open_url_title" = "¿Abrir URL?"; +"modal_open_url_explanation" = "¿Estás seguro de que quieres abrir %@?"; +"modal_open_url_button_title" = "Abrir"; +"modal_blocked_title" = "¿Desbloquear a %@?"; +"modal_blocked_explanation" = "¿Estás seguro de que quieres desbloquear a %@?"; +"modal_blocked_button_title" = "Desbloquear"; +"modal_link_previews_title" = "¿Habilitar Previsualizaciones de Enlace?"; +"modal_link_previews_explanation" = "Activar vista previa de enlaces mostrará las vistas previas para las URL que envíe y reciba. Esto puede ser útil, pero Session tendrá que ponerse en contacto con los sitios web enlazados para generar vistas previas. Siempre puedes desactivar las vistas previas de enlaces en la configuración de Session."; +"modal_link_previews_button_title" = "Activar"; +"vc_share_title" = "Compartir en Session"; +"vc_share_loading_message" = "Preparando archivos adjuntos..."; +"vc_share_sending_message" = "Enviando..."; +"view_open_group_invitation_description" = "Abrir invitación de grupo"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 19c64afab..930e41c57 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -285,13 +285,13 @@ /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Échec d’authentification"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ -"SEND_MEDIA_ABANDON_TITLE" = "Discard Media?"; +"SEND_MEDIA_ABANDON_TITLE" = "Abandonner le média ?"; /* alert action, confirming the user wants to exit the media flow and abandon any photos they've taken */ -"SEND_MEDIA_CONFIRM_ABANDON_ALBUM" = "Discard Media"; +"SEND_MEDIA_CONFIRM_ABANDON_ALBUM" = "Abandonner le média"; /* alert action when the user decides not to cancel the media flow after all. */ -"SEND_MEDIA_RETURN_TO_CAMERA" = "Return to Camera"; +"SEND_MEDIA_RETURN_TO_CAMERA" = "Retourner à l’appareil photo"; /* alert action when the user decides not to cancel the media flow after all. */ -"SEND_MEDIA_RETURN_TO_MEDIA_LIBRARY" = "Return to Media Library"; +"SEND_MEDIA_RETURN_TO_MEDIA_LIBRARY" = "Retourner à la bibliothèque média"; /* Format string for the default 'Note' sound. Embeds the system {{sound name}}. */ "SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT" = "%@ (par défaut)"; /* Label for the backup view in app settings. */ @@ -359,7 +359,7 @@ /* Label for the 'typing indicators' setting. */ "SETTINGS_TYPING_INDICATORS" = "Indicateurs de saisie"; /* Label for the 'no sound' option that allows users to disable sounds for notifications, etc. */ -"SOUNDS_NONE" = "None"; +"SOUNDS_NONE" = "Aucun"; /* {{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 days}}'. See other *_TIME_AMOUNT strings */ "TIME_AMOUNT_DAYS" = "%@ jours"; /* Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings */ @@ -490,38 +490,39 @@ "vc_qr_code_view_scan_qr_code_explanation" = "Scannez le code QR d'un autre utilisateur pour démarrer une session"; "vc_view_my_qr_code_explanation" = "Ceci est votre code QR. Les autres utilisateurs peuvent le scanner pour démarrer une session avec vous."; // MARK: - Not Yet Translated -"fast_mode_explanation" = "You’ll be notified of new messages reliably and immediately using Apple’s notification servers."; -"slow_mode_explanation" = "Session will occasionally check for new messages in the background."; -"vc_pn_mode_title" = "Message Notifications"; -"vc_notification_settings_notification_mode_title" = "Use Fast Mode"; -"vc_link_device_recovery_phrase_tab_title" = "Recovery Phrase"; -"vc_link_device_scan_qr_code_explanation" = "Navigate to Settings → Recovery Phrase on your other device to show your QR code."; -"vc_enter_recovery_phrase_title" = "Recovery Phrase"; -"vc_enter_recovery_phrase_explanation" = "To link your device, enter the recovery phrase that was given to you when you signed up."; -"vc_enter_public_key_text_field_hint" = "Enter Session ID or ONS name"; +"fast_mode_explanation" = "Vous serez notifiés de nouveaux messages de manière certaine et immédiate en utilisant les serveurs de notification d’Apple."; +"fast_mode" = "Mode rapide"; +"slow_mode_explanation" = "Session vérifiera occasionnellement la présence de nouveaux message en tâche de fond."; +"slow_mode" = "Mode lent"; +"vc_pn_mode_title" = "Notifications de message"; +"vc_notification_settings_notification_mode_title" = "Utiliser le mode rapide"; +"vc_link_device_recovery_phrase_tab_title" = "Phrase de récupération"; +"vc_link_device_scan_qr_code_explanation" = "Allez dans paramètre → Phrase de récupération sur votre autre appareil pour afficher votre QR Code."; +"vc_enter_recovery_phrase_title" = "Phrase de récupération"; +"vc_enter_recovery_phrase_explanation" = "Pour lier votre appareil, entrez la phrase de récupération qui vous a été donné lors de la création du compte."; +"vc_enter_public_key_text_field_hint" = "Entrez un ID Session ou un nom ONS"; "vc_home_title" = "Messages"; -"admin_group_leave_warning" = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."; -"vc_join_open_group_suggestions_title" = "Or join one of these..."; -"vc_settings_invite_a_friend_button_title" = "Invite a Friend"; -"vc_settings_help_us_translate_button_title" = "Help us Translate Session"; -"copied" = "Copied"; -"vc_conversation_settings_copy_session_id_button_title" = "Copy Session ID"; +"admin_group_leave_warning" = "Puisque vous êtes le créateur de ce groupe, il sera supprimé pour tout le monde. Ceci ne peut pas être annulé."; +"vc_join_open_group_suggestions_title" = "Ou rejoignez un de ceux-ci..."; +"vc_settings_invite_a_friend_button_title" = "Inviter un ami"; +"vc_settings_help_us_translate_button_title" = "Aidez-nous à traduire Session"; +"copied" = "Copié"; +"vc_conversation_settings_copy_session_id_button_title" = "Copier l’ID Session"; "vc_conversation_input_prompt" = "Message"; -"vc_conversation_voice_message_cancel_message" = "Slide to Cancel"; -"modal_download_attachment_title" = "Trust %@?"; -"modal_download_attachment_explanation" = "Are you sure you want to download media sent by %@?"; -"modal_download_button_title" = "Download"; -"modal_open_url_title" = "Open URL?"; -"modal_open_url_explanation" = "Are you sure you want to open %@?"; -"modal_open_url_button_title" = "Open"; -"modal_blocked_title" = "Unblock %@?"; -"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; -"modal_blocked_button_title" = "Unblock"; -"modal_link_previews_title" = "Enable Link Previews?"; -"modal_link_previews_explanation" = "Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session's settings."; -"modal_link_previews_button_title" = "Enable"; -"vc_share_title" = "Share to Session"; -"vc_share_loading_message" = "Preparing attachments..."; -"vc_share_sending_message" = "Sending..."; -"view_open_group_invitation_description" = "Open group invitation"; -"vc_conversation_settings_invite_button_title" = "Add Members"; +"vc_conversation_voice_message_cancel_message" = "Glisser pour annuler"; +"modal_download_attachment_title" = "Faire confiance à %@?"; +"modal_download_attachment_explanation" = "Êtes-vous sûr de vouloir télécharger la sélection de %@ ?"; +"modal_download_button_title" = "Télécharger"; +"modal_open_url_title" = "Ouvrir l'URL?"; +"modal_open_url_explanation" = "Êtes-vous sûr de vouloir ouvrir %@?"; +"modal_open_url_button_title" = "Ouvrir"; +"modal_blocked_title" = "Débloquer %@ ?"; +"modal_blocked_explanation" = "Confirmez-vous le déblocage de %@ ?"; +"modal_blocked_button_title" = "Débloquer"; +"modal_link_previews_title" = "Activer les aperçus de lien?"; +"modal_link_previews_explanation" = "L'activation des aperçus de lien affichera des aperçus pour les URL que vous envoyez et recevez. Cela peut être utile, mais Session devra contacter les sites Web liés pour générer des aperçus. Vous pouvez toujours désactiver les aperçus de lien dans les paramètres de Session."; +"modal_link_previews_button_title" = "Activer"; +"vc_share_title" = "Partager en Session"; +"vc_share_loading_message" = "Préparation des pièces jointes ..."; +"vc_share_sending_message" = "Envoi..."; +"view_open_group_invitation_description" = "Invitation à un groupe ouvert"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 6ee5b7833..20747a672 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -29,7 +29,7 @@ /* Alert title when picking a document fails for an unknown reason */ "ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Document kiezen mislukt."; /* Alert body when picking a document fails because user picked a directory/bundle */ -"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY" = "Please create a compressed archive of this file or directory and try sending that instead."; +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY" = "Maak een gecomprimeerd archief aan van deze bestand of map en probeer dat te verzenden."; /* Alert title when picking a document fails because user picked a directory/bundle */ "ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE" = "Niet-ondersteund bestand"; /* Short text label for a voice message attachment, used for thread preview and on the lock screen */ @@ -43,7 +43,7 @@ /* Indicates that the backup export is being configured. */ "BACKUP_EXPORT_PHASE_CONFIGURATION" = "Backup initialiseren"; /* Indicates that the database data is being exported. */ -"BACKUP_EXPORT_PHASE_DATABASE_EXPORT" = "Gegevens exporteren"; +"BACKUP_EXPORT_PHASE_DATABASE_EXPORT" = "Exporteer gegevens"; /* Indicates that the backup export data is being exported. */ "BACKUP_EXPORT_PHASE_EXPORT" = "Backup exporteren"; /* Indicates that the backup export data is being uploaded. */ @@ -53,7 +53,7 @@ /* Indicates that the backup import is being configured. */ "BACKUP_IMPORT_PHASE_CONFIGURATION" = "Backup configuratie"; /* Indicates that the backup import data is being downloaded. */ -"BACKUP_IMPORT_PHASE_DOWNLOAD" = "Download van backup..."; +"BACKUP_IMPORT_PHASE_DOWNLOAD" = "Back-up gegevens downloaden"; /* Indicates that the backup import data is being finalized. */ "BACKUP_IMPORT_PHASE_FINALIZING" = "Backup afronden"; /* Indicates that the backup import data is being imported. */ @@ -61,7 +61,7 @@ /* Indicates that the backup database is being restored. */ "BACKUP_IMPORT_PHASE_RESTORING_DATABASE" = "Database Herstellen"; /* Indicates that the backup import data is being restored. */ -"BACKUP_IMPORT_PHASE_RESTORING_FILES" = "Bestanden herstellen…"; +"BACKUP_IMPORT_PHASE_RESTORING_FILES" = "Bestand terugzetten"; /* Label for the backup restore decision section. */ "BACKUP_RESTORE_DECISION_TITLE" = "Backup beschikbaar"; /* Label for the backup restore description. */ @@ -93,437 +93,436 @@ /* Error indicating that user does not have an iCloud account. */ "CLOUDKIT_STATUS_NO_ACCOUNT" = "Geen iCloud-account niet bepalen. log in met je iCloud-account in de iOS-instellingen app om een backup te maken van je Session gegevens."; /* Error indicating that the app was prevented from accessing the user's iCloud account. */ -"CLOUDKIT_STATUS_RESTRICTED" = "Session was denied access your iCloud account for backups. Grant Session access to your iCloud Account in the iOS settings app to backup your Session data."; +"CLOUDKIT_STATUS_RESTRICTED" = "Session heeft geprobeerd toegang the krijgen tot uw iCloud account met backups Maar werd geweigerd. log in met uw iCloud-account in de iOS-instellingen app om een backup te maken van je Session gegevens."; /* Alert body */ -"CONFIRM_LEAVE_GROUP_DESCRIPTION" = "You will no longer be able to send or receive messages in this group."; +"CONFIRM_LEAVE_GROUP_DESCRIPTION" = "Je kunt geen berichten meer versturen of ontvangen in deze groep."; /* Alert title */ -"CONFIRM_LEAVE_GROUP_TITLE" = "Do you really want to leave?"; +"CONFIRM_LEAVE_GROUP_TITLE" = "Wilt u echt deze groep verlaten?"; /* Message for the 'conversation delete confirmation' alert. */ -"CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE" = "This cannot be undone."; +"CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE" = "Dit kan niet ongedaan worden gemaakt."; /* Title for the 'conversation delete confirmation' alert. */ -"CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE" = "Delete Conversation?"; +"CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE" = "Gesprek verwijderen?"; /* keyboard toolbar label when no messages match the search string */ -"CONVERSATION_SEARCH_NO_RESULTS" = "No matches"; +"CONVERSATION_SEARCH_NO_RESULTS" = "Geen overeenkomsten"; /* keyboard toolbar label when exactly 1 message matches the search string */ -"CONVERSATION_SEARCH_ONE_RESULT" = "1 match"; +"CONVERSATION_SEARCH_ONE_RESULT" = "1 overeenkomst"; /* keyboard toolbar label when more than 1 message matches the search string. Embeds {{number/position of the 'currently viewed' result}} and the {{total number of results}} */ -"CONVERSATION_SEARCH_RESULTS_FORMAT" = "%d of %d matches"; +"CONVERSATION_SEARCH_RESULTS_FORMAT" = "%d van de %d overeenkomsten"; /* title for conversation settings screen */ -"CONVERSATION_SETTINGS" = "Conversation Settings"; +"CONVERSATION_SETTINGS" = "Conversatie instellingen"; /* table cell label in conversation settings */ -"CONVERSATION_SETTINGS_BLOCK_THIS_USER" = "Block This User"; +"CONVERSATION_SETTINGS_BLOCK_THIS_USER" = "Deze gebruiker blokkeren"; /* Title of the 'mute this thread' action sheet. */ -"CONVERSATION_SETTINGS_MUTE_ACTION_SHEET_TITLE" = "Mute"; +"CONVERSATION_SETTINGS_MUTE_ACTION_SHEET_TITLE" = "Dempen"; /* label for 'mute thread' cell in conversation settings */ -"CONVERSATION_SETTINGS_MUTE_LABEL" = "Mute"; +"CONVERSATION_SETTINGS_MUTE_LABEL" = "Dempen"; /* Indicates that the current thread is not muted. */ -"CONVERSATION_SETTINGS_MUTE_NOT_MUTED" = "Not muted"; +"CONVERSATION_SETTINGS_MUTE_NOT_MUTED" = "Niet gedempt"; /* Label for button to mute a thread for a day. */ -"CONVERSATION_SETTINGS_MUTE_ONE_DAY_ACTION" = "Mute for one day"; +"CONVERSATION_SETTINGS_MUTE_ONE_DAY_ACTION" = "Demp voor 1 dag"; /* Label for button to mute a thread for a hour. */ -"CONVERSATION_SETTINGS_MUTE_ONE_HOUR_ACTION" = "Mute for one hour"; +"CONVERSATION_SETTINGS_MUTE_ONE_HOUR_ACTION" = "Demp voor 1 uur"; /* Label for button to mute a thread for a minute. */ -"CONVERSATION_SETTINGS_MUTE_ONE_MINUTE_ACTION" = "Mute for one minute"; +"CONVERSATION_SETTINGS_MUTE_ONE_MINUTE_ACTION" = "Een minuut dempen"; /* Label for button to mute a thread for a week. */ -"CONVERSATION_SETTINGS_MUTE_ONE_WEEK_ACTION" = "Mute for one week"; +"CONVERSATION_SETTINGS_MUTE_ONE_WEEK_ACTION" = "Een week dempen"; /* Label for button to mute a thread for a year. */ -"CONVERSATION_SETTINGS_MUTE_ONE_YEAR_ACTION" = "Mute for one year"; +"CONVERSATION_SETTINGS_MUTE_ONE_YEAR_ACTION" = "Een jaar dempen"; /* Indicates that this thread is muted until a given date or time. Embeds {{The date or time which the thread is muted until}}. */ -"CONVERSATION_SETTINGS_MUTED_UNTIL_FORMAT" = "until %@"; +"CONVERSATION_SETTINGS_MUTED_UNTIL_FORMAT" = "tot %@"; /* Table cell label in conversation settings which returns the user to the conversation with 'search mode' activated */ -"CONVERSATION_SETTINGS_SEARCH" = "Search Conversation"; +"CONVERSATION_SETTINGS_SEARCH" = "Zoek gesprek"; /* Label for button to unmute a thread. */ -"CONVERSATION_SETTINGS_UNMUTE_ACTION" = "Unmute"; +"CONVERSATION_SETTINGS_UNMUTE_ACTION" = "Niet langer dempen"; /* Title for the 'crop/scale image' dialog. */ -"CROP_SCALE_IMAGE_VIEW_TITLE" = "Move and Scale"; +"CROP_SCALE_IMAGE_VIEW_TITLE" = "Verplaatsen en in grootte veranderen"; /* Subtitle shown while the app is updating its database. */ -"DATABASE_VIEW_OVERLAY_SUBTITLE" = "This can take a few minutes."; +"DATABASE_VIEW_OVERLAY_SUBTITLE" = "Dit kan een paar minuten duren."; /* Title shown while the app is updating its database. */ -"DATABASE_VIEW_OVERLAY_TITLE" = "Optimizing Database"; +"DATABASE_VIEW_OVERLAY_TITLE" = "Optimaliseer Database"; /* Format string for a relative time, expressed as a certain number of hours in the past. Embeds {{The number of hours}}. */ -"DATE_HOURS_AGO_FORMAT" = "%@ Hr Ago"; +"DATE_HOURS_AGO_FORMAT" = "%@ Uur geleden"; /* Format string for a relative time, expressed as a certain number of minutes in the past. Embeds {{The number of minutes}}. */ -"DATE_MINUTES_AGO_FORMAT" = "%@ Min Ago"; +"DATE_MINUTES_AGO_FORMAT" = "%@ Min geleden"; /* The present; the current time. */ -"DATE_NOW" = "Now"; +"DATE_NOW" = "Nu"; /* The current day. */ -"DATE_TODAY" = "Today"; +"DATE_TODAY" = "Vandaag"; /* The day before today. */ -"DATE_YESTERDAY" = "Yesterday"; +"DATE_YESTERDAY" = "Gisteren"; /* table cell label in conversation settings */ -"DISAPPEARING_MESSAGES" = "Disappearing Messages"; +"DISAPPEARING_MESSAGES" = "Zelf-wissende berichten"; /* Info Message when added to a group which has enabled disappearing messages. Embeds {{time amount}} before messages disappear, see the *_TIME_AMOUNT strings for context. */ -"DISAPPEARING_MESSAGES_CONFIGURATION_GROUP_EXISTING_FORMAT" = "Messages in this conversation will disappear after %@."; +"DISAPPEARING_MESSAGES_CONFIGURATION_GROUP_EXISTING_FORMAT" = "Berichten in dit gesprek zullen na %@ verdwijnen."; /* table cell label in conversation settings */ -"EDIT_GROUP_ACTION" = "Edit Group"; +"EDIT_GROUP_ACTION" = "Groep bewerken"; /* Label indicating media gallery is empty */ -"GALLERY_TILES_EMPTY_GALLERY" = "You don't have any media in this conversation."; +"GALLERY_TILES_EMPTY_GALLERY" = "U heeft geen media in dit gesprek."; /* Label indicating loading is in progress */ -"GALLERY_TILES_LOADING_MORE_RECENT_LABEL" = "Loading Newer Media…"; +"GALLERY_TILES_LOADING_MORE_RECENT_LABEL" = "Nieuwere media laden…"; /* Label indicating loading is in progress */ -"GALLERY_TILES_LOADING_OLDER_LABEL" = "Loading Older Media…"; +"GALLERY_TILES_LOADING_OLDER_LABEL" = "Oudere media laden…"; /* Error displayed when there is a failure fetching a GIF from the remote service. */ -"GIF_PICKER_ERROR_FETCH_FAILURE" = "Failed to fetch the requested GIF. Please verify you are online."; +"GIF_PICKER_ERROR_FETCH_FAILURE" = "Het ophalen van de gevraagde GIF, is mislukt. Controleer of u online bent."; /* Generic error displayed when picking a GIF */ -"GIF_PICKER_ERROR_GENERIC" = "An unknown error occurred."; +"GIF_PICKER_ERROR_GENERIC" = "Er is een onbekende fout opgetreden."; /* Shown when selected GIF couldn't be fetched */ -"GIF_PICKER_FAILURE_ALERT_TITLE" = "Unable to Choose GIF"; +"GIF_PICKER_FAILURE_ALERT_TITLE" = "Kan GIF-Bestand niet kiezen"; /* Alert message shown when user tries to search for GIFs without entering any search terms. */ -"GIF_PICKER_VIEW_MISSING_QUERY" = "Please enter your search."; +"GIF_PICKER_VIEW_MISSING_QUERY" = "Vul alstublieft uw zoekopdracht in."; /* Indicates that an error occurred while searching. */ -"GIF_VIEW_SEARCH_ERROR" = "Error. Tap to Retry."; +"GIF_VIEW_SEARCH_ERROR" = "Fout. Tik om opnieuw te proberen."; /* Indicates that the user's search had no results. */ -"GIF_VIEW_SEARCH_NO_RESULTS" = "No Results."; +"GIF_VIEW_SEARCH_NO_RESULTS" = "Geen resultaten."; /* No comment provided by engineer. */ -"GROUP_CREATED" = "Group created"; +"GROUP_CREATED" = "Groep aangemaakt"; /* No comment provided by engineer. */ -"GROUP_MEMBER_JOINED" = " %@ joined the group. "; +"GROUP_MEMBER_JOINED" = " %@ is toegevoegd aan de groep. "; /* No comment provided by engineer. */ -"GROUP_MEMBER_LEFT" = " %@ left the group. "; +"GROUP_MEMBER_LEFT" = " %@ heeft de groep verlaten. "; /* No comment provided by engineer. */ -"GROUP_MEMBER_REMOVED" = " %@ was removed from the group. "; +"GROUP_MEMBER_REMOVED" = " %@ is verwijderd uit de groep. "; /* No comment provided by engineer. */ -"GROUP_MEMBERS_REMOVED" = " %@ were removed from the group. "; +"GROUP_MEMBERS_REMOVED" = " %@ zijn uit de groep verwijderd. "; /* No comment provided by engineer. */ -"GROUP_TITLE_CHANGED" = "Title is now '%@'. "; +"GROUP_TITLE_CHANGED" = "Titel is nu '%@'. "; /* No comment provided by engineer. */ -"GROUP_UPDATED" = "Group updated."; +"GROUP_UPDATED" = "Groep bijgewerkt."; /* No comment provided by engineer. */ -"GROUP_YOU_LEFT" = "You have left the group."; +"GROUP_YOU_LEFT" = "U heeft de groep verlaten."; /* No comment provided by engineer. */ -"YOU_WERE_REMOVED" = " You were removed from the group. "; +"YOU_WERE_REMOVED" = " Je bent verwijderd uit deze groep. "; /* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */ -"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items."; +"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "Je kunt niet meer dan %@ items delen."; /* alert title */ -"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment."; +"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Fout bij het selecteren van de bijlage."; /* Message for the alert indicating that an audio file is invalid. */ -"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE" = "Invalid audio file."; +"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE" = "Ongeldig audio-bestand."; /* Slider label when disappearing messages is off */ -"KEEP_MESSAGES_FOREVER" = "Messages do not disappear."; +"KEEP_MESSAGES_FOREVER" = "Berichten verdwijnen niet."; /* Confirmation button within contextual alert */ -"LEAVE_BUTTON_TITLE" = "Leave"; +"LEAVE_BUTTON_TITLE" = "Verlaten"; /* table cell label in conversation settings */ -"LEAVE_GROUP_ACTION" = "Leave Group"; +"LEAVE_GROUP_ACTION" = "Verlaat groep"; /* Title for the 'long text message' view. */ -"LONG_TEXT_VIEW_TITLE" = "Message"; +"LONG_TEXT_VIEW_TITLE" = "Bericht"; /* nav bar button item */ -"MEDIA_DETAIL_VIEW_ALL_MEDIA_BUTTON" = "All Media"; +"MEDIA_DETAIL_VIEW_ALL_MEDIA_BUTTON" = "Alle media"; /* media picker option to choose from library */ -"MEDIA_FROM_LIBRARY_BUTTON" = "Photo Library"; +"MEDIA_FROM_LIBRARY_BUTTON" = "Fotobibliotheek"; /* Confirmation button text to delete selected media from the gallery, embeds {{number of messages}} */ -"MEDIA_GALLERY_DELETE_MULTIPLE_MESSAGES_FORMAT" = "Delete %d Messages"; +"MEDIA_GALLERY_DELETE_MULTIPLE_MESSAGES_FORMAT" = "Verwijder %d Berichten"; /* Confirmation button text to delete selected media message from the gallery */ -"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "Delete Message"; +"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "Verwijder bericht"; /* embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' */ -"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ on %@"; +"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ op %@"; /* Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}. */ "MEDIA_GALLERY_MORE_ITEMS_FORMAT" = "+%@"; /* Short sender label for media sent by you */ -"MEDIA_GALLERY_SENDER_NAME_YOU" = "You"; +"MEDIA_GALLERY_SENDER_NAME_YOU" = "U"; /* Section header in media gallery collection view */ -"MEDIA_GALLERY_THIS_MONTH_HEADER" = "This Month"; +"MEDIA_GALLERY_THIS_MONTH_HEADER" = "Deze maand"; /* message status for message delivered to their recipient. */ -"MESSAGE_STATUS_DELIVERED" = "Delivered"; +"MESSAGE_STATUS_DELIVERED" = "Afgeleverd"; /* status message for failed messages */ -"MESSAGE_STATUS_FAILED" = "Sending failed."; +"MESSAGE_STATUS_FAILED" = "Verzenden mislukt."; /* status message for failed messages */ -"MESSAGE_STATUS_FAILED_SHORT" = "Failed"; +"MESSAGE_STATUS_FAILED_SHORT" = "Mislukt"; /* status message for read messages */ -"MESSAGE_STATUS_READ" = "Read"; +"MESSAGE_STATUS_READ" = "Lees"; /* message status if message delivery to a recipient is skipped. We skip delivering group messages to users who have left the group or unregistered their Session account. */ -"MESSAGE_STATUS_RECIPIENT_SKIPPED" = "Skipped"; +"MESSAGE_STATUS_RECIPIENT_SKIPPED" = "Overgeslagen"; /* message status while message is sending. */ -"MESSAGE_STATUS_SENDING" = "Sending…"; +"MESSAGE_STATUS_SENDING" = "Verzenden…"; /* status message for sent messages */ -"MESSAGE_STATUS_SENT" = "Sent"; +"MESSAGE_STATUS_SENT" = "Verzonden"; /* status message while attachment is uploading */ -"MESSAGE_STATUS_UPLOADING" = "Uploading…"; +"MESSAGE_STATUS_UPLOADING" = "Uploaden…"; /* Alert body when user has previously denied media library access */ -"MISSING_MEDIA_LIBRARY_PERMISSION_MESSAGE" = "You can enable this permission in the iOS Settings app."; +"MISSING_MEDIA_LIBRARY_PERMISSION_MESSAGE" = "U kunt deze rechten inschakelen in de iOS-instellingen app."; /* Alert title when user has previously denied media library access */ -"MISSING_MEDIA_LIBRARY_PERMISSION_TITLE" = "Session requires access to your photos for this feature."; +"MISSING_MEDIA_LIBRARY_PERMISSION_TITLE" = "Sessie vereist toegang tot uw foto's voor deze functie."; /* An explanation of the consequences of muting a thread. */ -"MUTE_BEHAVIOR_EXPLANATION" = "You will not receive notifications for muted conversations."; +"MUTE_BEHAVIOR_EXPLANATION" = "U zal geen meldingen ontvangen voor gedempte gesprekken."; /* notification title. Embeds {{author name}} and {{group name}} */ -"NEW_GROUP_MESSAGE_NOTIFICATION_TITLE" = "%@ to %@"; +"NEW_GROUP_MESSAGE_NOTIFICATION_TITLE" = "%@ naar %@"; /* Label for 1:1 conversation with yourself. */ -"NOTE_TO_SELF" = "Note to Self"; +"NOTE_TO_SELF" = "Notitie aan mezelf"; /* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */ -"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "You may have received messages while your %@ was restarting."; +"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "U heeft mogelijk berichten ontvangen terwijl u %@ opnieuw aan het opstarten was."; /* No comment provided by engineer. */ -"NOTIFICATIONS_FOOTER_WARNING" = "Due to known bugs in Apple's push framework, message previews will only be shown if the message is retrieved within 30 seconds after being sent. The application badge might be inaccurate as a result."; +"NOTIFICATIONS_FOOTER_WARNING" = "Vanwege bekende bugs in het push-framework van Apple worden berichtvoorbeelden alleen getoond als het bericht binnen 30 seconden na verzending wordt opgehaald. Hierdoor kan de applicatiebadge onnauwkeurig zijn."; /* Table cell switch label. When disabled, Session will not play notification sounds while the app is in the foreground. */ -"NOTIFICATIONS_SECTION_INAPP" = "Play While App is Open"; +"NOTIFICATIONS_SECTION_INAPP" = "Speel terwijl de app geopend is"; /* Label for settings UI that allows user to change the notification sound. */ -"NOTIFICATIONS_SECTION_SOUNDS" = "Sounds"; +"NOTIFICATIONS_SECTION_SOUNDS" = "Geluiden"; /* No comment provided by engineer. */ -"NOTIFICATIONS_SENDER_AND_MESSAGE" = "Name and Content"; +"NOTIFICATIONS_SENDER_AND_MESSAGE" = "Naam en inhoud"; /* No comment provided by engineer. */ -"NOTIFICATIONS_SENDER_ONLY" = "Name Only"; +"NOTIFICATIONS_SENDER_ONLY" = "Alleen naam"; /* No comment provided by engineer. */ -"NOTIFICATIONS_NONE" = "No Name or Content"; +"NOTIFICATIONS_NONE" = "Geen naam of inhoud"; /* No comment provided by engineer. */ -"NOTIFICATIONS_SHOW" = "Show"; +"NOTIFICATIONS_SHOW" = "Tonen"; /* No comment provided by engineer. */ "OK" = "OK"; /* Info Message when {{other user}} disables or doesn't support disappearing messages */ -"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ disabled disappearing messages."; +"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ heeft zelf-wissende berichten uitgeschakeld."; /* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */ -"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ set disappearing message time to %@"; +"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ heeft de timer voor zelf-wissende berichten op %@ ingesteld"; /* alert title, generic error preventing user from capturing a photo */ -"PHOTO_CAPTURE_GENERIC_ERROR" = "Unable to capture image."; +"PHOTO_CAPTURE_GENERIC_ERROR" = "Kan afbeelding niet vastleggen."; /* alert title */ -"PHOTO_CAPTURE_UNABLE_TO_CAPTURE_IMAGE" = "Unable to capture image."; +"PHOTO_CAPTURE_UNABLE_TO_CAPTURE_IMAGE" = "Kan afbeelding niet vastleggen."; /* alert title */ -"PHOTO_CAPTURE_UNABLE_TO_INITIALIZE_CAMERA" = "Failed to configure camera."; +"PHOTO_CAPTURE_UNABLE_TO_INITIALIZE_CAMERA" = "Instellen van de camera mislukt."; /* label for system photo collections which have no name. */ -"PHOTO_PICKER_UNNAMED_COLLECTION" = "Unnamed Album"; +"PHOTO_PICKER_UNNAMED_COLLECTION" = "Naamloze Album"; /* alert body during registration */ -"REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "We can't activate your account until you verify the code we sent you."; +"REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "We kunnen uw account pas activeren nadat u de code die we u hebben gestuurd verifieert."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ -"SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Instant"; +"SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Direct"; /* Title for alert indicating that screen lock could not be unlocked. */ -"SCREEN_LOCK_UNLOCK_FAILED" = "Authentication Failed"; +"SCREEN_LOCK_UNLOCK_FAILED" = "Verificatie mislukt"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ -"SEND_MEDIA_ABANDON_TITLE" = "Discard Media?"; +"SEND_MEDIA_ABANDON_TITLE" = "Media verwijderen?"; /* alert action, confirming the user wants to exit the media flow and abandon any photos they've taken */ -"SEND_MEDIA_CONFIRM_ABANDON_ALBUM" = "Discard Media"; +"SEND_MEDIA_CONFIRM_ABANDON_ALBUM" = "Media verwijderen"; /* alert action when the user decides not to cancel the media flow after all. */ -"SEND_MEDIA_RETURN_TO_CAMERA" = "Return to Camera"; +"SEND_MEDIA_RETURN_TO_CAMERA" = "Terugkeren naar Camera"; /* alert action when the user decides not to cancel the media flow after all. */ -"SEND_MEDIA_RETURN_TO_MEDIA_LIBRARY" = "Return to Media Library"; +"SEND_MEDIA_RETURN_TO_MEDIA_LIBRARY" = "Terug naar mediabibliotheek"; /* Format string for the default 'Note' sound. Embeds the system {{sound name}}. */ -"SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT" = "%@ (default)"; +"SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT" = "%@ (standaard)"; /* Label for the backup view in app settings. */ -"SETTINGS_BACKUP" = "Backup"; +"SETTINGS_BACKUP" = "Back-up"; /* Label for 'backup now' button in the backup settings view. */ -"SETTINGS_BACKUP_BACKUP_NOW" = "Backup Now"; +"SETTINGS_BACKUP_BACKUP_NOW" = "Nu back-up maken"; /* Label for 'cancel backup' button in the backup settings view. */ -"SETTINGS_BACKUP_CANCEL_BACKUP" = "Cancel Backup"; +"SETTINGS_BACKUP_CANCEL_BACKUP" = "Back-up annuleren"; /* Label for switch in settings that controls whether or not backup is enabled. */ -"SETTINGS_BACKUP_ENABLING_SWITCH" = "Backup Enabled"; +"SETTINGS_BACKUP_ENABLING_SWITCH" = "Back-up Ingeschakeld"; /* Label for iCloud status row in the in the backup settings view. */ "SETTINGS_BACKUP_ICLOUD_STATUS" = "iCloud Status"; /* Indicates that the last backup restore failed. */ -"SETTINGS_BACKUP_IMPORT_STATUS_FAILED" = "Backup Restore Failed"; +"SETTINGS_BACKUP_IMPORT_STATUS_FAILED" = "Back-up herstellen mislukt"; /* Indicates that app is not restoring up. */ -"SETTINGS_BACKUP_IMPORT_STATUS_IDLE" = "Backup Restore Idle"; +"SETTINGS_BACKUP_IMPORT_STATUS_IDLE" = "Back-up herstellen inactief"; /* Indicates that app is restoring up. */ -"SETTINGS_BACKUP_IMPORT_STATUS_IN_PROGRESS" = "Backup Restore In Progress"; +"SETTINGS_BACKUP_IMPORT_STATUS_IN_PROGRESS" = "Back-up terugzetten bezig"; /* Indicates that the last backup restore succeeded. */ -"SETTINGS_BACKUP_IMPORT_STATUS_SUCCEEDED" = "Backup Restore Succeeded"; +"SETTINGS_BACKUP_IMPORT_STATUS_SUCCEEDED" = "Back-up terugzetten geslaagd"; /* Label for phase row in the in the backup settings view. */ -"SETTINGS_BACKUP_PHASE" = "Phase"; +"SETTINGS_BACKUP_PHASE" = "Fase"; /* Label for phase row in the in the backup settings view. */ -"SETTINGS_BACKUP_PROGRESS" = "Progress"; +"SETTINGS_BACKUP_PROGRESS" = "Vooruitgang"; /* Label for backup status row in the in the backup settings view. */ "SETTINGS_BACKUP_STATUS" = "Status"; /* Indicates that the last backup failed. */ -"SETTINGS_BACKUP_STATUS_FAILED" = "Backup Failed"; +"SETTINGS_BACKUP_STATUS_FAILED" = "Back-up maken mislukt"; /* Indicates that app is not backing up. */ -"SETTINGS_BACKUP_STATUS_IDLE" = "Waiting"; +"SETTINGS_BACKUP_STATUS_IDLE" = "Bezig met wachten"; /* Indicates that app is backing up. */ -"SETTINGS_BACKUP_STATUS_IN_PROGRESS" = "Backing Up"; +"SETTINGS_BACKUP_STATUS_IN_PROGRESS" = "Bezig met Back-up maken"; /* Indicates that the last backup succeeded. */ -"SETTINGS_BACKUP_STATUS_SUCCEEDED" = "Backup Successful"; +"SETTINGS_BACKUP_STATUS_SUCCEEDED" = "Back-up succesvol"; /* No comment provided by engineer. */ -"SETTINGS_CLEAR_HISTORY" = "Clear Conversation History"; +"SETTINGS_CLEAR_HISTORY" = "Gespreksgeschiedenis wissen"; /* Confirmation text for button which deletes all message, calling, attachments, etc. */ -"SETTINGS_DELETE_HISTORYLOG_CONFIRMATION_BUTTON" = "Delete Everything"; +"SETTINGS_DELETE_HISTORYLOG_CONFIRMATION_BUTTON" = "Verwijder Alles"; /* Section header */ -"SETTINGS_HISTORYLOG_TITLE" = "Clear Conversation History"; +"SETTINGS_HISTORYLOG_TITLE" = "Gespreksgeschiedenis wissen"; /* Label for settings view that allows user to change the notification sound. */ -"SETTINGS_ITEM_NOTIFICATION_SOUND" = "Message Sound"; +"SETTINGS_ITEM_NOTIFICATION_SOUND" = "Geluid van bericht"; /* Setting for enabling & disabling link previews. */ -"SETTINGS_LINK_PREVIEWS" = "Send Link Previews"; +"SETTINGS_LINK_PREVIEWS" = "Link Previews verzenden"; /* Footer for setting for enabling & disabling link previews. */ -"SETTINGS_LINK_PREVIEWS_FOOTER" = "Previews are supported for Imgur, Instagram, Pinterest, Reddit, and YouTube links."; +"SETTINGS_LINK_PREVIEWS_FOOTER" = "Voorbeelden worden ondersteund van Imgur, Instagram, Pinterest Reddit, en YouTube links."; /* Header for setting for enabling & disabling link previews. */ -"SETTINGS_LINK_PREVIEWS_HEADER" = "Link Previews"; +"SETTINGS_LINK_PREVIEWS_HEADER" = "Link voorbeelden"; /* table section header */ -"SETTINGS_NOTIFICATION_CONTENT_TITLE" = "Notification Content"; +"SETTINGS_NOTIFICATION_CONTENT_TITLE" = "Notificatie Inhoud"; /* Label for the 'read receipts' setting. */ -"SETTINGS_READ_RECEIPT" = "Read Receipts"; +"SETTINGS_READ_RECEIPT" = "Leesbevestigingen"; /* An explanation of the 'read receipts' setting. */ -"SETTINGS_READ_RECEIPTS_SECTION_FOOTER" = "See and share when messages have been read. This setting is optional and applies to all conversations."; +"SETTINGS_READ_RECEIPTS_SECTION_FOOTER" = "Bekijk en deel wanneer berichten zijn gelezen. Deze instelling is optioneel en geldt voor alle gesprekken."; /* Label for the 'screen lock activity timeout' setting of the privacy settings. */ -"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout"; +"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Time-out schermvergrendeling"; /* Title for the 'screen lock' section of the privacy settings. */ -"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock"; +"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Schermvergrendeling"; /* Label for the 'enable screen lock' switch of the privacy settings. */ -"SETTINGS_SCREEN_LOCK_SWITCH_LABEL" = "Screen Lock"; +"SETTINGS_SCREEN_LOCK_SWITCH_LABEL" = "Schermvergrendeling"; /* Header Label for the sounds section of settings views. */ -"SETTINGS_SECTION_SOUNDS" = "Sounds"; +"SETTINGS_SECTION_SOUNDS" = "Geluiden"; /* Section header */ -"SETTINGS_SECURITY_TITLE" = "Screen Security"; +"SETTINGS_SECURITY_TITLE" = "Scherm beveiliging"; /* Label for the 'typing indicators' setting. */ -"SETTINGS_TYPING_INDICATORS" = "Typing Indicators"; +"SETTINGS_TYPING_INDICATORS" = "Typindicatoren"; /* Label for the 'no sound' option that allows users to disable sounds for notifications, etc. */ -"SOUNDS_NONE" = "None"; +"SOUNDS_NONE" = "Geen"; /* {{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 days}}'. See other *_TIME_AMOUNT strings */ -"TIME_AMOUNT_DAYS" = "%@ days"; +"TIME_AMOUNT_DAYS" = "%@ dagen"; /* Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings */ "TIME_AMOUNT_DAYS_SHORT_FORMAT" = "%@d"; /* {{number of hours}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 hours}}'. See other *_TIME_AMOUNT strings */ -"TIME_AMOUNT_HOURS" = "%@ hours"; +"TIME_AMOUNT_HOURS" = "%@ uren"; /* Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings */ "TIME_AMOUNT_HOURS_SHORT_FORMAT" = "%@h"; /* {{number of minutes}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 minutes}}'. See other *_TIME_AMOUNT strings */ -"TIME_AMOUNT_MINUTES" = "%@ minutes"; +"TIME_AMOUNT_MINUTES" = "%@ minuten"; /* Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings */ "TIME_AMOUNT_MINUTES_SHORT_FORMAT" = "%@m"; /* {{number of seconds}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 seconds}}'. See other *_TIME_AMOUNT strings */ -"TIME_AMOUNT_SECONDS" = "%@ seconds"; +"TIME_AMOUNT_SECONDS" = "%@ seconden"; /* Label text below navbar button, embeds {{number of seconds}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5s' not '5 s'. See other *_TIME_AMOUNT strings */ "TIME_AMOUNT_SECONDS_SHORT_FORMAT" = "%@s"; /* {{1 day}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 day}}'. See other *_TIME_AMOUNT strings */ -"TIME_AMOUNT_SINGLE_DAY" = "%@ day"; +"TIME_AMOUNT_SINGLE_DAY" = "%@ dag"; /* {{1 hour}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 hour}}'. See other *_TIME_AMOUNT strings */ -"TIME_AMOUNT_SINGLE_HOUR" = "%@ hour"; +"TIME_AMOUNT_SINGLE_HOUR" = "%@ uur"; /* {{1 minute}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 minute}}'. See other *_TIME_AMOUNT strings */ -"TIME_AMOUNT_SINGLE_MINUTE" = "%@ minute"; +"TIME_AMOUNT_SINGLE_MINUTE" = "%@ minuut"; /* {{1 week}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 week}}'. See other *_TIME_AMOUNT strings */ "TIME_AMOUNT_SINGLE_WEEK" = "%@ week"; /* {{number of weeks}}, embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 weeks}}'. See other *_TIME_AMOUNT strings */ -"TIME_AMOUNT_WEEKS" = "%@ weeks"; +"TIME_AMOUNT_WEEKS" = "%@ weken"; /* Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings */ "TIME_AMOUNT_WEEKS_SHORT_FORMAT" = "%@w"; /* Label for the cancel button in an alert or action sheet. */ -"TXT_CANCEL_TITLE" = "Cancel"; +"TXT_CANCEL_TITLE" = "Annuleren"; /* No comment provided by engineer. */ -"TXT_DELETE_TITLE" = "Delete"; +"TXT_DELETE_TITLE" = "Wissen"; /* Filename for voice messages. */ -"VOICE_MESSAGE_FILE_NAME" = "Voice Message"; +"VOICE_MESSAGE_FILE_NAME" = "Spraakbericht"; /* Message for the alert indicating the 'voice message' needs to be held to be held down to record. */ -"VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE" = "Tap and hold to record a voice message."; +"VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE" = "Houd deze knop ingedrukt om een audio bericht te versturen."; /* Title for the alert indicating the 'voice message' needs to be held to be held down to record. */ -"VOICE_MESSAGE_TOO_SHORT_ALERT_TITLE" = "Voice Message"; +"VOICE_MESSAGE_TOO_SHORT_ALERT_TITLE" = "Spraakbericht"; /* Info Message when you disable disappearing messages */ -"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "You disabled disappearing messages."; +"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "Je hebt zelf-wissende berichten uitgeschakeld."; /* Info message embedding a {{time amount}}, see the *_TIME_AMOUNT strings for context. */ -"YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "You set disappearing message time to %@"; +"YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "U heeft de timer voor zelf-wissende berichten op %@ ingesteld"; // MARK: - Session -"continue_2" = "Continue"; -"copy" = "Copy"; -"invalid_url" = "Invalid URL"; -"next" = "Next"; -"share" = "Share"; -"invalid_session_id" = "Invalid Session ID"; -"cancel" = "Cancel"; -"your_session_id" = "Your Session ID"; -"vc_landing_title_2" = "Your Session begins here..."; -"vc_landing_register_button_title" = "Create Session ID"; -"vc_landing_restore_button_title" = "Continue Your Session"; -"vc_landing_link_button_title" = "Link a Device"; -"view_fake_chat_bubble_1" = "What's Session?"; -"view_fake_chat_bubble_2" = "It's a decentralized, encrypted messaging app"; -"view_fake_chat_bubble_3" = "So it doesn't collect my personal information or my conversation metadata? How does it work?"; -"view_fake_chat_bubble_4" = "Using a combination of advanced anonymous routing and end-to-end encryption technologies."; -"view_fake_chat_bubble_5" = "Friends don't let friends use compromised messengers. You're welcome."; -"vc_register_title" = "Say hello to your Session ID"; -"vc_register_explanation" = "Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design."; -"vc_restore_title" = "Restore your account"; -"vc_restore_explanation" = "Enter the recovery phrase that was given to you when you signed up to restore your account."; -"vc_restore_seed_text_field_hint" = "Enter your recovery phrase"; -"vc_link_device_title" = "Link a Device"; -"vc_link_device_scan_qr_code_tab_title" = "Scan QR Code"; -"vc_display_name_title_2" = "Pick your display name"; -"vc_display_name_explanation" = "This will be your name when you use Session. It can be your real name, an alias, or anything else you like."; -"vc_display_name_text_field_hint" = "Enter a display name"; -"vc_display_name_display_name_missing_error" = "Please pick a display name"; -"vc_display_name_display_name_too_long_error" = "Please pick a shorter display name"; -"vc_pn_mode_recommended_option_tag" = "Recommended"; -"vc_pn_mode_no_option_picked_modal_title" = "Please Pick an Option"; -"vc_home_empty_state_message" = "You don't have any contacts yet"; -"vc_home_empty_state_button_title" = "Start a Session"; -"vc_seed_title" = "Your Recovery Phrase"; -"vc_seed_title_2" = "Meet your recovery phrase"; -"vc_seed_explanation" = "Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don’t give it to anyone."; -"vc_seed_reveal_button_title" = "Hold to reveal"; -"view_seed_reminder_subtitle_1" = "Secure your account by saving your recovery phrase"; -"view_seed_reminder_subtitle_2" = "Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID."; -"view_seed_reminder_subtitle_3" = "Make sure to store your recovery phrase in a safe place"; -"vc_path_title" = "Path"; -"vc_path_explanation" = "Session hides your IP by routing your messages through multiple Service Nodes in Session's decentralized network. These are the countries your connection is currently being routed through:"; -"vc_path_device_row_title" = "You"; -"vc_path_guard_node_row_title" = "Entry Node"; -"vc_path_service_node_row_title" = "Service Node"; -"vc_path_destination_row_title" = "Destination"; -"vc_path_learn_more_button_title" = "Learn More"; -"vc_create_private_chat_title" = "New Session"; -"vc_create_private_chat_enter_session_id_tab_title" = "Enter Session ID"; -"vc_create_private_chat_scan_qr_code_tab_title" = "Scan QR Code"; -"vc_create_private_chat_scan_qr_code_explanation" = "Scan a user’s QR code to start a session. QR codes can be found by tapping the QR code icon in account settings."; -"vc_enter_public_key_explanation" = "Users can share their Session ID by going into their account settings and tapping \"Share Session ID\", or by sharing their QR code."; -"vc_scan_qr_code_camera_access_explanation" = "Session needs camera access to scan QR codes"; -"vc_scan_qr_code_grant_camera_access_button_title" = "Grant Camera Access"; -"vc_create_closed_group_title" = "New Closed Group"; -"vc_create_closed_group_text_field_hint" = "Enter a group name"; -"vc_create_closed_group_empty_state_message" = "You don't have any contacts yet"; -"vc_create_closed_group_empty_state_button_title" = "Start a Session"; -"vc_create_closed_group_group_name_missing_error" = "Please enter a group name"; -"vc_create_closed_group_group_name_too_long_error" = "Please enter a shorter group name"; -"vc_create_closed_group_too_many_group_members_error" = "A closed group cannot have more than 100 members"; -"vc_join_public_chat_title" = "Join Open Group"; -"vc_join_public_chat_enter_group_url_tab_title" = "Open Group URL"; -"vc_join_public_chat_scan_qr_code_tab_title" = "Scan QR Code"; -"vc_join_public_chat_scan_qr_code_explanation" = "Scan the QR code of the open group you'd like to join"; -"vc_enter_chat_url_text_field_hint" = "Enter an open group URL"; -"vc_settings_title" = "Settings"; -"vc_settings_display_name_text_field_hint" = "Enter a display name"; -"vc_settings_display_name_missing_error" = "Please pick a display name"; -"vc_settings_display_name_too_long_error" = "Please pick a shorter display name"; +"continue_2" = "Doorgaan"; +"copy" = "Kopieer"; +"invalid_url" = "Ongeldige URL"; +"next" = "Volgende"; +"share" = "Delen"; +"invalid_session_id" = "Verkeerde Session ID"; +"cancel" = "Annuleren"; +"your_session_id" = "Uw Session-ID"; +"vc_landing_title_2" = "Uw Sessie begint hier..."; +"vc_landing_register_button_title" = "Session-ID aanmaken"; +"vc_landing_restore_button_title" = "Doorgaan met je sessie"; +"vc_landing_link_button_title" = "Koppel een apparaat"; +"view_fake_chat_bubble_1" = "Wat is Session?"; +"view_fake_chat_bubble_2" = "Het is een gedecentraliseerde, versleutelde berichten-app"; +"view_fake_chat_bubble_3" = "Dus het verzamelt niet mijn persoonlijke informatie of de metagegevens van mijn gesprek? Hoe werkt het?"; +"view_fake_chat_bubble_4" = "Met behulp van een combinatie van geavanceerde anonieme routing en end-to-end encryptietechnologieën."; +"view_fake_chat_bubble_5" = "Vrienden laten vrienden geen gecompromitteerde berichten apps gebruiken. Graag gedaan."; +"vc_register_title" = "Zeg hallo tegen uw Session-ID"; +"vc_register_explanation" = "Uw Session-ID is het unieke adres dat mensen kunnen gebruiken om contact met u op te nemen via Session. Zonder verbinding met je echte identiteit, is je Session-ID volledig anoniem en privé."; +"vc_restore_title" = "Account herstellen"; +"vc_restore_explanation" = "Voer de herstel zin in die je hebt gekregen toen je je hebt aangemeld om je account te herstellen."; +"vc_restore_seed_text_field_hint" = "Voer uw herstel zin in"; +"vc_link_device_title" = "Koppel een apparaat"; +"vc_link_device_scan_qr_code_tab_title" = "Scan QR-code"; +"vc_display_name_title_2" = "Kies je weergavenaam"; +"vc_display_name_explanation" = "Dit is je naam wanneer je Session gebruikt. Het kan je echte naam zijn, een alias, of wat je maar wilt."; +"vc_display_name_text_field_hint" = "Kies een weergavenaam"; +"vc_display_name_display_name_missing_error" = "Voer a. u. b. een weergave naam in"; +"vc_display_name_display_name_too_long_error" = "Kies een kortere weergavenaam"; +"vc_pn_mode_recommended_option_tag" = "Aanbevolen"; +"vc_pn_mode_no_option_picked_modal_title" = "Gelieve een optie selecteren"; +"vc_home_empty_state_message" = "U heeft nog geen contactpersonen"; +"vc_home_empty_state_button_title" = "Sessie starten"; +"vc_seed_title" = "Uw Herstel Zin"; +"vc_seed_title_2" = "Maak kennis met uw herstel zin"; +"vc_seed_explanation" = "Uw herstel zin is de hoofdsleutel van uw Session-ID - u kunt deze gebruiken om uw Session-ID te herstellen als u de toegang tot uw apparaat verliest. Sla uw herstel zin op een veilige plek op en geef deze aan niemand op."; +"vc_seed_reveal_button_title" = "Ingedrukt houden om te onthullen"; +"view_seed_reminder_subtitle_1" = "Beveilig je account door je herstel zin op te slaan"; +"view_seed_reminder_subtitle_2" = "Houd de aangepaste woorden ingedrukt om uw herstelzin te onthullen, en sla het vervolgens veilig op om uw Session-ID te beveiligen."; +"view_seed_reminder_subtitle_3" = "Zorg ervoor dat u uw herstel zin op een veilige plek opslaat"; +"vc_path_title" = "Locatie"; +"vc_path_explanation" = "Session verbergt uw IP door uw berichten te routeren via meerdere Service Nodes in het gedecentraliseerde netwerk van Session. Dit zijn de landen die uw verbinding momenteel doorvoert:"; +"vc_path_device_row_title" = "U"; +"vc_path_guard_node_row_title" = "Invoer node"; +"vc_path_service_node_row_title" = "Service node"; +"vc_path_destination_row_title" = "Bestemming"; +"vc_path_learn_more_button_title" = "Kom meer te weten"; +"vc_create_private_chat_title" = "Nieuwe sessie"; +"vc_create_private_chat_enter_session_id_tab_title" = "Voer Session-ID in"; +"vc_create_private_chat_scan_qr_code_tab_title" = "Scan QR-code"; +"vc_create_private_chat_scan_qr_code_explanation" = "Scan de QR-code van een gebruiker om een sessie te starten. QR-codes kunnen worden gevonden door op het QR-icoon in de accountinstellingen te tikken."; +"vc_enter_public_key_explanation" = "Gebruikers kunnen hun Session-ID delen door naar hun accountinstellingen te gaan en op \"Deel Session-ID\" te tikken, of door hun QR-code te delen."; +"vc_scan_qr_code_camera_access_explanation" = "Session heeft toegang nodig tot de camera om QR codes te scannen"; +"vc_scan_qr_code_grant_camera_access_button_title" = "Toegang tot camera verlenen"; +"vc_create_closed_group_title" = "Nieuwe Gesloten Groep"; +"vc_create_closed_group_text_field_hint" = "Vul een groepsnaam in"; +"vc_create_closed_group_empty_state_message" = "U heeft nog geen contactpersonen"; +"vc_create_closed_group_empty_state_button_title" = "Sessie starten"; +"vc_create_closed_group_group_name_missing_error" = "Vul een groepsnaam in"; +"vc_create_closed_group_group_name_too_long_error" = "Vul a. u. b een kortere groepsnaam in"; +"vc_create_closed_group_too_many_group_members_error" = "Een gesloten groep kan niet meer dan 100 leden hebben"; +"vc_join_public_chat_title" = "Deelnemen aan Open groep"; +"vc_join_public_chat_enter_group_url_tab_title" = "Open Groep-URL openen"; +"vc_join_public_chat_scan_qr_code_tab_title" = "QR-code scannen"; +"vc_join_public_chat_scan_qr_code_explanation" = "Scan de QR-code van de open groep waar u zich bij wilt aansluiten"; +"vc_enter_chat_url_text_field_hint" = "Voer een open groep URL in"; +"vc_settings_title" = "Instellingen"; +"vc_settings_display_name_text_field_hint" = "Kies een weergavenaam"; +"vc_settings_display_name_missing_error" = "Voer a. u. b. een weergave naam in"; +"vc_settings_display_name_too_long_error" = "Kies een kortere weergavenaam"; "vc_settings_privacy_button_title" = "Privacy"; -"vc_settings_notifications_button_title" = "Notifications"; -"vc_settings_recovery_phrase_button_title" = "Recovery Phrase"; -"vc_settings_clear_all_data_button_title" = "Clear Data"; -"vc_notification_settings_title" = "Notifications"; +"vc_settings_notifications_button_title" = "Meldingen"; +"vc_settings_recovery_phrase_button_title" = "Herstel zin"; +"vc_settings_clear_all_data_button_title" = "Gegevens wissen"; +"vc_notification_settings_title" = "Meldingen"; "vc_privacy_settings_title" = "Privacy"; -"preferences_notifications_strategy_category_title" = "Notification Strategy"; -"modal_seed_title" = "Your Recovery Phrase"; -"modal_seed_explanation" = "This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device."; -"modal_clear_all_data_title" = "Clear All Data"; -"modal_clear_all_data_explanation" = "This will permanently delete your messages, sessions, and contacts."; -"vc_qr_code_title" = "QR Code"; -"vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code"; -"vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code"; -"vc_qr_code_view_scan_qr_code_explanation" = "Scan someone's QR code to start a conversation with them"; -"vc_view_my_qr_code_explanation" = "This is your QR code. Other users can scan it to start a session with you."; +"preferences_notifications_strategy_category_title" = "Notificatie Inhoud"; +"modal_seed_title" = "Uw Herstel Zin"; +"modal_seed_explanation" = "Dit is uw herstel zin, Hiermee kun je je sessie-ID herstellen of migreren naar een nieuw apparaat."; +"modal_clear_all_data_title" = "Wis alle gegevens"; +"modal_clear_all_data_explanation" = "Hiermee worden uw berichten, sessies en contacten permanent verwijderd."; +"vc_qr_code_title" = "QR-code"; +"vc_qr_code_view_my_qr_code_tab_title" = "Bekijk mijn QR-code"; +"vc_qr_code_view_scan_qr_code_tab_title" = "QR-code scannen"; +"vc_qr_code_view_scan_qr_code_explanation" = "Scan iemands QR-code om een gesprek te beginnen"; +"vc_view_my_qr_code_explanation" = "Dit is je QR-code. Andere gebruikers kunnen deze scannen om een sessie met je te starten."; // MARK: - Not Yet Translated -"fast_mode_explanation" = "You’ll be notified of new messages reliably and immediately using Apple’s notification servers."; -"fast_mode" = "Fast Mode"; -"slow_mode_explanation" = "Session will occasionally check for new messages in the background."; -"slow_mode" = "Slow Mode"; -"vc_pn_mode_title" = "Message Notifications"; -"vc_notification_settings_notification_mode_title" = "Use Fast Mode"; -"vc_link_device_recovery_phrase_tab_title" = "Recovery Phrase"; -"vc_link_device_scan_qr_code_explanation" = "Navigate to Settings → Recovery Phrase on your other device to show your QR code."; -"vc_enter_recovery_phrase_title" = "Recovery Phrase"; -"vc_enter_recovery_phrase_explanation" = "To link your device, enter the recovery phrase that was given to you when you signed up."; -"vc_enter_public_key_text_field_hint" = "Enter Session ID or ONS name"; -"vc_home_title" = "Messages"; -"admin_group_leave_warning" = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."; -"vc_join_open_group_suggestions_title" = "Or join one of these..."; -"vc_settings_invite_a_friend_button_title" = "Invite a Friend"; -"vc_settings_help_us_translate_button_title" = "Help us Translate Session"; -"copied" = "Copied"; -"vc_conversation_settings_copy_session_id_button_title" = "Copy Session ID"; -"vc_conversation_input_prompt" = "Message"; -"vc_conversation_voice_message_cancel_message" = "Slide to Cancel"; -"modal_download_attachment_title" = "Trust %@?"; -"modal_download_attachment_explanation" = "Are you sure you want to download media sent by %@?"; -"modal_download_button_title" = "Download"; -"modal_open_url_title" = "Open URL?"; -"modal_open_url_explanation" = "Are you sure you want to open %@?"; -"modal_open_url_button_title" = "Open"; -"modal_blocked_title" = "Unblock %@?"; -"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; -"modal_blocked_button_title" = "Unblock"; -"modal_link_previews_title" = "Enable Link Previews?"; -"modal_link_previews_explanation" = "Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session's settings."; -"modal_link_previews_button_title" = "Enable"; -"vc_share_title" = "Share to Session"; -"vc_share_loading_message" = "Preparing attachments..."; -"vc_share_sending_message" = "Sending..."; -"view_open_group_invitation_description" = "Open group invitation"; -"vc_conversation_settings_invite_button_title" = "Add Members"; +"fast_mode_explanation" = "Je wordt op een betrouwbare en onmiddellijke manier op de hoogte gebracht van nieuwe berichten via Apple's notificatieservers."; +"fast_mode" = "Snelle Modus"; +"slow_mode_explanation" = "Sessie controleert af en toe op nieuwe berichten in de achtergrond."; +"slow_mode" = "Langzame modus"; +"vc_pn_mode_title" = "Berichtmeldingen"; +"vc_notification_settings_notification_mode_title" = "Gebruik snelle modus"; +"vc_link_device_recovery_phrase_tab_title" = "Herstel zin"; +"vc_link_device_scan_qr_code_explanation" = "Navigeer naar Instellingen → Herstel zin op je andere apparaat om je QR-code te tonen."; +"vc_enter_recovery_phrase_title" = "Herstel zin"; +"vc_enter_recovery_phrase_explanation" = "Om je apparaat te koppelen, voer je de herstelzin in die je kreeg toen je je aanmeldde."; +"vc_enter_public_key_text_field_hint" = "Voer Session-ID of ONS naam in"; +"vc_home_title" = "Berichten"; +"admin_group_leave_warning" = "Omdat u de maker van deze groep bent, wordt het voor iedereen verwijderd. Dit kan niet ongedaan worden gemaakt."; +"vc_join_open_group_suggestions_title" = "Of neem deel aan een van deze..."; +"vc_settings_invite_a_friend_button_title" = "Nodig een vriend uit"; +"vc_settings_help_us_translate_button_title" = "Help ons om Session the vertalen"; +"copied" = "Gekopieerd"; +"vc_conversation_settings_copy_session_id_button_title" = "Session-ID kopiëren"; +"vc_conversation_input_prompt" = "Bericht"; +"vc_conversation_voice_message_cancel_message" = "Veeg om te annuleren"; +"modal_download_attachment_title" = "Vertrouw %@?"; +"modal_download_attachment_explanation" = "Weet je zeker dat je deze media van %@ wilt downloaden?"; +"modal_download_button_title" = "Downloaden"; +"modal_open_url_title" = "URL openen?"; +"modal_open_url_explanation" = "Weet u zeker dat u %@ wilt openen?"; +"modal_open_url_button_title" = "Openen"; +"modal_blocked_title" = "Blokkering opheffen voor %@?"; +"modal_blocked_explanation" = "Weet je zeker dat je %@ weer wilt toelaten?"; +"modal_blocked_button_title" = "Deblokkeren"; +"modal_link_previews_title" = "Linkvoorbeeld inschakelen?"; +"modal_link_previews_explanation" = "Link previews inschakelen zal previews tonen voor URLs die u verstuurt en ontvangt. Dit kan nuttig zijn, maar Session moet contact opnemen met gekoppelde websites om previews te genereren. U kunt links altijd uitschakelen in de Session’s-instellingen."; +"modal_link_previews_button_title" = "Inschakelen"; +"vc_share_title" = "Delen naar de Session"; +"vc_share_loading_message" = "Bijlagen voorbereiden..."; +"vc_share_sending_message" = "Aan het verzenden..."; +"view_open_group_invitation_description" = "Open groepsuitnodiging"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index f3f5d87b6..4dd92513a 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -491,7 +491,9 @@ "vc_view_my_qr_code_explanation" = "Este é o seu código QR. Outros usuários podem escaneá-lo para iniciar uma sessão com você."; // MARK: - Not Yet Translated "fast_mode_explanation" = "Você será notificado de forma confiável e imediata sobre novas mensagens usando os servidores de notificação da Apple."; +"fast_mode" = "Modo Rápido"; "slow_mode_explanation" = "O session verificará ocasionalmente por novas mensagens em segundo plano."; +"slow_mode" = "Modo Lento"; "vc_pn_mode_title" = "Notificação de Mensangens"; "vc_notification_settings_notification_mode_title" = "Usar Modo Rápido"; "vc_link_device_recovery_phrase_tab_title" = "Frase de Recuperação"; @@ -523,5 +525,4 @@ "vc_share_title" = "Compartilhar no Session"; "vc_share_loading_message" = "Preparando anexos..."; "vc_share_sending_message" = "Enviando..."; -"view_open_group_invitation_description" = "Open group invitation"; -"vc_conversation_settings_invite_button_title" = "Add Members"; +"view_open_group_invitation_description" = "Convite para grupo aberto"; From f3afcc175e2c6a5c9c9438bc9693599d0d9323f7 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 13 May 2021 14:39:02 +1000 Subject: [PATCH 23/57] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6afcd65f9..2a1715b04 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5135,7 +5135,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 240; + CURRENT_PROJECT_VERSION = 241; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5204,7 +5204,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 240; + CURRENT_PROJECT_VERSION = 241; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5265,7 +5265,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 240; + CURRENT_PROJECT_VERSION = 241; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5335,7 +5335,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 240; + CURRENT_PROJECT_VERSION = 241; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6220,7 +6220,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 240; + CURRENT_PROJECT_VERSION = 241; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6288,7 +6288,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 240; + CURRENT_PROJECT_VERSION = 241; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From 38a7c93967a11ca6ba28fce9bd3e85ee336329c6 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 13 May 2021 16:11:21 +1000 Subject: [PATCH 24/57] Fix profile picture handling --- SessionMessagingKit/File Server/FileServerAPIV2.swift | 5 +++-- SignalUtilitiesKit/To Do/OWSProfileManager.m | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/SessionMessagingKit/File Server/FileServerAPIV2.swift b/SessionMessagingKit/File Server/FileServerAPIV2.swift index c7b627b81..0ff9a8f59 100644 --- a/SessionMessagingKit/File Server/FileServerAPIV2.swift +++ b/SessionMessagingKit/File Server/FileServerAPIV2.swift @@ -88,8 +88,9 @@ public final class FileServerAPIV2 : NSObject { } @objc(download:) - public static func objc_download(file: UInt64) -> AnyPromise { - return AnyPromise.from(download(file)) + public static func objc_download(file: String) -> AnyPromise { + guard let id = UInt64(file) else { return AnyPromise.from(Promise(error: Error.invalidURL)) } + return AnyPromise.from(download(id)) } public static func download(_ file: UInt64) -> Promise { diff --git a/SignalUtilitiesKit/To Do/OWSProfileManager.m b/SignalUtilitiesKit/To Do/OWSProfileManager.m index a451670c3..ee29b22b0 100644 --- a/SignalUtilitiesKit/To Do/OWSProfileManager.m +++ b/SignalUtilitiesKit/To Do/OWSProfileManager.m @@ -807,7 +807,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); AnyPromise *promise; if ([profilePictureURL containsString:SNFileServerAPIV2.server]) { - uint64_t *file = (uint64_t)[[profilePictureURL lastPathComponent] intValue]; + NSString *file = [profilePictureURL lastPathComponent]; promise = [SNFileServerAPIV2 download:file]; } else { promise = [SNFileServerAPI downloadAttachmentFrom:profilePictureURL]; From 31f1a2e8272a8691bafc64947a615ac5c2e51aee Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 13 May 2021 16:11:46 +1000 Subject: [PATCH 25/57] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 2a1715b04..0817cbf64 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5135,7 +5135,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 241; + CURRENT_PROJECT_VERSION = 242; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5204,7 +5204,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 241; + CURRENT_PROJECT_VERSION = 242; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5265,7 +5265,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 241; + CURRENT_PROJECT_VERSION = 242; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5335,7 +5335,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 241; + CURRENT_PROJECT_VERSION = 242; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6220,7 +6220,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 241; + CURRENT_PROJECT_VERSION = 242; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6288,7 +6288,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 241; + CURRENT_PROJECT_VERSION = 242; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From fa13778d703ec1e43fff1f56c6b09cae9e41fd41 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 14 May 2021 10:20:15 +1000 Subject: [PATCH 26/57] fix the badge number for muted threads --- SignalUtilitiesKit/Messaging/OWSMessageUtils.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m index 12f51b019..7c0e20575 100644 --- a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m +++ b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m @@ -73,6 +73,8 @@ NS_ASSUME_NONNULL_BEGIN YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName]; NSArray *allGroups = [unreadMessages allGroups]; for (NSString *groupID in allGroups) { + TSThread *thread = [TSThread fetchObjectWithUniqueID:groupID transaction:transaction]; + if (thread.isMuted) continue; [unreadMessages enumerateKeysAndObjectsInGroup:groupID usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { From cdf27a631aaf1849137d3636885e5c790d11381a Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 14 May 2021 10:55:17 +1000 Subject: [PATCH 27/57] Fix profile picture handling --- SessionMessagingKit/File Server/FileServerAPIV2.swift | 2 +- SignalUtilitiesKit/To Do/OWSProfileManager.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SessionMessagingKit/File Server/FileServerAPIV2.swift b/SessionMessagingKit/File Server/FileServerAPIV2.swift index 0ff9a8f59..dd19b528c 100644 --- a/SessionMessagingKit/File Server/FileServerAPIV2.swift +++ b/SessionMessagingKit/File Server/FileServerAPIV2.swift @@ -74,7 +74,7 @@ public final class FileServerAPIV2 : NSObject { // MARK: File Storage @objc(upload:) public static func objc_upload(file: Data) -> AnyPromise { - return AnyPromise.from(upload(file)) + return AnyPromise.from(upload(file).map { String($0) }) } public static func upload(_ file: Data) -> Promise { diff --git a/SignalUtilitiesKit/To Do/OWSProfileManager.m b/SignalUtilitiesKit/To Do/OWSProfileManager.m index ee29b22b0..c37b850f9 100644 --- a/SignalUtilitiesKit/To Do/OWSProfileManager.m +++ b/SignalUtilitiesKit/To Do/OWSProfileManager.m @@ -364,8 +364,8 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); AnyPromise *promise = [SNFileServerAPIV2 upload:encryptedAvatarData]; - [promise.thenOn(dispatch_get_main_queue(), ^(uint64_t fileID) { - NSString *downloadURL = [NSString stringWithFormat:@"%@/files/%llu", SNFileServerAPIV2.server, fileID]; + [promise.thenOn(dispatch_get_main_queue(), ^(NSString *fileID) { + NSString *downloadURL = [NSString stringWithFormat:@"%@/files/%@", SNFileServerAPIV2.server, fileID]; [NSUserDefaults.standardUserDefaults setObject:[NSDate new] forKey:@"lastProfilePictureUpload"]; [self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{ successBlock(downloadURL); From 0027481afea143be6b7d4e507aa8d145ad76d78e Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 14 May 2021 11:12:54 +1000 Subject: [PATCH 28/57] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 0817cbf64..f3f10aa43 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5135,7 +5135,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5204,7 +5204,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5265,7 +5265,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5335,7 +5335,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6220,7 +6220,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6288,7 +6288,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From 1f6a00d9e48461399bd397f225478f2dd6195c12 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 14 May 2021 11:24:21 +1000 Subject: [PATCH 29/57] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index f3f10aa43..5bdca3c0c 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5135,7 +5135,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5204,7 +5204,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5265,7 +5265,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5335,7 +5335,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6220,7 +6220,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6288,7 +6288,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From 65f369909332b81780a607bd0c1281ce1ed29f7d Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 14 May 2021 14:47:03 +1000 Subject: [PATCH 30/57] fix expiration timer not starting in conversation screen --- Session/Conversations/ConversationVC.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index b048216b8..f0aeb67fc 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -354,6 +354,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat // the previous value when the keyboard is shown. self.messagesTableView.reloadData() } + self.markAllAsRead() } if shouldAnimate { messagesTableView.performBatchUpdates(batchUpdates, completion: batchUpdatesCompletion) From d7a7abc11e004ff9a946987f8d05ab9f4d037a24 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 14 May 2021 15:38:06 +1000 Subject: [PATCH 31/57] start expiration timer after attachments downloaded --- .../Messages/Signal/TSIncomingMessage.m | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m index e452327b3..908d53f42 100644 --- a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m @@ -144,6 +144,17 @@ NS_ASSUME_NONNULL_BEGIN return; } + BOOL isAllAttachmentDownloaded = YES; + for (NSString *attachmentId in self.attachmentIds) { + TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction]; + isAllAttachmentDownloaded = isAllAttachmentDownloaded && attachment.isDownloaded; + if (!isAllAttachmentDownloaded) break; + } + + if (!isAllAttachmentDownloaded) { + return; + } + _read = YES; [self saveWithTransaction:transaction]; From 26b0a1f08a151ce53b14041e3a242963fdb44cca Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 17 May 2021 09:51:14 +1000 Subject: [PATCH 32/57] Possibly fix crash --- Session/Home/HomeVC.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index c9418248d..20a70778f 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -194,9 +194,16 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv } @objc private func handleYapDatabaseModifiedNotification(_ yapDatabase: YapDatabase) { - // This code is very finicky and crashes easily + // NOTE: This code is very finicky and crashes easily. Modify with care. AssertIsOnMainThread() - let notifications = dbConnection.beginLongLivedReadTransaction() // Jump to the latest commit + // If we don't capture `threads` here, a race condition can occur where the + // `thread.snapshotOfLastUpdate != firstSnapshot - 1` check below evaluates to + // `false`, but `threads` then changes between that check and the + // `ext.getSectionChanges(§ionChanges, rowChanges: &rowChanges, for: notifications, with: threads)` + // line. This causes `tableView.endUpdates()` to crash with an `NSInternalInconsistencyException`. + let threads = threads! + // Create a stable state for the connection and jump to the latest commit + let notifications = dbConnection.beginLongLivedReadTransaction() guard !notifications.isEmpty else { return } let ext = dbConnection.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewConnection let hasChanges = ext.hasChanges(forGroup: TSInboxGroup, in: notifications) From 40d3497aaf31c705bf0b508517a9af29da4348f0 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 17 May 2021 10:03:40 +1000 Subject: [PATCH 33/57] Add missing strings --- Session/Meta/Translations/de.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/en.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/es.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/fa.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/fr.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/id-ID.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/it.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/ja.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/nl.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/pl.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/pt_BR.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/ru.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/sk.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/vi-VN.lproj/Localizable.strings | 4 ++++ Session/Meta/Translations/zh_CN.lproj/Localizable.strings | 4 ++++ 15 files changed, 60 insertions(+) diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index d1e31458e..eea15cbf6 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "%@ blockieren?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Freigeben"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ wurde blockiert."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Benutzer blockiert"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Blockierte Benutzer werden dich nicht anrufen oder dir Nachrichten senden können."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 2a835e2e1..2b7579a34 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Block %@?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Unblock"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ has been blocked."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "User Blocked"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Blocked users will not be able to call you or send you messages."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index b85a2405d..360660bb2 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "¿Bloquear %@?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Desbloquear"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ ha sido bloqueado."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Contacto bloqueado"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Los contactos bloqueados no podrán llamarte ni enviarte mensajes."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index d3bb68b22..0919f160b 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "%@ مسدود شود؟"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "رفع مسدودی"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ مسدود شد."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "کاربر مسدود شده است"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "کاربری که مسدود شده است، امکان تماس یا ارسال پیام به شما را ندارد."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 930e41c57..bccae77d3 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Bloquer %@ ?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Débloquer"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ a été bloqué."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "L’utilisateur est bloqué"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Les utilisateurs bloqués ne pourront ni vous appeler ni vous envoyer des messages."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 2d8a1e992..0efe2cce5 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Blokir %@?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Buka blokir"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ has been blocked."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Pengguna telah diblokir"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Pengguna terblokir tidak bisa menghubungi atau mengirimkan pesan kepada Anda."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index 1c84802bb..b43e460f7 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Bloccare %@?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Sblocca"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ è stato bloccato."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Utente bloccato"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Gli utenti bloccati non potranno chiamarti o inviarti messaggi."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 03cd9f569..bd7873e46 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "%@ をブロックしますか?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "ブロックを解除する"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ はブロックされています。"; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "ユーザがブロックされました"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "ブロックされたユーザは、あなたにメッセージや通話を発信することができなくなります。"; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 20747a672..83eaa0361 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "%@ blokkeren?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Deblokkeren"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ is geblokkeerd."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Gebruiker Geblokkeerd"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Geblokkeerde gebruikers zijn niet in staat om u te bellen of berichten te sturen."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 921f70388..e44ec8175 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Zablokować %@?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Odblokuj"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "Zablokowano %@."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Zablokowano użytkownika"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Zablokowani użytkownicy nie będą mogli do Ciebie dzwonić ani wysyłać Ci wiadomości."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 4dd92513a..40e2af6e9 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Bloquear %@?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Desbloquear"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ foi bloqueado."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Pessoa sob Bloqueio"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Você não receberá mais ligações e mensagens de quem bloquear."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 59d15e9e2..076f52d8c 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Заблокировать %@?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Разблокировать"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ был(-а) заблокирован(-а)."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Пользователь заблокирован"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Заблокированные пользователи не смогут звонить или отправлять сообщения Вам."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 836a7d21a..ec9ef24e0 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Blokovať %@?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Odblokovať"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ has been blocked."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "User Blocked"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Blokovaný používateľ vám nebude mocť volať ani posielať správy."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 53cad3499..86ad34ed2 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Block %@?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Unblock"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ has been blocked."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "User Blocked"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "Blocked users will not be able to call you or send you messages."; /* Label for generic done button. */ diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index cd0acace1..093d22be5 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -78,6 +78,10 @@ "BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "屏蔽 %@?"; /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "从黑名单中移除"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "已屏蔽 %@。"; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "该用户已被加入黑名单"; /* An explanation of the consequences of blocking another user. */ "BLOCK_USER_BEHAVIOR_EXPLANATION" = "被屏蔽的用户将无法向您发起通话,或发送消息。"; /* Label for generic done button. */ From 221878885861626b899bfdb5600c2af8cc79532c Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 17 May 2021 10:06:59 +1000 Subject: [PATCH 34/57] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 5bdca3c0c..fa2718006 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5135,7 +5135,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5204,7 +5204,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5265,7 +5265,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5335,7 +5335,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6220,7 +6220,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6288,7 +6288,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From 4773350cb12579fd93712775b65af0a970aa493c Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 17 May 2021 13:42:17 +1000 Subject: [PATCH 35/57] Fix crash --- .../SessionWhite40.imageset/Contents.json | 23 ++++++++++++++++++ .../SessionWhite40.png | Bin 0 -> 1054 bytes .../SessionWhite40@2x.png | Bin 0 -> 2131 bytes .../SessionWhite40@3x.png | Bin 0 -> 3200 bytes 4 files changed, 23 insertions(+) create mode 100644 Session/Meta/Images.xcassets/Session/SessionWhite40.imageset/Contents.json create mode 100644 Session/Meta/Images.xcassets/Session/SessionWhite40.imageset/SessionWhite40.png create mode 100644 Session/Meta/Images.xcassets/Session/SessionWhite40.imageset/SessionWhite40@2x.png create mode 100644 Session/Meta/Images.xcassets/Session/SessionWhite40.imageset/SessionWhite40@3x.png diff --git a/Session/Meta/Images.xcassets/Session/SessionWhite40.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/SessionWhite40.imageset/Contents.json new file mode 100644 index 000000000..5447049fe --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/SessionWhite40.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "SessionWhite40.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "SessionWhite40@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "SessionWhite40@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Session/SessionWhite40.imageset/SessionWhite40.png b/Session/Meta/Images.xcassets/Session/SessionWhite40.imageset/SessionWhite40.png new file mode 100644 index 0000000000000000000000000000000000000000..5bcb78b52c307e2796b1ed45f0c286c3358f0f9f GIT binary patch literal 1054 zcmV+(1mXLMP)?(+p9>6ahf&myx&*=iVAQk32 zL84v(jHKd+s59yRJJbP~LVY*Iuc$YZRZOMXx?M1rsBdN?{561X-~(U}a1}5Lyw`&B zUBIOcnr{Pc1nvWRQ{C6VBUNYH)VW0Ysd1%m24>K;=?!&r+3Aa!!GR`$Zf^nNOaj@x zoYPI}@nl?4pRmD9tpK!SoOFlOtFzzMM!$4W{WEj;vAVrUpoi7NC9rAr0kvodq~v@^ zJ&`$EP+wl@t4ct_scu9)S_1oK*}rHNsJ;ZTm^qtO_p3d1?1#%hslHI}S5KC}Ce*=J zz=o{NQB|ExL5j?-E>PCqlRjoPvs+YOQhUqxjey;e(w*z5I?@PG=4Wfl{6c0w)oQj1 zb+565<~nTb$g2WnzP1|&OjUoV*ERwX)PDazWQUAHdCB@(6)5@cQXjXrZ9eXDdogv7 zI&R#sHLHezvRDVQcV8$s_Ni~R08v%NCi{aiQ%BPKb83G`4V#JQuN#u>Y2Y9*3H;um z|5G7^&zb>BvF`*XQt{Dh$p`J`|4DRJ%PHzTIYJ(R;Kv1Lmf)-rwd>& z@ksj*@K6Y$vhwR2qMy_A;(}~H8$y`r01UwS={Z*bm#4xH9fAS4COu~gU?~-v+9|G= z(g+_;#V-or*HmchnLg7#;uQ_RIO?;hhcBtuwUfknB~x7yCe;FUz}gCY%vQDpt-(|JHYFbM2Q zWr&vyW5ClP1k2>zDO2++ETHSDZ>_V(KIiDH4ge1lPqSB2J6*0-v%n1SIq~0^sJANo Y3s12y;(sawdjJ3c07*qoM6N<$f^MSf$p8QV literal 0 HcmV?d00001 diff --git a/Session/Meta/Images.xcassets/Session/SessionWhite40.imageset/SessionWhite40@2x.png b/Session/Meta/Images.xcassets/Session/SessionWhite40.imageset/SessionWhite40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c65cd1f78afa46de817f2184b58e3dfe69a37c08 GIT binary patch literal 2131 zcmV-Z2(0&sP)||IhZ0HWXSYVi3hpDHJIvX$d5>mZ%9A zLl7(wBA^mYNQ7uqXiE#hfFHb|CJ+#jHZith_)sI1f+gJAC=!xlN`bevg&+m%`_dNZ zd;R(0?2_s9nal3Z%x>3TvR~$0=6{|&bLO1qJZDTT0?d*01xfQHeMZvpl0G77N~?92 z#Gs@BNqv%Dkn~qckC@s1JcX^9I|0)qT_@>kNwb@h*wSjX4c;r@+O)Ea32R+tD0A4LAmv3tSI80*t1#XB+UbtV#{IH^VSb1Ls!M&O!lZ z1Aj9pvo0FEcD-s3zZ1()mVPPtR7mDw49DUu$NG(Daq z=?*Cv=y|8?S>;OWJiv4XXZ`1$>HJuoH%NM>!UmkLuwy$dBWtrvRhSIV zl$7T980juvi!1OwzYw%4RpPC{5r5V7pWBZ&cum27XwfG2%P{ zd?HC(O_I!PSkmoI`k9h$DakHTCRK2cI4?=M%*+;<+0M+|gkH4SvG5phRhdeGTPo@; zTYz5!=aBO){o&(dX2ZbOC2i7yz7dmjKQLrwtBW;F%X}c|=aM$$$U7{lUs9i$z1Q}g zN#0o<1C{}a0S|55v(+B77N|B(iLf{SK#Hj6fRhWp=W^#hcL8_eZ^uL#1y%yb6sajo z@C0C8il{;07li405%=?Y(DHiPEa2e`BLVCImH>wpp(&5(Nx-TMQT>Eh^yD`0wLHl{ zkv#K(XL1wQ0B7NEF2?yg zEyN9xqX)(EPb4_G`g3Sn4|x9OVj(FSer$1NTtH zKu!p0yQhBBG=}QpGu^kX8Yoi!#QL9-#a_anT>i`*I0;yjQ`8Dl2J&_d6scz(`3CY= zfdy?Dpb1fn2m@jk+X(~gK?8YF2CCO(gcn9qFj#|8;8ssh;nYQ4O1`(tK?8Y_28z@< zf$+M2CqvH~f^WKf2od>92$Qxf9tLLB%N1^*h^Fa;N$p^YmPY|^Z@wG|5nFeYZy^6w z{a1>CBAQMHo=wrRlB)gigqY;D8AeK?Py^NV)Cdc1FK83s=6IP_XTDP5H)3Y1fr*kH z)PbF3qNJ6;8D=(;t1{rrz>6(DChbu8Bj&X2wuF0Mo2T$`gy{NC@uLb1G@h_A5vPx| zw;Ew_`tK^O5n~kawuH+t(&z98?ZuKaNCOv1S}iHf$1@^nmqOsfzJfImNP57`{!(zg zi4Y<=E{R=UYi1Y4GtLF}rTnu`0@qNGC$U=~>~h2zBzkxUI2zcIV3?jc2IWCmD)l+- zzBroc_X)nlYdrKD6jhyXV+gg(}>Lldzh4dEQN@0_L zx17=28<%<(;55eu04LQXz2=npzd5T>=O*XcW;1)artsOSIA1MUP}e{R9KXy-TG`0( z6{lS9nd*wPyBo zS|<1LFtax$Es%7xr1uM#)>#H5Et7O=mT{zfpDO*hr0=Vo9P)@~8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H13;jt%K~#90?VV|`9Yqz#|9wMPf+PXLs)kS@34E~F0)&zsN-P!y z4TcDW(h{^N5RjsxSPEK&LIqG1C;<_R5H=ym7C;F}QLF`s0a7u9T^7lnK$4g3{rqt5 zgUidi-92+>?wxxxzpvAM=5(K#o}NCZPdnuV&`;97lEz5dUD7B?LnRH8w56m0dEL9k zbV*tvX}+Y_B+Zrdq@*XDbITeP?iv+P3ot;^iITn}=`cwHvy|WFtd}%H(k+tian7ww zBP0`p0V5<`C~2HZBgL>#((ff*lk}}?$Y8)INtZ}ENm9Q?WNoC(mp$l{opZ!gNM$f! z3rRn(-fYt}GD<$jqmm{#=jMkBCN>x_TGAbohNn=hq&^==8ta^U##gA4L?UpGY{pX> zD|>d9^cZlAuTaz*R-K|>Bk4yCD6~k@ef1g#{iRo)d~7;1u!Pf4J#>(^&xN}uw4#)(6#`( z05?=0nsZYSKBLpnP zexOSl1n37$3-tF#*y_>-l}8XB3ReP$w4iMr3$PQgIMDCk?W^LH;JXDATF|DB2e=^6 z?~nAZup6*0Q0Xr%YEQ?5%^c@@`hE?tRc(bo1&s9pFsOxX>Ue<512#COr{EUYkFSqY zTHKzF2N)f&!EbsBjt>-^4J2CGFG&vIfJGiVOzknkxPYw7oO9iIE4;B&^*aB{Q)FZX zbDbRODYQ(|-I;1u;^QsPwILNrdrKPZDRh@}u2X$iMPz~J+Q5pW0|Npc$WWP*Fuq`% z{uNn}XFa-QAps@L;|rc^A6H}%x%HUmoLiEyLM487_FP+5k+h4a!0Q<+QxfJYo@;Ma zByHs>@J`0clz0G!$UoxE4H;}jwknkbI8OdZwihe%hheSGR-cjprv#?EJ ztRxqJy(JytDe}5=?v0A1rJh1NWUE$*hsy%jr$`p6z1UNzG*}Y=#!LE|r%1P?$x_&0 z!?IJWq%VNslCBFByT>{AMo)z+JcAlLBQ__ z2Rng%j{4p!2HY8_Tt@gd05BA|B=BPN=F+}oxvIbuB~A6*bBc2=9F$)WfGyR!z1M?f znG{CIHuN1OX@4c#?Ru+h&vW0tF)Su-smI#ufY8UWD%W8#Hc<^eARuf#a6r!eo*I&EJ}eq5fgi-Q^V4zFIrp5ThXZAA z!O{~IkrAy{ASF*p+Rr)n=a|C1KZKVA%8!&()c>+}`}KHB()T1C?3|mOS_<$`;PvgM zfIC=#>3NS^8PE+p1xx@o$*SR_u(Sg{)(}Vl(rhTM!Ioh!!3AWH{-RHJG;ppg+nA3P zl8$rE&1_VOEC8mLA4vVp%P=4UToee)O+-v&-0HdjWg0rm$ziEP}%)%S)y37m<4 zhog@dfWunVwnPBmByM#XU@dS3;M-i0m!7^i?D1-uZGfvtmiMWazhg-~iw*X4CRv|b zfMwEY+AtYou$r>NfxjheQx9u^YbkmzEnqCRgp!2UfRkv#Hvs>T_;{T<`vXs;FdDSO z!~__FCHY9ov%o<)>z3GHHFb|CF8fbk{1+R6ikb5m8%|zH(Xejdc3{_>^h#~8YW+>I zY;?Xo#y(yE4$q@)4TJg5uEbXGlhOrDBIAh(4H~RQ&eqko#Uw}Ti~E5+vTR#s0Q&$> zr)=0FU?Q+tM!IBTuo`(ofm=v7F-5A2vl+cE`P6*(On%hNCq_;v<*WNNS)xnr>Z zxHK#WE~1DN6D&E)MEs!!KAxk2i3vFwtVVt#6O;ES>cj+#X1y947Siwl7QUSHzPTE# zdXN6qj!!dF26&M)$(Ne}ut&A?)6j)|__t1QzuXO0bFabJ#3{|hWPP=5#mtQi9F9F% zX;_3!wSysRG|^x+_uLixRY@~3d4&`sNmIb0i%$aHOwllacQ7w)-4zpBV6d8dj|OI@ zXxBoDoGcXkIX#!gCME~hk=-(b)#z{(@M^*~by4(8dBAqq0&1FxNjJ7|JG__7mK&_9 zM}KVLdqKiBMTNPw6kvDYz7$3SbCFyHY*Uq02!qwMZ7}9)ihU^80>?L7&r|@10?(&3 z++r-i<*6bWtfp=I5PuRxx-(Z=0X8u?1)E@GP0I{cZQCZmrNqr97XU+Bsc$0qY+|yc zQw-JvFb-H1q3?9+%!o60ECOGaMF?*rI|4XuMD7qt=Sj-Kvpd5%mqiF%!vKd$dPsIO zvX37-=l+<70=r{th>u{4okgS&Z&(l5nPQ=xHY13`;QN>-xg98!!6XS=2nc=C z8cQyUxl+!=_C2=~g)^8GwMD9%w4sB*p4du5JMvh@eVqx|k&AN!x<6Q3Cu%|foF(a5 z*Bb;M0Eh8Voxs=_X0nN-Cy1Ak11xH?&%bdQ>-3hSJRc zFenI{SlpZIK$SnUful3&*&M-U+P>)OB~)Wy>TNfw$kM%?oZ!CeB|Rf)x}=w7!_X)4 z=VQ#1HO{%V$*2n4CTYB<$O+E5`|4Gml;9~|2>cTGVlFM&NWfe(KJKVj@f*agd9B5c zPx9qiFNp;EWqQxk-;3zITm@`T+@7#^DR2sib_IVbMCct-0Q@z=ShIjKQ)v!hqnnR$ zwS`9$9tvkubr_ap59W#Y*ggaQ8P zvB4`nD;*Q$U-g*goV%w9`j$u^d9LlyV}C#bD`Ix z-O{qQJI4jCKV6Y|h3iqCY}6#MEwtbA6j>x`Rz?2Io4I7IDe05(ve5Q=%y7q6Xi%tv0Cytfu8#TuxYlx)1o1*o`%>>y7l6ovrbpfLyOLXsJY(`3z^a17& zzC2LuHs{>xo(co;Z|nSsqUMz70Dvihp>PeoGvEP_1}goeMeXTmfHMO94sdf{6;BOR zT8y39SdtOI5ri}8tlZ%3s#YFAIOFs~6fwC~8-k^-T^4+|;1@n27expNoi|((0QfqV zFF3>;e7PjC$;ba91YO+3_H_(cmV^t4JCtq}HD@>bgq{{54B$qJn6$B=0AB(gjO<&0 z@b;prV25okkKrZSA?bb#!R7`Ni5miYxH{=gwkPgTXt3A&M#=!w&jIWR{EXy@Fz+DE znc2XBgwGJGWi@asmS#1Sf1)G7HszfKOe6m2lUi;kA}^#b|KPEb?v<4EIF5CaW=WbS z=>wS)w9_1+gJh21fwGvGQIevwr}n`gB%SY^^X+w(6v%{AHQHU0ih59KyVE78(q2i8B_&7> zhB-1%l^qLQZs<$SI!U)l`jvC;&9p)idy|2IlFn9knbta;rIK!!bVVbE>LIoJA3HJq zL`kPfI#N Date: Mon, 17 May 2021 15:40:58 +1000 Subject: [PATCH 36/57] Add missing string --- Session/Meta/Translations/de.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/en.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/es.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/fa.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/fr.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/id-ID.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/it.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/ja.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/nl.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/pl.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/pt_BR.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/ru.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/sk.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/vi-VN.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/zh_CN.lproj/Localizable.strings | 2 ++ 15 files changed, 30 insertions(+) diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index eea15cbf6..2cf94efb8 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "Dein Konto kann nicht aktiviert werden, bevor du den dir gesendeten Code verifizierst."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Sofort"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Authentifizieren, um Session zu öffnen."; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Authentifizierung gescheitert"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 2b7579a34..29d3ebc24 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "We can't activate your account until you verify the code we sent you."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Instant"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Authenticate to open Session."; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Authentication Failed"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 360660bb2..b53b010aa 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "No podemos activar tu cuenta hasta que no verifiques el código que te hemos enviado."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Inmediato"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Identifícate para acceder a Session."; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Fallo al identificarse"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 0919f160b..b7463fe97 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "لطفا برای فعال شدن حساب خود، کدی که به شما فرستاده‌ایم را تائید کنید."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "لحظه"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "برای باز کردن Session هویت خود را احراز کنید"; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "احراز هویت ناموفق بود"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index bccae77d3..039808804 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "Nous ne pouvons pas activer votre compte tant que vous n’aurez pas vérifié le code que nous vous avons envoyé."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Instantané"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Authentifiez-vous pour ouvrir Session."; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Échec d’authentification"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 0efe2cce5..c7fd7a879 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "Kami tidak bisa mengaktifkan akun Anda hingga setelah Anda memverifikasi kode yang kami kirimkan."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Instan"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Autentikasi untuk membuka Session."; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Autentikasi gagal."; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index b43e460f7..f1a965711 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "Non possiamo attivare il tuo accanto fintanto che non verificherai il codice che ti abbiamo inviato."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Immediato"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Autenticarsi per aprire Session."; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Autenticazione fallita"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index bd7873e46..a7948c591 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "アカウントを有効化するには,お送りしたコードの確認を行ってください。"; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "即座"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Sessionの起動を認証する"; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "認証失敗"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 83eaa0361..713a84241 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "We kunnen uw account pas activeren nadat u de code die we u hebben gestuurd verifieert."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Direct"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Authenticate to open Session."; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Verificatie mislukt"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index e44ec8175..377a9ed3f 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "Nie możemy aktywować Twojego konta, dopóki nie zweryfikujesz kodu, który Ci wysłaliśmy."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Brak"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Uwierzytelnij, by używać Session"; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Uwierzytelnianie nie powiodło się."; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 40e2af6e9..334c86772 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "Não podemos ativar sua conta até que você verifique o código que enviamos."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Instante"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Autentique-se para abrir o Session."; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Falha na Autenticação"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 076f52d8c..9d57d6d71 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "Невозможно активировать аккаунт до подтверждения отправленного вам кода."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Мгновенно"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Аутентификация для открытия Session."; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Ошибка аутентификации"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index ec9ef24e0..ab04406c1 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "Nemôžeme aktivovať váš účet kým neoveríte kód, ktorý sme vám poslali."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Okamžite"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Authenticate to open Session."; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Overenie zlyhalo"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 86ad34ed2..8472e7311 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "We can't activate your account until you verify the code we sent you."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Instant"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Authenticate to open Session."; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "Authentication Failed"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index 093d22be5..a66242391 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -286,6 +286,8 @@ "REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "在验证发给你的验证码之前,我们无法激活你的帐户."; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ "SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "立即"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "认证来打开 Session。"; /* Title for alert indicating that screen lock could not be unlocked. */ "SCREEN_LOCK_UNLOCK_FAILED" = "认证失败"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ From 8fcb8e562dd7aa1492a82b3699aa92130bade90e Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 18 May 2021 10:00:11 +1000 Subject: [PATCH 37/57] Add Chinese (traditional) translation --- Session.xcodeproj/project.pbxproj | 3 + .../zh-Hant.lproj/Localizable.strings | 535 ++++++++++++++++++ 2 files changed, 538 insertions(+) create mode 100644 Session/Meta/Translations/zh-Hant.lproj/Localizable.strings diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index fa2718006..4afe5ad6f 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -1174,6 +1174,7 @@ B82B408B239A068800A248E7 /* RegisterVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterVC.swift; sourceTree = ""; }; B82B408D239DC00D00A248E7 /* DisplayNameVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayNameVC.swift; sourceTree = ""; }; B82B408F239DD75000A248E7 /* RestoreVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreVC.swift; sourceTree = ""; }; + B834C6DD26533AE5001091B2 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; B835246D25C38ABF0089A44F /* ConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationVC.swift; sourceTree = ""; }; B835247825C38D880089A44F /* MessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = ""; }; B835249A25C3AB650089A44F /* VisibleMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleMessageCell.swift; sourceTree = ""; }; @@ -4042,6 +4043,7 @@ "id-ID", sk, nl, + "zh-Hant", ); mainGroup = D221A07E169C9E5E00537ABF; productRefGroup = D221A08A169C9E5E00537ABF /* Products */; @@ -5106,6 +5108,7 @@ C3F0A5B2255C915C007BE2A3 /* en */, B8EB20E6263F7E4B00773E52 /* sk */, B87588582644CA9D000E60D0 /* nl */, + B834C6DD26533AE5001091B2 /* zh-Hant */, ); name = Localizable.strings; path = Meta/Translations; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings new file mode 100644 index 000000000..29d3ebc24 --- /dev/null +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -0,0 +1,535 @@ +/* Label for the 'dismiss' button in the 'new app version available' alert. */ +"APP_UPDATE_NAG_ALERT_DISMISS_BUTTON" = "Not Now"; +/* Message format for the 'new app version available' alert. Embeds: {{The latest app version number}} */ +"APP_UPDATE_NAG_ALERT_MESSAGE_FORMAT" = "Version %@ is now available in the App Store."; +/* Title for the 'new app version available' alert. */ +"APP_UPDATE_NAG_ALERT_TITLE" = "A New Version of Session Is Available"; +/* Label for the 'update' button in the 'new app version available' alert. */ +"APP_UPDATE_NAG_ALERT_UPDATE_BUTTON" = "Update"; +/* No comment provided by engineer. */ +"ATTACHMENT" = "Attachment"; +/* One-line label indicating the user can add no more text to the attachment caption. */ +"ATTACHMENT_APPROVAL_CAPTION_LENGTH_LIMIT_REACHED" = "Caption limit reached."; +/* placeholder text for an empty captioning field */ +"ATTACHMENT_APPROVAL_CAPTION_PLACEHOLDER" = "Add a caption…"; +/* Title for 'caption' mode of the attachment approval view. */ +"ATTACHMENT_APPROVAL_CAPTION_TITLE" = "Caption"; +/* Format string for file extension label in call interstitial view */ +"ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "File type: %@"; +/* Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}. */ +"ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "Size: %@"; +/* One-line label indicating the user can add no more text to the media message field. */ +"ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED" = "Message limit reached"; +/* Label for 'send' button in the 'attachment approval' dialog. */ +"ATTACHMENT_APPROVAL_SEND_BUTTON" = "Send"; +/* Generic filename for an attachment with no known name */ +"ATTACHMENT_DEFAULT_FILENAME" = "Attachment"; +/* The title of the 'attachment error' alert. */ +"ATTACHMENT_ERROR_ALERT_TITLE" = "Error Sending Attachment"; +/* Alert title when picking a document fails for an unknown reason */ +"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Failed to choose document."; +/* Alert body when picking a document fails because user picked a directory/bundle */ +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY" = "Please create a compressed archive of this file or directory and try sending that instead."; +/* Alert title when picking a document fails because user picked a directory/bundle */ +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE" = "Unsupported File"; +/* Short text label for a voice message attachment, used for thread preview and on the lock screen */ +"ATTACHMENT_TYPE_VOICE_MESSAGE" = "Voice Message"; +/* Error indicating the backup export could not export the user's data. */ +"BACKUP_EXPORT_ERROR_COULD_NOT_EXPORT" = "Backup data could not be exported."; +/* Error indicating that the app received an invalid response from CloudKit. */ +"BACKUP_EXPORT_ERROR_INVALID_CLOUDKIT_RESPONSE" = "Invalid Service Response"; +/* Indicates that the cloud is being cleaned up. */ +"BACKUP_EXPORT_PHASE_CLEAN_UP" = "Cleaning Up Backup"; +/* Indicates that the backup export is being configured. */ +"BACKUP_EXPORT_PHASE_CONFIGURATION" = "Initializing Backup"; +/* Indicates that the database data is being exported. */ +"BACKUP_EXPORT_PHASE_DATABASE_EXPORT" = "Exporting Data"; +/* Indicates that the backup export data is being exported. */ +"BACKUP_EXPORT_PHASE_EXPORT" = "Exporting Backup"; +/* Indicates that the backup export data is being uploaded. */ +"BACKUP_EXPORT_PHASE_UPLOAD" = "Uploading Backup"; +/* Error indicating the backup import could not import the user's data. */ +"BACKUP_IMPORT_ERROR_COULD_NOT_IMPORT" = "Backup could not be imported."; +/* Indicates that the backup import is being configured. */ +"BACKUP_IMPORT_PHASE_CONFIGURATION" = "Configuring Backup"; +/* Indicates that the backup import data is being downloaded. */ +"BACKUP_IMPORT_PHASE_DOWNLOAD" = "Downloading Backup Data"; +/* Indicates that the backup import data is being finalized. */ +"BACKUP_IMPORT_PHASE_FINALIZING" = "Finalizing Backup"; +/* Indicates that the backup import data is being imported. */ +"BACKUP_IMPORT_PHASE_IMPORT" = "Importing backup."; +/* Indicates that the backup database is being restored. */ +"BACKUP_IMPORT_PHASE_RESTORING_DATABASE" = "Restoring Database"; +/* Indicates that the backup import data is being restored. */ +"BACKUP_IMPORT_PHASE_RESTORING_FILES" = "Restoring Files"; +/* Label for the backup restore decision section. */ +"BACKUP_RESTORE_DECISION_TITLE" = "Backup Available"; +/* Label for the backup restore description. */ +"BACKUP_RESTORE_DESCRIPTION" = "Restoring Backup"; +/* Label for the backup restore progress. */ +"BACKUP_RESTORE_PROGRESS" = "Progress"; +/* Label for the backup restore status. */ +"BACKUP_RESTORE_STATUS" = "Status"; +/* Error shown when backup fails due to an unexpected error. */ +"BACKUP_UNEXPECTED_ERROR" = "Unexpected Backup Error"; +/* Button label for the 'block' button */ +"BLOCK_LIST_BLOCK_BUTTON" = "Block"; +/* A format for the 'block user' action sheet title. Embeds {{the blocked user's name or phone number}}. */ +"BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Block %@?"; +/* Button label for the 'unblock' button */ +"BLOCK_LIST_UNBLOCK_BUTTON" = "Unblock"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ has been blocked."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "User Blocked"; +/* An explanation of the consequences of blocking another user. */ +"BLOCK_USER_BEHAVIOR_EXPLANATION" = "Blocked users will not be able to call you or send you messages."; +/* Label for generic done button. */ +"BUTTON_DONE" = "Done"; +/* Button text to enable batch selection mode */ +"BUTTON_SELECT" = "Select"; +/* The label for the 'do not restore backup' button. */ +"CHECK_FOR_BACKUP_DO_NOT_RESTORE" = "Do Not Restore"; +/* The label for the 'restore backup' button. */ +"CHECK_FOR_BACKUP_RESTORE" = "Restore"; +/* Error indicating that the app could not determine that user's iCloud account status */ +"CLOUDKIT_STATUS_COULD_NOT_DETERMINE" = "Session could not determine your iCloud account status. Sign in to your iCloud Account in the iOS settings app to backup your Session data."; +/* Error indicating that user does not have an iCloud account. */ +"CLOUDKIT_STATUS_NO_ACCOUNT" = "No iCloud Account. Sign in to your iCloud Account in the iOS settings app to backup your Session data."; +/* Error indicating that the app was prevented from accessing the user's iCloud account. */ +"CLOUDKIT_STATUS_RESTRICTED" = "Session was denied access your iCloud account for backups. Grant Session access to your iCloud Account in the iOS settings app to backup your Session data."; +/* Alert body */ +"CONFIRM_LEAVE_GROUP_DESCRIPTION" = "You will no longer be able to send or receive messages in this group."; +/* Alert title */ +"CONFIRM_LEAVE_GROUP_TITLE" = "Do you really want to leave?"; +/* Message for the 'conversation delete confirmation' alert. */ +"CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE" = "This cannot be undone."; +/* Title for the 'conversation delete confirmation' alert. */ +"CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE" = "Delete Conversation?"; +/* keyboard toolbar label when no messages match the search string */ +"CONVERSATION_SEARCH_NO_RESULTS" = "No matches"; +/* keyboard toolbar label when exactly 1 message matches the search string */ +"CONVERSATION_SEARCH_ONE_RESULT" = "1 match"; +/* keyboard toolbar label when more than 1 message matches the search string. Embeds {{number/position of the 'currently viewed' result}} and the {{total number of results}} */ +"CONVERSATION_SEARCH_RESULTS_FORMAT" = "%d of %d matches"; +/* title for conversation settings screen */ +"CONVERSATION_SETTINGS" = "Conversation Settings"; +/* table cell label in conversation settings */ +"CONVERSATION_SETTINGS_BLOCK_THIS_USER" = "Block This User"; +/* Title of the 'mute this thread' action sheet. */ +"CONVERSATION_SETTINGS_MUTE_ACTION_SHEET_TITLE" = "Mute"; +/* label for 'mute thread' cell in conversation settings */ +"CONVERSATION_SETTINGS_MUTE_LABEL" = "Mute"; +/* Indicates that the current thread is not muted. */ +"CONVERSATION_SETTINGS_MUTE_NOT_MUTED" = "Not muted"; +/* Label for button to mute a thread for a day. */ +"CONVERSATION_SETTINGS_MUTE_ONE_DAY_ACTION" = "Mute for one day"; +/* Label for button to mute a thread for a hour. */ +"CONVERSATION_SETTINGS_MUTE_ONE_HOUR_ACTION" = "Mute for one hour"; +/* Label for button to mute a thread for a minute. */ +"CONVERSATION_SETTINGS_MUTE_ONE_MINUTE_ACTION" = "Mute for one minute"; +/* Label for button to mute a thread for a week. */ +"CONVERSATION_SETTINGS_MUTE_ONE_WEEK_ACTION" = "Mute for one week"; +/* Label for button to mute a thread for a year. */ +"CONVERSATION_SETTINGS_MUTE_ONE_YEAR_ACTION" = "Mute for one year"; +/* Indicates that this thread is muted until a given date or time. Embeds {{The date or time which the thread is muted until}}. */ +"CONVERSATION_SETTINGS_MUTED_UNTIL_FORMAT" = "until %@"; +/* Table cell label in conversation settings which returns the user to the conversation with 'search mode' activated */ +"CONVERSATION_SETTINGS_SEARCH" = "Search Conversation"; +/* Label for button to unmute a thread. */ +"CONVERSATION_SETTINGS_UNMUTE_ACTION" = "Unmute"; +/* Title for the 'crop/scale image' dialog. */ +"CROP_SCALE_IMAGE_VIEW_TITLE" = "Move and Scale"; +/* Subtitle shown while the app is updating its database. */ +"DATABASE_VIEW_OVERLAY_SUBTITLE" = "This can take a few minutes."; +/* Title shown while the app is updating its database. */ +"DATABASE_VIEW_OVERLAY_TITLE" = "Optimizing Database"; +/* Format string for a relative time, expressed as a certain number of hours in the past. Embeds {{The number of hours}}. */ +"DATE_HOURS_AGO_FORMAT" = "%@ Hr Ago"; +/* Format string for a relative time, expressed as a certain number of minutes in the past. Embeds {{The number of minutes}}. */ +"DATE_MINUTES_AGO_FORMAT" = "%@ Min Ago"; +/* The present; the current time. */ +"DATE_NOW" = "Now"; +/* The current day. */ +"DATE_TODAY" = "Today"; +/* The day before today. */ +"DATE_YESTERDAY" = "Yesterday"; +/* table cell label in conversation settings */ +"DISAPPEARING_MESSAGES" = "Disappearing Messages"; +/* Info Message when added to a group which has enabled disappearing messages. Embeds {{time amount}} before messages disappear, see the *_TIME_AMOUNT strings for context. */ +"DISAPPEARING_MESSAGES_CONFIGURATION_GROUP_EXISTING_FORMAT" = "Messages in this conversation will disappear after %@."; +/* table cell label in conversation settings */ +"EDIT_GROUP_ACTION" = "Edit Group"; +/* Label indicating media gallery is empty */ +"GALLERY_TILES_EMPTY_GALLERY" = "You don't have any media in this conversation."; +/* Label indicating loading is in progress */ +"GALLERY_TILES_LOADING_MORE_RECENT_LABEL" = "Loading Newer Media…"; +/* Label indicating loading is in progress */ +"GALLERY_TILES_LOADING_OLDER_LABEL" = "Loading Older Media…"; +/* Error displayed when there is a failure fetching a GIF from the remote service. */ +"GIF_PICKER_ERROR_FETCH_FAILURE" = "Failed to fetch the requested GIF. Please verify you are online."; +/* Generic error displayed when picking a GIF */ +"GIF_PICKER_ERROR_GENERIC" = "An unknown error occurred."; +/* Shown when selected GIF couldn't be fetched */ +"GIF_PICKER_FAILURE_ALERT_TITLE" = "Unable to Choose GIF"; +/* Alert message shown when user tries to search for GIFs without entering any search terms. */ +"GIF_PICKER_VIEW_MISSING_QUERY" = "Please enter your search."; +/* Indicates that an error occurred while searching. */ +"GIF_VIEW_SEARCH_ERROR" = "Error. Tap to Retry."; +/* Indicates that the user's search had no results. */ +"GIF_VIEW_SEARCH_NO_RESULTS" = "No Results."; +/* No comment provided by engineer. */ +"GROUP_CREATED" = "Group created"; +/* No comment provided by engineer. */ +"GROUP_MEMBER_JOINED" = " %@ joined the group. "; +/* No comment provided by engineer. */ +"GROUP_MEMBER_LEFT" = " %@ left the group. "; +/* No comment provided by engineer. */ +"GROUP_MEMBER_REMOVED" = " %@ was removed from the group. "; +/* No comment provided by engineer. */ +"GROUP_MEMBERS_REMOVED" = " %@ were removed from the group. "; +/* No comment provided by engineer. */ +"GROUP_TITLE_CHANGED" = "Title is now '%@'. "; +/* No comment provided by engineer. */ +"GROUP_UPDATED" = "Group updated."; +/* No comment provided by engineer. */ +"GROUP_YOU_LEFT" = "You have left the group."; +/* No comment provided by engineer. */ +"YOU_WERE_REMOVED" = " You were removed from the group. "; +/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */ +"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items."; +/* alert title */ +"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment."; +/* Message for the alert indicating that an audio file is invalid. */ +"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE" = "Invalid audio file."; +/* Slider label when disappearing messages is off */ +"KEEP_MESSAGES_FOREVER" = "Messages do not disappear."; +/* Confirmation button within contextual alert */ +"LEAVE_BUTTON_TITLE" = "Leave"; +/* table cell label in conversation settings */ +"LEAVE_GROUP_ACTION" = "Leave Group"; +/* Title for the 'long text message' view. */ +"LONG_TEXT_VIEW_TITLE" = "Message"; +/* nav bar button item */ +"MEDIA_DETAIL_VIEW_ALL_MEDIA_BUTTON" = "All Media"; +/* media picker option to choose from library */ +"MEDIA_FROM_LIBRARY_BUTTON" = "Photo Library"; +/* Confirmation button text to delete selected media from the gallery, embeds {{number of messages}} */ +"MEDIA_GALLERY_DELETE_MULTIPLE_MESSAGES_FORMAT" = "Delete %d Messages"; +/* Confirmation button text to delete selected media message from the gallery */ +"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "Delete Message"; +/* embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' */ +"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ on %@"; +/* Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}. */ +"MEDIA_GALLERY_MORE_ITEMS_FORMAT" = "+%@"; +/* Short sender label for media sent by you */ +"MEDIA_GALLERY_SENDER_NAME_YOU" = "You"; +/* Section header in media gallery collection view */ +"MEDIA_GALLERY_THIS_MONTH_HEADER" = "This Month"; +/* message status for message delivered to their recipient. */ +"MESSAGE_STATUS_DELIVERED" = "Delivered"; +/* status message for failed messages */ +"MESSAGE_STATUS_FAILED" = "Sending failed."; +/* status message for failed messages */ +"MESSAGE_STATUS_FAILED_SHORT" = "Failed"; +/* status message for read messages */ +"MESSAGE_STATUS_READ" = "Read"; +/* message status if message delivery to a recipient is skipped. We skip delivering group messages to users who have left the group or unregistered their Session account. */ +"MESSAGE_STATUS_RECIPIENT_SKIPPED" = "Skipped"; +/* message status while message is sending. */ +"MESSAGE_STATUS_SENDING" = "Sending…"; +/* status message for sent messages */ +"MESSAGE_STATUS_SENT" = "Sent"; +/* status message while attachment is uploading */ +"MESSAGE_STATUS_UPLOADING" = "Uploading…"; +/* Alert body when user has previously denied media library access */ +"MISSING_MEDIA_LIBRARY_PERMISSION_MESSAGE" = "You can enable this permission in the iOS Settings app."; +/* Alert title when user has previously denied media library access */ +"MISSING_MEDIA_LIBRARY_PERMISSION_TITLE" = "Session requires access to your photos for this feature."; +/* An explanation of the consequences of muting a thread. */ +"MUTE_BEHAVIOR_EXPLANATION" = "You will not receive notifications for muted conversations."; +/* notification title. Embeds {{author name}} and {{group name}} */ +"NEW_GROUP_MESSAGE_NOTIFICATION_TITLE" = "%@ to %@"; +/* Label for 1:1 conversation with yourself. */ +"NOTE_TO_SELF" = "Note to Self"; +/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */ +"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "You may have received messages while your %@ was restarting."; +/* No comment provided by engineer. */ +"NOTIFICATIONS_FOOTER_WARNING" = "Due to known bugs in Apple's push framework, message previews will only be shown if the message is retrieved within 30 seconds after being sent. The application badge might be inaccurate as a result."; +/* Table cell switch label. When disabled, Session will not play notification sounds while the app is in the foreground. */ +"NOTIFICATIONS_SECTION_INAPP" = "Play While App is Open"; +/* Label for settings UI that allows user to change the notification sound. */ +"NOTIFICATIONS_SECTION_SOUNDS" = "Sounds"; +/* No comment provided by engineer. */ +"NOTIFICATIONS_SENDER_AND_MESSAGE" = "Name and Content"; +/* No comment provided by engineer. */ +"NOTIFICATIONS_SENDER_ONLY" = "Name Only"; +/* No comment provided by engineer. */ +"NOTIFICATIONS_NONE" = "No Name or Content"; +/* No comment provided by engineer. */ +"NOTIFICATIONS_SHOW" = "Show"; +/* No comment provided by engineer. */ +"OK" = "OK"; +/* Info Message when {{other user}} disables or doesn't support disappearing messages */ +"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ disabled disappearing messages."; +/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */ +"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ set disappearing message time to %@"; +/* alert title, generic error preventing user from capturing a photo */ +"PHOTO_CAPTURE_GENERIC_ERROR" = "Unable to capture image."; +/* alert title */ +"PHOTO_CAPTURE_UNABLE_TO_CAPTURE_IMAGE" = "Unable to capture image."; +/* alert title */ +"PHOTO_CAPTURE_UNABLE_TO_INITIALIZE_CAMERA" = "Failed to configure camera."; +/* label for system photo collections which have no name. */ +"PHOTO_PICKER_UNNAMED_COLLECTION" = "Unnamed Album"; +/* alert body during registration */ +"REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "We can't activate your account until you verify the code we sent you."; +/* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ +"SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Instant"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Authenticate to open Session."; +/* Title for alert indicating that screen lock could not be unlocked. */ +"SCREEN_LOCK_UNLOCK_FAILED" = "Authentication Failed"; +/* alert title when user attempts to leave the send media flow when they have an in-progress album */ +"SEND_MEDIA_ABANDON_TITLE" = "Discard Media?"; +/* alert action, confirming the user wants to exit the media flow and abandon any photos they've taken */ +"SEND_MEDIA_CONFIRM_ABANDON_ALBUM" = "Discard Media"; +/* alert action when the user decides not to cancel the media flow after all. */ +"SEND_MEDIA_RETURN_TO_CAMERA" = "Return to Camera"; +/* alert action when the user decides not to cancel the media flow after all. */ +"SEND_MEDIA_RETURN_TO_MEDIA_LIBRARY" = "Return to Media Library"; +/* Format string for the default 'Note' sound. Embeds the system {{sound name}}. */ +"SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT" = "%@ (default)"; +/* Label for the backup view in app settings. */ +"SETTINGS_BACKUP" = "Backup"; +/* Label for 'backup now' button in the backup settings view. */ +"SETTINGS_BACKUP_BACKUP_NOW" = "Backup Now"; +/* Label for 'cancel backup' button in the backup settings view. */ +"SETTINGS_BACKUP_CANCEL_BACKUP" = "Cancel Backup"; +/* Label for switch in settings that controls whether or not backup is enabled. */ +"SETTINGS_BACKUP_ENABLING_SWITCH" = "Backup Enabled"; +/* Label for iCloud status row in the in the backup settings view. */ +"SETTINGS_BACKUP_ICLOUD_STATUS" = "iCloud Status"; +/* Indicates that the last backup restore failed. */ +"SETTINGS_BACKUP_IMPORT_STATUS_FAILED" = "Backup Restore Failed"; +/* Indicates that app is not restoring up. */ +"SETTINGS_BACKUP_IMPORT_STATUS_IDLE" = "Backup Restore Idle"; +/* Indicates that app is restoring up. */ +"SETTINGS_BACKUP_IMPORT_STATUS_IN_PROGRESS" = "Backup Restore In Progress"; +/* Indicates that the last backup restore succeeded. */ +"SETTINGS_BACKUP_IMPORT_STATUS_SUCCEEDED" = "Backup Restore Succeeded"; +/* Label for phase row in the in the backup settings view. */ +"SETTINGS_BACKUP_PHASE" = "Phase"; +/* Label for phase row in the in the backup settings view. */ +"SETTINGS_BACKUP_PROGRESS" = "Progress"; +/* Label for backup status row in the in the backup settings view. */ +"SETTINGS_BACKUP_STATUS" = "Status"; +/* Indicates that the last backup failed. */ +"SETTINGS_BACKUP_STATUS_FAILED" = "Backup Failed"; +/* Indicates that app is not backing up. */ +"SETTINGS_BACKUP_STATUS_IDLE" = "Waiting"; +/* Indicates that app is backing up. */ +"SETTINGS_BACKUP_STATUS_IN_PROGRESS" = "Backing Up"; +/* Indicates that the last backup succeeded. */ +"SETTINGS_BACKUP_STATUS_SUCCEEDED" = "Backup Successful"; +/* No comment provided by engineer. */ +"SETTINGS_CLEAR_HISTORY" = "Clear Conversation History"; +/* Confirmation text for button which deletes all message, calling, attachments, etc. */ +"SETTINGS_DELETE_HISTORYLOG_CONFIRMATION_BUTTON" = "Delete Everything"; +/* Section header */ +"SETTINGS_HISTORYLOG_TITLE" = "Clear Conversation History"; +/* Label for settings view that allows user to change the notification sound. */ +"SETTINGS_ITEM_NOTIFICATION_SOUND" = "Message Sound"; +/* Setting for enabling & disabling link previews. */ +"SETTINGS_LINK_PREVIEWS" = "Send Link Previews"; +/* Footer for setting for enabling & disabling link previews. */ +"SETTINGS_LINK_PREVIEWS_FOOTER" = "Previews are supported for Imgur, Instagram, Pinterest, Reddit, and YouTube links."; +/* Header for setting for enabling & disabling link previews. */ +"SETTINGS_LINK_PREVIEWS_HEADER" = "Link Previews"; +/* table section header */ +"SETTINGS_NOTIFICATION_CONTENT_TITLE" = "Notification Content"; +/* Label for the 'read receipts' setting. */ +"SETTINGS_READ_RECEIPT" = "Read Receipts"; +/* An explanation of the 'read receipts' setting. */ +"SETTINGS_READ_RECEIPTS_SECTION_FOOTER" = "See and share when messages have been read. This setting is optional and applies to all conversations."; +/* Label for the 'screen lock activity timeout' setting of the privacy settings. */ +"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout"; +/* Title for the 'screen lock' section of the privacy settings. */ +"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock"; +/* Label for the 'enable screen lock' switch of the privacy settings. */ +"SETTINGS_SCREEN_LOCK_SWITCH_LABEL" = "Screen Lock"; +/* Header Label for the sounds section of settings views. */ +"SETTINGS_SECTION_SOUNDS" = "Sounds"; +/* Section header */ +"SETTINGS_SECURITY_TITLE" = "Screen Security"; +/* Label for the 'typing indicators' setting. */ +"SETTINGS_TYPING_INDICATORS" = "Typing Indicators"; +/* Label for the 'no sound' option that allows users to disable sounds for notifications, etc. */ +"SOUNDS_NONE" = "None"; +/* {{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 days}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_DAYS" = "%@ days"; +/* Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_DAYS_SHORT_FORMAT" = "%@d"; +/* {{number of hours}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 hours}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_HOURS" = "%@ hours"; +/* Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_HOURS_SHORT_FORMAT" = "%@h"; +/* {{number of minutes}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 minutes}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_MINUTES" = "%@ minutes"; +/* Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_MINUTES_SHORT_FORMAT" = "%@m"; +/* {{number of seconds}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 seconds}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SECONDS" = "%@ seconds"; +/* Label text below navbar button, embeds {{number of seconds}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5s' not '5 s'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SECONDS_SHORT_FORMAT" = "%@s"; +/* {{1 day}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 day}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_DAY" = "%@ day"; +/* {{1 hour}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 hour}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_HOUR" = "%@ hour"; +/* {{1 minute}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 minute}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_MINUTE" = "%@ minute"; +/* {{1 week}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 week}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_WEEK" = "%@ week"; +/* {{number of weeks}}, embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 weeks}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_WEEKS" = "%@ weeks"; +/* Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_WEEKS_SHORT_FORMAT" = "%@w"; +/* Label for the cancel button in an alert or action sheet. */ +"TXT_CANCEL_TITLE" = "Cancel"; +/* No comment provided by engineer. */ +"TXT_DELETE_TITLE" = "Delete"; +/* Filename for voice messages. */ +"VOICE_MESSAGE_FILE_NAME" = "Voice Message"; +/* Message for the alert indicating the 'voice message' needs to be held to be held down to record. */ +"VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE" = "Tap and hold to record a voice message."; +/* Title for the alert indicating the 'voice message' needs to be held to be held down to record. */ +"VOICE_MESSAGE_TOO_SHORT_ALERT_TITLE" = "Voice Message"; +/* Info Message when you disable disappearing messages */ +"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "You disabled disappearing messages."; +/* Info message embedding a {{time amount}}, see the *_TIME_AMOUNT strings for context. */ +"YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "You set disappearing message time to %@"; +// MARK: - Session +"continue_2" = "Continue"; +"copy" = "Copy"; +"invalid_url" = "Invalid URL"; +"next" = "Next"; +"share" = "Share"; +"invalid_session_id" = "Invalid Session ID"; +"cancel" = "Cancel"; +"your_session_id" = "Your Session ID"; +"vc_landing_title_2" = "Your Session begins here..."; +"vc_landing_register_button_title" = "Create Session ID"; +"vc_landing_restore_button_title" = "Continue Your Session"; +"vc_landing_link_button_title" = "Link a Device"; +"view_fake_chat_bubble_1" = "What's Session?"; +"view_fake_chat_bubble_2" = "It's a decentralized, encrypted messaging app"; +"view_fake_chat_bubble_3" = "So it doesn't collect my personal information or my conversation metadata? How does it work?"; +"view_fake_chat_bubble_4" = "Using a combination of advanced anonymous routing and end-to-end encryption technologies."; +"view_fake_chat_bubble_5" = "Friends don't let friends use compromised messengers. You're welcome."; +"vc_register_title" = "Say hello to your Session ID"; +"vc_register_explanation" = "Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design."; +"vc_restore_title" = "Restore your account"; +"vc_restore_explanation" = "Enter the recovery phrase that was given to you when you signed up to restore your account."; +"vc_restore_seed_text_field_hint" = "Enter your recovery phrase"; +"vc_link_device_title" = "Link a Device"; +"vc_link_device_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_display_name_title_2" = "Pick your display name"; +"vc_display_name_explanation" = "This will be your name when you use Session. It can be your real name, an alias, or anything else you like."; +"vc_display_name_text_field_hint" = "Enter a display name"; +"vc_display_name_display_name_missing_error" = "Please pick a display name"; +"vc_display_name_display_name_too_long_error" = "Please pick a shorter display name"; +"vc_pn_mode_recommended_option_tag" = "Recommended"; +"vc_pn_mode_no_option_picked_modal_title" = "Please Pick an Option"; +"vc_home_empty_state_message" = "You don't have any contacts yet"; +"vc_home_empty_state_button_title" = "Start a Session"; +"vc_seed_title" = "Your Recovery Phrase"; +"vc_seed_title_2" = "Meet your recovery phrase"; +"vc_seed_explanation" = "Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don’t give it to anyone."; +"vc_seed_reveal_button_title" = "Hold to reveal"; +"view_seed_reminder_subtitle_1" = "Secure your account by saving your recovery phrase"; +"view_seed_reminder_subtitle_2" = "Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID."; +"view_seed_reminder_subtitle_3" = "Make sure to store your recovery phrase in a safe place"; +"vc_path_title" = "Path"; +"vc_path_explanation" = "Session hides your IP by routing your messages through multiple Service Nodes in Session's decentralized network. These are the countries your connection is currently being routed through:"; +"vc_path_device_row_title" = "You"; +"vc_path_guard_node_row_title" = "Entry Node"; +"vc_path_service_node_row_title" = "Service Node"; +"vc_path_destination_row_title" = "Destination"; +"vc_path_learn_more_button_title" = "Learn More"; +"vc_create_private_chat_title" = "New Session"; +"vc_create_private_chat_enter_session_id_tab_title" = "Enter Session ID"; +"vc_create_private_chat_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_create_private_chat_scan_qr_code_explanation" = "Scan a user’s QR code to start a session. QR codes can be found by tapping the QR code icon in account settings."; +"vc_enter_public_key_explanation" = "Users can share their Session ID by going into their account settings and tapping \"Share Session ID\", or by sharing their QR code."; +"vc_scan_qr_code_camera_access_explanation" = "Session needs camera access to scan QR codes"; +"vc_scan_qr_code_grant_camera_access_button_title" = "Grant Camera Access"; +"vc_create_closed_group_title" = "New Closed Group"; +"vc_create_closed_group_text_field_hint" = "Enter a group name"; +"vc_create_closed_group_empty_state_message" = "You don't have any contacts yet"; +"vc_create_closed_group_empty_state_button_title" = "Start a Session"; +"vc_create_closed_group_group_name_missing_error" = "Please enter a group name"; +"vc_create_closed_group_group_name_too_long_error" = "Please enter a shorter group name"; +"vc_create_closed_group_too_many_group_members_error" = "A closed group cannot have more than 100 members"; +"vc_join_public_chat_title" = "Join Open Group"; +"vc_join_public_chat_enter_group_url_tab_title" = "Open Group URL"; +"vc_join_public_chat_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_join_public_chat_scan_qr_code_explanation" = "Scan the QR code of the open group you'd like to join"; +"vc_enter_chat_url_text_field_hint" = "Enter an open group URL"; +"vc_settings_title" = "Settings"; +"vc_settings_display_name_text_field_hint" = "Enter a display name"; +"vc_settings_display_name_missing_error" = "Please pick a display name"; +"vc_settings_display_name_too_long_error" = "Please pick a shorter display name"; +"vc_settings_privacy_button_title" = "Privacy"; +"vc_settings_notifications_button_title" = "Notifications"; +"vc_settings_recovery_phrase_button_title" = "Recovery Phrase"; +"vc_settings_clear_all_data_button_title" = "Clear Data"; +"vc_notification_settings_title" = "Notifications"; +"vc_privacy_settings_title" = "Privacy"; +"preferences_notifications_strategy_category_title" = "Notification Strategy"; +"modal_seed_title" = "Your Recovery Phrase"; +"modal_seed_explanation" = "This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device."; +"modal_clear_all_data_title" = "Clear All Data"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages, sessions, and contacts."; +"vc_qr_code_title" = "QR Code"; +"vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code"; +"vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_qr_code_view_scan_qr_code_explanation" = "Scan someone's QR code to start a conversation with them"; +"vc_view_my_qr_code_explanation" = "This is your QR code. Other users can scan it to start a session with you."; +// MARK: - Not Yet Translated +"fast_mode_explanation" = "You’ll be notified of new messages reliably and immediately using Apple’s notification servers."; +"fast_mode" = "Fast Mode"; +"slow_mode_explanation" = "Session will occasionally check for new messages in the background."; +"slow_mode" = "Slow Mode"; +"vc_pn_mode_title" = "Message Notifications"; +"vc_notification_settings_notification_mode_title" = "Use Fast Mode"; +"vc_link_device_recovery_phrase_tab_title" = "Recovery Phrase"; +"vc_link_device_scan_qr_code_explanation" = "Navigate to Settings → Recovery Phrase on your other device to show your QR code."; +"vc_enter_recovery_phrase_title" = "Recovery Phrase"; +"vc_enter_recovery_phrase_explanation" = "To link your device, enter the recovery phrase that was given to you when you signed up."; +"vc_enter_public_key_text_field_hint" = "Enter Session ID or ONS name"; +"vc_home_title" = "Messages"; +"admin_group_leave_warning" = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."; +"vc_join_open_group_suggestions_title" = "Or join one of these..."; +"vc_settings_invite_a_friend_button_title" = "Invite a Friend"; +"vc_settings_help_us_translate_button_title" = "Help us Translate Session"; +"copied" = "Copied"; +"vc_conversation_settings_copy_session_id_button_title" = "Copy Session ID"; +"vc_conversation_input_prompt" = "Message"; +"vc_conversation_voice_message_cancel_message" = "Slide to Cancel"; +"modal_download_attachment_title" = "Trust %@?"; +"modal_download_attachment_explanation" = "Are you sure you want to download media sent by %@?"; +"modal_download_button_title" = "Download"; +"modal_open_url_title" = "Open URL?"; +"modal_open_url_explanation" = "Are you sure you want to open %@?"; +"modal_open_url_button_title" = "Open"; +"modal_blocked_title" = "Unblock %@?"; +"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; +"modal_blocked_button_title" = "Unblock"; +"modal_link_previews_title" = "Enable Link Previews?"; +"modal_link_previews_explanation" = "Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session's settings."; +"modal_link_previews_button_title" = "Enable"; +"vc_share_title" = "Share to Session"; +"vc_share_loading_message" = "Preparing attachments..."; +"vc_share_sending_message" = "Sending..."; +"view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; From 57e3f4b12c1a12fd28aa652dbedcb52f61bd31a5 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 18 May 2021 10:02:22 +1000 Subject: [PATCH 38/57] Implement partial Chinese (traditional) translation --- .../zh-Hant.lproj/Localizable.strings | 458 +++++++++--------- 1 file changed, 229 insertions(+), 229 deletions(-) diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 29d3ebc24..058d17f61 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -1,355 +1,355 @@ /* Label for the 'dismiss' button in the 'new app version available' alert. */ -"APP_UPDATE_NAG_ALERT_DISMISS_BUTTON" = "Not Now"; +"APP_UPDATE_NAG_ALERT_DISMISS_BUTTON" = "稍後"; /* Message format for the 'new app version available' alert. Embeds: {{The latest app version number}} */ -"APP_UPDATE_NAG_ALERT_MESSAGE_FORMAT" = "Version %@ is now available in the App Store."; +"APP_UPDATE_NAG_ALERT_MESSAGE_FORMAT" = "版本:%@ 現在已經可以下載。"; /* Title for the 'new app version available' alert. */ -"APP_UPDATE_NAG_ALERT_TITLE" = "A New Version of Session Is Available"; +"APP_UPDATE_NAG_ALERT_TITLE" = "更新版本的 Session 已推出"; /* Label for the 'update' button in the 'new app version available' alert. */ -"APP_UPDATE_NAG_ALERT_UPDATE_BUTTON" = "Update"; +"APP_UPDATE_NAG_ALERT_UPDATE_BUTTON" = "更新"; /* No comment provided by engineer. */ -"ATTACHMENT" = "Attachment"; +"ATTACHMENT" = "附件"; /* One-line label indicating the user can add no more text to the attachment caption. */ -"ATTACHMENT_APPROVAL_CAPTION_LENGTH_LIMIT_REACHED" = "Caption limit reached."; +"ATTACHMENT_APPROVAL_CAPTION_LENGTH_LIMIT_REACHED" = "附件標題長度已達限制"; /* placeholder text for an empty captioning field */ -"ATTACHMENT_APPROVAL_CAPTION_PLACEHOLDER" = "Add a caption…"; +"ATTACHMENT_APPROVAL_CAPTION_PLACEHOLDER" = "新增標題⋯"; /* Title for 'caption' mode of the attachment approval view. */ -"ATTACHMENT_APPROVAL_CAPTION_TITLE" = "Caption"; +"ATTACHMENT_APPROVAL_CAPTION_TITLE" = "標題"; /* Format string for file extension label in call interstitial view */ -"ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "File type: %@"; +"ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "檔案類型: %@"; /* Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}. */ -"ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "Size: %@"; +"ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "大小:%@"; /* One-line label indicating the user can add no more text to the media message field. */ -"ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED" = "Message limit reached"; +"ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED" = "訊息長度已達限制"; /* Label for 'send' button in the 'attachment approval' dialog. */ -"ATTACHMENT_APPROVAL_SEND_BUTTON" = "Send"; +"ATTACHMENT_APPROVAL_SEND_BUTTON" = "發送"; /* Generic filename for an attachment with no known name */ -"ATTACHMENT_DEFAULT_FILENAME" = "Attachment"; +"ATTACHMENT_DEFAULT_FILENAME" = "附件"; /* The title of the 'attachment error' alert. */ -"ATTACHMENT_ERROR_ALERT_TITLE" = "Error Sending Attachment"; +"ATTACHMENT_ERROR_ALERT_TITLE" = "寄送附件時發生錯誤"; /* Alert title when picking a document fails for an unknown reason */ -"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Failed to choose document."; +"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "選取檔案時發生錯誤"; /* Alert body when picking a document fails because user picked a directory/bundle */ -"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY" = "Please create a compressed archive of this file or directory and try sending that instead."; +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY" = "請嘗試將其轉換為壓縮檔或者再嘗試重新發送一次"; /* Alert title when picking a document fails because user picked a directory/bundle */ -"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE" = "Unsupported File"; +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE" = "不支援的檔案類型"; /* Short text label for a voice message attachment, used for thread preview and on the lock screen */ -"ATTACHMENT_TYPE_VOICE_MESSAGE" = "Voice Message"; +"ATTACHMENT_TYPE_VOICE_MESSAGE" = "語音訊息"; /* Error indicating the backup export could not export the user's data. */ -"BACKUP_EXPORT_ERROR_COULD_NOT_EXPORT" = "Backup data could not be exported."; +"BACKUP_EXPORT_ERROR_COULD_NOT_EXPORT" = "備份的檔案無法被匯出"; /* Error indicating that the app received an invalid response from CloudKit. */ -"BACKUP_EXPORT_ERROR_INVALID_CLOUDKIT_RESPONSE" = "Invalid Service Response"; +"BACKUP_EXPORT_ERROR_INVALID_CLOUDKIT_RESPONSE" = "系統傳送了無法辨識的回覆"; /* Indicates that the cloud is being cleaned up. */ -"BACKUP_EXPORT_PHASE_CLEAN_UP" = "Cleaning Up Backup"; +"BACKUP_EXPORT_PHASE_CLEAN_UP" = "清除備份"; /* Indicates that the backup export is being configured. */ -"BACKUP_EXPORT_PHASE_CONFIGURATION" = "Initializing Backup"; +"BACKUP_EXPORT_PHASE_CONFIGURATION" = "正在初始化備份程序"; /* Indicates that the database data is being exported. */ -"BACKUP_EXPORT_PHASE_DATABASE_EXPORT" = "Exporting Data"; +"BACKUP_EXPORT_PHASE_DATABASE_EXPORT" = "輸出檔案中"; /* Indicates that the backup export data is being exported. */ -"BACKUP_EXPORT_PHASE_EXPORT" = "Exporting Backup"; +"BACKUP_EXPORT_PHASE_EXPORT" = "輸出備份中"; /* Indicates that the backup export data is being uploaded. */ -"BACKUP_EXPORT_PHASE_UPLOAD" = "Uploading Backup"; +"BACKUP_EXPORT_PHASE_UPLOAD" = "上傳備份中"; /* Error indicating the backup import could not import the user's data. */ -"BACKUP_IMPORT_ERROR_COULD_NOT_IMPORT" = "Backup could not be imported."; +"BACKUP_IMPORT_ERROR_COULD_NOT_IMPORT" = "無法匯入備份"; /* Indicates that the backup import is being configured. */ -"BACKUP_IMPORT_PHASE_CONFIGURATION" = "Configuring Backup"; +"BACKUP_IMPORT_PHASE_CONFIGURATION" = "正在處理備份"; /* Indicates that the backup import data is being downloaded. */ -"BACKUP_IMPORT_PHASE_DOWNLOAD" = "Downloading Backup Data"; +"BACKUP_IMPORT_PHASE_DOWNLOAD" = "下載備份檔案"; /* Indicates that the backup import data is being finalized. */ -"BACKUP_IMPORT_PHASE_FINALIZING" = "Finalizing Backup"; +"BACKUP_IMPORT_PHASE_FINALIZING" = "即將完成"; /* Indicates that the backup import data is being imported. */ -"BACKUP_IMPORT_PHASE_IMPORT" = "Importing backup."; +"BACKUP_IMPORT_PHASE_IMPORT" = "導入備份中"; /* Indicates that the backup database is being restored. */ -"BACKUP_IMPORT_PHASE_RESTORING_DATABASE" = "Restoring Database"; +"BACKUP_IMPORT_PHASE_RESTORING_DATABASE" = "正在回復備份數據"; /* Indicates that the backup import data is being restored. */ -"BACKUP_IMPORT_PHASE_RESTORING_FILES" = "Restoring Files"; +"BACKUP_IMPORT_PHASE_RESTORING_FILES" = "正在回復匯入的備份"; /* Label for the backup restore decision section. */ -"BACKUP_RESTORE_DECISION_TITLE" = "Backup Available"; +"BACKUP_RESTORE_DECISION_TITLE" = "可以執行備份"; /* Label for the backup restore description. */ -"BACKUP_RESTORE_DESCRIPTION" = "Restoring Backup"; +"BACKUP_RESTORE_DESCRIPTION" = "從備份回復"; /* Label for the backup restore progress. */ -"BACKUP_RESTORE_PROGRESS" = "Progress"; +"BACKUP_RESTORE_PROGRESS" = "進度"; /* Label for the backup restore status. */ -"BACKUP_RESTORE_STATUS" = "Status"; +"BACKUP_RESTORE_STATUS" = "狀態"; /* Error shown when backup fails due to an unexpected error. */ -"BACKUP_UNEXPECTED_ERROR" = "Unexpected Backup Error"; +"BACKUP_UNEXPECTED_ERROR" = "不明的錯誤,請聯繫開發者"; /* Button label for the 'block' button */ -"BLOCK_LIST_BLOCK_BUTTON" = "Block"; +"BLOCK_LIST_BLOCK_BUTTON" = "封鎖"; /* A format for the 'block user' action sheet title. Embeds {{the blocked user's name or phone number}}. */ -"BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Block %@?"; +"BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "封鎖 %@?"; /* Button label for the 'unblock' button */ -"BLOCK_LIST_UNBLOCK_BUTTON" = "Unblock"; +"BLOCK_LIST_UNBLOCK_BUTTON" = "解除封鎖"; /* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ -"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ has been blocked."; +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "已封鎖 %@。"; /* The title of the 'user blocked' alert. */ -"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "User Blocked"; +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "使用者已封鎖"; /* An explanation of the consequences of blocking another user. */ -"BLOCK_USER_BEHAVIOR_EXPLANATION" = "Blocked users will not be able to call you or send you messages."; +"BLOCK_USER_BEHAVIOR_EXPLANATION" = "被您封鎖的使用者將無法傳送訊息與撥打電話給您"; /* Label for generic done button. */ -"BUTTON_DONE" = "Done"; +"BUTTON_DONE" = "完成"; /* Button text to enable batch selection mode */ -"BUTTON_SELECT" = "Select"; +"BUTTON_SELECT" = "選擇"; /* The label for the 'do not restore backup' button. */ -"CHECK_FOR_BACKUP_DO_NOT_RESTORE" = "Do Not Restore"; +"CHECK_FOR_BACKUP_DO_NOT_RESTORE" = "不要回復備份"; /* The label for the 'restore backup' button. */ -"CHECK_FOR_BACKUP_RESTORE" = "Restore"; +"CHECK_FOR_BACKUP_RESTORE" = "回復"; /* Error indicating that the app could not determine that user's iCloud account status */ -"CLOUDKIT_STATUS_COULD_NOT_DETERMINE" = "Session could not determine your iCloud account status. Sign in to your iCloud Account in the iOS settings app to backup your Session data."; +"CLOUDKIT_STATUS_COULD_NOT_DETERMINE" = "Session 無法確定您的 iCloud 帳號狀態,請進入設定值重新登入您的 iCloud 帳號以備份您的資料。"; /* Error indicating that user does not have an iCloud account. */ -"CLOUDKIT_STATUS_NO_ACCOUNT" = "No iCloud Account. Sign in to your iCloud Account in the iOS settings app to backup your Session data."; +"CLOUDKIT_STATUS_NO_ACCOUNT" = "沒有偵測到 iCloud 帳戶,請進入設定登入您的 iCloud 帳號。"; /* Error indicating that the app was prevented from accessing the user's iCloud account. */ -"CLOUDKIT_STATUS_RESTRICTED" = "Session was denied access your iCloud account for backups. Grant Session access to your iCloud Account in the iOS settings app to backup your Session data."; +"CLOUDKIT_STATUS_RESTRICTED" = "Session 因為您的 iCloud 帳號設定並沒有將 Session 的備份選項開啟,請進入設定並打開您iCloud 的設定開啟 Session 的備份功能。"; /* Alert body */ -"CONFIRM_LEAVE_GROUP_DESCRIPTION" = "You will no longer be able to send or receive messages in this group."; +"CONFIRM_LEAVE_GROUP_DESCRIPTION" = "您已經無法再於此群組傳送訊息或撥打電話。"; /* Alert title */ -"CONFIRM_LEAVE_GROUP_TITLE" = "Do you really want to leave?"; +"CONFIRM_LEAVE_GROUP_TITLE" = "您真的想要離開嗎?"; /* Message for the 'conversation delete confirmation' alert. */ -"CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE" = "This cannot be undone."; +"CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE" = "這個動作無法被撤銷。"; /* Title for the 'conversation delete confirmation' alert. */ -"CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE" = "Delete Conversation?"; +"CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE" = "刪除對話?"; /* keyboard toolbar label when no messages match the search string */ -"CONVERSATION_SEARCH_NO_RESULTS" = "No matches"; +"CONVERSATION_SEARCH_NO_RESULTS" = "沒有結果。"; /* keyboard toolbar label when exactly 1 message matches the search string */ -"CONVERSATION_SEARCH_ONE_RESULT" = "1 match"; +"CONVERSATION_SEARCH_ONE_RESULT" = "一個結果。"; /* keyboard toolbar label when more than 1 message matches the search string. Embeds {{number/position of the 'currently viewed' result}} and the {{total number of results}} */ -"CONVERSATION_SEARCH_RESULTS_FORMAT" = "%d of %d matches"; +"CONVERSATION_SEARCH_RESULTS_FORMAT" = "(%d/%d) 符合。"; /* title for conversation settings screen */ -"CONVERSATION_SETTINGS" = "Conversation Settings"; +"CONVERSATION_SETTINGS" = "對話設定"; /* table cell label in conversation settings */ -"CONVERSATION_SETTINGS_BLOCK_THIS_USER" = "Block This User"; +"CONVERSATION_SETTINGS_BLOCK_THIS_USER" = "封鎖此用戶"; /* Title of the 'mute this thread' action sheet. */ -"CONVERSATION_SETTINGS_MUTE_ACTION_SHEET_TITLE" = "Mute"; +"CONVERSATION_SETTINGS_MUTE_ACTION_SHEET_TITLE" = "靜音"; /* label for 'mute thread' cell in conversation settings */ -"CONVERSATION_SETTINGS_MUTE_LABEL" = "Mute"; +"CONVERSATION_SETTINGS_MUTE_LABEL" = "靜音"; /* Indicates that the current thread is not muted. */ -"CONVERSATION_SETTINGS_MUTE_NOT_MUTED" = "Not muted"; +"CONVERSATION_SETTINGS_MUTE_NOT_MUTED" = "未靜音"; /* Label for button to mute a thread for a day. */ -"CONVERSATION_SETTINGS_MUTE_ONE_DAY_ACTION" = "Mute for one day"; +"CONVERSATION_SETTINGS_MUTE_ONE_DAY_ACTION" = "靜音一天"; /* Label for button to mute a thread for a hour. */ -"CONVERSATION_SETTINGS_MUTE_ONE_HOUR_ACTION" = "Mute for one hour"; +"CONVERSATION_SETTINGS_MUTE_ONE_HOUR_ACTION" = "靜音一個小時"; /* Label for button to mute a thread for a minute. */ -"CONVERSATION_SETTINGS_MUTE_ONE_MINUTE_ACTION" = "Mute for one minute"; +"CONVERSATION_SETTINGS_MUTE_ONE_MINUTE_ACTION" = "靜音一分鐘"; /* Label for button to mute a thread for a week. */ -"CONVERSATION_SETTINGS_MUTE_ONE_WEEK_ACTION" = "Mute for one week"; +"CONVERSATION_SETTINGS_MUTE_ONE_WEEK_ACTION" = "靜音一個禮拜"; /* Label for button to mute a thread for a year. */ -"CONVERSATION_SETTINGS_MUTE_ONE_YEAR_ACTION" = "Mute for one year"; +"CONVERSATION_SETTINGS_MUTE_ONE_YEAR_ACTION" = "靜音一年"; /* Indicates that this thread is muted until a given date or time. Embeds {{The date or time which the thread is muted until}}. */ -"CONVERSATION_SETTINGS_MUTED_UNTIL_FORMAT" = "until %@"; +"CONVERSATION_SETTINGS_MUTED_UNTIL_FORMAT" = "直到:%@"; /* Table cell label in conversation settings which returns the user to the conversation with 'search mode' activated */ -"CONVERSATION_SETTINGS_SEARCH" = "Search Conversation"; +"CONVERSATION_SETTINGS_SEARCH" = "搜尋對話"; /* Label for button to unmute a thread. */ -"CONVERSATION_SETTINGS_UNMUTE_ACTION" = "Unmute"; +"CONVERSATION_SETTINGS_UNMUTE_ACTION" = "解除靜音"; /* Title for the 'crop/scale image' dialog. */ -"CROP_SCALE_IMAGE_VIEW_TITLE" = "Move and Scale"; +"CROP_SCALE_IMAGE_VIEW_TITLE" = "移動與裁切"; /* Subtitle shown while the app is updating its database. */ -"DATABASE_VIEW_OVERLAY_SUBTITLE" = "This can take a few minutes."; +"DATABASE_VIEW_OVERLAY_SUBTITLE" = "這會花上幾分鐘。"; /* Title shown while the app is updating its database. */ -"DATABASE_VIEW_OVERLAY_TITLE" = "Optimizing Database"; +"DATABASE_VIEW_OVERLAY_TITLE" = "最佳化資料庫中"; /* Format string for a relative time, expressed as a certain number of hours in the past. Embeds {{The number of hours}}. */ -"DATE_HOURS_AGO_FORMAT" = "%@ Hr Ago"; +"DATE_HOURS_AGO_FORMAT" = "%@ 個小時前"; /* Format string for a relative time, expressed as a certain number of minutes in the past. Embeds {{The number of minutes}}. */ -"DATE_MINUTES_AGO_FORMAT" = "%@ Min Ago"; +"DATE_MINUTES_AGO_FORMAT" = "%@ 分鐘前"; /* The present; the current time. */ -"DATE_NOW" = "Now"; +"DATE_NOW" = "現在"; /* The current day. */ -"DATE_TODAY" = "Today"; +"DATE_TODAY" = "今天"; /* The day before today. */ -"DATE_YESTERDAY" = "Yesterday"; +"DATE_YESTERDAY" = "昨天"; /* table cell label in conversation settings */ -"DISAPPEARING_MESSAGES" = "Disappearing Messages"; +"DISAPPEARING_MESSAGES" = "消失的訊息"; /* Info Message when added to a group which has enabled disappearing messages. Embeds {{time amount}} before messages disappear, see the *_TIME_AMOUNT strings for context. */ -"DISAPPEARING_MESSAGES_CONFIGURATION_GROUP_EXISTING_FORMAT" = "Messages in this conversation will disappear after %@."; +"DISAPPEARING_MESSAGES_CONFIGURATION_GROUP_EXISTING_FORMAT" = "在這則對話的訊息將於 %@ 後消失"; /* table cell label in conversation settings */ -"EDIT_GROUP_ACTION" = "Edit Group"; +"EDIT_GROUP_ACTION" = "編輯群組"; /* Label indicating media gallery is empty */ -"GALLERY_TILES_EMPTY_GALLERY" = "You don't have any media in this conversation."; +"GALLERY_TILES_EMPTY_GALLERY" = "此對話中沒有媒體。"; /* Label indicating loading is in progress */ -"GALLERY_TILES_LOADING_MORE_RECENT_LABEL" = "Loading Newer Media…"; +"GALLERY_TILES_LOADING_MORE_RECENT_LABEL" = "讀取新媒體中⋯"; /* Label indicating loading is in progress */ -"GALLERY_TILES_LOADING_OLDER_LABEL" = "Loading Older Media…"; +"GALLERY_TILES_LOADING_OLDER_LABEL" = "讀取舊媒體中⋯"; /* Error displayed when there is a failure fetching a GIF from the remote service. */ -"GIF_PICKER_ERROR_FETCH_FAILURE" = "Failed to fetch the requested GIF. Please verify you are online."; +"GIF_PICKER_ERROR_FETCH_FAILURE" = "無法讀取此 GIF。請驗證您是否連結到網路。"; /* Generic error displayed when picking a GIF */ -"GIF_PICKER_ERROR_GENERIC" = "An unknown error occurred."; +"GIF_PICKER_ERROR_GENERIC" = "發生不知名的錯誤。"; /* Shown when selected GIF couldn't be fetched */ -"GIF_PICKER_FAILURE_ALERT_TITLE" = "Unable to Choose GIF"; +"GIF_PICKER_FAILURE_ALERT_TITLE" = "無法選取此 GIF。"; /* Alert message shown when user tries to search for GIFs without entering any search terms. */ -"GIF_PICKER_VIEW_MISSING_QUERY" = "Please enter your search."; +"GIF_PICKER_VIEW_MISSING_QUERY" = "請輸入您的搜尋內容"; /* Indicates that an error occurred while searching. */ -"GIF_VIEW_SEARCH_ERROR" = "Error. Tap to Retry."; +"GIF_VIEW_SEARCH_ERROR" = "錯誤,請重試。"; /* Indicates that the user's search had no results. */ -"GIF_VIEW_SEARCH_NO_RESULTS" = "No Results."; +"GIF_VIEW_SEARCH_NO_RESULTS" = "沒有結果"; /* No comment provided by engineer. */ -"GROUP_CREATED" = "Group created"; +"GROUP_CREATED" = "已創立群組"; /* No comment provided by engineer. */ -"GROUP_MEMBER_JOINED" = " %@ joined the group. "; +"GROUP_MEMBER_JOINED" = " %@ 已加入群組。 "; /* No comment provided by engineer. */ -"GROUP_MEMBER_LEFT" = " %@ left the group. "; +"GROUP_MEMBER_LEFT" = " %@ 已離開群組。 "; /* No comment provided by engineer. */ -"GROUP_MEMBER_REMOVED" = " %@ was removed from the group. "; +"GROUP_MEMBER_REMOVED" = " %@ 被踢出群組。 "; /* No comment provided by engineer. */ -"GROUP_MEMBERS_REMOVED" = " %@ were removed from the group. "; +"GROUP_MEMBERS_REMOVED" = " %@ 已被群組踢出 "; /* No comment provided by engineer. */ -"GROUP_TITLE_CHANGED" = "Title is now '%@'. "; +"GROUP_TITLE_CHANGED" = "標題已更改為 ‘%@‘ "; /* No comment provided by engineer. */ -"GROUP_UPDATED" = "Group updated."; +"GROUP_UPDATED" = "群組已更新"; /* No comment provided by engineer. */ -"GROUP_YOU_LEFT" = "You have left the group."; +"GROUP_YOU_LEFT" = "您已離開群組"; /* No comment provided by engineer. */ -"YOU_WERE_REMOVED" = " You were removed from the group. "; +"YOU_WERE_REMOVED" = " 您已被群組踢出 "; /* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */ -"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items."; +"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "無法分享超過 %@ 個項目。"; /* alert title */ -"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment."; +"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "無法選取此附件。"; /* Message for the alert indicating that an audio file is invalid. */ -"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE" = "Invalid audio file."; +"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE" = "無效的聲音檔案。"; /* Slider label when disappearing messages is off */ -"KEEP_MESSAGES_FOREVER" = "Messages do not disappear."; +"KEEP_MESSAGES_FOREVER" = "訊息不會消失。"; /* Confirmation button within contextual alert */ -"LEAVE_BUTTON_TITLE" = "Leave"; +"LEAVE_BUTTON_TITLE" = "離開"; /* table cell label in conversation settings */ -"LEAVE_GROUP_ACTION" = "Leave Group"; +"LEAVE_GROUP_ACTION" = "離開群組"; /* Title for the 'long text message' view. */ -"LONG_TEXT_VIEW_TITLE" = "Message"; +"LONG_TEXT_VIEW_TITLE" = "訊息"; /* nav bar button item */ -"MEDIA_DETAIL_VIEW_ALL_MEDIA_BUTTON" = "All Media"; +"MEDIA_DETAIL_VIEW_ALL_MEDIA_BUTTON" = "全部媒體"; /* media picker option to choose from library */ -"MEDIA_FROM_LIBRARY_BUTTON" = "Photo Library"; +"MEDIA_FROM_LIBRARY_BUTTON" = "照片圖庫"; /* Confirmation button text to delete selected media from the gallery, embeds {{number of messages}} */ -"MEDIA_GALLERY_DELETE_MULTIPLE_MESSAGES_FORMAT" = "Delete %d Messages"; +"MEDIA_GALLERY_DELETE_MULTIPLE_MESSAGES_FORMAT" = "刪除 %d 訊息"; /* Confirmation button text to delete selected media message from the gallery */ -"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "Delete Message"; +"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "刪除訊息"; /* embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' */ -"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ on %@"; +"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ 於 %@"; /* Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}. */ "MEDIA_GALLERY_MORE_ITEMS_FORMAT" = "+%@"; /* Short sender label for media sent by you */ -"MEDIA_GALLERY_SENDER_NAME_YOU" = "You"; +"MEDIA_GALLERY_SENDER_NAME_YOU" = "您"; /* Section header in media gallery collection view */ -"MEDIA_GALLERY_THIS_MONTH_HEADER" = "This Month"; +"MEDIA_GALLERY_THIS_MONTH_HEADER" = "這個月"; /* message status for message delivered to their recipient. */ -"MESSAGE_STATUS_DELIVERED" = "Delivered"; +"MESSAGE_STATUS_DELIVERED" = "已傳送"; /* status message for failed messages */ -"MESSAGE_STATUS_FAILED" = "Sending failed."; +"MESSAGE_STATUS_FAILED" = "傳送失敗"; /* status message for failed messages */ -"MESSAGE_STATUS_FAILED_SHORT" = "Failed"; +"MESSAGE_STATUS_FAILED_SHORT" = "失敗"; /* status message for read messages */ -"MESSAGE_STATUS_READ" = "Read"; +"MESSAGE_STATUS_READ" = "已讀"; /* message status if message delivery to a recipient is skipped. We skip delivering group messages to users who have left the group or unregistered their Session account. */ -"MESSAGE_STATUS_RECIPIENT_SKIPPED" = "Skipped"; +"MESSAGE_STATUS_RECIPIENT_SKIPPED" = "已跳過傳送"; /* message status while message is sending. */ -"MESSAGE_STATUS_SENDING" = "Sending…"; +"MESSAGE_STATUS_SENDING" = "傳送中⋯"; /* status message for sent messages */ -"MESSAGE_STATUS_SENT" = "Sent"; +"MESSAGE_STATUS_SENT" = "已傳送"; /* status message while attachment is uploading */ -"MESSAGE_STATUS_UPLOADING" = "Uploading…"; +"MESSAGE_STATUS_UPLOADING" = "上傳中⋯"; /* Alert body when user has previously denied media library access */ -"MISSING_MEDIA_LIBRARY_PERMISSION_MESSAGE" = "You can enable this permission in the iOS Settings app."; +"MISSING_MEDIA_LIBRARY_PERMISSION_MESSAGE" = "您可以於設定中啟用這項權限(系統設定)。"; /* Alert title when user has previously denied media library access */ -"MISSING_MEDIA_LIBRARY_PERMISSION_TITLE" = "Session requires access to your photos for this feature."; +"MISSING_MEDIA_LIBRARY_PERMISSION_TITLE" = "Session 需要存取您的相片圖庫以使用此功能。"; /* An explanation of the consequences of muting a thread. */ -"MUTE_BEHAVIOR_EXPLANATION" = "You will not receive notifications for muted conversations."; +"MUTE_BEHAVIOR_EXPLANATION" = "您將不會收到被禁音的對話的通知。"; /* notification title. Embeds {{author name}} and {{group name}} */ -"NEW_GROUP_MESSAGE_NOTIFICATION_TITLE" = "%@ to %@"; +"NEW_GROUP_MESSAGE_NOTIFICATION_TITLE" = "%@ 傳送到 %@"; /* Label for 1:1 conversation with yourself. */ -"NOTE_TO_SELF" = "Note to Self"; +"NOTE_TO_SELF" = "小筆記"; /* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */ -"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "You may have received messages while your %@ was restarting."; +"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "在您的設備 ‘%@’ 重新啟動時接收到訊息。"; /* No comment provided by engineer. */ -"NOTIFICATIONS_FOOTER_WARNING" = "Due to known bugs in Apple's push framework, message previews will only be shown if the message is retrieved within 30 seconds after being sent. The application badge might be inaccurate as a result."; +"NOTIFICATIONS_FOOTER_WARNING" = "因為 Apple 系統推送通知的一個已知漏洞,訊息預覽只會在傳送後30秒內被接收的狀況下顯示,圖示的通知數字角標可能會不同。"; /* Table cell switch label. When disabled, Session will not play notification sounds while the app is in the foreground. */ -"NOTIFICATIONS_SECTION_INAPP" = "Play While App is Open"; +"NOTIFICATIONS_SECTION_INAPP" = "當 App 開啟時播放通知聲音"; /* Label for settings UI that allows user to change the notification sound. */ -"NOTIFICATIONS_SECTION_SOUNDS" = "Sounds"; +"NOTIFICATIONS_SECTION_SOUNDS" = "音效"; /* No comment provided by engineer. */ -"NOTIFICATIONS_SENDER_AND_MESSAGE" = "Name and Content"; +"NOTIFICATIONS_SENDER_AND_MESSAGE" = "名稱與內容"; /* No comment provided by engineer. */ -"NOTIFICATIONS_SENDER_ONLY" = "Name Only"; +"NOTIFICATIONS_SENDER_ONLY" = "只有名稱"; /* No comment provided by engineer. */ -"NOTIFICATIONS_NONE" = "No Name or Content"; +"NOTIFICATIONS_NONE" = "沒有名稱與內容"; /* No comment provided by engineer. */ -"NOTIFICATIONS_SHOW" = "Show"; +"NOTIFICATIONS_SHOW" = "顯示"; /* No comment provided by engineer. */ "OK" = "OK"; /* Info Message when {{other user}} disables or doesn't support disappearing messages */ -"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ disabled disappearing messages."; +"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ 取消了閱後即焚模式"; /* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */ -"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ set disappearing message time to %@"; +"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ 設定閱後即焚模式時間至 %@"; /* alert title, generic error preventing user from capturing a photo */ -"PHOTO_CAPTURE_GENERIC_ERROR" = "Unable to capture image."; +"PHOTO_CAPTURE_GENERIC_ERROR" = "無法讀取圖片。"; /* alert title */ -"PHOTO_CAPTURE_UNABLE_TO_CAPTURE_IMAGE" = "Unable to capture image."; +"PHOTO_CAPTURE_UNABLE_TO_CAPTURE_IMAGE" = "無法讀取圖片。"; /* alert title */ -"PHOTO_CAPTURE_UNABLE_TO_INITIALIZE_CAMERA" = "Failed to configure camera."; +"PHOTO_CAPTURE_UNABLE_TO_INITIALIZE_CAMERA" = "無法打開相機。"; /* label for system photo collections which have no name. */ -"PHOTO_PICKER_UNNAMED_COLLECTION" = "Unnamed Album"; +"PHOTO_PICKER_UNNAMED_COLLECTION" = "未命名相簿"; /* alert body during registration */ -"REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "We can't activate your account until you verify the code we sent you."; +"REGISTRATION_ERROR_BLANK_VERIFICATION_CODE" = "我們無法為您啟用帳號,直到您驗證了傳送給您的代碼。"; /* Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately. */ -"SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "Instant"; +"SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE" = "即時"; /* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ "SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Authenticate to open Session."; /* Title for alert indicating that screen lock could not be unlocked. */ -"SCREEN_LOCK_UNLOCK_FAILED" = "Authentication Failed"; +"SCREEN_LOCK_UNLOCK_FAILED" = "驗證失敗"; /* alert title when user attempts to leave the send media flow when they have an in-progress album */ -"SEND_MEDIA_ABANDON_TITLE" = "Discard Media?"; +"SEND_MEDIA_ABANDON_TITLE" = "放棄媒體?"; /* alert action, confirming the user wants to exit the media flow and abandon any photos they've taken */ -"SEND_MEDIA_CONFIRM_ABANDON_ALBUM" = "Discard Media"; +"SEND_MEDIA_CONFIRM_ABANDON_ALBUM" = "放棄媒體"; /* alert action when the user decides not to cancel the media flow after all. */ -"SEND_MEDIA_RETURN_TO_CAMERA" = "Return to Camera"; +"SEND_MEDIA_RETURN_TO_CAMERA" = "回到相機"; /* alert action when the user decides not to cancel the media flow after all. */ -"SEND_MEDIA_RETURN_TO_MEDIA_LIBRARY" = "Return to Media Library"; +"SEND_MEDIA_RETURN_TO_MEDIA_LIBRARY" = "回到媒體庫"; /* Format string for the default 'Note' sound. Embeds the system {{sound name}}. */ "SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT" = "%@ (default)"; /* Label for the backup view in app settings. */ -"SETTINGS_BACKUP" = "Backup"; +"SETTINGS_BACKUP" = "備份"; /* Label for 'backup now' button in the backup settings view. */ -"SETTINGS_BACKUP_BACKUP_NOW" = "Backup Now"; +"SETTINGS_BACKUP_BACKUP_NOW" = "現在備份"; /* Label for 'cancel backup' button in the backup settings view. */ -"SETTINGS_BACKUP_CANCEL_BACKUP" = "Cancel Backup"; +"SETTINGS_BACKUP_CANCEL_BACKUP" = "取消備份"; /* Label for switch in settings that controls whether or not backup is enabled. */ -"SETTINGS_BACKUP_ENABLING_SWITCH" = "Backup Enabled"; +"SETTINGS_BACKUP_ENABLING_SWITCH" = "已啟用備份"; /* Label for iCloud status row in the in the backup settings view. */ -"SETTINGS_BACKUP_ICLOUD_STATUS" = "iCloud Status"; +"SETTINGS_BACKUP_ICLOUD_STATUS" = "iCloud 狀態"; /* Indicates that the last backup restore failed. */ -"SETTINGS_BACKUP_IMPORT_STATUS_FAILED" = "Backup Restore Failed"; +"SETTINGS_BACKUP_IMPORT_STATUS_FAILED" = "回復失敗。"; /* Indicates that app is not restoring up. */ -"SETTINGS_BACKUP_IMPORT_STATUS_IDLE" = "Backup Restore Idle"; +"SETTINGS_BACKUP_IMPORT_STATUS_IDLE" = "回復已停止。"; /* Indicates that app is restoring up. */ -"SETTINGS_BACKUP_IMPORT_STATUS_IN_PROGRESS" = "Backup Restore In Progress"; +"SETTINGS_BACKUP_IMPORT_STATUS_IN_PROGRESS" = "回復中⋯"; /* Indicates that the last backup restore succeeded. */ -"SETTINGS_BACKUP_IMPORT_STATUS_SUCCEEDED" = "Backup Restore Succeeded"; +"SETTINGS_BACKUP_IMPORT_STATUS_SUCCEEDED" = "回復完成。"; /* Label for phase row in the in the backup settings view. */ -"SETTINGS_BACKUP_PHASE" = "Phase"; +"SETTINGS_BACKUP_PHASE" = "階段"; /* Label for phase row in the in the backup settings view. */ -"SETTINGS_BACKUP_PROGRESS" = "Progress"; +"SETTINGS_BACKUP_PROGRESS" = "進度"; /* Label for backup status row in the in the backup settings view. */ -"SETTINGS_BACKUP_STATUS" = "Status"; +"SETTINGS_BACKUP_STATUS" = "狀態"; /* Indicates that the last backup failed. */ -"SETTINGS_BACKUP_STATUS_FAILED" = "Backup Failed"; +"SETTINGS_BACKUP_STATUS_FAILED" = "備份失敗。"; /* Indicates that app is not backing up. */ -"SETTINGS_BACKUP_STATUS_IDLE" = "Waiting"; +"SETTINGS_BACKUP_STATUS_IDLE" = "等待中"; /* Indicates that app is backing up. */ -"SETTINGS_BACKUP_STATUS_IN_PROGRESS" = "Backing Up"; +"SETTINGS_BACKUP_STATUS_IN_PROGRESS" = "備份中⋯"; /* Indicates that the last backup succeeded. */ -"SETTINGS_BACKUP_STATUS_SUCCEEDED" = "Backup Successful"; +"SETTINGS_BACKUP_STATUS_SUCCEEDED" = "備份完成。"; /* No comment provided by engineer. */ -"SETTINGS_CLEAR_HISTORY" = "Clear Conversation History"; +"SETTINGS_CLEAR_HISTORY" = "清除對話"; /* Confirmation text for button which deletes all message, calling, attachments, etc. */ -"SETTINGS_DELETE_HISTORYLOG_CONFIRMATION_BUTTON" = "Delete Everything"; +"SETTINGS_DELETE_HISTORYLOG_CONFIRMATION_BUTTON" = "刪除所有內容"; /* Section header */ -"SETTINGS_HISTORYLOG_TITLE" = "Clear Conversation History"; +"SETTINGS_HISTORYLOG_TITLE" = "刪除對話紀錄"; /* Label for settings view that allows user to change the notification sound. */ -"SETTINGS_ITEM_NOTIFICATION_SOUND" = "Message Sound"; +"SETTINGS_ITEM_NOTIFICATION_SOUND" = "訊息通知"; /* Setting for enabling & disabling link previews. */ -"SETTINGS_LINK_PREVIEWS" = "Send Link Previews"; +"SETTINGS_LINK_PREVIEWS" = "送出的連結預覽"; /* Footer for setting for enabling & disabling link previews. */ -"SETTINGS_LINK_PREVIEWS_FOOTER" = "Previews are supported for Imgur, Instagram, Pinterest, Reddit, and YouTube links."; +"SETTINGS_LINK_PREVIEWS_FOOTER" = "預覽功能支援 Imgur, Instagram, Pinterest, Reddit, 跟 YouTube 連結。"; /* Header for setting for enabling & disabling link previews. */ -"SETTINGS_LINK_PREVIEWS_HEADER" = "Link Previews"; +"SETTINGS_LINK_PREVIEWS_HEADER" = "連結預覽"; /* table section header */ -"SETTINGS_NOTIFICATION_CONTENT_TITLE" = "Notification Content"; +"SETTINGS_NOTIFICATION_CONTENT_TITLE" = "通知內容"; /* Label for the 'read receipts' setting. */ -"SETTINGS_READ_RECEIPT" = "Read Receipts"; +"SETTINGS_READ_RECEIPT" = "已讀回條"; /* An explanation of the 'read receipts' setting. */ "SETTINGS_READ_RECEIPTS_SECTION_FOOTER" = "See and share when messages have been read. This setting is optional and applies to all conversations."; /* Label for the 'screen lock activity timeout' setting of the privacy settings. */ @@ -429,11 +429,11 @@ "vc_register_title" = "Say hello to your Session ID"; "vc_register_explanation" = "Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design."; "vc_restore_title" = "Restore your account"; -"vc_restore_explanation" = "Enter the recovery phrase that was given to you when you signed up to restore your account."; -"vc_restore_seed_text_field_hint" = "Enter your recovery phrase"; -"vc_link_device_title" = "Link a Device"; -"vc_link_device_scan_qr_code_tab_title" = "Scan QR Code"; -"vc_display_name_title_2" = "Pick your display name"; +"vc_restore_explanation" = "請輸入註冊時的回復用字句來回復中您的帳號。"; +"vc_restore_seed_text_field_hint" = "請輸入您的回復用字句"; +"vc_link_device_title" = "連結設備"; +"vc_link_device_scan_qr_code_tab_title" = "掃描 QR Code"; +"vc_display_name_title_2" = "請輸入您的名稱"; "vc_display_name_explanation" = "This will be your name when you use Session. It can be your real name, an alias, or anything else you like."; "vc_display_name_text_field_hint" = "Enter a display name"; "vc_display_name_display_name_missing_error" = "Please pick a display name"; @@ -479,57 +479,57 @@ "vc_settings_display_name_text_field_hint" = "Enter a display name"; "vc_settings_display_name_missing_error" = "Please pick a display name"; "vc_settings_display_name_too_long_error" = "Please pick a shorter display name"; -"vc_settings_privacy_button_title" = "Privacy"; -"vc_settings_notifications_button_title" = "Notifications"; -"vc_settings_recovery_phrase_button_title" = "Recovery Phrase"; -"vc_settings_clear_all_data_button_title" = "Clear Data"; -"vc_notification_settings_title" = "Notifications"; -"vc_privacy_settings_title" = "Privacy"; -"preferences_notifications_strategy_category_title" = "Notification Strategy"; -"modal_seed_title" = "Your Recovery Phrase"; -"modal_seed_explanation" = "This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device."; -"modal_clear_all_data_title" = "Clear All Data"; -"modal_clear_all_data_explanation" = "This will permanently delete your messages, sessions, and contacts."; +"vc_settings_privacy_button_title" = "隱私權條款"; +"vc_settings_notifications_button_title" = "通知"; +"vc_settings_recovery_phrase_button_title" = "回復用字句"; +"vc_settings_clear_all_data_button_title" = "清除資料"; +"vc_notification_settings_title" = "通知"; +"vc_privacy_settings_title" = "隱私權條款"; +"preferences_notifications_strategy_category_title" = "通知類型"; +"modal_seed_title" = "您的回復用字句"; +"modal_seed_explanation" = "這是您的回復用字句,您可以利用此字句來回復或轉移您的帳號至新的裝置上。"; +"modal_clear_all_data_title" = "清除所有資料"; +"modal_clear_all_data_explanation" = "這樣做將永久清除您的訊息,帳號與聯絡人。"; "vc_qr_code_title" = "QR Code"; -"vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code"; -"vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code"; -"vc_qr_code_view_scan_qr_code_explanation" = "Scan someone's QR code to start a conversation with them"; -"vc_view_my_qr_code_explanation" = "This is your QR code. Other users can scan it to start a session with you."; +"vc_qr_code_view_my_qr_code_tab_title" = "查看我的 QR Code"; +"vc_qr_code_view_scan_qr_code_tab_title" = "掃描 QR Code"; +"vc_qr_code_view_scan_qr_code_explanation" = "掃描朋友的 QR Code 來開始對話。"; +"vc_view_my_qr_code_explanation" = "這是您的 QR Code,其他使用者可以掃描來與您對話。"; // MARK: - Not Yet Translated -"fast_mode_explanation" = "You’ll be notified of new messages reliably and immediately using Apple’s notification servers."; -"fast_mode" = "Fast Mode"; -"slow_mode_explanation" = "Session will occasionally check for new messages in the background."; -"slow_mode" = "Slow Mode"; -"vc_pn_mode_title" = "Message Notifications"; -"vc_notification_settings_notification_mode_title" = "Use Fast Mode"; -"vc_link_device_recovery_phrase_tab_title" = "Recovery Phrase"; -"vc_link_device_scan_qr_code_explanation" = "Navigate to Settings → Recovery Phrase on your other device to show your QR code."; -"vc_enter_recovery_phrase_title" = "Recovery Phrase"; -"vc_enter_recovery_phrase_explanation" = "To link your device, enter the recovery phrase that was given to you when you signed up."; -"vc_enter_public_key_text_field_hint" = "Enter Session ID or ONS name"; -"vc_home_title" = "Messages"; -"admin_group_leave_warning" = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."; -"vc_join_open_group_suggestions_title" = "Or join one of these..."; -"vc_settings_invite_a_friend_button_title" = "Invite a Friend"; -"vc_settings_help_us_translate_button_title" = "Help us Translate Session"; -"copied" = "Copied"; -"vc_conversation_settings_copy_session_id_button_title" = "Copy Session ID"; -"vc_conversation_input_prompt" = "Message"; -"vc_conversation_voice_message_cancel_message" = "Slide to Cancel"; -"modal_download_attachment_title" = "Trust %@?"; -"modal_download_attachment_explanation" = "Are you sure you want to download media sent by %@?"; -"modal_download_button_title" = "Download"; -"modal_open_url_title" = "Open URL?"; -"modal_open_url_explanation" = "Are you sure you want to open %@?"; -"modal_open_url_button_title" = "Open"; -"modal_blocked_title" = "Unblock %@?"; -"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; -"modal_blocked_button_title" = "Unblock"; -"modal_link_previews_title" = "Enable Link Previews?"; -"modal_link_previews_explanation" = "Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session's settings."; -"modal_link_previews_button_title" = "Enable"; -"vc_share_title" = "Share to Session"; -"vc_share_loading_message" = "Preparing attachments..."; -"vc_share_sending_message" = "Sending..."; -"view_open_group_invitation_description" = "Open group invitation"; -"vc_conversation_settings_invite_button_title" = "Add Members"; +"fast_mode_explanation" = "您將會透過 Apple 的通知服務可靠且迅速的收到通知。"; +"fast_mode" = "性能模式"; +"slow_mode_explanation" = "Session 會偶爾在背景執行時檢查新訊息。"; +"slow_mode" = "慢速模式"; +"vc_pn_mode_title" = "訊息通知"; +"vc_notification_settings_notification_mode_title" = "使用性能模式"; +"vc_link_device_recovery_phrase_tab_title" = "回復用字句"; +"vc_link_device_scan_qr_code_explanation" = "請使用您其他的裝置並前往設定 → 回復用字句 來顯示您的QR Code。"; +"vc_enter_recovery_phrase_title" = "回復用字句"; +"vc_enter_recovery_phrase_explanation" = "如您需要連結您的裝置,請輸入申請帳號時您的回復用字句。"; +"vc_enter_public_key_text_field_hint" = "請輸入您的 ID 或 ONS 的名稱"; +"vc_home_title" = "訊息"; +"admin_group_leave_warning" = "因為您是此群組的創立人所以將會刪除所有人的群組,此動作將不能被撤銷。"; +"vc_join_open_group_suggestions_title" = "或加入這些群組⋯"; +"vc_settings_invite_a_friend_button_title" = "邀請好友"; +"vc_settings_help_us_translate_button_title" = "幫助我們翻譯 Session"; +"copied" = "已複製"; +"vc_conversation_settings_copy_session_id_button_title" = "複製 Session ID"; +"vc_conversation_input_prompt" = "訊息"; +"vc_conversation_voice_message_cancel_message" = "滑動以取消"; +"modal_download_attachment_title" = "是否信任 %@?"; +"modal_download_attachment_explanation" = "您確定要下載 %@ 傳送的媒體嗎?"; +"modal_download_button_title" = "下載"; +"modal_open_url_title" = "打開連結?"; +"modal_open_url_explanation" = "您確定要打開 %@ 嗎?"; +"modal_open_url_button_title" = "打開"; +"modal_blocked_title" = "解除封鎖 %@ 嗎?"; +"modal_blocked_explanation" = "您確定要解除封鎖 %@ 嗎?"; +"modal_blocked_button_title" = "解除封鎖"; +"modal_link_previews_title" = "是否要啟用連結預覽?"; +"modal_link_previews_explanation" = "啟用連結預覽將會讓您送出與接收的 URLs 啟用預覽,這是一項好用的功能,但是 Session 會需要連結這些網站來產生預覽,您可以隨時關閉連結預覽功能。"; +"modal_link_previews_button_title" = "啟用"; +"vc_share_title" = "分享至 Session"; +"vc_share_loading_message" = "準備附件中⋯"; +"vc_share_sending_message" = "傳送中⋯"; +"view_open_group_invitation_description" = "打開群組邀請"; +"vc_conversation_settings_invite_button_title" = "新增成員"; From 9cff7f8d4fd690e0d1887b82d6a316897c6f0d55 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 18 May 2021 10:02:48 +1000 Subject: [PATCH 39/57] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 4afe5ad6f..0a21b22a2 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5138,7 +5138,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5207,7 +5207,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5268,7 +5268,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5338,7 +5338,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6223,7 +6223,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6291,7 +6291,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From 53a4dff9ddd18ca258d9bf20f1e9e27c5f340aed Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Tue, 18 May 2021 16:42:08 +1000 Subject: [PATCH 40/57] fix move-insert conflict --- Session/Home/HomeVC.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 20a70778f..2808e224f 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -226,12 +226,26 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv switch rowChange.type { case .delete: tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic) case .insert: tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.automatic) - case .move: tableView.moveRow(at: rowChange.indexPath!, to: rowChange.newIndexPath!) case .update: tableView.reloadRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic) default: break } } tableView.endUpdates() + // HACK: Moves can have conflicts with other 3 types of change. + // Just batch perform all the moves seperately to prevent crashing. + // Since all the changes are from original state to final state, + // it will still be correct if we pick the Moves out. + tableView.beginUpdates() + rowChanges.forEach { rowChange in + let rowChange = rowChange as! YapDatabaseViewRowChange + let key = rowChange.collectionKey.key + threadViewModelCache[key] = nil + switch rowChange.type { + case .move: tableView.moveRow(at: rowChange.indexPath!, to: rowChange.newIndexPath!) + default: break + } + } + tableView.endUpdates() emptyStateView.isHidden = (threadCount != 0) } From c36cf1a36d1fb1ccea7da5acc2846854854a0c8d Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 19 May 2021 08:42:58 +1000 Subject: [PATCH 41/57] Hopefully fix closed group handling bug --- Session/Utilities/BackgroundPoller.swift | 2 +- .../Sending & Receiving/MessageReceiver.swift | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index d16570ae1..4ce1f8147 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -43,7 +43,7 @@ public final class BackgroundPoller : NSObject { } private static func getMessages(for publicKey: String) -> Promise { - return SnodeAPI.getSwarm(for: publicKey).then2 { swarm -> Promise in + return SnodeAPI.getSwarm(for: publicKey).then(on: DispatchQueue.main) { swarm -> Promise in guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index cf724fe9f..8e6e53bca 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -54,11 +54,6 @@ public enum MessageReceiver { // Parse the envelope let envelope = try SNProtoEnvelope.parseData(data) let storage = SNMessagingKitConfiguration.shared.storage - // If the message failed to process the first time around we retry it later (if the error is retryable). In this case the timestamp - // will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround - // for this issue. - guard !Set(storage.getReceivedMessageTimestamps(using: transaction)).contains(envelope.timestamp) || isRetry else { throw Error.duplicateMessage } - storage.addReceivedMessageTimestamp(envelope.timestamp, using: transaction) // Decrypt the contents guard let ciphertext = envelope.content else { throw Error.noData } var plaintext: Data! @@ -159,6 +154,19 @@ public enum MessageReceiver { guard isValid else { throw Error.invalidMessage } + // If the message failed to process the first time around we retry it later (if the error is retryable). In this case the timestamp + // will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround + // for this issue. + if let message = message as? ClosedGroupControlMessage, case .new = message.kind { + // Allow duplicates in this case to avoid the following situation: + // • The app performed a background poll or received a push notification + // • This method was invoked and the received message timestamps table was updated + // • Processing wasn't finished + // • The user doesn't see the new closed group + } else { + guard !Set(storage.getReceivedMessageTimestamps(using: transaction)).contains(envelope.timestamp) || isRetry else { throw Error.duplicateMessage } + storage.addReceivedMessageTimestamp(envelope.timestamp, using: transaction) + } // Return return (message, proto) } else { From 6760341a2e7a56348828c2d1808088d75ca42b40 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 19 May 2021 08:49:20 +1000 Subject: [PATCH 42/57] Add missing retry --- Session/Utilities/BackgroundPoller.swift | 31 +++++++++++------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index 4ce1f8147..3a79a7450 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -13,12 +13,6 @@ public final class BackgroundPoller : NSObject { promises = [] promises.append(pollForMessages()) promises.append(contentsOf: pollForClosedGroupMessages()) - let openGroups: [String:OpenGroup] = Storage.shared.getAllUserOpenGroups() - openGroups.values.forEach { openGroup in - let poller = OpenGroupPoller(for: openGroup) - poller.stop() - promises.append(poller.pollForNewMessages(isBackgroundPoll: true)) - } let v2OpenGroupServers = Set(Storage.shared.getAllV2OpenGroups().values.map { $0.server }) v2OpenGroupServers.forEach { server in let poller = OpenGroupPollerV2(for: server) @@ -27,7 +21,8 @@ public final class BackgroundPoller : NSObject { } when(resolved: promises).done { _ in completionHandler(.newData) - }.catch { _ in + }.catch { error in + SNLog("Background poll failed due to error: \(error)") completionHandler(.failed) } } @@ -45,17 +40,19 @@ public final class BackgroundPoller : NSObject { private static func getMessages(for publicKey: String) -> Promise { return SnodeAPI.getSwarm(for: publicKey).then(on: DispatchQueue.main) { swarm -> Promise in guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } - return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in - let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) - let promises = messages.compactMap { json -> Promise? in - // Use a best attempt approach here; we don't want to fail the entire process if one of the - // messages failed to parse. - guard let envelope = SNProtoEnvelope.from(json), - let data = try? envelope.serializedData() else { return nil } - let job = MessageReceiveJob(data: data, isBackgroundPoll: true) - return job.execute() + return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { + return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in + let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) + let promises = messages.compactMap { json -> Promise? in + // Use a best attempt approach here; we don't want to fail the entire process if one of the + // messages failed to parse. + guard let envelope = SNProtoEnvelope.from(json), + let data = try? envelope.serializedData() else { return nil } + let job = MessageReceiveJob(data: data, isBackgroundPoll: true) + return job.execute() + } + return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects } - return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects } } } From 8338ba3eca7fafca99715eeafd799ed6cc835e45 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 19 May 2021 13:32:42 +1000 Subject: [PATCH 43/57] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ Session/Home/HomeVC.swift | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 0a21b22a2..52eced0e1 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5138,7 +5138,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5207,7 +5207,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5268,7 +5268,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5338,7 +5338,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6223,7 +6223,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6291,7 +6291,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 2808e224f..9fde3b929 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -231,10 +231,10 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv } } tableView.endUpdates() - // HACK: Moves can have conflicts with other 3 types of change. - // Just batch perform all the moves seperately to prevent crashing. - // Since all the changes are from original state to final state, - // it will still be correct if we pick the Moves out. + // HACK: Moves can have conflicts with the other 3 types of change. + // Just batch perform all the moves separately to prevent crashing. + // Since all the changes are from the original state to the final state, + // it will still be correct if we pick the moves out. tableView.beginUpdates() rowChanges.forEach { rowChange in let rowChange = rowChange as! YapDatabaseViewRowChange From 8dffa249bbd89a26b295906869088ed25073cd09 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 19 May 2021 13:43:24 +1000 Subject: [PATCH 44/57] Update version number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 52eced0e1..25ade3331 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5159,7 +5159,7 @@ INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5233,7 +5233,7 @@ INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5287,7 +5287,7 @@ INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -5362,7 +5362,7 @@ INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6259,7 +6259,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -6327,7 +6327,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; From 4da8a0933c5db5b7d1cbe7192e8ccdd977624c2e Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 19 May 2021 15:15:54 +1000 Subject: [PATCH 45/57] Add file server instability modal --- Session.xcodeproj/project.pbxproj | 4 ++ Session/Home/HomeVC.swift | 8 ++++ Session/Sheets & Modals/FileServerModal.swift | 44 +++++++++++++++++++ .../General/SNUserDefaults.swift | 1 + 4 files changed, 57 insertions(+) create mode 100644 Session/Sheets & Modals/FileServerModal.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 25ade3331..322724eb6 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -773,6 +773,7 @@ C3DB66CC260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66CB260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift */; }; C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; + C3ECBBDB2654D4640081996B /* FileServerModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECBBDA2654D4640081996B /* FileServerModal.swift */; }; C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECBF7A257056B700EA7FCE /* Threading.swift */; }; C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */; }; D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; @@ -1762,6 +1763,7 @@ C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = ""; }; C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClosedGroupVC.swift; sourceTree = ""; }; C3E7134E251C867C009649BB /* Sodium+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Conversion.swift"; sourceTree = ""; }; + C3ECBBDA2654D4640081996B /* FileServerModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServerModal.swift; sourceTree = ""; }; C3ECBF7A257056B700EA7FCE /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = ""; }; C3F0A5B2255C915C007BE2A3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -2872,6 +2874,7 @@ B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */, C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */, + C3ECBBDA2654D4640081996B /* FileServerModal.swift */, ); path = "Sheets & Modals"; sourceTree = ""; @@ -4888,6 +4891,7 @@ 34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */, 450DF2051E0D74AC003D14BE /* Platform.swift in Sources */, 4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */, + C3ECBBDB2654D4640081996B /* FileServerModal.swift in Sources */, B82149B825D60393009C0F2A /* BlockedModal.swift in Sources */, B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */, 346129991FD1E4DA00532771 /* SignalApp.m in Sources */, diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 9fde3b929..688aed4b7 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -164,6 +164,14 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) reload() + let userDefaults = UserDefaults.standard + if !userDefaults[.hasSeenFileServerInstabilityNotification] { + let fileServerModal = FileServerModal() + fileServerModal.modalPresentationStyle = .overFullScreen + fileServerModal.modalTransitionStyle = .crossDissolve + present(fileServerModal, animated: true, completion: nil) + userDefaults[.hasSeenFileServerInstabilityNotification] = true + } } deinit { diff --git a/Session/Sheets & Modals/FileServerModal.swift b/Session/Sheets & Modals/FileServerModal.swift new file mode 100644 index 000000000..d4a036fb3 --- /dev/null +++ b/Session/Sheets & Modals/FileServerModal.swift @@ -0,0 +1,44 @@ + +final class FileServerModal : Modal { + + override func populateContentView() { + // Title + let titleLabel = UILabel() + titleLabel.textColor = Colors.text + titleLabel.font = .boldSystemFont(ofSize: Values.largeFontSize) + titleLabel.text = "Session" + titleLabel.textAlignment = .center + // Message + let messageLabel = UILabel() + messageLabel.textColor = Colors.text + messageLabel.font = .systemFont(ofSize: Values.smallFontSize) + let message = "We're upgrading the way files are stored. File transfer may be unstable for the next 24-48 hours." + messageLabel.text = message + messageLabel.numberOfLines = 0 + messageLabel.lineBreakMode = .byWordWrapping + messageLabel.textAlignment = .center + // OK button + let okButton = UIButton() + okButton.set(.height, to: Values.mediumButtonHeight) + okButton.layer.cornerRadius = Modal.buttonCornerRadius + okButton.backgroundColor = Colors.buttonBackground + okButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) + okButton.setTitleColor(Colors.text, for: UIControl.State.normal) + okButton.setTitle("OK", for: UIControl.State.normal) + okButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + // Button stack view + let buttonStackView = UIStackView(arrangedSubviews: [ okButton ]) + buttonStackView.axis = .horizontal + buttonStackView.spacing = Values.mediumSpacing + buttonStackView.distribution = .fillEqually + // Main stack view + let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, messageLabel, buttonStackView ]) + mainStackView.axis = .vertical + mainStackView.spacing = Values.largeSpacing + contentView.addSubview(mainStackView) + mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing) + mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) + contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing) + contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing) + } +} diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index aa563e94f..982fabe4f 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -8,6 +8,7 @@ public enum SNUserDefaults { case hasSeenLinkPreviewSuggestion case isUsingFullAPNs case isMigratingToV2KeyPair + case hasSeenFileServerInstabilityNotification } public enum Date : Swift.String { From 20e0103d2df3271e8c9368616054bf11f898bffd Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 20 May 2021 10:53:56 +1000 Subject: [PATCH 46/57] Switch to dedicated server --- .../File Server/FileServerAPIV2.swift | 22 +++++++++++-------- .../Jobs/AttachmentDownloadJob.swift | 5 +++-- SignalUtilitiesKit/To Do/OWSProfileManager.m | 5 +++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/SessionMessagingKit/File Server/FileServerAPIV2.swift b/SessionMessagingKit/File Server/FileServerAPIV2.swift index dd19b528c..16146b59a 100644 --- a/SessionMessagingKit/File Server/FileServerAPIV2.swift +++ b/SessionMessagingKit/File Server/FileServerAPIV2.swift @@ -5,8 +5,10 @@ import SessionSnodeKit public final class FileServerAPIV2 : NSObject { // MARK: Settings - @objc public static let server = "http://88.99.175.227" - public static let serverPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" + @objc public static let oldServer = "http://88.99.175.227" + public static let oldServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" + @objc public static let server = "http://filev2.getsession.org" + public static let serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" // MARK: Initialization private override init() { } @@ -47,7 +49,9 @@ public final class FileServerAPIV2 : NSObject { } // MARK: Convenience - private static func send(_ request: Request) -> Promise { + private static func send(_ request: Request, useOldServer: Bool) -> Promise { + let server = useOldServer ? oldServer : server + let serverPublicKey = useOldServer ? oldServerPublicKey : serverPublicKey let tsRequest: TSRequest switch request.verb { case .get: @@ -81,21 +85,21 @@ public final class FileServerAPIV2 : NSObject { let base64EncodedFile = file.base64EncodedString() let parameters = [ "file" : base64EncodedFile ] let request = Request(verb: .post, endpoint: "files", parameters: parameters) - return send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { json in + return send(request, useOldServer: false).map(on: DispatchQueue.global(qos: .userInitiated)) { json in guard let fileID = json["result"] as? UInt64 else { throw Error.parsingFailed } return fileID } } - @objc(download:) - public static func objc_download(file: String) -> AnyPromise { + @objc(download:useOldServer:) + public static func objc_download(file: String, useOldServer: Bool) -> AnyPromise { guard let id = UInt64(file) else { return AnyPromise.from(Promise(error: Error.invalidURL)) } - return AnyPromise.from(download(id)) + return AnyPromise.from(download(id, useOldServer: useOldServer)) } - public static func download(_ file: UInt64) -> Promise { + public static func download(_ file: UInt64, useOldServer: Bool) -> Promise { let request = Request(verb: .get, endpoint: "files/\(file)") - return send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { json in + return send(request, useOldServer: useOldServer).map(on: DispatchQueue.global(qos: .userInitiated)) { json in guard let base64EncodedFile = json["result"] as? String, let file = Data(base64Encoded: base64EncodedFile) else { throw Error.parsingFailed } return file } diff --git a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift index d9b96b5b9..ef1897af7 100644 --- a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift @@ -100,11 +100,12 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject }.catch(on: DispatchQueue.global()) { error in handleFailure(error) } - } else if pointer.downloadURL.contains(FileServerAPIV2.server) { + } else if pointer.downloadURL.contains(FileServerAPIV2.server) || pointer.downloadURL.contains(FileServerAPIV2.oldServer) { guard let fileAsString = pointer.downloadURL.split(separator: "/").last, let file = UInt64(fileAsString) else { return handleFailure(Error.invalidURL) } - FileServerAPIV2.download(file).done(on: DispatchQueue.global(qos: .userInitiated)) { data in + let useOldServer = pointer.downloadURL.contains(FileServerAPIV2.oldServer) + FileServerAPIV2.download(file, useOldServer: useOldServer).done(on: DispatchQueue.global(qos: .userInitiated)) { data in self.handleDownloadedAttachment(data: data, temporaryFilePath: temporaryFilePath, pointer: pointer, failureHandler: handleFailure) }.catch(on: DispatchQueue.global()) { error in handleFailure(error) diff --git a/SignalUtilitiesKit/To Do/OWSProfileManager.m b/SignalUtilitiesKit/To Do/OWSProfileManager.m index c37b850f9..afc9428cc 100644 --- a/SignalUtilitiesKit/To Do/OWSProfileManager.m +++ b/SignalUtilitiesKit/To Do/OWSProfileManager.m @@ -806,9 +806,10 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); NSString *profilePictureURL = userProfile.avatarUrlPath; AnyPromise *promise; - if ([profilePictureURL containsString:SNFileServerAPIV2.server]) { + if ([profilePictureURL containsString:SNFileServerAPIV2.server] || [profilePictureURL containsString:SNFileServerAPIV2.oldServer]) { NSString *file = [profilePictureURL lastPathComponent]; - promise = [SNFileServerAPIV2 download:file]; + BOOL useOldServer = [profilePictureURL containsString:SNFileServerAPIV2.oldServer]; + promise = [SNFileServerAPIV2 download:file useOldServer:useOldServer]; } else { promise = [SNFileServerAPI downloadAttachmentFrom:profilePictureURL]; } From 9cbc2921e32bfc30c50b3696ab0a449cae30a5c7 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Tue, 18 May 2021 16:42:08 +1000 Subject: [PATCH 47/57] fix move-insert conflict --- Session/Home/HomeVC.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 20a70778f..2808e224f 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -226,12 +226,26 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv switch rowChange.type { case .delete: tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic) case .insert: tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.automatic) - case .move: tableView.moveRow(at: rowChange.indexPath!, to: rowChange.newIndexPath!) case .update: tableView.reloadRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic) default: break } } tableView.endUpdates() + // HACK: Moves can have conflicts with other 3 types of change. + // Just batch perform all the moves seperately to prevent crashing. + // Since all the changes are from original state to final state, + // it will still be correct if we pick the Moves out. + tableView.beginUpdates() + rowChanges.forEach { rowChange in + let rowChange = rowChange as! YapDatabaseViewRowChange + let key = rowChange.collectionKey.key + threadViewModelCache[key] = nil + switch rowChange.type { + case .move: tableView.moveRow(at: rowChange.indexPath!, to: rowChange.newIndexPath!) + default: break + } + } + tableView.endUpdates() emptyStateView.isHidden = (threadCount != 0) } From 340b9fcdf0078fb9279d4ae7f125ba1acd514456 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 19 May 2021 13:32:42 +1000 Subject: [PATCH 48/57] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ Session/Home/HomeVC.swift | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 0a21b22a2..52eced0e1 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5138,7 +5138,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5207,7 +5207,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5268,7 +5268,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5338,7 +5338,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6223,7 +6223,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6291,7 +6291,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 2808e224f..9fde3b929 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -231,10 +231,10 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv } } tableView.endUpdates() - // HACK: Moves can have conflicts with other 3 types of change. - // Just batch perform all the moves seperately to prevent crashing. - // Since all the changes are from original state to final state, - // it will still be correct if we pick the Moves out. + // HACK: Moves can have conflicts with the other 3 types of change. + // Just batch perform all the moves separately to prevent crashing. + // Since all the changes are from the original state to the final state, + // it will still be correct if we pick the moves out. tableView.beginUpdates() rowChanges.forEach { rowChange in let rowChange = rowChange as! YapDatabaseViewRowChange From b1bb087c752cb0dbb62d53ddca01cc81a215df6c Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 19 May 2021 13:43:24 +1000 Subject: [PATCH 49/57] Update version number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 52eced0e1..25ade3331 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5159,7 +5159,7 @@ INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5233,7 +5233,7 @@ INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5287,7 +5287,7 @@ INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -5362,7 +5362,7 @@ INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6259,7 +6259,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -6327,7 +6327,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.0; + MARKETING_VERSION = 1.11.1; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; From 1d028e5f6c0ee63c74289f2f8ef15a2b974ef723 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 19 May 2021 15:15:54 +1000 Subject: [PATCH 50/57] Add file server instability modal --- Session.xcodeproj/project.pbxproj | 4 ++ Session/Home/HomeVC.swift | 8 ++++ Session/Sheets & Modals/FileServerModal.swift | 44 +++++++++++++++++++ .../General/SNUserDefaults.swift | 1 + 4 files changed, 57 insertions(+) create mode 100644 Session/Sheets & Modals/FileServerModal.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 25ade3331..322724eb6 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -773,6 +773,7 @@ C3DB66CC260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66CB260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift */; }; C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; + C3ECBBDB2654D4640081996B /* FileServerModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECBBDA2654D4640081996B /* FileServerModal.swift */; }; C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECBF7A257056B700EA7FCE /* Threading.swift */; }; C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */; }; D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; @@ -1762,6 +1763,7 @@ C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = ""; }; C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClosedGroupVC.swift; sourceTree = ""; }; C3E7134E251C867C009649BB /* Sodium+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Conversion.swift"; sourceTree = ""; }; + C3ECBBDA2654D4640081996B /* FileServerModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServerModal.swift; sourceTree = ""; }; C3ECBF7A257056B700EA7FCE /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = ""; }; C3F0A5B2255C915C007BE2A3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -2872,6 +2874,7 @@ B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */, C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */, + C3ECBBDA2654D4640081996B /* FileServerModal.swift */, ); path = "Sheets & Modals"; sourceTree = ""; @@ -4888,6 +4891,7 @@ 34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */, 450DF2051E0D74AC003D14BE /* Platform.swift in Sources */, 4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */, + C3ECBBDB2654D4640081996B /* FileServerModal.swift in Sources */, B82149B825D60393009C0F2A /* BlockedModal.swift in Sources */, B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */, 346129991FD1E4DA00532771 /* SignalApp.m in Sources */, diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 9fde3b929..688aed4b7 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -164,6 +164,14 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) reload() + let userDefaults = UserDefaults.standard + if !userDefaults[.hasSeenFileServerInstabilityNotification] { + let fileServerModal = FileServerModal() + fileServerModal.modalPresentationStyle = .overFullScreen + fileServerModal.modalTransitionStyle = .crossDissolve + present(fileServerModal, animated: true, completion: nil) + userDefaults[.hasSeenFileServerInstabilityNotification] = true + } } deinit { diff --git a/Session/Sheets & Modals/FileServerModal.swift b/Session/Sheets & Modals/FileServerModal.swift new file mode 100644 index 000000000..d4a036fb3 --- /dev/null +++ b/Session/Sheets & Modals/FileServerModal.swift @@ -0,0 +1,44 @@ + +final class FileServerModal : Modal { + + override func populateContentView() { + // Title + let titleLabel = UILabel() + titleLabel.textColor = Colors.text + titleLabel.font = .boldSystemFont(ofSize: Values.largeFontSize) + titleLabel.text = "Session" + titleLabel.textAlignment = .center + // Message + let messageLabel = UILabel() + messageLabel.textColor = Colors.text + messageLabel.font = .systemFont(ofSize: Values.smallFontSize) + let message = "We're upgrading the way files are stored. File transfer may be unstable for the next 24-48 hours." + messageLabel.text = message + messageLabel.numberOfLines = 0 + messageLabel.lineBreakMode = .byWordWrapping + messageLabel.textAlignment = .center + // OK button + let okButton = UIButton() + okButton.set(.height, to: Values.mediumButtonHeight) + okButton.layer.cornerRadius = Modal.buttonCornerRadius + okButton.backgroundColor = Colors.buttonBackground + okButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) + okButton.setTitleColor(Colors.text, for: UIControl.State.normal) + okButton.setTitle("OK", for: UIControl.State.normal) + okButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + // Button stack view + let buttonStackView = UIStackView(arrangedSubviews: [ okButton ]) + buttonStackView.axis = .horizontal + buttonStackView.spacing = Values.mediumSpacing + buttonStackView.distribution = .fillEqually + // Main stack view + let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, messageLabel, buttonStackView ]) + mainStackView.axis = .vertical + mainStackView.spacing = Values.largeSpacing + contentView.addSubview(mainStackView) + mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing) + mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) + contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing) + contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing) + } +} diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index aa563e94f..982fabe4f 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -8,6 +8,7 @@ public enum SNUserDefaults { case hasSeenLinkPreviewSuggestion case isUsingFullAPNs case isMigratingToV2KeyPair + case hasSeenFileServerInstabilityNotification } public enum Date : Swift.String { From 5e1a33c32e70846b9da2ec40bc8502f6e9fcfeab Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 20 May 2021 10:53:56 +1000 Subject: [PATCH 51/57] Switch to dedicated server --- .../File Server/FileServerAPIV2.swift | 22 +++++++++++-------- .../Jobs/AttachmentDownloadJob.swift | 5 +++-- SignalUtilitiesKit/To Do/OWSProfileManager.m | 5 +++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/SessionMessagingKit/File Server/FileServerAPIV2.swift b/SessionMessagingKit/File Server/FileServerAPIV2.swift index dd19b528c..16146b59a 100644 --- a/SessionMessagingKit/File Server/FileServerAPIV2.swift +++ b/SessionMessagingKit/File Server/FileServerAPIV2.swift @@ -5,8 +5,10 @@ import SessionSnodeKit public final class FileServerAPIV2 : NSObject { // MARK: Settings - @objc public static let server = "http://88.99.175.227" - public static let serverPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" + @objc public static let oldServer = "http://88.99.175.227" + public static let oldServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" + @objc public static let server = "http://filev2.getsession.org" + public static let serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" // MARK: Initialization private override init() { } @@ -47,7 +49,9 @@ public final class FileServerAPIV2 : NSObject { } // MARK: Convenience - private static func send(_ request: Request) -> Promise { + private static func send(_ request: Request, useOldServer: Bool) -> Promise { + let server = useOldServer ? oldServer : server + let serverPublicKey = useOldServer ? oldServerPublicKey : serverPublicKey let tsRequest: TSRequest switch request.verb { case .get: @@ -81,21 +85,21 @@ public final class FileServerAPIV2 : NSObject { let base64EncodedFile = file.base64EncodedString() let parameters = [ "file" : base64EncodedFile ] let request = Request(verb: .post, endpoint: "files", parameters: parameters) - return send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { json in + return send(request, useOldServer: false).map(on: DispatchQueue.global(qos: .userInitiated)) { json in guard let fileID = json["result"] as? UInt64 else { throw Error.parsingFailed } return fileID } } - @objc(download:) - public static func objc_download(file: String) -> AnyPromise { + @objc(download:useOldServer:) + public static func objc_download(file: String, useOldServer: Bool) -> AnyPromise { guard let id = UInt64(file) else { return AnyPromise.from(Promise(error: Error.invalidURL)) } - return AnyPromise.from(download(id)) + return AnyPromise.from(download(id, useOldServer: useOldServer)) } - public static func download(_ file: UInt64) -> Promise { + public static func download(_ file: UInt64, useOldServer: Bool) -> Promise { let request = Request(verb: .get, endpoint: "files/\(file)") - return send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { json in + return send(request, useOldServer: useOldServer).map(on: DispatchQueue.global(qos: .userInitiated)) { json in guard let base64EncodedFile = json["result"] as? String, let file = Data(base64Encoded: base64EncodedFile) else { throw Error.parsingFailed } return file } diff --git a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift index d9b96b5b9..ef1897af7 100644 --- a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift @@ -100,11 +100,12 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject }.catch(on: DispatchQueue.global()) { error in handleFailure(error) } - } else if pointer.downloadURL.contains(FileServerAPIV2.server) { + } else if pointer.downloadURL.contains(FileServerAPIV2.server) || pointer.downloadURL.contains(FileServerAPIV2.oldServer) { guard let fileAsString = pointer.downloadURL.split(separator: "/").last, let file = UInt64(fileAsString) else { return handleFailure(Error.invalidURL) } - FileServerAPIV2.download(file).done(on: DispatchQueue.global(qos: .userInitiated)) { data in + let useOldServer = pointer.downloadURL.contains(FileServerAPIV2.oldServer) + FileServerAPIV2.download(file, useOldServer: useOldServer).done(on: DispatchQueue.global(qos: .userInitiated)) { data in self.handleDownloadedAttachment(data: data, temporaryFilePath: temporaryFilePath, pointer: pointer, failureHandler: handleFailure) }.catch(on: DispatchQueue.global()) { error in handleFailure(error) diff --git a/SignalUtilitiesKit/To Do/OWSProfileManager.m b/SignalUtilitiesKit/To Do/OWSProfileManager.m index c37b850f9..afc9428cc 100644 --- a/SignalUtilitiesKit/To Do/OWSProfileManager.m +++ b/SignalUtilitiesKit/To Do/OWSProfileManager.m @@ -806,9 +806,10 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); NSString *profilePictureURL = userProfile.avatarUrlPath; AnyPromise *promise; - if ([profilePictureURL containsString:SNFileServerAPIV2.server]) { + if ([profilePictureURL containsString:SNFileServerAPIV2.server] || [profilePictureURL containsString:SNFileServerAPIV2.oldServer]) { NSString *file = [profilePictureURL lastPathComponent]; - promise = [SNFileServerAPIV2 download:file]; + BOOL useOldServer = [profilePictureURL containsString:SNFileServerAPIV2.oldServer]; + promise = [SNFileServerAPIV2 download:file useOldServer:useOldServer]; } else { promise = [SNFileServerAPI downloadAttachmentFrom:profilePictureURL]; } From 0fe8dc7aeedd5821b778a7254a8e3116070d0ee5 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 20 May 2021 11:09:42 +1000 Subject: [PATCH 52/57] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 322724eb6..ef5475669 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5142,7 +5142,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5211,7 +5211,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5272,7 +5272,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5342,7 +5342,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6227,7 +6227,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6295,7 +6295,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From 08fc17e0f9c1203528fd89191d5d6be6cd7a9a7e Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 20 May 2021 12:03:59 +1000 Subject: [PATCH 53/57] Remove file server instability modal --- Session.xcodeproj/project.pbxproj | 4 -- Session/Home/HomeVC.swift | 8 ---- Session/Sheets & Modals/FileServerModal.swift | 44 ------------------- .../General/SNUserDefaults.swift | 1 - 4 files changed, 57 deletions(-) delete mode 100644 Session/Sheets & Modals/FileServerModal.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 322724eb6..25ade3331 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -773,7 +773,6 @@ C3DB66CC260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66CB260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift */; }; C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; - C3ECBBDB2654D4640081996B /* FileServerModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECBBDA2654D4640081996B /* FileServerModal.swift */; }; C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECBF7A257056B700EA7FCE /* Threading.swift */; }; C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */; }; D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; @@ -1763,7 +1762,6 @@ C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = ""; }; C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClosedGroupVC.swift; sourceTree = ""; }; C3E7134E251C867C009649BB /* Sodium+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Conversion.swift"; sourceTree = ""; }; - C3ECBBDA2654D4640081996B /* FileServerModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServerModal.swift; sourceTree = ""; }; C3ECBF7A257056B700EA7FCE /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = ""; }; C3F0A5B2255C915C007BE2A3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -2874,7 +2872,6 @@ B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */, C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */, - C3ECBBDA2654D4640081996B /* FileServerModal.swift */, ); path = "Sheets & Modals"; sourceTree = ""; @@ -4891,7 +4888,6 @@ 34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */, 450DF2051E0D74AC003D14BE /* Platform.swift in Sources */, 4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */, - C3ECBBDB2654D4640081996B /* FileServerModal.swift in Sources */, B82149B825D60393009C0F2A /* BlockedModal.swift in Sources */, B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */, 346129991FD1E4DA00532771 /* SignalApp.m in Sources */, diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 688aed4b7..9fde3b929 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -164,14 +164,6 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) reload() - let userDefaults = UserDefaults.standard - if !userDefaults[.hasSeenFileServerInstabilityNotification] { - let fileServerModal = FileServerModal() - fileServerModal.modalPresentationStyle = .overFullScreen - fileServerModal.modalTransitionStyle = .crossDissolve - present(fileServerModal, animated: true, completion: nil) - userDefaults[.hasSeenFileServerInstabilityNotification] = true - } } deinit { diff --git a/Session/Sheets & Modals/FileServerModal.swift b/Session/Sheets & Modals/FileServerModal.swift deleted file mode 100644 index d4a036fb3..000000000 --- a/Session/Sheets & Modals/FileServerModal.swift +++ /dev/null @@ -1,44 +0,0 @@ - -final class FileServerModal : Modal { - - override func populateContentView() { - // Title - let titleLabel = UILabel() - titleLabel.textColor = Colors.text - titleLabel.font = .boldSystemFont(ofSize: Values.largeFontSize) - titleLabel.text = "Session" - titleLabel.textAlignment = .center - // Message - let messageLabel = UILabel() - messageLabel.textColor = Colors.text - messageLabel.font = .systemFont(ofSize: Values.smallFontSize) - let message = "We're upgrading the way files are stored. File transfer may be unstable for the next 24-48 hours." - messageLabel.text = message - messageLabel.numberOfLines = 0 - messageLabel.lineBreakMode = .byWordWrapping - messageLabel.textAlignment = .center - // OK button - let okButton = UIButton() - okButton.set(.height, to: Values.mediumButtonHeight) - okButton.layer.cornerRadius = Modal.buttonCornerRadius - okButton.backgroundColor = Colors.buttonBackground - okButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) - okButton.setTitleColor(Colors.text, for: UIControl.State.normal) - okButton.setTitle("OK", for: UIControl.State.normal) - okButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) - // Button stack view - let buttonStackView = UIStackView(arrangedSubviews: [ okButton ]) - buttonStackView.axis = .horizontal - buttonStackView.spacing = Values.mediumSpacing - buttonStackView.distribution = .fillEqually - // Main stack view - let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, messageLabel, buttonStackView ]) - mainStackView.axis = .vertical - mainStackView.spacing = Values.largeSpacing - contentView.addSubview(mainStackView) - mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing) - mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) - contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing) - contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing) - } -} diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index 982fabe4f..aa563e94f 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -8,7 +8,6 @@ public enum SNUserDefaults { case hasSeenLinkPreviewSuggestion case isUsingFullAPNs case isMigratingToV2KeyPair - case hasSeenFileServerInstabilityNotification } public enum Date : Swift.String { From c84a94247892a416c42bfe708118565bf057f4d1 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 20 May 2021 13:57:42 +1000 Subject: [PATCH 54/57] Fix push notification handling This was crashing every time the extension started --- SessionMessagingKit/Database/OWSPrimaryStorage.m | 3 --- 1 file changed, 3 deletions(-) diff --git a/SessionMessagingKit/Database/OWSPrimaryStorage.m b/SessionMessagingKit/Database/OWSPrimaryStorage.m index d35635cae..ae897bf48 100644 --- a/SessionMessagingKit/Database/OWSPrimaryStorage.m +++ b/SessionMessagingKit/Database/OWSPrimaryStorage.m @@ -69,9 +69,6 @@ void VerifyRegistrationsForPrimaryStorage(OWSStorage *storage) _dbReadPool = [[YapDatabaseConnectionPool alloc] initWithDatabase:self.database]; _dbReadWriteConnection = [self newDatabaseConnection]; _uiDatabaseConnection = [self newDatabaseConnection]; - - // Vacuum the database - [self.dbReadWriteConnection vacuum]; // Increase object cache limit. Default is 250. _uiDatabaseConnection.objectCacheLimit = 500; From fe3493ff127431a2e77418c7571754e4d0aa58f4 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 20 May 2021 13:58:31 +1000 Subject: [PATCH 55/57] Update version number --- Session.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 25ade3331..878709073 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5138,7 +5138,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 249; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5159,7 +5159,7 @@ INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.1; + MARKETING_VERSION = 1.11.2; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5207,7 +5207,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 249; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5233,7 +5233,7 @@ INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.1; + MARKETING_VERSION = 1.11.2; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5268,7 +5268,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 249; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5287,7 +5287,7 @@ INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.1; + MARKETING_VERSION = 1.11.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -5338,7 +5338,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 249; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5362,7 +5362,7 @@ INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.11.1; + MARKETING_VERSION = 1.11.2; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6223,7 +6223,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 249; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6259,7 +6259,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.1; + MARKETING_VERSION = 1.11.2; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -6291,7 +6291,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 249; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6327,7 +6327,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.1; + MARKETING_VERSION = 1.11.2; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; From 1e246389be95d7fd827f77e3a1a0e945f57fcbf9 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 20 May 2021 14:29:28 +1000 Subject: [PATCH 56/57] Clean --- Session.xcodeproj/project.pbxproj | 4 -- Session/Home/HomeVC.swift | 8 ---- Session/Sheets & Modals/FileServerModal.swift | 44 ------------------- .../General/SNUserDefaults.swift | 1 - 4 files changed, 57 deletions(-) delete mode 100644 Session/Sheets & Modals/FileServerModal.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index abf1f1f27..878709073 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -773,7 +773,6 @@ C3DB66CC260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66CB260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift */; }; C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; - C3ECBBDB2654D4640081996B /* FileServerModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECBBDA2654D4640081996B /* FileServerModal.swift */; }; C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECBF7A257056B700EA7FCE /* Threading.swift */; }; C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */; }; D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; @@ -1763,7 +1762,6 @@ C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = ""; }; C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClosedGroupVC.swift; sourceTree = ""; }; C3E7134E251C867C009649BB /* Sodium+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Conversion.swift"; sourceTree = ""; }; - C3ECBBDA2654D4640081996B /* FileServerModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServerModal.swift; sourceTree = ""; }; C3ECBF7A257056B700EA7FCE /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = ""; }; C3F0A5B2255C915C007BE2A3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -2874,7 +2872,6 @@ B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */, C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */, - C3ECBBDA2654D4640081996B /* FileServerModal.swift */, ); path = "Sheets & Modals"; sourceTree = ""; @@ -4891,7 +4888,6 @@ 34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */, 450DF2051E0D74AC003D14BE /* Platform.swift in Sources */, 4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */, - C3ECBBDB2654D4640081996B /* FileServerModal.swift in Sources */, B82149B825D60393009C0F2A /* BlockedModal.swift in Sources */, B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */, 346129991FD1E4DA00532771 /* SignalApp.m in Sources */, diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 688aed4b7..9fde3b929 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -164,14 +164,6 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) reload() - let userDefaults = UserDefaults.standard - if !userDefaults[.hasSeenFileServerInstabilityNotification] { - let fileServerModal = FileServerModal() - fileServerModal.modalPresentationStyle = .overFullScreen - fileServerModal.modalTransitionStyle = .crossDissolve - present(fileServerModal, animated: true, completion: nil) - userDefaults[.hasSeenFileServerInstabilityNotification] = true - } } deinit { diff --git a/Session/Sheets & Modals/FileServerModal.swift b/Session/Sheets & Modals/FileServerModal.swift deleted file mode 100644 index d4a036fb3..000000000 --- a/Session/Sheets & Modals/FileServerModal.swift +++ /dev/null @@ -1,44 +0,0 @@ - -final class FileServerModal : Modal { - - override func populateContentView() { - // Title - let titleLabel = UILabel() - titleLabel.textColor = Colors.text - titleLabel.font = .boldSystemFont(ofSize: Values.largeFontSize) - titleLabel.text = "Session" - titleLabel.textAlignment = .center - // Message - let messageLabel = UILabel() - messageLabel.textColor = Colors.text - messageLabel.font = .systemFont(ofSize: Values.smallFontSize) - let message = "We're upgrading the way files are stored. File transfer may be unstable for the next 24-48 hours." - messageLabel.text = message - messageLabel.numberOfLines = 0 - messageLabel.lineBreakMode = .byWordWrapping - messageLabel.textAlignment = .center - // OK button - let okButton = UIButton() - okButton.set(.height, to: Values.mediumButtonHeight) - okButton.layer.cornerRadius = Modal.buttonCornerRadius - okButton.backgroundColor = Colors.buttonBackground - okButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) - okButton.setTitleColor(Colors.text, for: UIControl.State.normal) - okButton.setTitle("OK", for: UIControl.State.normal) - okButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) - // Button stack view - let buttonStackView = UIStackView(arrangedSubviews: [ okButton ]) - buttonStackView.axis = .horizontal - buttonStackView.spacing = Values.mediumSpacing - buttonStackView.distribution = .fillEqually - // Main stack view - let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, messageLabel, buttonStackView ]) - mainStackView.axis = .vertical - mainStackView.spacing = Values.largeSpacing - contentView.addSubview(mainStackView) - mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing) - mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) - contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing) - contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing) - } -} diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index 982fabe4f..aa563e94f 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -8,7 +8,6 @@ public enum SNUserDefaults { case hasSeenLinkPreviewSuggestion case isUsingFullAPNs case isMigratingToV2KeyPair - case hasSeenFileServerInstabilityNotification } public enum Date : Swift.String { From 61dd768e5cd519bd2452996448553306c43ab88f Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 20 May 2021 16:22:11 +1000 Subject: [PATCH 57/57] Minor refactoring --- SessionMessagingKit/Messages/Signal/TSIncomingMessage.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m index 908d53f42..cdf88f736 100644 --- a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m @@ -144,14 +144,14 @@ NS_ASSUME_NONNULL_BEGIN return; } - BOOL isAllAttachmentDownloaded = YES; + BOOL areAllAttachmentsDownloaded = YES; for (NSString *attachmentId in self.attachmentIds) { TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction]; - isAllAttachmentDownloaded = isAllAttachmentDownloaded && attachment.isDownloaded; - if (!isAllAttachmentDownloaded) break; + areAllAttachmentsDownloaded = areAllAttachmentsDownloaded && attachment.isDownloaded; + if (!areAllAttachmentsDownloaded) break; } - if (!isAllAttachmentDownloaded) { + if (!areAllAttachmentsDownloaded) { return; }