diff --git a/README.md b/README.md index 2d6092a32..40038b0d0 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,6 @@ Copyright 2011 Whisper Systems Copyright 2013-2017 Open Whisper Systems -Copyright 2019-2021 The Loki Project +Copyright 2019-2021 The Oxen Project Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index f1fc3371c..46092ed28 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5088,7 +5088,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 288; + CURRENT_PROJECT_VERSION = 294; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5113,7 +5113,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.13; + MARKETING_VERSION = 1.11.15; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5161,7 +5161,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 288; + CURRENT_PROJECT_VERSION = 294; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5191,7 +5191,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.13; + MARKETING_VERSION = 1.11.15; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5227,7 +5227,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 288; + CURRENT_PROJECT_VERSION = 294; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5250,7 +5250,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.13; + MARKETING_VERSION = 1.11.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -5301,7 +5301,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 288; + CURRENT_PROJECT_VERSION = 294; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5329,7 +5329,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.13; + MARKETING_VERSION = 1.11.15; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6237,7 +6237,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 288; + CURRENT_PROJECT_VERSION = 294; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6277,7 +6277,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.13; + MARKETING_VERSION = 1.11.15; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -6309,7 +6309,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 288; + CURRENT_PROJECT_VERSION = 294; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6349,7 +6349,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.13; + MARKETING_VERSION = 1.11.15; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index c8f59bb0d..75c6df4c2 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -367,6 +367,13 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc snInputView.hideMentionsUI() self.oldText = newText } + + func showInputAccessoryView() { + UIView.animate(withDuration: 0.25, animations: { + self.inputAccessoryView?.isHidden = false + self.inputAccessoryView?.alpha = 1 + }) + } // MARK: View Item Interaction func handleViewItemLongPressed(_ viewItem: ConversationViewItem) { @@ -568,19 +575,12 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc return } - func showInputAccessoryView() { - UIView.animate(withDuration: 0.25, animations: { - self.inputAccessoryView?.isHidden = false - self.inputAccessoryView?.alpha = 1 - }) - } - if viewItem.interaction.interactionType() == .outgoingMessage, let message = viewItem.interaction as? TSMessage, message.serverHash != nil { let alertVC = UIAlertController.init(title: nil, message: nil, preferredStyle: .actionSheet) let deleteLocallyAction = UIAlertAction.init(title: NSLocalizedString("delete_message_for_me", comment: ""), style: .destructive) { _ in self.deleteLocally(viewItem) - showInputAccessoryView() + self.showInputAccessoryView() } alertVC.addAction(deleteLocallyAction) @@ -590,12 +590,12 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc } let deleteRemotelyAction = UIAlertAction.init(title: title, style: .destructive) { _ in self.deleteForEveryone(viewItem) - showInputAccessoryView() + self.showInputAccessoryView() } alertVC.addAction(deleteRemotelyAction) let cancelAction = UIAlertAction.init(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel) {_ in - showInputAccessoryView() + self.showInputAccessoryView() } alertVC.addAction(cancelAction) @@ -682,10 +682,24 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc func openURL(_ url: URL) { // URLs can be unsafe, so always ask the user whether they want to open one - let urlModal = URLModal(url: url) - urlModal.modalPresentationStyle = .overFullScreen - urlModal.modalTransitionStyle = .crossDissolve - present(urlModal, animated: true, completion: nil) + let title = NSLocalizedString("modal_open_url_title", comment: "") + let message = String(format: NSLocalizedString("modal_open_url_explanation", comment: ""), url.absoluteString) + let alertVC = UIAlertController.init(title: title, message: message, preferredStyle: .actionSheet) + let openAction = UIAlertAction.init(title: NSLocalizedString("modal_open_url_button_title", comment: ""), style: .default) { _ in + UIApplication.shared.open(url, options: [:], completionHandler: nil) + self.showInputAccessoryView() + } + alertVC.addAction(openAction) + let copyAction = UIAlertAction.init(title: NSLocalizedString("modal_copy_url_button_title", comment: ""), style: .default) { _ in + UIPasteboard.general.string = url.absoluteString + self.showInputAccessoryView() + } + alertVC.addAction(copyAction) + let cancelAction = UIAlertAction.init(title: NSLocalizedString("cancel", comment: ""), style: .cancel) {_ in + self.showInputAccessoryView() + } + alertVC.addAction(cancelAction) + self.presentAlert(alertVC) } func joinOpenGroup(name: String, url: String) { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index ae4ccfdf5..08a895707 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -258,10 +258,8 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) let text = snInputView.text - if !text.isEmpty { - Storage.write { transaction in - self.thread.setDraft(text, transaction: transaction) - } + Storage.write { transaction in + self.thread.setDraft(text, transaction: transaction) } inputAccessoryView?.resignFirstResponder() } diff --git a/Session/Conversations/ConversationViewModel.m b/Session/Conversations/ConversationViewModel.m index efcd2cee8..2d63d03b0 100644 --- a/Session/Conversations/ConversationViewModel.m +++ b/Session/Conversations/ConversationViewModel.m @@ -1158,7 +1158,7 @@ NS_ASSUME_NONNULL_BEGIN break; } - uint64_t viewItemTimestamp = viewItem.interaction.timestampForUI; + uint64_t viewItemTimestamp = viewItem.interaction.timestamp; OWSAssertDebug(viewItemTimestamp > 0); BOOL shouldShowDate = NO; @@ -1225,7 +1225,7 @@ NS_ASSUME_NONNULL_BEGIN NSAttributedString *_Nullable senderName = nil; OWSInteractionType interactionType = viewItem.interaction.interactionType; - NSString *timestampText = [DateUtil formatTimestampShort:viewItem.interaction.timestampForUI]; + NSString *timestampText = [DateUtil formatTimestampShort:viewItem.interaction.timestamp]; if (interactionType == OWSInteractionType_OutgoingMessage) { TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction; diff --git a/Session/Conversations/Input View/ExpandingAttachmentsButton.swift b/Session/Conversations/Input View/ExpandingAttachmentsButton.swift index a42f4488e..8652d57c6 100644 --- a/Session/Conversations/Input View/ExpandingAttachmentsButton.swift +++ b/Session/Conversations/Input View/ExpandingAttachmentsButton.swift @@ -10,15 +10,35 @@ final class ExpandingAttachmentsButton : UIView, InputViewButtonDelegate { private lazy var cameraButtonContainerBottomConstraint = cameraButtonContainer.pin(.bottom, to: .bottom, of: self) // MARK: UI Components - lazy var gifButton = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_gif_black"), delegate: self, hasOpaqueBackground: true) + lazy var gifButton: InputViewButton = { + let result = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_gif_black"), delegate: self, hasOpaqueBackground: true) + result.accessibilityLabel = NSLocalizedString("accessibility_gif_button", comment: "") + return result + }() lazy var gifButtonContainer = container(for: gifButton) - lazy var documentButton = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_document_black"), delegate: self, hasOpaqueBackground: true) + lazy var documentButton: InputViewButton = { + let result = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_document_black"), delegate: self, hasOpaqueBackground: true) + result.accessibilityLabel = NSLocalizedString("accessibility_document_button", comment: "") + return result + }() lazy var documentButtonContainer = container(for: documentButton) - lazy var libraryButton = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_camera_roll_black"), delegate: self, hasOpaqueBackground: true) + lazy var libraryButton: InputViewButton = { + let result = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_camera_roll_black"), delegate: self, hasOpaqueBackground: true) + result.accessibilityLabel = NSLocalizedString("accessibility_library_button", comment: "") + return result + }() lazy var libraryButtonContainer = container(for: libraryButton) - lazy var cameraButton = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_camera_black"), delegate: self, hasOpaqueBackground: true) + lazy var cameraButton: InputViewButton = { + let result = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_camera_black"), delegate: self, hasOpaqueBackground: true) + result.accessibilityLabel = NSLocalizedString("accessibility_camera_button", comment: "") + return result + }() lazy var cameraButtonContainer = container(for: cameraButton) - lazy var mainButton = InputViewButton(icon: #imageLiteral(resourceName: "ic_plus_24"), delegate: self) + lazy var mainButton: InputViewButton = { + let result = InputViewButton(icon: #imageLiteral(resourceName: "ic_plus_24"), delegate: self) + result.accessibilityLabel = NSLocalizedString("accessibility_expanding_attachments_button", comment: "") + return result + }() lazy var mainButtonContainer = container(for: mainButton) // MARK: Lifecycle @@ -66,6 +86,7 @@ final class ExpandingAttachmentsButton : UIView, InputViewButtonDelegate { // MARK: Animation private func expandOrCollapse() { if isExpanded { + mainButton.accessibilityLabel = NSLocalizedString("accessibility_main_button_collapse", comment: "") let expandedButtonSize = InputViewButton.expandedSize let spacing: CGFloat = 4 cameraButtonContainerBottomConstraint.constant = -1 * (expandedButtonSize + spacing) @@ -79,6 +100,7 @@ final class ExpandingAttachmentsButton : UIView, InputViewButtonDelegate { self.layoutIfNeeded() } } else { + mainButton.accessibilityLabel = NSLocalizedString("accessibility_expanding_attachments_button", comment: "") [ gifButtonContainerBottomConstraint, documentButtonContainerBottomConstraint, libraryButtonContainerBottomConstraint, cameraButtonContainerBottomConstraint ].forEach { $0.constant = 0 } diff --git a/Session/Conversations/Input View/InputTextView.swift b/Session/Conversations/Input View/InputTextView.swift index 1c8fb3595..57546e407 100644 --- a/Session/Conversations/Input View/InputTextView.swift +++ b/Session/Conversations/Input View/InputTextView.swift @@ -26,6 +26,8 @@ public final class InputTextView : UITextView, UITextViewDelegate { super.init(frame: CGRect.zero, textContainer: nil) setUpViewHierarchy() self.delegate = self + self.isAccessibilityElement = true + self.accessibilityLabel = NSLocalizedString("vc_conversation_input_prompt", comment: "") } public override init(frame: CGRect, textContainer: NSTextContainer?) { diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index af84dec92..599af7599 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -22,11 +22,17 @@ final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate, // MARK: UI Components private lazy var attachmentsButton = ExpandingAttachmentsButton(delegate: delegate) - private lazy var voiceMessageButton = InputViewButton(icon: #imageLiteral(resourceName: "Microphone"), delegate: self) + private lazy var voiceMessageButton: InputViewButton = { + let result = InputViewButton(icon: #imageLiteral(resourceName: "Microphone"), delegate: self) + result.accessibilityLabel = NSLocalizedString("VOICE_MESSAGE_TOO_SHORT_ALERT_TITLE", comment: "") + result.accessibilityHint = NSLocalizedString("VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE", comment: "") + return result + }() private lazy var sendButton: InputViewButton = { let result = InputViewButton(icon: #imageLiteral(resourceName: "ArrowUp"), isSendButton: true, delegate: self) result.isHidden = true + result.accessibilityLabel = NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON", comment: "") return result }() private lazy var voiceMessageButtonContainer = container(for: voiceMessageButton) diff --git a/Session/Conversations/Input View/InputViewButton.swift b/Session/Conversations/Input View/InputViewButton.swift index 92707c7fc..082442faa 100644 --- a/Session/Conversations/Input View/InputViewButton.swift +++ b/Session/Conversations/Input View/InputViewButton.swift @@ -25,6 +25,7 @@ final class InputViewButton : UIView { self.hasOpaqueBackground = hasOpaqueBackground super.init(frame: CGRect.zero) setUpViewHierarchy() + self.isAccessibilityElement = true } override init(frame: CGRect) { diff --git a/Session/Media Viewing & Editing/PhotoLibrary.swift b/Session/Media Viewing & Editing/PhotoLibrary.swift index b5e011da2..164c8b542 100644 --- a/Session/Media Viewing & Editing/PhotoLibrary.swift +++ b/Session/Media Viewing & Editing/PhotoLibrary.swift @@ -221,6 +221,10 @@ class PhotoCollectionContents { class PhotoCollection { private let collection: PHAssetCollection + + // The user never sees this collection, but we use it for a null object pattern + // when the user has denied photos access. + static let empty = PhotoCollection(collection: PHAssetCollection()) init(collection: PHAssetCollection) { self.collection = collection @@ -275,11 +279,30 @@ class PhotoLibrary: NSObject, PHPhotoLibraryChangeObserver { deinit { PHPhotoLibrary.shared().unregisterChangeObserver(self) } + + private lazy var fetchOptions: PHFetchOptions = { + let fetchOptions = PHFetchOptions() + fetchOptions.sortDescriptors = [NSSortDescriptor(key: "endDate", ascending: true)] + return fetchOptions + }() func defaultPhotoCollection() -> PhotoCollection { - guard let photoCollection = allPhotoCollections().first else { - owsFail("Could not locate Camera Roll.") + var fetchedCollection: PhotoCollection? + PHAssetCollection.fetchAssetCollections( + with: .smartAlbum, + subtype: .smartAlbumUserLibrary, + options: fetchOptions + ).enumerateObjects { collection, _, stop in + fetchedCollection = PhotoCollection(collection: collection) + stop.pointee = true } + + guard let photoCollection = fetchedCollection else { + Logger.info("Using empty photo collection.") + assert(PHPhotoLibrary.authorizationStatus() == .denied) + return PhotoCollection.empty + } + return photoCollection } diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 5ca82d5a0..e63e229d4 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "URL öffnen?"; "modal_open_url_explanation" = "Möchten Sie %@ wirklich öffnen?"; "modal_open_url_button_title" = "Öffnen"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "%@ entsperren?"; "modal_blocked_explanation" = "Sind Sie sicher, dass Sie %@ entsperren möchten?"; "modal_blocked_button_title" = "Entsperren"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 430e5c044..77a6a7650 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "Open URL?"; "modal_open_url_explanation" = "Are you sure you want to open %@?"; "modal_open_url_button_title" = "Open"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Unblock %@?"; "modal_blocked_explanation" = "Are you sure you want to unblock %@?"; "modal_blocked_button_title" = "Unblock"; @@ -557,4 +558,11 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 4f3111a6b..4a291409e 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "¿Abrir URL?"; "modal_open_url_explanation" = "¿Estás seguro de que quieres abrir %@?"; "modal_open_url_button_title" = "Abrir"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "¿Desbloquear a %@?"; "modal_blocked_explanation" = "¿Estás seguro de que quieres desbloquear a %@?"; "modal_blocked_button_title" = "Desbloquear"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 1021d569f..8980aea86 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "URL باز شود؟"; "modal_open_url_explanation" = "آیا مطمئن هستید که میخواهید %@ را باز کنید؟"; "modal_open_url_button_title" = "باز کن"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Unblock %@?"; "modal_blocked_explanation" = "Are you sure you want to unblock %@?"; "modal_blocked_button_title" = "رفع مسدودی"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index ef67453b8..b0e6e5e63 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "Avataanko URL?"; "modal_open_url_explanation" = "Oletko varma, että haluat avata linkin %@?"; "modal_open_url_button_title" = "Avaa"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Poista esto henkilöltä %@?"; "modal_blocked_explanation" = "Oletko varma, että haluat poistaa eston henkilöltä %@?"; "modal_blocked_button_title" = "Poista esto"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index f97d407c7..f060c6e28 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -531,6 +531,7 @@ "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_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Débloquer %@ ?"; "modal_blocked_explanation" = "Confirmez-vous le déblocage de %@ ?"; "modal_blocked_button_title" = "Débloquer"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 204731d63..db6d2cfab 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "यूआरएल खोलें"; "modal_open_url_explanation" = "क्या आप वाकई %@ खोलना चाहते हैं?"; "modal_open_url_button_title" = "खोलें"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "%@ अनब्लॉक करें?"; "modal_blocked_explanation" = "क्या आप वाकई %@ को अनब्लॉक करना चाहते हैं?"; "modal_blocked_button_title" = "अनब्लॉक करें"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 860a546a3..437d90817 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "Otvori poveznicu?"; "modal_open_url_explanation" = "Jeste li sigurni da želite otvoriti %@?"; "modal_open_url_button_title" = "Otvori"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Deblokiraj %@?"; "modal_blocked_explanation" = "Jeste li sigurni da želite deblokirati %@?"; "modal_blocked_button_title" = "Deblokiraj"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index af4f39234..b09799f22 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "Open URL?"; "modal_open_url_explanation" = "Are you sure you want to open %@?"; "modal_open_url_button_title" = "Open"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Unblock %@?"; "modal_blocked_explanation" = "Are you sure you want to unblock %@?"; "modal_blocked_button_title" = "Unblock"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index f8018c99c..fe374ac9c 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "Aprire URL?"; "modal_open_url_explanation" = "Sei sicuro di voler aprire %@?"; "modal_open_url_button_title" = "Apri"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Sbloccare %@?"; "modal_blocked_explanation" = "Sei sicuro di voler sbloccare %@?"; "modal_blocked_button_title" = "Sblocca"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index b43619cd7..ffade96b2 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "URLを開きますか?"; "modal_open_url_explanation" = "%@を本当に開いてもよろしいですか?"; "modal_open_url_button_title" = "開く"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "%@のブロックを解除しますか?"; "modal_blocked_explanation" = "%@のブロックを解除してもよろしいですか?"; "modal_blocked_button_title" = "ブロックを解除する"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index df6a8d106..ef1eb532e 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "URL openen?"; "modal_open_url_explanation" = "Weet u zeker dat u %@ wilt openen?"; "modal_open_url_button_title" = "Openen"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Blokkering opheffen voor %@?"; "modal_blocked_explanation" = "Weet je zeker dat je %@ weer wilt toelaten?"; "modal_blocked_button_title" = "Deblokkeren"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 204ba918b..2c4c8d9e6 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "Otworzyć URL?"; "modal_open_url_explanation" = "Czy na pewno chcesz otworzyć %@?"; "modal_open_url_button_title" = "Otwórz"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Odblokować %@?"; "modal_blocked_explanation" = "Czy na pewno chcesz odblokować %@?"; "modal_blocked_button_title" = "Odblokuj"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 583e81ff9..07d303ece 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "Abrir URL?"; "modal_open_url_explanation" = "Você tem certeza que deseja abrir %@/?"; "modal_open_url_button_title" = "Abrir"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Desbloquear %@?"; "modal_blocked_explanation" = "Você tem certeza que deseja desbloquear %@?"; "modal_blocked_button_title" = "Desbloquear"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index b851e2a59..b51f0d475 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "Открыть ссылку?"; "modal_open_url_explanation" = "Вы уверены, что хотите открыть %@?"; "modal_open_url_button_title" = "Открыть"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Разблокировать %@?"; "modal_blocked_explanation" = "Вы уверены, что хотите разблокировать %@?"; "modal_blocked_button_title" = "Разблокировать"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 129b79ad4..8e905f1e9 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "Otvoriť URL?"; "modal_open_url_explanation" = "Ste si istý, že chcete otvoriť %@?"; "modal_open_url_button_title" = "Otvoriť"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Odblokovať %@?"; "modal_blocked_explanation" = "Ste si istý/á, že chcete odblokovať %@?"; "modal_blocked_button_title" = "Odblokovať"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index dd4b04329..9035c2970 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "Öppna URL?"; "modal_open_url_explanation" = "Är du säker på att du vill öppna %@?"; "modal_open_url_button_title" = "Öppna"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Avblockera %@?"; "modal_blocked_explanation" = "Är du säker på att du vill avblockera %@?"; "modal_blocked_button_title" = "Avblockera"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 30f4a0ae2..d6b2137f6 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "เปิดลิงค์ URL ไหม"; "modal_open_url_explanation" = "แน่ใจไหมว่าคุณต้องกาเปิดดู %@"; "modal_open_url_button_title" = "เปิด"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "เลิกบล็อก %@ ไหม"; "modal_blocked_explanation" = "แน่ใจไหมว่าคุณต้องการเลิกบล็อก %@"; "modal_blocked_button_title" = "เลิกบล็อก"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 2ee0add3b..af2ab31c3 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "Open URL?"; "modal_open_url_explanation" = "Are you sure you want to open %@?"; "modal_open_url_button_title" = "Open"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "Unblock %@?"; "modal_blocked_explanation" = "Are you sure you want to unblock %@?"; "modal_blocked_button_title" = "Unblock"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 117376ddb..0e8cb4773 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "打開連結?"; "modal_open_url_explanation" = "您確定要打開 %@ 嗎?"; "modal_open_url_button_title" = "打開"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "解除封鎖 %@ 嗎?"; "modal_blocked_explanation" = "您確定要解除封鎖 %@ 嗎?"; "modal_blocked_button_title" = "解除封鎖"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index aab0afae7..a7a52671f 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -531,6 +531,7 @@ "modal_open_url_title" = "打开链接?"; "modal_open_url_explanation" = "确定要打开 %@ 吗?"; "modal_open_url_button_title" = "打开"; +"modal_copy_url_button_title" = "Copy Link"; "modal_blocked_title" = "从黑名单中移除 %@ 吗?"; "modal_blocked_explanation" = "确定解除屏蔽%@吗?"; "modal_blocked_button_title" = "解除屏蔽"; @@ -557,3 +558,9 @@ "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; "context_menu_ban_and_delete_all" = "Ban and Delete All"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Camera"; +"accessibility_main_button_collapse" = "Collapse attachment options"; diff --git a/Session/Onboarding/LinkDeviceVC.swift b/Session/Onboarding/LinkDeviceVC.swift index f88f00659..e346bc325 100644 --- a/Session/Onboarding/LinkDeviceVC.swift +++ b/Session/Onboarding/LinkDeviceVC.swift @@ -122,6 +122,14 @@ final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewCon } func continueWithSeed(_ seed: Data) { + if (seed.count != 16) { + let alert = UIAlertController(title: NSLocalizedString("invalid_recovery_phrase", comment: ""), message: NSLocalizedString("Please check the Recovery Phrase and try again.", comment: ""), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: { _ in + self.scanQRCodeWrapperVC.startCapture() + })) + presentAlert(alert) + return + } let (ed25519KeyPair, x25519KeyPair) = KeyPairUtilities.generate(from: seed) Onboarding.Flow.link.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) TSAccountManager.sharedInstance().didRegister() diff --git a/Session/Shared/ConversationCell.swift b/Session/Shared/ConversationCell.swift index 3b1d8ca6a..32ae0d4bd 100644 --- a/Session/Shared/ConversationCell.swift +++ b/Session/Shared/ConversationCell.swift @@ -1,4 +1,5 @@ import UIKit +import SessionUIKit final class ConversationCell : UITableViewCell { var threadViewModel: ThreadViewModel! { didSet { update() } } @@ -37,6 +38,26 @@ final class ConversationCell : UITableViewCell { return result }() + private lazy var hasMentionView: UIView = { + let result = UIView() + result.backgroundColor = Colors.accent + let size = ConversationCell.unreadCountViewSize + result.set(.width, to: size) + result.set(.height, to: size) + result.layer.masksToBounds = true + result.layer.cornerRadius = size / 2 + return result + }() + + private lazy var hasMentionLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.verySmallFontSize) + result.textColor = Colors.text + result.text = "@" + result.textAlignment = .center + return result + }() + private lazy var timestampLabel: UILabel = { let result = UILabel() result.font = .systemFont(ofSize: Values.smallFontSize) @@ -98,9 +119,12 @@ final class ConversationCell : UITableViewCell { // Unread count view unreadCountView.addSubview(unreadCountLabel) unreadCountLabel.pin(to: unreadCountView) + // Has mention view + hasMentionView.addSubview(hasMentionLabel) + hasMentionLabel.pin(to: hasMentionView) // Label stack view let topLabelSpacer = UIView.hStretchingSpacer() - let topLabelStackView = UIStackView(arrangedSubviews: [ displayNameLabel, unreadCountView, topLabelSpacer, timestampLabel ]) + let topLabelStackView = UIStackView(arrangedSubviews: [ displayNameLabel, unreadCountView, hasMentionView, topLabelSpacer, timestampLabel ]) topLabelStackView.axis = .horizontal topLabelStackView.alignment = .center topLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer @@ -176,6 +200,7 @@ final class ConversationCell : UITableViewCell { unreadCountLabel.text = unreadCount < 100 ? "\(unreadCount)" : "99+" let fontSize = (unreadCount < 100) ? Values.verySmallFontSize : 8 unreadCountLabel.font = .boldSystemFont(ofSize: fontSize) + hasMentionView.isHidden = !threadViewModel.hasUnreadMentions profilePictureView.update(for: thread) displayNameLabel.text = getDisplayName() timestampLabel.text = DateUtil.formatDate(forDisplay: threadViewModel.lastMessageDate) diff --git a/Session/Shared/ScanQRCodeWrapperVC.swift b/Session/Shared/ScanQRCodeWrapperVC.swift index 57b0eefd8..e3a8c07e2 100644 --- a/Session/Shared/ScanQRCodeWrapperVC.swift +++ b/Session/Shared/ScanQRCodeWrapperVC.swift @@ -71,4 +71,10 @@ final class ScanQRCodeWrapperVC : BaseVC { @objc private func close() { presentingViewController?.dismiss(animated: true, completion: nil) } + + public func startCapture() { + DispatchQueue.main.async { [weak self] in + self?.scanQRCodeVC.startCapture() + } + } } diff --git a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift index f7a281b20..164f422a9 100644 --- a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift @@ -162,4 +162,3 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject delegate?.handleJobFailed(self, with: error) } } - diff --git a/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift b/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift index 81e60ab3d..6849fd50c 100644 --- a/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift +++ b/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift @@ -19,7 +19,6 @@ public extension TSIncomingMessage { expiresInSeconds: !isOpenGroupMessage ? expiration : 0, // Ensure we don't ever expire open group messages quotedMessage: quotedMessage, linkPreview: linkPreview, - serverTimestamp: visibleMessage.openGroupServerTimestamp as NSNumber?, wasReceivedByUD: true, openGroupInvitationName: visibleMessage.openGroupInvitation?.name, openGroupInvitationURL: visibleMessage.openGroupInvitation?.url, diff --git a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.h b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.h index 59c11e6fe..b4a0edb0e 100644 --- a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.h +++ b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.h @@ -12,8 +12,6 @@ NS_ASSUME_NONNULL_BEGIN @interface TSIncomingMessage : TSMessage -@property (nonatomic, readonly, nullable) NSNumber *serverTimestamp; - @property (nonatomic, readonly) BOOL wasReceivedByUD; @property (nonatomic, readonly) BOOL isUserMentioned; @@ -61,7 +59,6 @@ NS_ASSUME_NONNULL_BEGIN expiresInSeconds:(uint32_t)expiresInSeconds quotedMessage:(nullable TSQuotedMessage *)quotedMessage linkPreview:(nullable OWSLinkPreview *)linkPreview - serverTimestamp:(nullable NSNumber *)serverTimestamp wasReceivedByUD:(BOOL)wasReceivedByUD openGroupInvitationName:(nullable NSString *)openGroupInvitationName openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL diff --git a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m index 90d893b9f..ecfc9168f 100644 --- a/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSIncomingMessage.m @@ -21,8 +21,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, getter=wasRead) BOOL read; -@property (nonatomic, nullable) NSNumber *serverTimestamp; - @end #pragma mark - @@ -52,7 +50,6 @@ NS_ASSUME_NONNULL_BEGIN expiresInSeconds:(uint32_t)expiresInSeconds quotedMessage:(nullable TSQuotedMessage *)quotedMessage linkPreview:(nullable OWSLinkPreview *)linkPreview - serverTimestamp:(nullable NSNumber *)serverTimestamp wasReceivedByUD:(BOOL)wasReceivedByUD openGroupInvitationName:(nullable NSString *)openGroupInvitationName openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL @@ -77,7 +74,6 @@ NS_ASSUME_NONNULL_BEGIN _authorId = authorId; _sourceDeviceId = sourceDeviceId; _read = NO; - _serverTimestamp = serverTimestamp; _wasReceivedByUD = wasReceivedByUD; _notificationIdentifier = nil; diff --git a/SessionMessagingKit/Messages/Signal/TSInteraction.h b/SessionMessagingKit/Messages/Signal/TSInteraction.h index 4af4ce1cf..e6b77faf3 100644 --- a/SessionMessagingKit/Messages/Signal/TSInteraction.h +++ b/SessionMessagingKit/Messages/Signal/TSInteraction.h @@ -38,9 +38,6 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value); @property (nonatomic, readonly) uint64_t timestamp; @property (nonatomic, readonly) uint64_t sortId; @property (nonatomic, readonly) uint64_t receivedAtTimestamp; -@property (nonatomic, readonly) BOOL shouldUseServerTime; - -- (uint64_t)timestampForUI; - (NSDate *)dateForUI; @@ -80,6 +77,8 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value); - (void)saveNextSortIdWithTransaction:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(saveNextSortId(transaction:)); +- (void)updateTimestamp:(uint64_t)timestamp; + @end NS_ASSUME_NONNULL_END diff --git a/SessionMessagingKit/Messages/Signal/TSInteraction.m b/SessionMessagingKit/Messages/Signal/TSInteraction.m index e6d1a6a93..f3522712d 100644 --- a/SessionMessagingKit/Messages/Signal/TSInteraction.m +++ b/SessionMessagingKit/Messages/Signal/TSInteraction.m @@ -165,17 +165,6 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value) #pragma mark Date operations -- (uint64_t)timestampForUI -{ - // We always want to show the sent timestamp. In the case of one-on-one, closed group and V2 open group messages we get - // this from the protobuf. In the case of V1 open group messages we get it from the envelope in which the message is - // wrapped, which gets parsed to `serverTimestamp` in that case. - if ([self isKindOfClass:TSIncomingMessage.class] && ((TSIncomingMessage *)self).isOpenGroupMessage && ((TSIncomingMessage *)self).serverTimestamp != nil) { - return ((TSIncomingMessage *)self).serverTimestamp.unsignedLongLongValue; - } - return _timestamp; -} - - (uint64_t)timestampForLegacySorting { return self.timestamp; @@ -183,7 +172,7 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value) - (NSDate *)dateForUI { - return [NSDate ows_dateWithMillisecondsSince1970:self.timestampForUI]; + return [NSDate ows_dateWithMillisecondsSince1970:self.timestamp]; } - (NSDate *)receivedAtDate @@ -229,12 +218,6 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value) - (uint64_t)sortId { - // We always want to sort on the sent timestamp. In the case of one-on-one, closed group and V2 open group messages we get - // this from the protobuf. In the case of V1 open group messages we get it from the envelope in which the message is - // wrapped, which gets parsed to `serverTimestamp` in that case. - if ([self isKindOfClass:TSIncomingMessage.class] && ((TSIncomingMessage *)self).isOpenGroupMessage && ((TSIncomingMessage *)self).serverTimestamp != nil) { - return ((TSIncomingMessage *)self).serverTimestamp.unsignedLongLongValue; - } return self.timestamp; } @@ -280,6 +263,12 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value) [self saveWithTransaction:transaction]; } +- (void)updateTimestamp:(uint64_t)timestamp +{ + _timestamp = timestamp; +} + + @end NS_ASSUME_NONNULL_END diff --git a/SessionMessagingKit/Messages/Signal/TSOutgoingMessage.h b/SessionMessagingKit/Messages/Signal/TSOutgoingMessage.h index f38a173f7..b61bada56 100644 --- a/SessionMessagingKit/Messages/Signal/TSOutgoingMessage.h +++ b/SessionMessagingKit/Messages/Signal/TSOutgoingMessage.h @@ -159,6 +159,9 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) { #pragma mark - Update With... Methods +- (void)updateOpenGroupServerID:(uint64_t)openGroupServerID + serverTimeStamp:(uint64_t)timestamp; + // This method is used to record a successful send to one recipient. - (void)updateWithSentRecipient:(NSString *)recipientId wasSentByUD:(BOOL)wasSentByUD diff --git a/SessionMessagingKit/Messages/Signal/TSOutgoingMessage.m b/SessionMessagingKit/Messages/Signal/TSOutgoingMessage.m index 0e9fee8a7..f2985104d 100644 --- a/SessionMessagingKit/Messages/Signal/TSOutgoingMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSOutgoingMessage.m @@ -450,6 +450,12 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt #pragma mark - Update With... Methods +- (void)updateOpenGroupServerID:(uint64_t)openGroupServerID serverTimeStamp:(uint64_t)timestamp +{ + self.openGroupServerMessageID = openGroupServerID; + [super updateTimestamp:timestamp]; +} + - (void)updateWithSendingError:(NSError *)error transaction:(YapDatabaseReadWriteTransaction *)transaction { [self applyChangeToSelfAndLatestCopy:transaction diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index a1f16f751..d32b07681 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -317,7 +317,7 @@ public final class MessageSender : NSObject { OpenGroupAPIV2.send(openGroupMessage, to: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in message.openGroupServerMessageID = given(openGroupMessage.serverID) { UInt64($0) } storage.write(with: { transaction in - MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction) + MessageSender.handleSuccessfulMessageSend(message, to: destination, serverTimestamp: openGroupMessage.sentTimestamp, using: transaction) seal.fulfill(()) }, completion: { }) }.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in @@ -330,7 +330,7 @@ public final class MessageSender : NSObject { } // MARK: Success & Failure Handling - public static func handleSuccessfulMessageSend(_ message: Message, to destination: Message.Destination, isSyncMessage: Bool = false, using transaction: Any) { + public static func handleSuccessfulMessageSend(_ message: Message, to destination: Message.Destination, serverTimestamp: UInt64? = nil, isSyncMessage: Bool = false, using transaction: Any) { let transaction = transaction as! YapDatabaseReadWriteTransaction // Get the visible message if possible if let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) { @@ -338,8 +338,13 @@ public final class MessageSender : NSObject { // will be replaced by the hash value of the sync message. Since the hash value of the // real message has no use when we delete a message. It is OK to let it be. tsMessage.serverHash = message.serverHash - // Track the open group server message ID - tsMessage.openGroupServerMessageID = message.openGroupServerMessageID ?? 0 + // Track the open group server message ID and update server timestamp + if let openGroupServerMessageID = message.openGroupServerMessageID, let timestamp = serverTimestamp { + // Use server timestamp for open group messages + // Otherwise the quote messages may not be able + // to be found by the timestamp on other devices + tsMessage.updateOpenGroupServerID(openGroupServerMessageID, serverTimeStamp: timestamp) + } // Mark the message as sent var recipients = [ message.recipient! ] if case .closedGroup(_) = destination, let threadID = message.threadID, // threadID should always be set at this point diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 6a4fb2c92..00148075a 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -5,6 +5,7 @@ import PromiseKit public final class ClosedGroupPoller : NSObject { private var isPolling: [String:Bool] = [:] private var timers: [String:Timer] = [:] + private let internalQueue: DispatchQueue = DispatchQueue(label:"isPollingQueue") // MARK: Settings private static let minPollInterval: Double = 2 @@ -40,8 +41,11 @@ public final class ClosedGroupPoller : NSObject { public func startPolling(for groupPublicKey: String) { guard !isPolling(for: groupPublicKey) else { return } - setUpPolling(for: groupPublicKey) + // Might be a race condition that the setUpPolling finishes too soon, + // and the timer is not created, if we mark the group as is polling + // after setUpPolling. So the poller may not work, thus misses messages. isPolling[groupPublicKey] = true + setUpPolling(for: groupPublicKey) } @objc public func stop() { @@ -51,8 +55,8 @@ public final class ClosedGroupPoller : NSObject { } public func stopPolling(for groupPublicKey: String) { - timers[groupPublicKey]?.invalidate() isPolling[groupPublicKey] = false + timers[groupPublicKey]?.invalidate() } // MARK: Private API @@ -134,6 +138,6 @@ public final class ClosedGroupPoller : NSObject { // MARK: Convenience private func isPolling(for groupPublicKey: String) -> Bool { - return isPolling[groupPublicKey] ?? false + return internalQueue.sync{ isPolling[groupPublicKey] ?? false } } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift index 946a564d7..d5eee9ad1 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift @@ -73,7 +73,6 @@ public final class OpenGroupPollerV2 : NSObject { let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: message.sentTimestamp) envelope.setContent(data) envelope.setSource(message.sender!) // Safe because messages with a nil sender are filtered out - envelope.setServerTimestamp(message.sentTimestamp) do { let data = try envelope.buildSerializedData() let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.serverID!), isRetry: false, using: transaction) diff --git a/SessionMessagingKit/Threads/TSThread.h b/SessionMessagingKit/Threads/TSThread.h index 82f556d57..7ed517919 100644 --- a/SessionMessagingKit/Threads/TSThread.h +++ b/SessionMessagingKit/Threads/TSThread.h @@ -57,6 +57,9 @@ BOOL IsNoteToSelfEnabled(void); - (NSUInteger)unreadMessageCountWithTransaction:(YapDatabaseReadTransaction *)transaction NS_SWIFT_NAME(unreadMessageCount(transaction:)); +- (BOOL)hasUnreadMentionMessageWithTransaction:(YapDatabaseReadTransaction *)transaction + NS_SWIFT_NAME(hasUnreadMentionMessage(transaction:)); + - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; /** diff --git a/SessionMessagingKit/Threads/TSThread.m b/SessionMessagingKit/Threads/TSThread.m index 14b16ed09..dbfa1ecd4 100644 --- a/SessionMessagingKit/Threads/TSThread.m +++ b/SessionMessagingKit/Threads/TSThread.m @@ -264,6 +264,32 @@ BOOL IsNoteToSelfEnabled(void) return count; } +- (BOOL)hasUnreadMentionMessageWithTransaction:(YapDatabaseReadTransaction *)transaction +{ + __block BOOL hasUnreadMention = false; + + YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName]; + [unreadMessages enumerateKeysAndObjectsInGroup:self.uniqueId + usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { + if (![object isKindOfClass:[TSIncomingMessage class]]) { + return; + } + TSIncomingMessage* unreadMessage = (TSIncomingMessage*)object; + if (unreadMessage.read) { + NSLog(@"Found an already read message in the * unread * messages list."); + return; + } + + if (unreadMessage.isUserMentioned) { + hasUnreadMention = true; + *stop = YES; + } + }]; + + return hasUnreadMention; + +} + - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { for (id message in [self unseenMessagesWithTransaction:transaction]) { diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index bc40de1f7..3b1818124 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -48,7 +48,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension case let visibleMessage as VisibleMessage: let tsIncomingMessageID = try MessageReceiver.handleVisibleMessage(visibleMessage, associatedWithProto: proto, openGroupID: nil, isBackgroundPoll: false, using: transaction) guard let tsIncomingMessage = TSIncomingMessage.fetch(uniqueId: tsIncomingMessageID, transaction: transaction) else { - return self.handleFailure(for: notificationContent) + return self.completeSilenty() } let thread = tsIncomingMessage.thread(with: transaction) if thread.isMuted { diff --git a/SignalUtilitiesKit/Messaging/ThreadViewModel.swift b/SignalUtilitiesKit/Messaging/ThreadViewModel.swift index 30177ec11..0162b873f 100644 --- a/SignalUtilitiesKit/Messaging/ThreadViewModel.swift +++ b/SignalUtilitiesKit/Messaging/ThreadViewModel.swift @@ -15,6 +15,7 @@ public class ThreadViewModel: NSObject { @objc public let name: String @objc public let isMuted: Bool @objc public let isOnlyNotifyingForMentions: Bool + @objc public let hasUnreadMentions: Bool var isContactThread: Bool { return !isGroupThread @@ -49,6 +50,7 @@ public class ThreadViewModel: NSObject { self.unreadCount = thread.unreadMessageCount(transaction: transaction) self.hasUnreadMessages = unreadCount > 0 + self.hasUnreadMentions = thread.hasUnreadMentionMessage(transaction: transaction) } @objc