Merge branch 'master' of https://github.com/oxen-io/session-ios into voice-calls-2

pull/560/head
ryanzhao 3 years ago
commit 6e07c56e7d

@ -26,6 +26,6 @@ Copyright 2011 Whisper Systems
Copyright 2013-2017 Open 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 Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html

@ -5088,7 +5088,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 288; CURRENT_PROJECT_VERSION = 294;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5113,7 +5113,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.11.13; MARKETING_VERSION = 1.11.15;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -5161,7 +5161,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 288; CURRENT_PROJECT_VERSION = 294;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
@ -5191,7 +5191,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.11.13; MARKETING_VERSION = 1.11.15;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -5227,7 +5227,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 288; CURRENT_PROJECT_VERSION = 294;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5250,7 +5250,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.11.13; MARKETING_VERSION = 1.11.15;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -5301,7 +5301,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 288; CURRENT_PROJECT_VERSION = 294;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
@ -5329,7 +5329,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.11.13; MARKETING_VERSION = 1.11.15;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -6237,7 +6237,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 288; CURRENT_PROJECT_VERSION = 294;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -6277,7 +6277,7 @@
"$(SRCROOT)", "$(SRCROOT)",
); );
LLVM_LTO = NO; LLVM_LTO = NO;
MARKETING_VERSION = 1.11.13; MARKETING_VERSION = 1.11.15;
OTHER_LDFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
@ -6309,7 +6309,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 288; CURRENT_PROJECT_VERSION = 294;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -6349,7 +6349,7 @@
"$(SRCROOT)", "$(SRCROOT)",
); );
LLVM_LTO = NO; LLVM_LTO = NO;
MARKETING_VERSION = 1.11.13; MARKETING_VERSION = 1.11.15;
OTHER_LDFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
PRODUCT_NAME = Session; PRODUCT_NAME = Session;

@ -367,6 +367,13 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
snInputView.hideMentionsUI() snInputView.hideMentionsUI()
self.oldText = newText self.oldText = newText
} }
func showInputAccessoryView() {
UIView.animate(withDuration: 0.25, animations: {
self.inputAccessoryView?.isHidden = false
self.inputAccessoryView?.alpha = 1
})
}
// MARK: View Item Interaction // MARK: View Item Interaction
func handleViewItemLongPressed(_ viewItem: ConversationViewItem) { func handleViewItemLongPressed(_ viewItem: ConversationViewItem) {
@ -568,19 +575,12 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
return return
} }
func showInputAccessoryView() {
UIView.animate(withDuration: 0.25, animations: {
self.inputAccessoryView?.isHidden = false
self.inputAccessoryView?.alpha = 1
})
}
if viewItem.interaction.interactionType() == .outgoingMessage, if viewItem.interaction.interactionType() == .outgoingMessage,
let message = viewItem.interaction as? TSMessage, message.serverHash != nil { let message = viewItem.interaction as? TSMessage, message.serverHash != nil {
let alertVC = UIAlertController.init(title: nil, message: nil, preferredStyle: .actionSheet) let alertVC = UIAlertController.init(title: nil, message: nil, preferredStyle: .actionSheet)
let deleteLocallyAction = UIAlertAction.init(title: NSLocalizedString("delete_message_for_me", comment: ""), style: .destructive) { _ in let deleteLocallyAction = UIAlertAction.init(title: NSLocalizedString("delete_message_for_me", comment: ""), style: .destructive) { _ in
self.deleteLocally(viewItem) self.deleteLocally(viewItem)
showInputAccessoryView() self.showInputAccessoryView()
} }
alertVC.addAction(deleteLocallyAction) alertVC.addAction(deleteLocallyAction)
@ -590,12 +590,12 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
} }
let deleteRemotelyAction = UIAlertAction.init(title: title, style: .destructive) { _ in let deleteRemotelyAction = UIAlertAction.init(title: title, style: .destructive) { _ in
self.deleteForEveryone(viewItem) self.deleteForEveryone(viewItem)
showInputAccessoryView() self.showInputAccessoryView()
} }
alertVC.addAction(deleteRemotelyAction) alertVC.addAction(deleteRemotelyAction)
let cancelAction = UIAlertAction.init(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel) {_ in let cancelAction = UIAlertAction.init(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel) {_ in
showInputAccessoryView() self.showInputAccessoryView()
} }
alertVC.addAction(cancelAction) alertVC.addAction(cancelAction)
@ -682,10 +682,24 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
func openURL(_ url: URL) { func openURL(_ url: URL) {
// URLs can be unsafe, so always ask the user whether they want to open one // URLs can be unsafe, so always ask the user whether they want to open one
let urlModal = URLModal(url: url) let title = NSLocalizedString("modal_open_url_title", comment: "")
urlModal.modalPresentationStyle = .overFullScreen let message = String(format: NSLocalizedString("modal_open_url_explanation", comment: ""), url.absoluteString)
urlModal.modalTransitionStyle = .crossDissolve let alertVC = UIAlertController.init(title: title, message: message, preferredStyle: .actionSheet)
present(urlModal, animated: true, completion: nil) 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) { func joinOpenGroup(name: String, url: String) {

@ -258,10 +258,8 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
override func viewWillDisappear(_ animated: Bool) { override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated) super.viewWillDisappear(animated)
let text = snInputView.text let text = snInputView.text
if !text.isEmpty { Storage.write { transaction in
Storage.write { transaction in self.thread.setDraft(text, transaction: transaction)
self.thread.setDraft(text, transaction: transaction)
}
} }
inputAccessoryView?.resignFirstResponder() inputAccessoryView?.resignFirstResponder()
} }

@ -1158,7 +1158,7 @@ NS_ASSUME_NONNULL_BEGIN
break; break;
} }
uint64_t viewItemTimestamp = viewItem.interaction.timestampForUI; uint64_t viewItemTimestamp = viewItem.interaction.timestamp;
OWSAssertDebug(viewItemTimestamp > 0); OWSAssertDebug(viewItemTimestamp > 0);
BOOL shouldShowDate = NO; BOOL shouldShowDate = NO;
@ -1225,7 +1225,7 @@ NS_ASSUME_NONNULL_BEGIN
NSAttributedString *_Nullable senderName = nil; NSAttributedString *_Nullable senderName = nil;
OWSInteractionType interactionType = viewItem.interaction.interactionType; OWSInteractionType interactionType = viewItem.interaction.interactionType;
NSString *timestampText = [DateUtil formatTimestampShort:viewItem.interaction.timestampForUI]; NSString *timestampText = [DateUtil formatTimestampShort:viewItem.interaction.timestamp];
if (interactionType == OWSInteractionType_OutgoingMessage) { if (interactionType == OWSInteractionType_OutgoingMessage) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction; TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction;

@ -10,15 +10,35 @@ final class ExpandingAttachmentsButton : UIView, InputViewButtonDelegate {
private lazy var cameraButtonContainerBottomConstraint = cameraButtonContainer.pin(.bottom, to: .bottom, of: self) private lazy var cameraButtonContainerBottomConstraint = cameraButtonContainer.pin(.bottom, to: .bottom, of: self)
// MARK: UI Components // 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 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 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 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 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) lazy var mainButtonContainer = container(for: mainButton)
// MARK: Lifecycle // MARK: Lifecycle
@ -66,6 +86,7 @@ final class ExpandingAttachmentsButton : UIView, InputViewButtonDelegate {
// MARK: Animation // MARK: Animation
private func expandOrCollapse() { private func expandOrCollapse() {
if isExpanded { if isExpanded {
mainButton.accessibilityLabel = NSLocalizedString("accessibility_main_button_collapse", comment: "")
let expandedButtonSize = InputViewButton.expandedSize let expandedButtonSize = InputViewButton.expandedSize
let spacing: CGFloat = 4 let spacing: CGFloat = 4
cameraButtonContainerBottomConstraint.constant = -1 * (expandedButtonSize + spacing) cameraButtonContainerBottomConstraint.constant = -1 * (expandedButtonSize + spacing)
@ -79,6 +100,7 @@ final class ExpandingAttachmentsButton : UIView, InputViewButtonDelegate {
self.layoutIfNeeded() self.layoutIfNeeded()
} }
} else { } else {
mainButton.accessibilityLabel = NSLocalizedString("accessibility_expanding_attachments_button", comment: "")
[ gifButtonContainerBottomConstraint, documentButtonContainerBottomConstraint, libraryButtonContainerBottomConstraint, cameraButtonContainerBottomConstraint ].forEach { [ gifButtonContainerBottomConstraint, documentButtonContainerBottomConstraint, libraryButtonContainerBottomConstraint, cameraButtonContainerBottomConstraint ].forEach {
$0.constant = 0 $0.constant = 0
} }

@ -26,6 +26,8 @@ public final class InputTextView : UITextView, UITextViewDelegate {
super.init(frame: CGRect.zero, textContainer: nil) super.init(frame: CGRect.zero, textContainer: nil)
setUpViewHierarchy() setUpViewHierarchy()
self.delegate = self self.delegate = self
self.isAccessibilityElement = true
self.accessibilityLabel = NSLocalizedString("vc_conversation_input_prompt", comment: "")
} }
public override init(frame: CGRect, textContainer: NSTextContainer?) { public override init(frame: CGRect, textContainer: NSTextContainer?) {

@ -22,11 +22,17 @@ final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate,
// MARK: UI Components // MARK: UI Components
private lazy var attachmentsButton = ExpandingAttachmentsButton(delegate: delegate) 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 = { private lazy var sendButton: InputViewButton = {
let result = InputViewButton(icon: #imageLiteral(resourceName: "ArrowUp"), isSendButton: true, delegate: self) let result = InputViewButton(icon: #imageLiteral(resourceName: "ArrowUp"), isSendButton: true, delegate: self)
result.isHidden = true result.isHidden = true
result.accessibilityLabel = NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON", comment: "")
return result return result
}() }()
private lazy var voiceMessageButtonContainer = container(for: voiceMessageButton) private lazy var voiceMessageButtonContainer = container(for: voiceMessageButton)

@ -25,6 +25,7 @@ final class InputViewButton : UIView {
self.hasOpaqueBackground = hasOpaqueBackground self.hasOpaqueBackground = hasOpaqueBackground
super.init(frame: CGRect.zero) super.init(frame: CGRect.zero)
setUpViewHierarchy() setUpViewHierarchy()
self.isAccessibilityElement = true
} }
override init(frame: CGRect) { override init(frame: CGRect) {

@ -221,6 +221,10 @@ class PhotoCollectionContents {
class PhotoCollection { class PhotoCollection {
private let collection: PHAssetCollection 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) { init(collection: PHAssetCollection) {
self.collection = collection self.collection = collection
@ -275,11 +279,30 @@ class PhotoLibrary: NSObject, PHPhotoLibraryChangeObserver {
deinit { deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self) PHPhotoLibrary.shared().unregisterChangeObserver(self)
} }
private lazy var fetchOptions: PHFetchOptions = {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "endDate", ascending: true)]
return fetchOptions
}()
func defaultPhotoCollection() -> PhotoCollection { func defaultPhotoCollection() -> PhotoCollection {
guard let photoCollection = allPhotoCollections().first else { var fetchedCollection: PhotoCollection?
owsFail("Could not locate Camera Roll.") 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 return photoCollection
} }

@ -531,6 +531,7 @@
"modal_open_url_title" = "URL öffnen?"; "modal_open_url_title" = "URL öffnen?";
"modal_open_url_explanation" = "Möchten Sie %@ wirklich öffnen?"; "modal_open_url_explanation" = "Möchten Sie %@ wirklich öffnen?";
"modal_open_url_button_title" = "Öffnen"; "modal_open_url_button_title" = "Öffnen";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "%@ entsperren?"; "modal_blocked_title" = "%@ entsperren?";
"modal_blocked_explanation" = "Sind Sie sicher, dass Sie %@ entsperren möchten?"; "modal_blocked_explanation" = "Sind Sie sicher, dass Sie %@ entsperren möchten?";
"modal_blocked_button_title" = "Entsperren"; "modal_blocked_button_title" = "Entsperren";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Open URL?"; "modal_open_url_title" = "Open URL?";
"modal_open_url_explanation" = "Are you sure you want to open %@?"; "modal_open_url_explanation" = "Are you sure you want to open %@?";
"modal_open_url_button_title" = "Open"; "modal_open_url_button_title" = "Open";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Unblock %@?"; "modal_blocked_title" = "Unblock %@?";
"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; "modal_blocked_explanation" = "Are you sure you want to unblock %@?";
"modal_blocked_button_title" = "Unblock"; "modal_blocked_button_title" = "Unblock";
@ -557,4 +558,11 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "¿Abrir URL?"; "modal_open_url_title" = "¿Abrir URL?";
"modal_open_url_explanation" = "¿Estás seguro de que quieres abrir %@?"; "modal_open_url_explanation" = "¿Estás seguro de que quieres abrir %@?";
"modal_open_url_button_title" = "Abrir"; "modal_open_url_button_title" = "Abrir";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "¿Desbloquear a %@?"; "modal_blocked_title" = "¿Desbloquear a %@?";
"modal_blocked_explanation" = "¿Estás seguro de que quieres desbloquear a %@?"; "modal_blocked_explanation" = "¿Estás seguro de que quieres desbloquear a %@?";
"modal_blocked_button_title" = "Desbloquear"; "modal_blocked_button_title" = "Desbloquear";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "URL باز شود؟"; "modal_open_url_title" = "URL باز شود؟";
"modal_open_url_explanation" = "آیا مطمئن هستید که میخواهید %@ را باز کنید؟"; "modal_open_url_explanation" = "آیا مطمئن هستید که میخواهید %@ را باز کنید؟";
"modal_open_url_button_title" = "باز کن"; "modal_open_url_button_title" = "باز کن";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Unblock %@?"; "modal_blocked_title" = "Unblock %@?";
"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; "modal_blocked_explanation" = "Are you sure you want to unblock %@?";
"modal_blocked_button_title" = "رفع مسدودی"; "modal_blocked_button_title" = "رفع مسدودی";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Avataanko URL?"; "modal_open_url_title" = "Avataanko URL?";
"modal_open_url_explanation" = "Oletko varma, että haluat avata linkin %@?"; "modal_open_url_explanation" = "Oletko varma, että haluat avata linkin %@?";
"modal_open_url_button_title" = "Avaa"; "modal_open_url_button_title" = "Avaa";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Poista esto henkilöltä %@?"; "modal_blocked_title" = "Poista esto henkilöltä %@?";
"modal_blocked_explanation" = "Oletko varma, että haluat poistaa eston henkilöltä %@?"; "modal_blocked_explanation" = "Oletko varma, että haluat poistaa eston henkilöltä %@?";
"modal_blocked_button_title" = "Poista esto"; "modal_blocked_button_title" = "Poista esto";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Ouvrir l'URL?"; "modal_open_url_title" = "Ouvrir l'URL?";
"modal_open_url_explanation" = "Êtes-vous sûr de vouloir ouvrir %@?"; "modal_open_url_explanation" = "Êtes-vous sûr de vouloir ouvrir %@?";
"modal_open_url_button_title" = "Ouvrir"; "modal_open_url_button_title" = "Ouvrir";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Débloquer %@?"; "modal_blocked_title" = "Débloquer %@?";
"modal_blocked_explanation" = "Confirmez-vous le déblocage de %@ ?"; "modal_blocked_explanation" = "Confirmez-vous le déblocage de %@ ?";
"modal_blocked_button_title" = "Débloquer"; "modal_blocked_button_title" = "Débloquer";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "यूआरएल खोलें"; "modal_open_url_title" = "यूआरएल खोलें";
"modal_open_url_explanation" = "क्या आप वाकई %@ खोलना चाहते हैं?"; "modal_open_url_explanation" = "क्या आप वाकई %@ खोलना चाहते हैं?";
"modal_open_url_button_title" = "खोलें"; "modal_open_url_button_title" = "खोलें";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "%@ अनब्लॉक करें?"; "modal_blocked_title" = "%@ अनब्लॉक करें?";
"modal_blocked_explanation" = "क्या आप वाकई %@ को अनब्लॉक करना चाहते हैं?"; "modal_blocked_explanation" = "क्या आप वाकई %@ को अनब्लॉक करना चाहते हैं?";
"modal_blocked_button_title" = "अनब्लॉक करें"; "modal_blocked_button_title" = "अनब्लॉक करें";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Otvori poveznicu?"; "modal_open_url_title" = "Otvori poveznicu?";
"modal_open_url_explanation" = "Jeste li sigurni da želite otvoriti %@?"; "modal_open_url_explanation" = "Jeste li sigurni da želite otvoriti %@?";
"modal_open_url_button_title" = "Otvori"; "modal_open_url_button_title" = "Otvori";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Deblokiraj %@?"; "modal_blocked_title" = "Deblokiraj %@?";
"modal_blocked_explanation" = "Jeste li sigurni da želite deblokirati %@?"; "modal_blocked_explanation" = "Jeste li sigurni da želite deblokirati %@?";
"modal_blocked_button_title" = "Deblokiraj"; "modal_blocked_button_title" = "Deblokiraj";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Open URL?"; "modal_open_url_title" = "Open URL?";
"modal_open_url_explanation" = "Are you sure you want to open %@?"; "modal_open_url_explanation" = "Are you sure you want to open %@?";
"modal_open_url_button_title" = "Open"; "modal_open_url_button_title" = "Open";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Unblock %@?"; "modal_blocked_title" = "Unblock %@?";
"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; "modal_blocked_explanation" = "Are you sure you want to unblock %@?";
"modal_blocked_button_title" = "Unblock"; "modal_blocked_button_title" = "Unblock";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Aprire URL?"; "modal_open_url_title" = "Aprire URL?";
"modal_open_url_explanation" = "Sei sicuro di voler aprire %@?"; "modal_open_url_explanation" = "Sei sicuro di voler aprire %@?";
"modal_open_url_button_title" = "Apri"; "modal_open_url_button_title" = "Apri";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Sbloccare %@?"; "modal_blocked_title" = "Sbloccare %@?";
"modal_blocked_explanation" = "Sei sicuro di voler sbloccare %@?"; "modal_blocked_explanation" = "Sei sicuro di voler sbloccare %@?";
"modal_blocked_button_title" = "Sblocca"; "modal_blocked_button_title" = "Sblocca";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "URLを開きますか"; "modal_open_url_title" = "URLを開きますか";
"modal_open_url_explanation" = "%@を本当に開いてもよろしいですか?"; "modal_open_url_explanation" = "%@を本当に開いてもよろしいですか?";
"modal_open_url_button_title" = "開く"; "modal_open_url_button_title" = "開く";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "%@のブロックを解除しますか?"; "modal_blocked_title" = "%@のブロックを解除しますか?";
"modal_blocked_explanation" = "%@のブロックを解除してもよろしいですか?"; "modal_blocked_explanation" = "%@のブロックを解除してもよろしいですか?";
"modal_blocked_button_title" = "ブロックを解除する"; "modal_blocked_button_title" = "ブロックを解除する";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "URL openen?"; "modal_open_url_title" = "URL openen?";
"modal_open_url_explanation" = "Weet u zeker dat u %@ wilt openen?"; "modal_open_url_explanation" = "Weet u zeker dat u %@ wilt openen?";
"modal_open_url_button_title" = "Openen"; "modal_open_url_button_title" = "Openen";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Blokkering opheffen voor %@?"; "modal_blocked_title" = "Blokkering opheffen voor %@?";
"modal_blocked_explanation" = "Weet je zeker dat je %@ weer wilt toelaten?"; "modal_blocked_explanation" = "Weet je zeker dat je %@ weer wilt toelaten?";
"modal_blocked_button_title" = "Deblokkeren"; "modal_blocked_button_title" = "Deblokkeren";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Otworzyć URL?"; "modal_open_url_title" = "Otworzyć URL?";
"modal_open_url_explanation" = "Czy na pewno chcesz otworzyć %@?"; "modal_open_url_explanation" = "Czy na pewno chcesz otworzyć %@?";
"modal_open_url_button_title" = "Otwórz"; "modal_open_url_button_title" = "Otwórz";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Odblokować %@?"; "modal_blocked_title" = "Odblokować %@?";
"modal_blocked_explanation" = "Czy na pewno chcesz odblokować %@?"; "modal_blocked_explanation" = "Czy na pewno chcesz odblokować %@?";
"modal_blocked_button_title" = "Odblokuj"; "modal_blocked_button_title" = "Odblokuj";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Abrir URL?"; "modal_open_url_title" = "Abrir URL?";
"modal_open_url_explanation" = "Você tem certeza que deseja abrir %@/?"; "modal_open_url_explanation" = "Você tem certeza que deseja abrir %@/?";
"modal_open_url_button_title" = "Abrir"; "modal_open_url_button_title" = "Abrir";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Desbloquear %@?"; "modal_blocked_title" = "Desbloquear %@?";
"modal_blocked_explanation" = "Você tem certeza que deseja desbloquear %@?"; "modal_blocked_explanation" = "Você tem certeza que deseja desbloquear %@?";
"modal_blocked_button_title" = "Desbloquear"; "modal_blocked_button_title" = "Desbloquear";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Открыть ссылку?"; "modal_open_url_title" = "Открыть ссылку?";
"modal_open_url_explanation" = "Вы уверены, что хотите открыть %@?"; "modal_open_url_explanation" = "Вы уверены, что хотите открыть %@?";
"modal_open_url_button_title" = "Открыть"; "modal_open_url_button_title" = "Открыть";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Разблокировать %@?"; "modal_blocked_title" = "Разблокировать %@?";
"modal_blocked_explanation" = "Вы уверены, что хотите разблокировать %@?"; "modal_blocked_explanation" = "Вы уверены, что хотите разблокировать %@?";
"modal_blocked_button_title" = "Разблокировать"; "modal_blocked_button_title" = "Разблокировать";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Otvoriť URL?"; "modal_open_url_title" = "Otvoriť URL?";
"modal_open_url_explanation" = "Ste si istý, že chcete otvoriť %@?"; "modal_open_url_explanation" = "Ste si istý, že chcete otvoriť %@?";
"modal_open_url_button_title" = "Otvoriť"; "modal_open_url_button_title" = "Otvoriť";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Odblokovať %@?"; "modal_blocked_title" = "Odblokovať %@?";
"modal_blocked_explanation" = "Ste si istý/á, že chcete odblokovať %@?"; "modal_blocked_explanation" = "Ste si istý/á, že chcete odblokovať %@?";
"modal_blocked_button_title" = "Odblokovať"; "modal_blocked_button_title" = "Odblokovať";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Öppna URL?"; "modal_open_url_title" = "Öppna URL?";
"modal_open_url_explanation" = "Är du säker på att du vill öppna %@?"; "modal_open_url_explanation" = "Är du säker på att du vill öppna %@?";
"modal_open_url_button_title" = "Öppna"; "modal_open_url_button_title" = "Öppna";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Avblockera %@?"; "modal_blocked_title" = "Avblockera %@?";
"modal_blocked_explanation" = "Är du säker på att du vill avblockera %@?"; "modal_blocked_explanation" = "Är du säker på att du vill avblockera %@?";
"modal_blocked_button_title" = "Avblockera"; "modal_blocked_button_title" = "Avblockera";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "เปิดลิงค์ URL ไหม"; "modal_open_url_title" = "เปิดลิงค์ URL ไหม";
"modal_open_url_explanation" = "แน่ใจไหมว่าคุณต้องกาเปิดดู %@"; "modal_open_url_explanation" = "แน่ใจไหมว่าคุณต้องกาเปิดดู %@";
"modal_open_url_button_title" = "เปิด"; "modal_open_url_button_title" = "เปิด";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "เลิกบล็อก %@ ไหม"; "modal_blocked_title" = "เลิกบล็อก %@ ไหม";
"modal_blocked_explanation" = "แน่ใจไหมว่าคุณต้องการเลิกบล็อก %@"; "modal_blocked_explanation" = "แน่ใจไหมว่าคุณต้องการเลิกบล็อก %@";
"modal_blocked_button_title" = "เลิกบล็อก"; "modal_blocked_button_title" = "เลิกบล็อก";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "Open URL?"; "modal_open_url_title" = "Open URL?";
"modal_open_url_explanation" = "Are you sure you want to open %@?"; "modal_open_url_explanation" = "Are you sure you want to open %@?";
"modal_open_url_button_title" = "Open"; "modal_open_url_button_title" = "Open";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "Unblock %@?"; "modal_blocked_title" = "Unblock %@?";
"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; "modal_blocked_explanation" = "Are you sure you want to unblock %@?";
"modal_blocked_button_title" = "Unblock"; "modal_blocked_button_title" = "Unblock";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "打開連結?"; "modal_open_url_title" = "打開連結?";
"modal_open_url_explanation" = "您確定要打開 %@ 嗎?"; "modal_open_url_explanation" = "您確定要打開 %@ 嗎?";
"modal_open_url_button_title" = "打開"; "modal_open_url_button_title" = "打開";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "解除封鎖 %@ 嗎?"; "modal_blocked_title" = "解除封鎖 %@ 嗎?";
"modal_blocked_explanation" = "您確定要解除封鎖 %@ 嗎?"; "modal_blocked_explanation" = "您確定要解除封鎖 %@ 嗎?";
"modal_blocked_button_title" = "解除封鎖"; "modal_blocked_button_title" = "解除封鎖";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -531,6 +531,7 @@
"modal_open_url_title" = "打开链接?"; "modal_open_url_title" = "打开链接?";
"modal_open_url_explanation" = "确定要打开 %@ 吗?"; "modal_open_url_explanation" = "确定要打开 %@ 吗?";
"modal_open_url_button_title" = "打开"; "modal_open_url_button_title" = "打开";
"modal_copy_url_button_title" = "Copy Link";
"modal_blocked_title" = "从黑名单中移除 %@ 吗?"; "modal_blocked_title" = "从黑名单中移除 %@ 吗?";
"modal_blocked_explanation" = "确定解除屏蔽%@吗?"; "modal_blocked_explanation" = "确定解除屏蔽%@吗?";
"modal_blocked_button_title" = "解除屏蔽"; "modal_blocked_button_title" = "解除屏蔽";
@ -557,3 +558,9 @@
"context_menu_save" = "Save"; "context_menu_save" = "Save";
"context_menu_ban_user" = "Ban User"; "context_menu_ban_user" = "Ban User";
"context_menu_ban_and_delete_all" = "Ban and Delete All"; "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";

@ -122,6 +122,14 @@ final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewCon
} }
func continueWithSeed(_ seed: Data) { 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) let (ed25519KeyPair, x25519KeyPair) = KeyPairUtilities.generate(from: seed)
Onboarding.Flow.link.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) Onboarding.Flow.link.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair)
TSAccountManager.sharedInstance().didRegister() TSAccountManager.sharedInstance().didRegister()

@ -1,4 +1,5 @@
import UIKit import UIKit
import SessionUIKit
final class ConversationCell : UITableViewCell { final class ConversationCell : UITableViewCell {
var threadViewModel: ThreadViewModel! { didSet { update() } } var threadViewModel: ThreadViewModel! { didSet { update() } }
@ -37,6 +38,26 @@ final class ConversationCell : UITableViewCell {
return result 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 = { private lazy var timestampLabel: UILabel = {
let result = UILabel() let result = UILabel()
result.font = .systemFont(ofSize: Values.smallFontSize) result.font = .systemFont(ofSize: Values.smallFontSize)
@ -98,9 +119,12 @@ final class ConversationCell : UITableViewCell {
// Unread count view // Unread count view
unreadCountView.addSubview(unreadCountLabel) unreadCountView.addSubview(unreadCountLabel)
unreadCountLabel.pin(to: unreadCountView) unreadCountLabel.pin(to: unreadCountView)
// Has mention view
hasMentionView.addSubview(hasMentionLabel)
hasMentionLabel.pin(to: hasMentionView)
// Label stack view // Label stack view
let topLabelSpacer = UIView.hStretchingSpacer() 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.axis = .horizontal
topLabelStackView.alignment = .center topLabelStackView.alignment = .center
topLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer 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+" unreadCountLabel.text = unreadCount < 100 ? "\(unreadCount)" : "99+"
let fontSize = (unreadCount < 100) ? Values.verySmallFontSize : 8 let fontSize = (unreadCount < 100) ? Values.verySmallFontSize : 8
unreadCountLabel.font = .boldSystemFont(ofSize: fontSize) unreadCountLabel.font = .boldSystemFont(ofSize: fontSize)
hasMentionView.isHidden = !threadViewModel.hasUnreadMentions
profilePictureView.update(for: thread) profilePictureView.update(for: thread)
displayNameLabel.text = getDisplayName() displayNameLabel.text = getDisplayName()
timestampLabel.text = DateUtil.formatDate(forDisplay: threadViewModel.lastMessageDate) timestampLabel.text = DateUtil.formatDate(forDisplay: threadViewModel.lastMessageDate)

@ -71,4 +71,10 @@ final class ScanQRCodeWrapperVC : BaseVC {
@objc private func close() { @objc private func close() {
presentingViewController?.dismiss(animated: true, completion: nil) presentingViewController?.dismiss(animated: true, completion: nil)
} }
public func startCapture() {
DispatchQueue.main.async { [weak self] in
self?.scanQRCodeVC.startCapture()
}
}
} }

@ -162,4 +162,3 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject
delegate?.handleJobFailed(self, with: error) delegate?.handleJobFailed(self, with: error)
} }
} }

@ -19,7 +19,6 @@ public extension TSIncomingMessage {
expiresInSeconds: !isOpenGroupMessage ? expiration : 0, // Ensure we don't ever expire open group messages expiresInSeconds: !isOpenGroupMessage ? expiration : 0, // Ensure we don't ever expire open group messages
quotedMessage: quotedMessage, quotedMessage: quotedMessage,
linkPreview: linkPreview, linkPreview: linkPreview,
serverTimestamp: visibleMessage.openGroupServerTimestamp as NSNumber?,
wasReceivedByUD: true, wasReceivedByUD: true,
openGroupInvitationName: visibleMessage.openGroupInvitation?.name, openGroupInvitationName: visibleMessage.openGroupInvitation?.name,
openGroupInvitationURL: visibleMessage.openGroupInvitation?.url, openGroupInvitationURL: visibleMessage.openGroupInvitation?.url,

@ -12,8 +12,6 @@ NS_ASSUME_NONNULL_BEGIN
@interface TSIncomingMessage : TSMessage <OWSReadTracking> @interface TSIncomingMessage : TSMessage <OWSReadTracking>
@property (nonatomic, readonly, nullable) NSNumber *serverTimestamp;
@property (nonatomic, readonly) BOOL wasReceivedByUD; @property (nonatomic, readonly) BOOL wasReceivedByUD;
@property (nonatomic, readonly) BOOL isUserMentioned; @property (nonatomic, readonly) BOOL isUserMentioned;
@ -61,7 +59,6 @@ NS_ASSUME_NONNULL_BEGIN
expiresInSeconds:(uint32_t)expiresInSeconds expiresInSeconds:(uint32_t)expiresInSeconds
quotedMessage:(nullable TSQuotedMessage *)quotedMessage quotedMessage:(nullable TSQuotedMessage *)quotedMessage
linkPreview:(nullable OWSLinkPreview *)linkPreview linkPreview:(nullable OWSLinkPreview *)linkPreview
serverTimestamp:(nullable NSNumber *)serverTimestamp
wasReceivedByUD:(BOOL)wasReceivedByUD wasReceivedByUD:(BOOL)wasReceivedByUD
openGroupInvitationName:(nullable NSString *)openGroupInvitationName openGroupInvitationName:(nullable NSString *)openGroupInvitationName
openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL

@ -21,8 +21,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, getter=wasRead) BOOL read; @property (nonatomic, getter=wasRead) BOOL read;
@property (nonatomic, nullable) NSNumber *serverTimestamp;
@end @end
#pragma mark - #pragma mark -
@ -52,7 +50,6 @@ NS_ASSUME_NONNULL_BEGIN
expiresInSeconds:(uint32_t)expiresInSeconds expiresInSeconds:(uint32_t)expiresInSeconds
quotedMessage:(nullable TSQuotedMessage *)quotedMessage quotedMessage:(nullable TSQuotedMessage *)quotedMessage
linkPreview:(nullable OWSLinkPreview *)linkPreview linkPreview:(nullable OWSLinkPreview *)linkPreview
serverTimestamp:(nullable NSNumber *)serverTimestamp
wasReceivedByUD:(BOOL)wasReceivedByUD wasReceivedByUD:(BOOL)wasReceivedByUD
openGroupInvitationName:(nullable NSString *)openGroupInvitationName openGroupInvitationName:(nullable NSString *)openGroupInvitationName
openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL
@ -77,7 +74,6 @@ NS_ASSUME_NONNULL_BEGIN
_authorId = authorId; _authorId = authorId;
_sourceDeviceId = sourceDeviceId; _sourceDeviceId = sourceDeviceId;
_read = NO; _read = NO;
_serverTimestamp = serverTimestamp;
_wasReceivedByUD = wasReceivedByUD; _wasReceivedByUD = wasReceivedByUD;
_notificationIdentifier = nil; _notificationIdentifier = nil;

@ -38,9 +38,6 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value);
@property (nonatomic, readonly) uint64_t timestamp; @property (nonatomic, readonly) uint64_t timestamp;
@property (nonatomic, readonly) uint64_t sortId; @property (nonatomic, readonly) uint64_t sortId;
@property (nonatomic, readonly) uint64_t receivedAtTimestamp; @property (nonatomic, readonly) uint64_t receivedAtTimestamp;
@property (nonatomic, readonly) BOOL shouldUseServerTime;
- (uint64_t)timestampForUI;
- (NSDate *)dateForUI; - (NSDate *)dateForUI;
@ -80,6 +77,8 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value);
- (void)saveNextSortIdWithTransaction:(YapDatabaseReadWriteTransaction *)transaction - (void)saveNextSortIdWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
NS_SWIFT_NAME(saveNextSortId(transaction:)); NS_SWIFT_NAME(saveNextSortId(transaction:));
- (void)updateTimestamp:(uint64_t)timestamp;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -165,17 +165,6 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value)
#pragma mark Date operations #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 - (uint64_t)timestampForLegacySorting
{ {
return self.timestamp; return self.timestamp;
@ -183,7 +172,7 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value)
- (NSDate *)dateForUI - (NSDate *)dateForUI
{ {
return [NSDate ows_dateWithMillisecondsSince1970:self.timestampForUI]; return [NSDate ows_dateWithMillisecondsSince1970:self.timestamp];
} }
- (NSDate *)receivedAtDate - (NSDate *)receivedAtDate
@ -229,12 +218,6 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value)
- (uint64_t)sortId - (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; return self.timestamp;
} }
@ -280,6 +263,12 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value)
[self saveWithTransaction:transaction]; [self saveWithTransaction:transaction];
} }
- (void)updateTimestamp:(uint64_t)timestamp
{
_timestamp = timestamp;
}
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -159,6 +159,9 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
#pragma mark - Update With... Methods #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. // This method is used to record a successful send to one recipient.
- (void)updateWithSentRecipient:(NSString *)recipientId - (void)updateWithSentRecipient:(NSString *)recipientId
wasSentByUD:(BOOL)wasSentByUD wasSentByUD:(BOOL)wasSentByUD

@ -450,6 +450,12 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
#pragma mark - Update With... Methods #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 - (void)updateWithSendingError:(NSError *)error transaction:(YapDatabaseReadWriteTransaction *)transaction
{ {
[self applyChangeToSelfAndLatestCopy:transaction [self applyChangeToSelfAndLatestCopy:transaction

@ -317,7 +317,7 @@ public final class MessageSender : NSObject {
OpenGroupAPIV2.send(openGroupMessage, to: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in OpenGroupAPIV2.send(openGroupMessage, to: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in
message.openGroupServerMessageID = given(openGroupMessage.serverID) { UInt64($0) } message.openGroupServerMessageID = given(openGroupMessage.serverID) { UInt64($0) }
storage.write(with: { transaction in storage.write(with: { transaction in
MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction) MessageSender.handleSuccessfulMessageSend(message, to: destination, serverTimestamp: openGroupMessage.sentTimestamp, using: transaction)
seal.fulfill(()) seal.fulfill(())
}, completion: { }) }, completion: { })
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in }.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
@ -330,7 +330,7 @@ public final class MessageSender : NSObject {
} }
// MARK: Success & Failure Handling // 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 let transaction = transaction as! YapDatabaseReadWriteTransaction
// Get the visible message if possible // Get the visible message if possible
if let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) { 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 // 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. // real message has no use when we delete a message. It is OK to let it be.
tsMessage.serverHash = message.serverHash tsMessage.serverHash = message.serverHash
// Track the open group server message ID // Track the open group server message ID and update server timestamp
tsMessage.openGroupServerMessageID = message.openGroupServerMessageID ?? 0 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 // Mark the message as sent
var recipients = [ message.recipient! ] var recipients = [ message.recipient! ]
if case .closedGroup(_) = destination, let threadID = message.threadID, // threadID should always be set at this point if case .closedGroup(_) = destination, let threadID = message.threadID, // threadID should always be set at this point

@ -5,6 +5,7 @@ import PromiseKit
public final class ClosedGroupPoller : NSObject { public final class ClosedGroupPoller : NSObject {
private var isPolling: [String:Bool] = [:] private var isPolling: [String:Bool] = [:]
private var timers: [String:Timer] = [:] private var timers: [String:Timer] = [:]
private let internalQueue: DispatchQueue = DispatchQueue(label:"isPollingQueue")
// MARK: Settings // MARK: Settings
private static let minPollInterval: Double = 2 private static let minPollInterval: Double = 2
@ -40,8 +41,11 @@ public final class ClosedGroupPoller : NSObject {
public func startPolling(for groupPublicKey: String) { public func startPolling(for groupPublicKey: String) {
guard !isPolling(for: groupPublicKey) else { return } 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 isPolling[groupPublicKey] = true
setUpPolling(for: groupPublicKey)
} }
@objc public func stop() { @objc public func stop() {
@ -51,8 +55,8 @@ public final class ClosedGroupPoller : NSObject {
} }
public func stopPolling(for groupPublicKey: String) { public func stopPolling(for groupPublicKey: String) {
timers[groupPublicKey]?.invalidate()
isPolling[groupPublicKey] = false isPolling[groupPublicKey] = false
timers[groupPublicKey]?.invalidate()
} }
// MARK: Private API // MARK: Private API
@ -134,6 +138,6 @@ public final class ClosedGroupPoller : NSObject {
// MARK: Convenience // MARK: Convenience
private func isPolling(for groupPublicKey: String) -> Bool { private func isPolling(for groupPublicKey: String) -> Bool {
return isPolling[groupPublicKey] ?? false return internalQueue.sync{ isPolling[groupPublicKey] ?? false }
} }
} }

@ -73,7 +73,6 @@ public final class OpenGroupPollerV2 : NSObject {
let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: message.sentTimestamp) let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: message.sentTimestamp)
envelope.setContent(data) envelope.setContent(data)
envelope.setSource(message.sender!) // Safe because messages with a nil sender are filtered out envelope.setSource(message.sender!) // Safe because messages with a nil sender are filtered out
envelope.setServerTimestamp(message.sentTimestamp)
do { do {
let data = try envelope.buildSerializedData() let data = try envelope.buildSerializedData()
let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.serverID!), isRetry: false, using: transaction) let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.serverID!), isRetry: false, using: transaction)

@ -57,6 +57,9 @@ BOOL IsNoteToSelfEnabled(void);
- (NSUInteger)unreadMessageCountWithTransaction:(YapDatabaseReadTransaction *)transaction - (NSUInteger)unreadMessageCountWithTransaction:(YapDatabaseReadTransaction *)transaction
NS_SWIFT_NAME(unreadMessageCount(transaction:)); NS_SWIFT_NAME(unreadMessageCount(transaction:));
- (BOOL)hasUnreadMentionMessageWithTransaction:(YapDatabaseReadTransaction *)transaction
NS_SWIFT_NAME(hasUnreadMentionMessage(transaction:));
- (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
/** /**

@ -264,6 +264,32 @@ BOOL IsNoteToSelfEnabled(void)
return count; 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 - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{ {
for (id<OWSReadTracking> message in [self unseenMessagesWithTransaction:transaction]) { for (id<OWSReadTracking> message in [self unseenMessagesWithTransaction:transaction]) {

@ -48,7 +48,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension
case let visibleMessage as VisibleMessage: case let visibleMessage as VisibleMessage:
let tsIncomingMessageID = try MessageReceiver.handleVisibleMessage(visibleMessage, associatedWithProto: proto, openGroupID: nil, isBackgroundPoll: false, using: transaction) let tsIncomingMessageID = try MessageReceiver.handleVisibleMessage(visibleMessage, associatedWithProto: proto, openGroupID: nil, isBackgroundPoll: false, using: transaction)
guard let tsIncomingMessage = TSIncomingMessage.fetch(uniqueId: tsIncomingMessageID, transaction: transaction) else { guard let tsIncomingMessage = TSIncomingMessage.fetch(uniqueId: tsIncomingMessageID, transaction: transaction) else {
return self.handleFailure(for: notificationContent) return self.completeSilenty()
} }
let thread = tsIncomingMessage.thread(with: transaction) let thread = tsIncomingMessage.thread(with: transaction)
if thread.isMuted { if thread.isMuted {

@ -15,6 +15,7 @@ public class ThreadViewModel: NSObject {
@objc public let name: String @objc public let name: String
@objc public let isMuted: Bool @objc public let isMuted: Bool
@objc public let isOnlyNotifyingForMentions: Bool @objc public let isOnlyNotifyingForMentions: Bool
@objc public let hasUnreadMentions: Bool
var isContactThread: Bool { var isContactThread: Bool {
return !isGroupThread return !isGroupThread
@ -49,6 +50,7 @@ public class ThreadViewModel: NSObject {
self.unreadCount = thread.unreadMessageCount(transaction: transaction) self.unreadCount = thread.unreadMessageCount(transaction: transaction)
self.hasUnreadMessages = unreadCount > 0 self.hasUnreadMessages = unreadCount > 0
self.hasUnreadMentions = thread.hasUnreadMentionMessage(transaction: transaction)
} }
@objc @objc

Loading…
Cancel
Save