diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 219d691b9..a65d097fd 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -419,7 +419,8 @@ 4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; }; 4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */; }; 4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */; }; - 4CB5F26720F6E1E2004D1B42 /* MessageActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MessageActionsViewController.swift */; }; + 4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */; }; + 4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB5F26820F7D060004D1B42 /* MessageActions.swift */; }; 4CC0B59C20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */; }; 70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70377AAA1918450100CAF501 /* MobileCoreServices.framework */; }; 768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; }; @@ -1083,8 +1084,9 @@ 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = ""; }; 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = ""; }; 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableTextField.swift; sourceTree = ""; }; + 4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = ""; }; 4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationConfigurationSyncOperation.swift; sourceTree = ""; }; - 4CFF4C0920F55BBA005DA313 /* MessageActionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActionsViewController.swift; sourceTree = ""; }; + 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuActionsViewController.swift; sourceTree = ""; }; 69349DE607F5BA6036C9AC60 /* Pods-SignalShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.debug.xcconfig"; sourceTree = ""; }; 70377AAA1918450100CAF501 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 748A5CAEDD7C919FC64C6807 /* Pods_SignalTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1691,7 +1693,7 @@ 452EC6DE205E9E30000E787C /* MediaGalleryViewController.swift */, 45F32C1D205718B000A300D5 /* MediaPageViewController.swift */, 454A84032059C787008B8C75 /* MediaTileViewController.swift */, - 4CFF4C0920F55BBA005DA313 /* MessageActionsViewController.swift */, + 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */, 34CA1C261F7156F300E51C51 /* MessageDetailViewController.swift */, 34B3F84F1E8DF1700035BE1A /* NewContactThreadViewController.h */, 34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */, @@ -1989,6 +1991,7 @@ 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */, 458E38351D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.h */, 458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */, + 4CB5F26820F7D060004D1B42 /* MessageActions.swift */, ); path = Models; sourceTree = ""; @@ -3264,7 +3267,7 @@ 340FC8CD20518C77007AEB0F /* OWSBackupJob.m in Sources */, 34D1F0AE1F867BFC0066283D /* OWSMessageCell.m in Sources */, 4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */, - 4CB5F26720F6E1E2004D1B42 /* MessageActionsViewController.swift in Sources */, + 4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */, 451686AB1F520CDA00AC3D4B /* MultiDeviceProfileKeyUpdateJob.swift in Sources */, 45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */, 340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */, @@ -3350,6 +3353,7 @@ 76EB054018170B33006006FC /* AppDelegate.m in Sources */, 34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */, 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */, + 4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */, 340FC8B5204DAC8D007AEB0F /* AboutTableViewController.m in Sources */, 34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */, 340FC8B9204DAC8D007AEB0F /* UpdateGroupViewController.m in Sources */, diff --git a/Signal/src/Models/MessageActions.swift b/Signal/src/Models/MessageActions.swift new file mode 100644 index 000000000..c35c0a05b --- /dev/null +++ b/Signal/src/Models/MessageActions.swift @@ -0,0 +1,131 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +protocol MessageActionsDelegate: class { + func messageActionsShowDetailsForItem(_ conversationViewItem: ConversationViewItem) + func messageActionsReplyToItem(_ conversationViewItem: ConversationViewItem) +} + +struct MessageActionBuilder { + static func reply(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { + return MenuAction(image: #imageLiteral(resourceName: "ic_reply"), + title: NSLocalizedString("MESSAGE_ACTION_REPLY", comment: "Action sheet button title"), + subtitle: nil, + block: { [weak delegate] (_) in + delegate?.messageActionsReplyToItem(conversationViewItem) + + }) + } + + static func copyText(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { + return MenuAction(image: #imageLiteral(resourceName: "ic_copy"), + title: NSLocalizedString("MESSAGE_ACTION_COPY_TEXT", comment: "Action sheet button title"), + subtitle: nil, + block: { (_) in + conversationViewItem.copyTextAction() + }) + } + + static func showDetails(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { + return MenuAction(image: #imageLiteral(resourceName: "ic_info"), + title: NSLocalizedString("MESSAGE_ACTION_DETAILS", comment: "Action sheet button title"), + subtitle: nil, + block: { [weak delegate] (_) in + delegate?.messageActionsShowDetailsForItem(conversationViewItem) + }) + } + + static func deleteMessage(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { + return MenuAction(image: #imageLiteral(resourceName: "ic_trash"), + title: NSLocalizedString("MESSAGE_ACTION_DELETE_MESSAGE", comment: "Action sheet button title"), + subtitle: NSLocalizedString("MESSAGE_ACTION_DELETE_MESSAGE_SUBTITLE", comment: "Action sheet button subtitle"), + block: { (_) in + conversationViewItem.deleteAction() + }) + } + + static func copyMedia(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { + return MenuAction(image: #imageLiteral(resourceName: "ic_copy"), + title: NSLocalizedString("MESSAGE_ACTION_COPY_MEDIA", comment: "Action sheet button title"), + subtitle: nil, + block: { (_) in + conversationViewItem.copyMediaAction() + }) + } + + static func saveMedia(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MenuAction { + return MenuAction(image: #imageLiteral(resourceName: "ic_download"), + title: NSLocalizedString("MESSAGE_ACTION_SAVE_MEDIA", comment: "Action sheet button title"), + subtitle: nil, + block: { (_) in + conversationViewItem.saveMediaAction() + }) + } +} + +extension ConversationViewItem { + + @objc + func textActions(delegate: MessageActionsDelegate) -> [MenuAction] { + var actions: [MenuAction] = [] + + let replyAction = MessageActionBuilder.reply(conversationViewItem: self, delegate: delegate) + actions.append(replyAction) + + if self.hasBodyTextActionContent { + let copyTextAction = MessageActionBuilder.copyText(conversationViewItem: self, delegate: delegate) + actions.append(copyTextAction) + } + + let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: self, delegate: delegate) + actions.append(deleteAction) + + let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: self, delegate: delegate) + actions.append(showDetailsAction) + + return actions + } + + @objc + func mediaActions(delegate: MessageActionsDelegate) -> [MenuAction] { + var actions: [MenuAction] = [] + + let replyAction = MessageActionBuilder.reply(conversationViewItem: self, delegate: delegate) + actions.append(replyAction) + + if self.hasMediaActionContent { + let copyMediaAction = MessageActionBuilder.copyMedia(conversationViewItem: self, delegate: delegate) + actions.append(copyMediaAction) + let saveMediaAction = MessageActionBuilder.saveMedia(conversationViewItem: self, delegate: delegate) + actions.append(saveMediaAction) + } + + let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: self, delegate: delegate) + actions.append(deleteAction) + + let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: self, delegate: delegate) + actions.append(showDetailsAction) + + return actions + } + + @objc + func quotedMessageActions(delegate: MessageActionsDelegate) -> [MenuAction] { + let replyAction = MessageActionBuilder.reply(conversationViewItem: self, delegate: delegate) + let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: self, delegate: delegate) + let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: self, delegate: delegate) + + return [replyAction, deleteAction, showDetailsAction] + } + + @objc + func infoMessageActions(delegate: MessageActionsDelegate) -> [MenuAction] { + let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: self, delegate: delegate) + + return [deleteAction] + } +} diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 2021d4d8f..454666edb 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -132,6 +132,7 @@ typedef enum : NSUInteger { ConversationViewCellDelegate, ConversationInputTextViewDelegate, MessageActionsDelegate, + MenuActionsViewControllerDelegate, OWSMessageBubbleViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource, @@ -1981,13 +1982,6 @@ typedef enum : NSUInteger { #pragma mark - MessageActionsDelegate -- (void)messageActionsDidHide:(MessageActionsViewController *)messageActionsViewController -{ - [[OWSWindowManager sharedManager] hideMessageActionsWindow:messageActionsViewController]; - - [self updateShouldObserveDBModifications]; -} - - (void)messageActionsShowDetailsForItem:(ConversationViewItem *)conversationViewItem { [self showDetailViewForViewItem:conversationViewItem]; @@ -1998,7 +1992,16 @@ typedef enum : NSUInteger { [self populateReplyForViewItem:conversationViewItem]; } -- (void)messageActions:(MessageActionsViewController *)messageActionsViewController +#pragma mark - MenuActionsViewControllerDelegate + +- (void)menuActionsDidHide:(MenuActionsViewController *)menuActionsViewController +{ + [[OWSWindowManager sharedManager] hideMenuActionsWindow]; + + [self updateShouldObserveDBModifications]; +} + +- (void)menuActions:(MenuActionsViewController *)menuActionsViewController isPresentingWithVerticalFocusChange:(CGFloat)verticalChange { UIEdgeInsets oldInset = self.collectionView.contentInset; @@ -2026,7 +2029,7 @@ typedef enum : NSUInteger { self.collectionView.contentInset = newInset; } -- (void)messageActions:(MessageActionsViewController *)messageActionsViewController +- (void)menuActions:(MenuActionsViewController *)menuActionsViewController isDismissingWithVerticalFocusChange:(CGFloat)verticalChange { UIEdgeInsets oldInset = self.collectionView.contentInset; @@ -2058,36 +2061,36 @@ typedef enum : NSUInteger { - (void)conversationCell:(ConversationViewCell *)cell didLongpressMediaViewItem:(ConversationViewItem *)viewItem { - NSArray *messageActions = [viewItem mediaActionsWithDelegate:self]; + NSArray *messageActions = [viewItem mediaActionsWithDelegate:self]; [self presentMessageActions:messageActions withFocusedCell:cell]; } - (void)conversationCell:(ConversationViewCell *)cell didLongpressTextViewItem:(ConversationViewItem *)viewItem { - NSArray *messageActions = [viewItem textActionsWithDelegate:self]; + NSArray *messageActions = [viewItem textActionsWithDelegate:self]; [self presentMessageActions:messageActions withFocusedCell:cell]; } - (void)conversationCell:(ConversationViewCell *)cell didLongpressQuoteViewItem:(ConversationViewItem *)viewItem { - NSArray *messageActions = [viewItem quotedMessageActionsWithDelegate:self]; + NSArray *messageActions = [viewItem quotedMessageActionsWithDelegate:self]; [self presentMessageActions:messageActions withFocusedCell:cell]; } - (void)conversationCell:(ConversationViewCell *)cell didLongpressSystemMessageViewItem:(ConversationViewItem *)viewItem { - NSArray *messageActions = [viewItem infoMessageActionsWithDelegate:self]; + NSArray *messageActions = [viewItem infoMessageActionsWithDelegate:self]; [self presentMessageActions:messageActions withFocusedCell:cell]; } -- (void)presentMessageActions:(NSArray *)messageActions withFocusedCell:(ConversationViewCell *)cell +- (void)presentMessageActions:(NSArray *)messageActions withFocusedCell:(ConversationViewCell *)cell { - MessageActionsViewController *messageActionsViewController = - [[MessageActionsViewController alloc] initWithFocusedView:cell actions:messageActions]; + MenuActionsViewController *menuActionsViewController = + [[MenuActionsViewController alloc] initWithFocusedView:cell actions:messageActions]; - messageActionsViewController.delegate = self; + menuActionsViewController.delegate = self; - [[OWSWindowManager sharedManager] showMessageActionsWindow:messageActionsViewController]; + [[OWSWindowManager sharedManager] showMenuActionsWindow:menuActionsViewController]; [self updateShouldObserveDBModifications]; } @@ -4550,7 +4553,7 @@ typedef enum : NSUInteger { return; } - if (OWSWindowManager.sharedManager.isPresentingMessageActions) { + if (OWSWindowManager.sharedManager.isPresentingMenuActions) { self.shouldObserveDBModifications = NO; return; } diff --git a/Signal/src/ViewControllers/MessageActionsViewController.swift b/Signal/src/ViewControllers/MenuActionsViewController.swift similarity index 55% rename from Signal/src/ViewControllers/MessageActionsViewController.swift rename to Signal/src/ViewControllers/MenuActionsViewController.swift index 99f43c596..af44f5ca7 100644 --- a/Signal/src/ViewControllers/MessageActionsViewController.swift +++ b/Signal/src/ViewControllers/MenuActionsViewController.swift @@ -5,148 +5,41 @@ import Foundation @objc -protocol MessageActionsDelegate: class { - func messageActionsDidHide(_ messageActionsViewController: MessageActionsViewController) - func messageActionsShowDetailsForItem(_ conversationViewItem: ConversationViewItem) - func messageActionsReplyToItem(_ conversationViewItem: ConversationViewItem) - func messageActions(_ messageActionsViewController: MessageActionsViewController, isPresentingWithVerticalFocusChange: CGFloat) - func messageActions(_ messageActionsViewController: MessageActionsViewController, isDismissingWithVerticalFocusChange: CGFloat) -} +public class MenuAction: NSObject { + let block: (MenuAction) -> Void + let image: UIImage + let title: String + let subtitle: String? -struct MessageActionBuilder { - static func reply(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MessageAction { - return MessageAction(image: #imageLiteral(resourceName: "ic_reply"), - title: NSLocalizedString("MESSAGE_ACTION_REPLY", comment: "Action sheet button title"), - subtitle: nil, - block: { [weak delegate] (_) in - delegate?.messageActionsReplyToItem(conversationViewItem) - - }) - } - - static func copyText(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MessageAction { - return MessageAction(image: #imageLiteral(resourceName: "ic_copy"), - title: NSLocalizedString("MESSAGE_ACTION_COPY_TEXT", comment: "Action sheet button title"), - subtitle: nil, - block: { (_) in - conversationViewItem.copyTextAction() - }) - } - - static func showDetails(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MessageAction { - return MessageAction(image: #imageLiteral(resourceName: "ic_info"), - title: NSLocalizedString("MESSAGE_ACTION_DETAILS", comment: "Action sheet button title"), - subtitle: nil, - block: { [weak delegate] (_) in - delegate?.messageActionsShowDetailsForItem(conversationViewItem) - }) - } - - static func deleteMessage(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MessageAction { - return MessageAction(image: #imageLiteral(resourceName: "ic_trash"), - title: NSLocalizedString("MESSAGE_ACTION_DELETE_MESSAGE", comment: "Action sheet button title"), - subtitle: NSLocalizedString("MESSAGE_ACTION_DELETE_MESSAGE_SUBTITLE", comment: "Action sheet button subtitle"), - block: { (_) in - conversationViewItem.deleteAction() - }) - } - - static func copyMedia(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MessageAction { - return MessageAction(image: #imageLiteral(resourceName: "ic_copy"), - title: NSLocalizedString("MESSAGE_ACTION_COPY_MEDIA", comment: "Action sheet button title"), - subtitle: nil, - block: { (_) in - conversationViewItem.copyMediaAction() - }) - } - - static func saveMedia(conversationViewItem: ConversationViewItem, delegate: MessageActionsDelegate) -> MessageAction { - return MessageAction(image: #imageLiteral(resourceName: "ic_download"), - title: NSLocalizedString("MESSAGE_ACTION_SAVE_MEDIA", comment: "Action sheet button title"), - subtitle: nil, - block: { (_) in - conversationViewItem.saveMediaAction() - }) - } -} - -extension ConversationViewItem { - - @objc - func textActions(delegate: MessageActionsDelegate) -> [MessageAction] { - var actions: [MessageAction] = [] - - let replyAction = MessageActionBuilder.reply(conversationViewItem: self, delegate: delegate) - actions.append(replyAction) - - if self.hasBodyTextActionContent { - let copyTextAction = MessageActionBuilder.copyText(conversationViewItem: self, delegate: delegate) - actions.append(copyTextAction) - } - - let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: self, delegate: delegate) - actions.append(deleteAction) - - let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: self, delegate: delegate) - actions.append(showDetailsAction) - - return actions - } - - @objc - func mediaActions(delegate: MessageActionsDelegate) -> [MessageAction] { - var actions: [MessageAction] = [] - - let replyAction = MessageActionBuilder.reply(conversationViewItem: self, delegate: delegate) - actions.append(replyAction) - - if self.hasMediaActionContent { - let copyMediaAction = MessageActionBuilder.copyMedia(conversationViewItem: self, delegate: delegate) - actions.append(copyMediaAction) - let saveMediaAction = MessageActionBuilder.saveMedia(conversationViewItem: self, delegate: delegate) - actions.append(saveMediaAction) - } - - let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: self, delegate: delegate) - actions.append(deleteAction) - - let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: self, delegate: delegate) - actions.append(showDetailsAction) - - return actions - } - - @objc - func quotedMessageActions(delegate: MessageActionsDelegate) -> [MessageAction] { - let replyAction = MessageActionBuilder.reply(conversationViewItem: self, delegate: delegate) - let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: self, delegate: delegate) - let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: self, delegate: delegate) - - return [replyAction, deleteAction, showDetailsAction] - } - - @objc - func infoMessageActions(delegate: MessageActionsDelegate) -> [MessageAction] { - let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: self, delegate: delegate) - - return [deleteAction] + public init(image: UIImage, title: String, subtitle: String?, block: @escaping (MenuAction) -> Void) { + self.image = image + self.title = title + self.subtitle = subtitle + self.block = block } } @objc -class MessageActionsViewController: UIViewController, MessageActionSheetDelegate { +protocol MenuActionsViewControllerDelegate: class { + func menuActionsDidHide(_ menuActionsViewController: MenuActionsViewController) + func menuActions(_ menuActionsViewController: MenuActionsViewController, isPresentingWithVerticalFocusChange: CGFloat) + func menuActions(_ menuActionsViewController: MenuActionsViewController, isDismissingWithVerticalFocusChange: CGFloat) +} + +@objc +class MenuActionsViewController: UIViewController, MenuActionSheetDelegate { @objc - weak var delegate: MessageActionsDelegate? + weak var delegate: MenuActionsViewControllerDelegate? private let focusedView: UIView - private let actionSheetView: MessageActionSheetView + private let actionSheetView: MenuActionSheetView @objc - required init(focusedView: UIView, actions: [MessageAction]) { + required init(focusedView: UIView, actions: [MenuAction]) { self.focusedView = focusedView - self.actionSheetView = MessageActionSheetView(actions: actions) + self.actionSheetView = MenuActionSheetView(actions: actions) super.init(nibName: nil, bundle: nil) actionSheetView.delegate = self @@ -206,8 +99,6 @@ class MessageActionsViewController: UIViewController, MessageActionSheetDelegate } private func animatePresentation() { - // TODO first time only? - guard let actionSheetViewVerticalConstraint = self.actionSheetViewVerticalConstraint else { owsFail("\(self.logTag) in \(#function) actionSheetViewVerticalConstraint was unexpectedly nil") return @@ -255,27 +146,27 @@ class MessageActionsViewController: UIViewController, MessageActionSheetDelegate let offset = -overlap self.presentationFocusOffset = offset - self.delegate?.messageActions(self, isPresentingWithVerticalFocusChange: offset) + self.delegate?.menuActions(self, isPresentingWithVerticalFocusChange: offset) }, completion: nil) } - private func animateDismiss(action: MessageAction?) { + private func animateDismiss(action: MenuAction?) { guard let actionSheetViewVerticalConstraint = self.actionSheetViewVerticalConstraint else { owsFail("\(self.logTag) in \(#function) actionSheetVerticalConstraint was unexpectedly nil") - self.delegate?.messageActionsDidHide(self) + self.delegate?.menuActionsDidHide(self) return } guard let snapshotView = self.snapshotView else { owsFail("\(self.logTag) in \(#function) snapshotView was unexpectedly nil") - self.delegate?.messageActionsDidHide(self) + self.delegate?.menuActionsDidHide(self) return } guard let presentationFocusOffset = self.presentationFocusOffset else { owsFail("\(self.logTag) in \(#function) presentationFocusOffset was unexpectedly nil") - self.delegate?.messageActionsDidHide(self) + self.delegate?.menuActionsDidHide(self) return } @@ -293,11 +184,11 @@ class MessageActionsViewController: UIViewController, MessageActionSheetDelegate snapshotView.frame.origin.y -= presentationFocusOffset // this helps when focused view is above navbars, etc. snapshotView.alpha = 0 - self.delegate?.messageActions(self, isDismissingWithVerticalFocusChange: presentationFocusOffset) + self.delegate?.menuActions(self, isDismissingWithVerticalFocusChange: presentationFocusOffset) }, completion: { _ in self.view.isHidden = true - self.delegate?.messageActionsDidHide(self) + self.delegate?.menuActionsDidHide(self) if let action = action { action.block(action) } @@ -316,43 +207,92 @@ class MessageActionsViewController: UIViewController, MessageActionSheetDelegate animateDismiss(action: nil) } - // MARK: MessageActionSheetDelegate + // MARK: MenuActionSheetDelegate - func actionSheet(_ actionSheet: MessageActionSheetView, didSelectAction action: MessageAction) { + func actionSheet(_ actionSheet: MenuActionSheetView, didSelectAction action: MenuAction) { animateDismiss(action: action) } } -// MARK: ActionView +protocol MenuActionSheetDelegate: class { + func actionSheet(_ actionSheet: MenuActionSheetView, didSelectAction action: MenuAction) +} -@objc -public class MessageAction: NSObject { - let block: (MessageAction) -> Void - let image: UIImage - let title: String - let subtitle: String? +class MenuActionSheetView: UIView, MenuActionViewDelegate { - public init(image: UIImage, title: String, subtitle: String?, block: @escaping (MessageAction) -> Void) { - self.image = image - self.title = title - self.subtitle = subtitle - self.block = block + private let actionStackView: UIStackView + private var actions: [MenuAction] + weak var delegate: MenuActionSheetDelegate? + + override var bounds: CGRect { + didSet { + updateMask() + } + } + + convenience init(actions: [MenuAction]) { + self.init(frame: CGRect.zero) + actions.forEach { self.addAction($0) } + } + + override init(frame: CGRect) { + actionStackView = UIStackView() + actionStackView.axis = .vertical + actionStackView.spacing = CGHairlineWidth() + + actions = [] + + super.init(frame: frame) + + backgroundColor = UIColor.ows_light10 + addSubview(actionStackView) + actionStackView.autoPinToSuperviewEdges() + + self.clipsToBounds = true + + // Prevent panning from percolating to the superview, which would + // cause us to dismiss + let panGestureSink = UIPanGestureRecognizer(target: nil, action: nil) + self.addGestureRecognizer(panGestureSink) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("not implemented") + } + + public func addAction(_ action: MenuAction) { + let actionView = MenuActionView(action: action) + actionView.delegate = self + actions.append(action) + self.actionStackView.addArrangedSubview(actionView) + } + + // MARK: MenuActionViewDelegate + + func actionView(_ actionView: MenuActionView, didSelectAction action: MenuAction) { + self.delegate?.actionSheet(self, didSelectAction: action) + } + + // MARK: + + private func updateMask() { + let cornerRadius: CGFloat = 16 + let path: UIBezierPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)) + let mask = CAShapeLayer() + mask.path = path.cgPath + self.layer.mask = mask } } -protocol MessageActionSheetDelegate: class { - func actionSheet(_ actionSheet: MessageActionSheetView, didSelectAction action: MessageAction) +protocol MenuActionViewDelegate: class { + func actionView(_ actionView: MenuActionView, didSelectAction action: MenuAction) } -protocol MessageActionViewDelegate: class { - func actionView(_ actionView: MessageActionView, didSelectAction action: MessageAction) -} +class MenuActionView: UIButton { + public weak var delegate: MenuActionViewDelegate? + private let action: MenuAction -class MessageActionView: UIButton { - public weak var delegate: MessageActionViewDelegate? - private let action: MessageAction - - required init(action: MessageAction) { + required init(action: MenuAction) { self.action = action super.init(frame: CGRect.zero) @@ -413,69 +353,3 @@ class MessageActionView: UIButton { fatalError("not implemented") } } - -class MessageActionSheetView: UIView, MessageActionViewDelegate { - - private let actionStackView: UIStackView - private var actions: [MessageAction] - weak var delegate: MessageActionSheetDelegate? - - override var bounds: CGRect { - didSet { - updateMask() - } - } - - convenience init(actions: [MessageAction]) { - self.init(frame: CGRect.zero) - actions.forEach { self.addAction($0) } - } - - override init(frame: CGRect) { - actionStackView = UIStackView() - actionStackView.axis = .vertical - actionStackView.spacing = CGHairlineWidth() - - actions = [] - - super.init(frame: frame) - - backgroundColor = UIColor.ows_light10 - addSubview(actionStackView) - actionStackView.autoPinToSuperviewEdges() - - self.clipsToBounds = true - - // Prevent panning from percolating to the superview, which would - // cause us to dismiss - let panGestureSink = UIPanGestureRecognizer(target: nil, action: nil) - self.addGestureRecognizer(panGestureSink) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("not implemented") - } - - public func addAction(_ action: MessageAction) { - let actionView = MessageActionView(action: action) - actionView.delegate = self - actions.append(action) - self.actionStackView.addArrangedSubview(actionView) - } - - // MARK: MessageActionViewDelegate - - func actionView(_ actionView: MessageActionView, didSelectAction action: MessageAction) { - self.delegate?.actionSheet(self, didSelectAction: action) - } - - // MARK: - - private func updateMask() { - let cornerRadius: CGFloat = 16 - let path: UIBezierPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)) - let mask = CAShapeLayer() - mask.path = path.cgPath - self.layer.mask = mask - } -} diff --git a/SignalMessaging/utils/OWSWindowManager.h b/SignalMessaging/utils/OWSWindowManager.h index 2719d2161..4fee1784d 100644 --- a/SignalMessaging/utils/OWSWindowManager.h +++ b/SignalMessaging/utils/OWSWindowManager.h @@ -29,10 +29,10 @@ extern const UIWindowLevel UIWindowLevel_Background; #pragma mark - Message Actions -@property (nonatomic, readonly) BOOL isPresentingMessageActions; +@property (nonatomic, readonly) BOOL isPresentingMenuActions; -- (void)showMessageActionsWindow:(UIViewController *)messageActionsViewController; -- (void)hideMessageActionsWindow:(UIViewController *)messageActionsViewController; +- (void)showMenuActionsWindow:(UIViewController *)menuActionsViewController; +- (void)hideMenuActionsWindow; #pragma mark - Calls diff --git a/SignalMessaging/utils/OWSWindowManager.m b/SignalMessaging/utils/OWSWindowManager.m index af65f5db1..dd35b9cb1 100644 --- a/SignalMessaging/utils/OWSWindowManager.m +++ b/SignalMessaging/utils/OWSWindowManager.m @@ -101,8 +101,8 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) @property (nonatomic) UINavigationController *callNavigationController; // UIWindowLevel_MessageActions -@property (nonatomic) UIWindow *messageActionsWindow; -@property (nonatomic, nullable) UIViewController *messageActionsViewController; +@property (nonatomic) UIWindow *menuActionsWindow; +@property (nonatomic, nullable) UIViewController *menuActionsViewController; // UIWindowLevel_Background if inactive, // UIWindowLevel_ScreenBlocking() if active. @@ -157,7 +157,7 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) self.returnToCallWindow = [self createReturnToCallWindow:rootWindow]; self.callViewWindow = [self createCallViewWindow:rootWindow]; - self.messageActionsWindow = [self createMessageActionsWindowWithRoowWindow:rootWindow]; + self.menuActionsWindow = [self createMenuActionsWindowWithRoowWindow:rootWindow]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarFrame:) @@ -200,7 +200,7 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) return window; } -- (UIWindow *)createMessageActionsWindowWithRoowWindow:(UIWindow *)rootWindow +- (UIWindow *)createMenuActionsWindowWithRoowWindow:(UIWindow *)rootWindow { UIWindow *window; if (@available(iOS 11, *)) { @@ -262,25 +262,23 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) #pragma mark - Message Actions -- (BOOL)isPresentingMessageActions +- (BOOL)isPresentingMenuActions { - return self.messageActionsViewController != nil; + return self.menuActionsViewController != nil; } -- (void)showMessageActionsWindow:(UIViewController *)messageActionsViewController +- (void)showMenuActionsWindow:(UIViewController *)menuActionsViewController { - self.messageActionsViewController = messageActionsViewController; - self.messageActionsWindow.rootViewController = messageActionsViewController; + self.menuActionsViewController = menuActionsViewController; + self.menuActionsWindow.rootViewController = menuActionsViewController; [self ensureWindowState]; } -- (void)hideMessageActionsWindow:(UIViewController *)messageActionsViewController +- (void)hideMenuActionsWindow { - OWSAssert(self.messageActionsViewController == messageActionsViewController); - - self.messageActionsWindow.rootViewController = nil; - self.messageActionsViewController = nil; + self.menuActionsWindow.rootViewController = nil; + self.menuActionsViewController = nil; [self ensureWindowState]; } @@ -404,7 +402,7 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) [self ensureCallViewWindowHidden]; [self ensureMessageActionsWindowHidden]; [self ensureScreenBlockWindowHidden]; - } else if (self.messageActionsViewController) { + } else if (self.menuActionsViewController) { // Show Message Actions [self ensureRootWindowShown]; @@ -503,23 +501,23 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) { OWSAssertIsOnMainThread(); - if (self.messageActionsWindow.hidden) { + if (self.menuActionsWindow.hidden) { DDLogInfo(@"%@ showing message actions window.", self.logTag); } // Do not make key, we want the keyboard to stay popped. - self.messageActionsWindow.hidden = NO; + self.menuActionsWindow.hidden = NO; } - (void)ensureMessageActionsWindowHidden { OWSAssertIsOnMainThread(); - if (!self.messageActionsWindow.hidden) { + if (!self.menuActionsWindow.hidden) { DDLogInfo(@"%@ hiding message actions window.", self.logTag); } - self.messageActionsWindow.hidden = YES; + self.menuActionsWindow.hidden = YES; } - (void)ensureScreenBlockWindowShown