diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 473c025560..220a6c7933 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7942,3 +7942,13 @@ Sorry for the inconvenience."; "StickerPacks.DeleteEmojiPacksConfirmation_1" = "Delete 1 Emoji Pack"; "StickerPacks.DeleteEmojiPacksConfirmation_any" = "Delete %@ Emoji Packs"; + +"KeyCommand.LockWithPasscode" = "Lock with Passcode"; +"KeyCommand.Play" = "Play Video"; +"KeyCommand.Pause" = "Pause Video"; +"KeyCommand.SeekBackward" = "Seek Backward"; +"KeyCommand.SeekForward" = "Seek Forward"; +"KeyCommand.Share" = "Share"; +"KeyCommand.SwitchToPIP" = "Switch to Picture-in-Picture"; +"KeyCommand.EnterFullscreen" = "Enter Fullscreen"; +"KeyCommand.ExitFullscreen" = "Exit Fullscreen"; diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift index 4749f78362..b52a183491 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift @@ -25,6 +25,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { self.gestureRecognizer?.isEnabled = self.sendButtonLongPressEnabled } } + + private var sendButtonPointerInteraction: PointerInteraction? private var validLayout: CGSize? @@ -39,7 +41,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { self.backgroundNode = ASDisplayNode() self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor self.backgroundNode.clipsToBounds = true - self.sendButton = HighlightTrackingButtonNode(pointerStyle: .lift) + self.sendButton = HighlightTrackingButtonNode(pointerStyle: nil) self.textNode = ImmediateTextNode() self.textNode.attributedText = NSAttributedString(string: self.strings.MediaPicker_Send, font: Font.semibold(17.0), textColor: theme.chat.inputPanel.actionControlForegroundColor) @@ -90,6 +92,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { strongSelf.sendButtonLongPressed?(strongSelf.sendContainerNode, recognizer) } } + + self.sendButtonPointerInteraction = PointerInteraction(view: self.sendButton.view, customInteractionView: self.backgroundNode.view, style: .lift) } func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) { diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index c5de228318..8bfcc7fd59 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2424,10 +2424,30 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.composePressed() } }), + KeyShortcut(title: strings.KeyCommand_LockWithPasscode, input: "L", modifiers: [.command], action: { [weak self] in + if let strongSelf = self { + strongSelf.context.sharedContext.appLockContext.lock() + } + }), KeyShortcut(title: strings.KeyCommand_Find, input: "\t", modifiers: [], action: toggleSearch), KeyShortcut(input: UIKeyCommand.inputEscape, modifiers: [], action: toggleSearch) ] + let openTab: (Int) -> Void = { [weak self] index in + if let strongSelf = self { + let filters = strongSelf.chatListDisplayNode.containerNode.availableFilters + if index > filters.count - 1 { + return + } + switch filters[index] { + case .all: + strongSelf.selectTab(id: .all) + case let .filter(filter): + strongSelf.selectTab(id: .filter(filter.id)) + } + } + } + let openChat: (Int) -> Void = { [weak self] index in if let strongSelf = self { if index == 0 { @@ -2438,13 +2458,23 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - let chatShortcuts: [KeyShortcut] = (0 ... 9).map { index in + let folderShortcuts: [KeyShortcut] = (0 ... 9).map { index in return KeyShortcut(input: "\(index)", modifiers: [.command], action: { + if index == 0 { + openChat(0) + } else { + openTab(index - 1) + } + }) + } + + let chatShortcuts: [KeyShortcut] = (0 ... 9).map { index in + return KeyShortcut(input: "\(index)", modifiers: [.command, .alternate], action: { openChat(index) }) } - return inputShortcuts + chatShortcuts + return inputShortcuts + folderShortcuts + chatShortcuts } override public func toolbarActionSelected(action: ToolbarActionOption) { diff --git a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift index b9f8461875..46a7d32b1a 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift @@ -88,6 +88,8 @@ private final class ItemNode: ASDisplayNode { private var theme: PresentationTheme? + private var pointerInteraction: PointerInteraction? + init(pressed: @escaping (Bool) -> Void, requestedDeletion: @escaping () -> Void, contextGesture: @escaping (ContextExtractedContentContainingNode, ContextGesture, Bool) -> Void) { self.pressed = pressed self.requestedDeletion = requestedDeletion @@ -189,6 +191,12 @@ private final class ItemNode: ASDisplayNode { } } + override func didLoad() { + super.didLoad() + + self.pointerInteraction = PointerInteraction(view: self.containerNode.view, customInteractionView: nil, style: .insetRectangle(-10.0, 4.0)) + } + @objc private func buttonPressed() { self.pressed(self.isDisabled) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 658e20eb15..e78ae7392b 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -2106,7 +2106,7 @@ public final class ChatListNode: ListView { |> take(1) |> deliverOnMainQueue).start(next: { update in let entries = update.view.entries - if entries.count > index, case let .MessageEntry(index, _, _, _, _, renderedPeer, _, _, _, _) = entries[10 - index - 1] { + if entries.count > index, case let .MessageEntry(index, _, _, _, _, renderedPeer, _, _, _, _) = entries[9 - index - 1] { let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: .absoluteLowerBound, scrollPosition: .center(.top), animated: true, filter: filter) self.setChatListLocation(location) self.peerSelected?(EnginePeer(renderedPeer.peer!), false, false, nil) diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 4c73aa192f..bbee69c4ba 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -405,11 +405,10 @@ public class ContactsController: ViewController { let controller = strongSelf.context.sharedContext.makePeersNearbyController(context: strongSelf.context) controller.navigationPresentation = .master if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { - navigationController.pushViewController(controller, animated: true, completion: { [weak self] in - if let strongSelf = self { - strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) - } - }) + var controllers = navigationController.viewControllers.filter { !($0 is PermissionController) } + controllers.append(controller) + navigationController.setViewControllers(controllers, animated: true) + strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) } } diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 98d5d21b73..d3d8c83b6e 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -2205,6 +2205,18 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi return self.dismissNode.view } + + fileprivate func performHighlightedAction() { + self.presentationNode?.highlightGestureFinished(performAction: true) + } + + fileprivate func decreaseHighlightedIndex() { + self.presentationNode?.decreaseHighlightedIndex() + } + + fileprivate func increaseHighlightedIndex() { + self.presentationNode?.increaseHighlightedIndex() + } } public final class ContextControllerLocationViewInfo { @@ -2350,7 +2362,7 @@ public protocol ContextControllerItemsContent: AnyObject { ) -> ContextControllerItemsNode } -public final class ContextController: ViewController, StandalonePresentableController, ContextControllerProtocol { +public final class ContextController: ViewController, StandalonePresentableController, ContextControllerProtocol, KeyShortcutResponder { public struct Items { public enum Content { case list([ContextMenuItem]) @@ -2644,4 +2656,44 @@ public final class ContextController: ViewController, StandalonePresentableContr public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) { self.controllerNode.addRelativeContentOffset(offset, transition: transition) } + + public var keyShortcuts: [KeyShortcut] { + return [ + KeyShortcut( + input: UIKeyCommand.inputEscape, + modifiers: [], + action: { [weak self] in + self?.dismissWithoutContent() + } + ), + KeyShortcut( + input: "W", + modifiers: [.command], + action: { [weak self] in + self?.dismissWithoutContent() + } + ), + KeyShortcut( + input: "\r", + modifiers: [], + action: { [weak self] in + self?.controllerNode.performHighlightedAction() + } + ), + KeyShortcut( + input: UIKeyCommand.inputUpArrow, + modifiers: [], + action: { [weak self] in + self?.controllerNode.decreaseHighlightedIndex() + } + ), + KeyShortcut( + input: UIKeyCommand.inputDownArrow, + modifiers: [], + action: { [weak self] in + self?.controllerNode.increaseHighlightedIndex() + } + ) + ] + } } diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 26ea4f0131..6abcc060b9 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -21,6 +21,9 @@ public protocol ContextControllerActionsStackItemNode: ASDisplayNode { func highlightGestureMoved(location: CGPoint) func highlightGestureFinished(performAction: Bool) + + func decreaseHighlightedIndex() + func increaseHighlightedIndex() } public protocol ContextControllerActionsStackItem: AnyObject { @@ -585,6 +588,34 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack } } } + + func decreaseHighlightedIndex() { + let previousHighlightedItemNode: Item? = self.highlightedItemNode + if let highlightedItemNode = self.highlightedItemNode, let index = self.itemNodes.firstIndex(where: { $0 === highlightedItemNode }) { + self.highlightedItemNode = self.itemNodes[max(0, index - 1)] + } else { + self.highlightedItemNode = self.itemNodes.first + } + + if previousHighlightedItemNode !== self.highlightedItemNode { + previousHighlightedItemNode?.node.updateIsHighlighted(isHighlighted: false) + self.highlightedItemNode?.node.updateIsHighlighted(isHighlighted: true) + } + } + + func increaseHighlightedIndex() { + let previousHighlightedItemNode: Item? = self.highlightedItemNode + if let highlightedItemNode = self.highlightedItemNode, let index = self.itemNodes.firstIndex(where: { $0 === highlightedItemNode }) { + self.highlightedItemNode = self.itemNodes[min(self.itemNodes.count - 1, index + 1)] + } else { + self.highlightedItemNode = self.itemNodes.first + } + + if previousHighlightedItemNode !== self.highlightedItemNode { + previousHighlightedItemNode?.node.updateIsHighlighted(isHighlighted: false) + self.highlightedItemNode?.node.updateIsHighlighted(isHighlighted: true) + } + } } private let items: [ContextMenuItem] @@ -663,6 +694,12 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta func highlightGestureFinished(performAction: Bool) { } + + func decreaseHighlightedIndex() { + } + + func increaseHighlightedIndex() { + } } private let content: ContextControllerItemsContent @@ -925,6 +962,14 @@ final class ContextControllerActionsStackNode: ASDisplayNode { } self.node.highlightGestureFinished(performAction: performAction) } + + func decreaseHighlightedIndex() { + self.node.decreaseHighlightedIndex() + } + + func increaseHighlightedIndex() { + self.node.increaseHighlightedIndex() + } } private let getController: () -> ContextControllerProtocol? @@ -1229,6 +1274,18 @@ final class ContextControllerActionsStackNode: ASDisplayNode { } } + func decreaseHighlightedIndex() { + if let topItemContainer = self.itemContainers.last { + topItemContainer.decreaseHighlightedIndex() + } + } + + func increaseHighlightedIndex() { + if let topItemContainer = self.itemContainers.last { + topItemContainer.increaseHighlightedIndex() + } + } + func updatePanSelection(isEnabled: Bool) { if let selectionPanGesture = self.selectionPanGesture { selectionPanGesture.isEnabled = isEnabled diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index 66d0b94623..491f725642 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -315,6 +315,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } } + func decreaseHighlightedIndex() { + self.actionsStackNode.decreaseHighlightedIndex() + } + + func increaseHighlightedIndex() { + self.actionsStackNode.increaseHighlightedIndex() + } + func replaceItems(items: ContextController.Items, animated: Bool) { self.actionsStackNode.replace(item: makeContextControllerActionsStackItem(items: items), animated: animated) } diff --git a/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift index 95a0af80d9..2d79af8384 100644 --- a/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift @@ -31,5 +31,8 @@ protocol ContextControllerPresentationNode: ASDisplayNode { func highlightGestureMoved(location: CGPoint, hover: Bool) func highlightGestureFinished(performAction: Bool) + func decreaseHighlightedIndex() + func increaseHighlightedIndex() + func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) } diff --git a/submodules/Display/Source/ActionSheetButtonItem.swift b/submodules/Display/Source/ActionSheetButtonItem.swift index 749de57596..7a37d67160 100644 --- a/submodules/Display/Source/ActionSheetButtonItem.swift +++ b/submodules/Display/Source/ActionSheetButtonItem.swift @@ -87,13 +87,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { self.button.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { - if highlighted { - strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor - } else { - UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor - }) - } + strongSelf.setHighlighted(highlighted, animated: true) } } @@ -104,16 +98,35 @@ public class ActionSheetButtonNode: ActionSheetItemNode { } } + override func setHighlighted(_ highlighted: Bool, animated: Bool) { + self.highlightedUpdated(highlighted) + if highlighted { + self.backgroundNode.backgroundColor = self.theme.itemHighlightedBackgroundColor + } else { + if animated { + UIView.animate(withDuration: 0.3, animations: { + self.backgroundNode.backgroundColor = self.theme.itemBackgroundColor + }) + } else { + self.backgroundNode.backgroundColor = self.theme.itemBackgroundColor + } + } + } + + override func performAction() { + self.buttonPressed() + } + public override func didLoad() { super.didLoad() self.pointerInteraction = PointerInteraction(node: self, style: .hover, willEnter: { [weak self] in if let strongSelf = self { - strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor + strongSelf.setHighlighted(true, animated: false) } }, willExit: { [weak self] in if let strongSelf = self { - strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor + strongSelf.setHighlighted(false, animated: false) } }) } diff --git a/submodules/Display/Source/ActionSheetController.swift b/submodules/Display/Source/ActionSheetController.swift index a4d62aa976..8727b6e887 100644 --- a/submodules/Display/Source/ActionSheetController.swift +++ b/submodules/Display/Source/ActionSheetController.swift @@ -6,7 +6,7 @@ public protocol ActionSheetGroupOverlayNode: ASDisplayNode { func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) } -open class ActionSheetController: ViewController, PresentableController, StandalonePresentableController { +open class ActionSheetController: ViewController, PresentableController, StandalonePresentableController, KeyShortcutResponder { private var actionSheetNode: ActionSheetControllerNode { return self.displayNode as! ActionSheetControllerNode } @@ -94,4 +94,44 @@ open class ActionSheetController: ViewController, PresentableController, Standal self.actionSheetNode.setItemGroupOverlayNode(groupIndex: groupIndex, node: node) } } + + public var keyShortcuts: [KeyShortcut] { + return [ + KeyShortcut( + input: UIKeyCommand.inputEscape, + modifiers: [], + action: { [weak self] in + self?.dismissAnimated() + } + ), + KeyShortcut( + input: "W", + modifiers: [.command], + action: { [weak self] in + self?.dismissAnimated() + } + ), + KeyShortcut( + input: "\r", + modifiers: [], + action: { [weak self] in + self?.actionSheetNode.performHighlightedAction() + } + ), + KeyShortcut( + input: UIKeyCommand.inputUpArrow, + modifiers: [], + action: { [weak self] in + self?.actionSheetNode.decreaseHighlightedIndex() + } + ), + KeyShortcut( + input: UIKeyCommand.inputDownArrow, + modifiers: [], + action: { [weak self] in + self?.actionSheetNode.increaseHighlightedIndex() + } + ) + ] + } } diff --git a/submodules/Display/Source/ActionSheetControllerNode.swift b/submodules/Display/Source/ActionSheetControllerNode.swift index a1331ad307..80d94f3ddd 100644 --- a/submodules/Display/Source/ActionSheetControllerNode.swift +++ b/submodules/Display/Source/ActionSheetControllerNode.swift @@ -88,6 +88,18 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } } + func performHighlightedAction() { + self.itemGroupsContainerNode.performHighlightedAction() + } + + func decreaseHighlightedIndex() { + self.itemGroupsContainerNode.decreaseHighlightedIndex() + } + + func increaseHighlightedIndex() { + self.itemGroupsContainerNode.increaseHighlightedIndex() + } + func updateTheme() { self.leftDimView.backgroundColor = self.theme.dimColor self.rightDimView.backgroundColor = self.theme.dimColor diff --git a/submodules/Display/Source/ActionSheetItemGroupNode.swift b/submodules/Display/Source/ActionSheetItemGroupNode.swift index edb551e46f..46a8f81fc7 100644 --- a/submodules/Display/Source/ActionSheetItemGroupNode.swift +++ b/submodules/Display/Source/ActionSheetItemGroupNode.swift @@ -13,7 +13,7 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate { private let backgroundEffectView: UIVisualEffectView private let scrollNode: ASScrollNode - private var itemNodes: [ActionSheetItemNode] = [] + var itemNodes: [ActionSheetItemNode] = [] private var leadingVisibleNodeCount: CGFloat = 100.0 private var validLayout: CGSize? diff --git a/submodules/Display/Source/ActionSheetItemGroupsContainerNode.swift b/submodules/Display/Source/ActionSheetItemGroupsContainerNode.swift index aab21e81e1..83b063fe4e 100644 --- a/submodules/Display/Source/ActionSheetItemGroupsContainerNode.swift +++ b/submodules/Display/Source/ActionSheetItemGroupsContainerNode.swift @@ -13,6 +13,8 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode { } } + private var highlightedItemIndex: Int? = nil + private var groups: [ActionSheetItemGroup] = [] var groupNodes: [ActionSheetItemGroupNode] = [] @@ -26,6 +28,66 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode { super.init() } + func setHighlightedItemIndex(_ index: Int?, update: Bool = false) { + self.highlightedItemIndex = index + + if update { + var groupIndex = 0 + var i = 0 + for _ in self.groups { + for itemNode in self.groupNodes[groupIndex].itemNodes { + if i == index { + itemNode.setHighlighted(true, animated: false) + } else { + itemNode.setHighlighted(false, animated: false) + } + i += 1 + } + groupIndex += 1 + } + } + } + + func decreaseHighlightedIndex() { + let currentHighlightedIndex = self.highlightedItemIndex ?? 0 + + self.setHighlightedItemIndex(max(0, currentHighlightedIndex - 1), update: true) + } + + func increaseHighlightedIndex() { + let currentHighlightedIndex = self.highlightedItemIndex ?? -1 + + var groupIndex = 0 + var maxAvailabledIndex = 0 + for _ in self.groups { + for _ in self.groupNodes[groupIndex].itemNodes { + maxAvailabledIndex += 1 + } + groupIndex += 1 + } + + self.setHighlightedItemIndex(min(maxAvailabledIndex - 1, currentHighlightedIndex + 1), update: true) + } + + func performHighlightedAction() { + guard let highlightedItemIndex = self.highlightedItemIndex else { + return + } + + var i = 0 + var groupIndex = 0 + for _ in self.groups { + for itemNode in self.groupNodes[groupIndex].itemNodes { + if i == highlightedItemIndex { + itemNode.performAction() + return + } + i += 1 + } + groupIndex += 1 + } + } + func setGroups(_ groups: [ActionSheetItemGroup]) { self.groups = groups @@ -34,6 +96,7 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode { } self.groupNodes.removeAll() + var i = 0 for group in groups { let groupNode = ActionSheetItemGroupNode(theme: self.theme) let itemNodes = group.items.map({ $0.node(theme: self.theme) }) @@ -42,6 +105,13 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode { node.requestLayout = { [weak self] in self?.requestLayout?() } + let index = i + node.highlightedUpdated = { [weak self] highlighted in + if highlighted { + self?.highlightedItemIndex = index + } + } + i += 1 } groupNode.updateItemNodes(itemNodes, leadingVisibleNodeCount: group.leadingVisibleNodeCount ?? 1000.0) self.groupNodes.append(groupNode) diff --git a/submodules/Display/Source/ActionSheetItemNode.swift b/submodules/Display/Source/ActionSheetItemNode.swift index 684245b258..f4ca614a97 100644 --- a/submodules/Display/Source/ActionSheetItemNode.swift +++ b/submodules/Display/Source/ActionSheetItemNode.swift @@ -13,6 +13,8 @@ open class ActionSheetItemNode: ASDisplayNode { private var validSize: CGSize? + var highlightedUpdated: (Bool) -> Void = { _ in } + public init(theme: ActionSheetControllerTheme) { self.theme = theme @@ -34,6 +36,14 @@ open class ActionSheetItemNode: ASDisplayNode { return size } + func setHighlighted(_ highlighted: Bool, animated: Bool) { + + } + + func performAction() { + + } + public func updateInternalLayout(_ calculatedSize: CGSize, constrainedSize: CGSize) { self.validSize = constrainedSize diff --git a/submodules/Display/Source/AlertContentNode.swift b/submodules/Display/Source/AlertContentNode.swift index 7659a9ab30..b95b6e38ad 100644 --- a/submodules/Display/Source/AlertContentNode.swift +++ b/submodules/Display/Source/AlertContentNode.swift @@ -18,4 +18,16 @@ open class AlertContentNode: ASDisplayNode { open func updateTheme(_ theme: AlertControllerTheme) { } + + open func performHighlightedAction() { + + } + + open func decreaseHighlightedIndex() { + + } + + open func increaseHighlightedIndex() { + + } } diff --git a/submodules/Display/Source/AlertController.swift b/submodules/Display/Source/AlertController.swift index 35b6975c78..00bb5cf123 100644 --- a/submodules/Display/Source/AlertController.swift +++ b/submodules/Display/Source/AlertController.swift @@ -71,7 +71,7 @@ public final class AlertControllerTheme: Equatable { } } -open class AlertController: ViewController, StandalonePresentableController { +open class AlertController: ViewController, StandalonePresentableController, KeyShortcutResponder { private var controllerNode: AlertControllerNode { return self.displayNode as! AlertControllerNode } @@ -155,4 +155,44 @@ open class AlertController: ViewController, StandalonePresentableController { self?.dismiss() } } + + public var keyShortcuts: [KeyShortcut] { + return [ + KeyShortcut( + input: UIKeyCommand.inputEscape, + modifiers: [], + action: { [weak self] in + self?.dismissAnimated() + } + ), + KeyShortcut( + input: "W", + modifiers: [.command], + action: { [weak self] in + self?.dismissAnimated() + } + ), + KeyShortcut( + input: "\r", + modifiers: [], + action: { [weak self] in + self?.controllerNode.performHighlightedAction() + } + ), + KeyShortcut( + input: UIKeyCommand.inputUpArrow, + modifiers: [], + action: { [weak self] in + self?.controllerNode.decreaseHighlightedIndex() + } + ), + KeyShortcut( + input: UIKeyCommand.inputDownArrow, + modifiers: [], + action: { [weak self] in + self?.controllerNode.increaseHighlightedIndex() + } + ) + ] + } } diff --git a/submodules/Display/Source/AlertControllerNode.swift b/submodules/Display/Source/AlertControllerNode.swift index e2973469aa..51ee92168e 100644 --- a/submodules/Display/Source/AlertControllerNode.swift +++ b/submodules/Display/Source/AlertControllerNode.swift @@ -83,6 +83,18 @@ final class AlertControllerNode: ASDisplayNode { self.rightDimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:)))) } + func performHighlightedAction() { + self.contentNode.performHighlightedAction() + } + + func decreaseHighlightedIndex() { + self.contentNode.decreaseHighlightedIndex() + } + + func increaseHighlightedIndex() { + self.contentNode.increaseHighlightedIndex() + } + func updateTheme(_ theme: AlertControllerTheme) { if let effectView = self.effectNode.view as? UIVisualEffectView { effectView.effect = UIBlurEffect(style: theme.backgroundType == .light ? .light : .dark) diff --git a/submodules/Display/Source/HighlightableButton.swift b/submodules/Display/Source/HighlightableButton.swift index 35caa00566..a2ab483fc6 100644 --- a/submodules/Display/Source/HighlightableButton.swift +++ b/submodules/Display/Source/HighlightableButton.swift @@ -32,7 +32,7 @@ open class HighlightTrackingButtonNode: ASButtonNode { public var highligthedChanged: (Bool) -> Void = { _ in } private let pointerStyle: PointerStyle? - private var pointerInteraction: PointerInteraction? + public var pointerInteraction: PointerInteraction? public init(pointerStyle: PointerStyle? = nil) { self.pointerStyle = pointerStyle diff --git a/submodules/Display/Source/KeyShortcut.swift b/submodules/Display/Source/KeyShortcut.swift index 7d917eac6d..a0883b547c 100644 --- a/submodules/Display/Source/KeyShortcut.swift +++ b/submodules/Display/Source/KeyShortcut.swift @@ -31,15 +31,11 @@ extension UIKeyModifierFlags: Hashable { extension KeyShortcut { var uiKeyCommand: UIKeyCommand { - if #available(iOSApplicationExtension 9.0, iOS 9.0, *), !self.title.isEmpty { - let command = UIKeyCommand(input: self.input, modifierFlags: self.modifiers, action: #selector(KeyShortcutsController.handleKeyCommand(_:)), discoverabilityTitle: self.title) - if #available(iOS 15.0, *), ["\t", UIKeyCommand.inputUpArrow].contains(command.input) { - command.wantsPriorityOverSystemBehavior = true - } - return command - } else { - return UIKeyCommand(input: self.input, modifierFlags: self.modifiers, action: #selector(KeyShortcutsController.handleKeyCommand(_:))) + let command = UIKeyCommand(input: self.input, modifierFlags: self.modifiers, action: #selector(KeyShortcutsController.handleKeyCommand(_:)), discoverabilityTitle: self.title) + if #available(iOS 15.0, *), ["\t", UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, UIKeyCommand.inputLeftArrow, UIKeyCommand.inputRightArrow].contains(command.input) && self.modifiers.isEmpty { + command.wantsPriorityOverSystemBehavior = true } + return command } func isEqual(to command: UIKeyCommand) -> Bool { diff --git a/submodules/Display/Source/Navigation/NavigationContainer.swift b/submodules/Display/Source/Navigation/NavigationContainer.swift index ffbe9442bf..79704b14e9 100644 --- a/submodules/Display/Source/Navigation/NavigationContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationContainer.swift @@ -117,6 +117,8 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega var statusBarStyle: StatusBarStyle = .Ignore var statusBarStyleUpdated: ((ContainedViewLayoutTransition) -> Void)? + private var panRecognizer: InteractiveTransitionGestureRecognizer? + public init(controllerRemoved: @escaping (ViewController) -> Void) { self.controllerRemoved = controllerRemoved @@ -132,9 +134,13 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega } return .right }) + if #available(iOS 13.4, *) { + panRecognizer.allowedScrollTypesMask = .continuous + } panRecognizer.delegate = self panRecognizer.delaysTouchesBegan = false panRecognizer.cancelsTouchesInView = true + self.panRecognizer = panRecognizer self.view.addGestureRecognizer(panRecognizer) /*self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in @@ -155,6 +161,24 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega return false } + public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer == self.panRecognizer, let gestureRecognizer = self.panRecognizer, gestureRecognizer.numberOfTouches == 0 { + let translation = gestureRecognizer.velocity(in: gestureRecognizer.view) + if abs(translation.y) > 4.0 && abs(translation.y) > abs(translation.x) * 2.5 { + return false + } + if translation.x < 4.0 { + return false + } + if self.controllers.count == 1 { + return false + } + return true + } else { + return true + } + } + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } diff --git a/submodules/Display/Source/Navigation/NavigationModalContainer.swift b/submodules/Display/Source/Navigation/NavigationModalContainer.swift index e823d543a4..c08e229c17 100644 --- a/submodules/Display/Source/Navigation/NavigationModalContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationModalContainer.swift @@ -97,6 +97,9 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes } return .right }) + if #available(iOS 13.4, *) { + panRecognizer.allowedScrollTypesMask = .continuous + } self.panRecognizer = panRecognizer if let layout = self.validLayout { switch layout.metrics.widthClass { @@ -115,6 +118,24 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes } } + public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer == self.panRecognizer, let gestureRecognizer = self.panRecognizer, gestureRecognizer.numberOfTouches == 0 { + let translation = gestureRecognizer.velocity(in: gestureRecognizer.view) + if abs(translation.y) > 4.0 && abs(translation.y) > abs(translation.x) * 2.5 { + return false + } + if translation.x < 4.0 { + return false + } + if self.isDismissed { + return false + } + return true + } else { + return true + } + } + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } diff --git a/submodules/Display/Source/NavigationButtonNode.swift b/submodules/Display/Source/NavigationButtonNode.swift index dc7db05b37..dea5779c0b 100644 --- a/submodules/Display/Source/NavigationButtonNode.swift +++ b/submodules/Display/Source/NavigationButtonNode.swift @@ -99,6 +99,7 @@ private final class NavigationButtonItemNode: ImmediateTextNode { self.addSubnode(node) self.invalidateCalculatedLayout() self.setNeedsLayout() + self.updatePointerInteraction() } } } @@ -210,7 +211,7 @@ private final class NavigationButtonItemNode: ImmediateTextNode { if self.node != nil { pointerStyle = .lift } else { - pointerStyle = .default + pointerStyle = .insetRectangle(-8.0, 2.0) } self.pointerInteraction = PointerInteraction(node: self, style: pointerStyle) } diff --git a/submodules/Display/Source/PointerInteraction.swift b/submodules/Display/Source/PointerInteraction.swift index 3a60a7e028..b1aeec8356 100644 --- a/submodules/Display/Source/PointerInteraction.swift +++ b/submodules/Display/Source/PointerInteraction.swift @@ -3,8 +3,9 @@ import AsyncDisplayKit public enum PointerStyle { case `default` + case insetRectangle(CGFloat, CGFloat) case rectangle(CGSize) - case circle + case circle(CGFloat?) case caret case lift case hover @@ -12,7 +13,8 @@ public enum PointerStyle { @available(iOSApplicationExtension 13.4, iOS 13.4, *) private final class PointerInteractionImpl: NSObject, UIPointerInteractionDelegate { - weak var pointerInteraction: UIPointerInteraction? + private weak var pointerInteraction: UIPointerInteraction? + private weak var customInteractionView: UIView? private let style: PointerStyle @@ -33,7 +35,9 @@ private final class PointerInteractionImpl: NSObject, UIPointerInteractionDelega } } - func setup(view: UIView) { + func setup(view: UIView, customInteractionView: UIView?) { + self.customInteractionView = customInteractionView + let pointerInteraction = UIPointerInteraction(delegate: self) view.addInteraction(pointerInteraction) self.pointerInteraction = pointerInteraction @@ -41,7 +45,10 @@ private final class PointerInteractionImpl: NSObject, UIPointerInteractionDelega func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? { var pointerStyle: UIPointerStyle? = nil - if let interactionView = interaction.view { + + let interactionView = self.customInteractionView ?? interaction.view + + if let interactionView = interactionView { let targetedPreview = UITargetedPreview(view: interactionView) switch self.style { case .default: @@ -50,11 +57,15 @@ private final class PointerInteractionImpl: NSObject, UIPointerInteractionDelega let minHeight: CGFloat = 40.0 let size: CGSize = CGSize(width: targetedPreview.size.width + horizontalPadding * 2.0, height: max(minHeight, targetedPreview.size.height + verticalPadding * 2.0)) pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .roundedRect(CGRect(origin: CGPoint(x: targetedPreview.view.center.x - size.width / 2.0, y: targetedPreview.view.center.y - size.height / 2.0), size: size), radius: UIPointerShape.defaultCornerRadius)) + case let .insetRectangle(x, y): + let insetSize = CGSize(width: targetedPreview.size.width - x * 2.0, height: targetedPreview.size.height - y * 2.0) + pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .roundedRect(CGRect(origin: CGPoint(x: targetedPreview.view.center.x - insetSize.width / 2.0, y: targetedPreview.view.center.y - insetSize.height / 2.0), size: insetSize), radius: UIPointerShape.defaultCornerRadius)) case let .rectangle(size): pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .roundedRect(CGRect(origin: CGPoint(x: targetedPreview.view.center.x - size.width / 2.0, y: targetedPreview.view.center.y - size.height / 2.0), size: size), radius: UIPointerShape.defaultCornerRadius)) - case .circle: + case let .circle(diameter): let maxSide = max(targetedPreview.size.width, targetedPreview.size.height) - pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .path(UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: CGSize(width: maxSide, height: maxSide))))) + let finalDiameter = diameter ?? maxSide + pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .path(UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: floorToScreenPixels(targetedPreview.view.center.x - finalDiameter / 2.0), y: floorToScreenPixels(targetedPreview.view.center.y - finalDiameter / 2.0)), size: CGSize(width: finalDiameter, height: finalDiameter))))) case .caret: pointerStyle = UIPointerStyle(shape: .verticalBeam(length: 24.0), constrainedAxes: .vertical) case .lift: @@ -106,13 +117,13 @@ public final class PointerInteraction { self.init(view: node.view, style: style, willEnter: willEnter, willExit: willExit) } - public init(view: UIView, style: PointerStyle = .default, willEnter: @escaping () -> Void = {}, willExit: @escaping () -> Void = {}) { + public init(view: UIView, customInteractionView: UIView? = nil, style: PointerStyle = .default, willEnter: @escaping () -> Void = {}, willExit: @escaping () -> Void = {}) { self.style = style self.willEnter = willEnter self.willExit = willExit if #available(iOSApplicationExtension 13.4, iOS 13.4, *) { self.withImpl { impl in - impl.setup(view: view) + impl.setup(view: view, customInteractionView: customInteractionView) } } } diff --git a/submodules/Display/Source/TextAlertController.swift b/submodules/Display/Source/TextAlertController.swift index c9c2120160..6c34171c13 100644 --- a/submodules/Display/Source/TextAlertController.swift +++ b/submodules/Display/Source/TextAlertController.swift @@ -29,8 +29,8 @@ public final class TextAlertContentActionNode: HighlightableButtonNode { private let backgroundNode: ASDisplayNode - private var pointerInteraction: PointerInteraction? - + var highlightedUpdated: (Bool) -> Void = { _ in } + public init(theme: AlertControllerTheme, action: TextAlertAction) { self.theme = theme self.action = action @@ -45,16 +45,7 @@ public final class TextAlertContentActionNode: HighlightableButtonNode { self.highligthedChanged = { [weak self] value in if let strongSelf = self { - if value { - if strongSelf.backgroundNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) - } - strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.backgroundNode.alpha = 1.0 - } else if !strongSelf.backgroundNode.alpha.isZero { - strongSelf.backgroundNode.alpha = 0.0 - strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) - } + strongSelf.setHighlighted(value, animated: true) } } @@ -68,15 +59,38 @@ public final class TextAlertContentActionNode: HighlightableButtonNode { self.pointerInteraction = PointerInteraction(node: self, style: .hover, willEnter: { [weak self] in if let strongSelf = self { - strongSelf.backgroundNode.alpha = 0.25 + strongSelf.setHighlighted(true, animated: false) } }, willExit: { [weak self] in if let strongSelf = self { - strongSelf.backgroundNode.alpha = 1.0 + strongSelf.setHighlighted(false, animated: false) } }) } + func performAction() { + if self.actionEnabled { + self.action.action() + } + } + + func setHighlighted(_ highlighted: Bool, animated: Bool) { + self.highlightedUpdated(highlighted) + if highlighted { + if self.backgroundNode.supernode == nil { + self.insertSubnode(self.backgroundNode, at: 0) + } + self.backgroundNode.alpha = 1.0 + } else { + if animated { + UIView.animate(withDuration: 0.3, animations: { + self.backgroundNode.alpha = 0.0 + }) + } else { + self.backgroundNode.alpha = 0.0 + } + } + } public var actionEnabled: Bool = true { didSet { self.isUserInteractionEnabled = self.actionEnabled @@ -142,6 +156,8 @@ public final class TextAlertContentNode: AlertContentNode { return self._dismissOnOutsideTap } + private var highlightedItemIndex: Int? = nil + public var textAttributeAction: (NSAttributedString.Key, (Any) -> Void)? { didSet { if let (attribute, textAttributeAction) = self.textAttributeAction { @@ -224,8 +240,17 @@ public final class TextAlertContentNode: AlertContentNode { self.addSubnode(self.actionNodesSeparator) + var i = 0 for actionNode in self.actionNodes { self.addSubnode(actionNode) + + let index = i + actionNode.highlightedUpdated = { [weak self] highlighted in + if highlighted { + self?.highlightedItemIndex = index + } + } + i += 1 } for separatorNode in self.actionVerticalSeparators { @@ -233,6 +258,49 @@ public final class TextAlertContentNode: AlertContentNode { } } + func setHighlightedItemIndex(_ index: Int?, update: Bool = false) { + self.highlightedItemIndex = index + + if update { + var i = 0 + for actionNode in self.actionNodes { + if i == index { + actionNode.setHighlighted(true, animated: false) + } else { + actionNode.setHighlighted(false, animated: false) + } + i += 1 + } + } + } + + override public func decreaseHighlightedIndex() { + let currentHighlightedIndex = self.highlightedItemIndex ?? 0 + + self.setHighlightedItemIndex(max(0, currentHighlightedIndex - 1), update: true) + } + + override public func increaseHighlightedIndex() { + let currentHighlightedIndex = self.highlightedItemIndex ?? -1 + + self.setHighlightedItemIndex(min(self.actionNodes.count - 1, currentHighlightedIndex + 1), update: true) + } + + override public func performHighlightedAction() { + guard let highlightedItemIndex = self.highlightedItemIndex else { + return + } + + var i = 0 + for itemNode in self.actionNodes { + if i == highlightedItemIndex { + itemNode.performAction() + return + } + i += 1 + } + } + override public func updateTheme(_ theme: AlertControllerTheme) { self.theme = theme diff --git a/submodules/Display/Source/ToolbarNode.swift b/submodules/Display/Source/ToolbarNode.swift index d1fb9e5fed..37b5c757fb 100644 --- a/submodules/Display/Source/ToolbarNode.swift +++ b/submodules/Display/Source/ToolbarNode.swift @@ -52,13 +52,13 @@ public final class ToolbarNode: ASDisplayNode { self.leftTitle = ImmediateTextNode() self.leftTitle.displaysAsynchronously = false - self.leftButton = HighlightTrackingButtonNode() + self.leftButton = HighlightTrackingButtonNode(pointerStyle: .insetRectangle(2.0, 2.0)) self.rightTitle = ImmediateTextNode() self.rightTitle.displaysAsynchronously = false - self.rightButton = HighlightTrackingButtonNode() + self.rightButton = HighlightTrackingButtonNode(pointerStyle: .insetRectangle(2.0, 2.0)) self.middleTitle = ImmediateTextNode() self.middleTitle.displaysAsynchronously = false - self.middleButton = HighlightTrackingButtonNode() + self.middleButton = HighlightTrackingButtonNode(pointerStyle: .insetRectangle(2.0, 2.0)) super.init() diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 37653494fa..26ed2e167b 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -329,7 +329,7 @@ public struct GalleryConfiguration { } } -public class GalleryController: ViewController, StandalonePresentableController { +public class GalleryController: ViewController, StandalonePresentableController, KeyShortcutResponder { public static let darkNavigationTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) public static let lightNavigationTheme = NavigationBarTheme(buttonColor: UIColor(rgb: 0x007aff), disabledButtonColor: UIColor(rgb: 0xd0d0d0), primaryTextColor: .black, backgroundColor: UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0), enableBackgroundBlur: false, separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) @@ -1384,4 +1384,67 @@ public class GalleryController: ViewController, StandalonePresentableController } } } + + public var keyShortcuts: [KeyShortcut] { + var keyShortcuts: [KeyShortcut] = [] + keyShortcuts.append( + KeyShortcut( + title: "", + input: UIKeyCommand.inputUpArrow, + modifiers: [.command], + action: { [weak self] in + self?.dismiss(forceAway: false) + } + ) + ) + keyShortcuts.append( + KeyShortcut( + title: "", + input: "W", + modifiers: [.command], + action: { [weak self] in + self?.dismiss(forceAway: false) + } + ) + ) + keyShortcuts.append( + KeyShortcut( + title: self.galleryNode.areControlsHidden ? self.presentationData.strings.KeyCommand_ExitFullscreen : self.presentationData.strings.KeyCommand_EnterFullscreen, + input: "F", + modifiers: [.control, .command], + action: { [weak self] in + if let strongSelf = self { + strongSelf.galleryNode.setControlsHidden(!strongSelf.galleryNode.areControlsHidden, animated: true) + } + } + ) + ) + if self.galleryNode.pager.items.count > 1 { + if self.galleryNode.pager.canGoToPreviousItem() { + keyShortcuts.append( + KeyShortcut( + input: UIKeyCommand.inputLeftArrow, + modifiers: [], + action: { [weak self] in + self?.galleryNode.pager.goToPreviousItem() + } + ) + ) + } + if self.galleryNode.pager.canGoToNextItem() { + keyShortcuts.append( + KeyShortcut( + input: UIKeyCommand.inputRightArrow, + modifiers: [], + action: { [weak self] in + self?.galleryNode.pager.goToNextItem() + } + ) + ) + } + } + let itemNodeShortcuts = self.galleryNode.pager.centralItemNode()?.keyShortcuts ?? [] + keyShortcuts.append(contentsOf: itemNodeShortcuts) + return keyShortcuts + } } diff --git a/submodules/GalleryUI/Sources/GalleryItemNode.swift b/submodules/GalleryUI/Sources/GalleryItemNode.swift index bfb4cc7c21..3cbd05e5a6 100644 --- a/submodules/GalleryUI/Sources/GalleryItemNode.swift +++ b/submodules/GalleryUI/Sources/GalleryItemNode.swift @@ -103,4 +103,8 @@ open class GalleryItemNode: ASDisplayNode { open func contentSize() -> CGSize? { return nil } + + open var keyShortcuts: [KeyShortcut] { + return [] + } } diff --git a/submodules/GalleryUI/Sources/GalleryPagerNode.swift b/submodules/GalleryUI/Sources/GalleryPagerNode.swift index 6c59f7cbfd..07af78c66f 100644 --- a/submodules/GalleryUI/Sources/GalleryPagerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryPagerNode.swift @@ -457,7 +457,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest } } - private func canGoToPreviousItem() -> Bool { + func canGoToPreviousItem() -> Bool { if self.disableTapNavigation { return false } @@ -468,7 +468,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest } } - private func canGoToNextItem() -> Bool { + func canGoToNextItem() -> Bool { if self.disableTapNavigation { return false } @@ -479,13 +479,13 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest } } - private func goToPreviousItem() { + func goToPreviousItem() { if let index = self.centralItemIndex, index > 0 { self.transaction(GalleryPagerTransaction(deleteItems: [], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false)) } } - private func goToNextItem() { + func goToNextItem() { if let index = self.centralItemIndex, index < self.items.count - 1 { self.transaction(GalleryPagerTransaction(deleteItems: [], insertItems: [], updateItems: [], focusOnItem: index + 1, synchronous: false)) } diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index b146287009..63f0d18bd1 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -192,6 +192,7 @@ class ChatImageGalleryItem: GalleryItem { final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { private let context: AccountContext private var message: Message? + private let presentationData: PresentationData private let imageNode: TransformImageNode private var recognizedContentNode: RecognizedContentContainer? @@ -220,6 +221,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.context = context + self.presentationData = presentationData self.imageNode = TransformImageNode() self.imageNode.contentAnimations = .subsequentUpdates @@ -798,6 +800,60 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { self.recognitionOverlayContentNode.isHidden = true } + + private func canDelete() -> Bool { + guard let message = self.message else { + return false + } + + var canDelete = false + if let peer = message.peers[message.id.peerId] { + if peer is TelegramUser || peer is TelegramSecretChat { + canDelete = true + } else if let _ = peer as? TelegramGroup { + canDelete = true + } else if let channel = peer as? TelegramChannel { + if message.flags.contains(.Incoming) { + canDelete = channel.hasPermission(.deleteAllMessages) + } else { + canDelete = true + } + } else { + canDelete = false + } + } else { + canDelete = false + } + return canDelete + } + + override var keyShortcuts: [KeyShortcut] { + let strings = self.presentationData.strings + + var keyShortcuts: [KeyShortcut] = [] + keyShortcuts.append( + KeyShortcut( + title: strings.KeyCommand_Share, + input: "S", + modifiers: [.command], + action: { [weak self] in + self?.footerContentNode.actionButtonPressed() + } + ) + ) + if self.canDelete() { + keyShortcuts.append( + KeyShortcut( + input: "\u{8}", + modifiers: [], + action: { [weak self] in + self?.footerContentNode.deleteButtonPressed() + } + ) + ) + } + return keyShortcuts + } } /*private func tileRectForImage(_ mappedImage: CGImage, rect: CGRect) -> CGRect { diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 66b2bbf180..9f148ea62b 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -775,6 +775,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private var scrubbingFrames = false private var scrubbingFrameDisposable: Disposable? + private var isPlaying = false private let isPlayingPromise = ValuePromise(false, ignoreRepeated: true) private let isInteractingPromise = ValuePromise(false, ignoreRepeated: true) private let controlsVisiblePromise = ValuePromise(true, ignoreRepeated: true) @@ -1066,6 +1067,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } self.footerContentNode.scrubberView = scrubberView + self.isPlaying = false self.isPlayingPromise.set(false) if item.hideControls { @@ -1346,6 +1348,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } else if strongSelf.actionAtEnd == .stop { strongSelf.isPlayingPromise.set(false) + strongSelf.isPlaying = false if strongSelf.isCentral == true { strongSelf.updateControlsVisibility(true) } @@ -1358,8 +1361,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if !disablePlayerControls && strongSelf.isCentral == true && isPlaying { strongSelf.isPlayingPromise.set(true) + strongSelf.isPlaying = true } else if !isPlaying { strongSelf.isPlayingPromise.set(false) + strongSelf.isPlaying = false } var fetching = false @@ -1471,6 +1476,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if strongSelf.actionAtEnd == .stop && strongSelf.isCentral == true { strongSelf.isPlayingPromise.set(false) + strongSelf.isPlaying = false strongSelf.updateControlsVisibility(true) } } @@ -1591,6 +1597,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } else { self.isPlayingPromise.set(false) + self.isPlaying = false self.dismissOnOrientationChange = false if videoNode.ownsContentNode { @@ -2710,6 +2717,78 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.playbackRatePromise.set(self.playbackRate ?? 1.0) } + + override var keyShortcuts: [KeyShortcut] { + let strings = self.presentationData.strings + + var keyShortcuts: [KeyShortcut] = [] + keyShortcuts.append( + KeyShortcut( + title: self.isPlaying ? strings.KeyCommand_Pause : strings.KeyCommand_Play, + input: " ", + modifiers: [], + action: { [weak self] in + self?.footerContentNode.playbackControl?() + } + ) + ) + + keyShortcuts.append( + KeyShortcut( + title: strings.KeyCommand_SeekBackward, + input: UIKeyCommand.inputLeftArrow, + modifiers: [.shift], + action: { [weak self] in + self?.footerContentNode.seekBackward?(5) + } + ) + ) + keyShortcuts.append( + KeyShortcut( + title: strings.KeyCommand_SeekForward, + input: UIKeyCommand.inputRightArrow, + modifiers: [.shift], + action: { [weak self] in + self?.footerContentNode.seekForward?(5) + } + ) + ) + + keyShortcuts.append( + KeyShortcut( + title: strings.KeyCommand_Share, + input: "S", + modifiers: [.command], + action: { [weak self] in + self?.footerContentNode.actionButtonPressed() + } + ) + ) + if self.hasPictureInPicture { + keyShortcuts.append( + KeyShortcut( + title: strings.KeyCommand_SwitchToPIP, + input: "P", + modifiers: [.command], + action: { [weak self] in + self?.pictureInPictureButtonPressed() + } + ) + ) + } + if self.canDelete() { + keyShortcuts.append( + KeyShortcut( + input: "\u{8}", + modifiers: [], + action: { [weak self] in + self?.footerContentNode.deleteButtonPressed() + } + ) + ) + } + return keyShortcuts + } } private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { diff --git a/submodules/PremiumUI/Sources/PremiumStarComponent.swift b/submodules/PremiumUI/Sources/PremiumStarComponent.swift index ce5558ca11..bf6529d886 100644 --- a/submodules/PremiumUI/Sources/PremiumStarComponent.swift +++ b/submodules/PremiumUI/Sources/PremiumStarComponent.swift @@ -197,11 +197,18 @@ class PremiumStarComponent: Component { self.previousInteractionTimestamp = CACurrentMediaTime() + let keys = [ + "rotate", + "tapRotate" + ] if #available(iOS 11.0, *) { - node.removeAnimation(forKey: "rotate", blendOutDuration: 0.1) - node.removeAnimation(forKey: "tapRotate", blendOutDuration: 0.1) + for key in keys { + node.removeAnimation(forKey: key, blendOutDuration: 0.1) + } } else { - node.removeAllAnimations() + for key in keys { + node.removeAnimation(forKey: key) + } } switch gesture.state { @@ -337,6 +344,7 @@ class PremiumStarComponent: Component { } private func onReady() { + self.setupScaleAnimation() self.setupGradientAnimation() self.setupShineAnimation() @@ -355,6 +363,22 @@ class PremiumStarComponent: Component { self.timer?.start() } + private func setupScaleAnimation() { + guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else { + return + } + + let animation = CABasicAnimation(keyPath: "scale") + animation.duration = 2.0 + animation.fromValue = NSValue(scnVector3: SCNVector3(x: 0.1, y: 0.1, z: 0.1)) + animation.toValue = NSValue(scnVector3: SCNVector3(x: 0.115, y: 0.115, z: 0.115)) + animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + animation.autoreverses = true + animation.repeatCount = .infinity + + node.addAnimation(animation, forKey: "scale") + } + private func setupGradientAnimation() { guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else { return diff --git a/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift index 87ae41801a..c5b9d906c6 100644 --- a/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift +++ b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift @@ -125,6 +125,8 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg if let layout = self.validLayout { let _ = self.updateLayout(layout, transition: .immediate) } + + self.updatePointerInteraction() } } @@ -140,6 +142,8 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg if let layout = self.validLayout { let _ = self.updateLayout(layout, transition: .immediate) } + + self.updatePointerInteraction() } } @@ -151,6 +155,8 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg if let layout = self.validLayout { let _ = self.updateLayout(layout, transition: .animated(duration: 0.2, curve: .easeInOut)) } + + self.updatePointerInteraction() } public var selectedIndexChanged: (Int) -> Void = { _ in } @@ -212,6 +218,18 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg self.gestureRecognizer = gestureRecognizer } + private func updatePointerInteraction() { + for i in 0 ..< self.itemNodes.count { + let itemNode = self.itemNodes[i] + + if i == self.selectedIndex { + itemNode.pointerInteraction = PointerInteraction(view: itemNode.view, customInteractionView: self.selectionNode.view, style: .lift) + } else { + itemNode.pointerInteraction = PointerInteraction(view: itemNode.view, style: .insetRectangle(2.0, 2.0)) + } + } + } + private func setupButtons() { for i in 0 ..< self.itemNodes.count { let itemNode = self.itemNodes[i] @@ -233,6 +251,8 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg } } } + + self.updatePointerInteraction() } private func updateButtonsHighlights(highlightedIndex: Int?, gestureSelectedIndex: Int?) { @@ -379,6 +399,8 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg if let layout = strongSelf.validLayout { let _ = strongSelf.updateLayout(layout, transition: .animated(duration: 0.2, curve: .slide)) } + + strongSelf.updatePointerInteraction() } }) } @@ -415,6 +437,8 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg if commit { strongSelf._selectedIndex = gestureSelectedIndex strongSelf.selectedIndexChanged(gestureSelectedIndex) + + strongSelf.updatePointerInteraction() } else { if let layout = strongSelf.validLayout { let _ = strongSelf.updateLayout(layout, transition: .animated(duration: 0.2, curve: .slide)) diff --git a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift index 131562017c..406b1cd4e3 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift @@ -1325,8 +1325,9 @@ public final class SparseItemGrid: ASDisplayNode { } } - public init(theme: PresentationTheme) { + public init(theme: PresentationTheme, initialZoomLevel: ZoomLevel? = nil) { self.theme = theme + self.initialZoomLevel = initialZoomLevel self.scrollingArea = SparseItemGridScrollingArea() diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index adef8abf1a..2f254b9681 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -226,6 +226,11 @@ final class AuthorizedApplicationContext { } } strongSelf.mainWindow.forEachViewController(f) + if let globalOverlayController = strongSelf.rootController.globalOverlayControllers.last { + if !f(globalOverlayController) { + return + } + } }) } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d01d6efb70..6f9f259009 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -15841,6 +15841,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } }), + KeyShortcut(input: "U", modifiers: [.command], action: { [weak self] in + if let strongSelf = self { + strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in + return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.underline), inputMode) + } + } + }), + KeyShortcut(input: "X", modifiers: [.command, .shift], action: { [weak self] in + if let strongSelf = self { + strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in + return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.strikethrough), inputMode) + } + } + }), + KeyShortcut(input: "P", modifiers: [.command, .shift], action: { [weak self] in + if let strongSelf = self { + strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in + return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.spoiler), inputMode) + } + } + }), KeyShortcut(input: "K", modifiers: [.command], action: { [weak self] in if let strongSelf = self { strongSelf.interfaceInteraction?.openLinkEditing() @@ -15912,11 +15933,107 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G inputShortcuts = [] } + inputShortcuts.append( + KeyShortcut( + input: "W", + modifiers: [.command], + action: { [weak self] in + self?.dismiss(animated: true, completion: nil) + } + ) + ) + + if canReplyInChat(self.presentationInterfaceState) { + inputShortcuts.append( + KeyShortcut( + input: UIKeyCommand.inputUpArrow, + modifiers: [.alternate, .command], + action: { [weak self] in + guard let strongSelf = self else { + return + } + + if let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId { + if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(before: replyMessageId) { + strongSelf.updateChatPresentationInterfaceState(interactive: true, { state in + var updatedState = state.updatedInterfaceState({ state in + return state.withUpdatedReplyMessageId(message.id) + }) + if updatedState.inputMode == .none { + updatedState = updatedState.updatedInputMode({ _ in .text }) + } + return updatedState + }) + + strongSelf.navigateToMessage(messageLocation: .id(message.id, nil), animated: true) + } + } else { + strongSelf.scrollToEndOfHistory() + Queue.mainQueue().after(0.1, { + let lastMessage = strongSelf.chatDisplayNode.historyNode.latestMessageInCurrentHistoryView() + strongSelf.updateChatPresentationInterfaceState(interactive: true, { state in + var updatedState = state.updatedInterfaceState({ state in + return state.withUpdatedReplyMessageId(lastMessage?.id) + }) + if updatedState.inputMode == .none { + updatedState = updatedState.updatedInputMode({ _ in .text }) + } + return updatedState + }) + + if let lastMessage = lastMessage { + strongSelf.navigateToMessage(messageLocation: .id(lastMessage.id, nil), animated: true) + } + }) + } + } + ) + ) + + inputShortcuts.append( + KeyShortcut( + input: UIKeyCommand.inputDownArrow, + modifiers: [.alternate, .command], + action: { [weak self] in + guard let strongSelf = self else { + return + } + + if let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId { + let lastMessage = strongSelf.chatDisplayNode.historyNode.latestMessageInCurrentHistoryView() + var updatedReplyMessageId: MessageId? + if replyMessageId == lastMessage?.id { + updatedReplyMessageId = nil + } else if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(after: replyMessageId) { + updatedReplyMessageId = message.id + } + + strongSelf.updateChatPresentationInterfaceState(interactive: true, { state in + var updatedState = state.updatedInterfaceState({ state in + return state.withUpdatedReplyMessageId(updatedReplyMessageId) + }) + if updatedState.inputMode == .none { + updatedState = updatedState.updatedInputMode({ _ in .text }) + } else if updatedReplyMessageId == nil { + updatedState = updatedState.updatedInputMode({ _ in .none }) + } + return updatedState + }) + + if let updatedReplyMessageId = updatedReplyMessageId { + strongSelf.navigateToMessage(messageLocation: .id(updatedReplyMessageId, nil), animated: true) + } + } + } + ) + ) + } + var canEdit = false if self.presentationInterfaceState.interfaceState.effectiveInputState.inputText.length == 0 && self.presentationInterfaceState.interfaceState.editMessage == nil { canEdit = true } - + if canEdit, let message = self.chatDisplayNode.historyNode.firstMessageForEditInCurrentHistoryView() { inputShortcuts.append(KeyShortcut(input: UIKeyCommand.inputUpArrow, action: { [weak self] in if let strongSelf = self { diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift index dc2ea4241d..abe410c220 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift @@ -88,6 +88,23 @@ enum ChatHistoryEntry: Identifiable, Comparable { return MessageIndex.absoluteLowerBound() } } + + var firstIndex: MessageIndex { + switch self { + case let .MessageEntry(message, _, _, _, _, _): + return message.index + case let .MessageGroupEntry(_, messages, _): + return messages[0].0.index + case let .UnreadEntry(index, _): + return index + case let .ReplyCountEntry(index, _, _, _): + return index + case .ChatInfoEntry: + return MessageIndex.absoluteLowerBound() + case .SearchEntry: + return MessageIndex.absoluteLowerBound() + } + } static func ==(lhs: ChatHistoryEntry, rhs: ChatHistoryEntry) -> Bool { switch lhs { diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 63d9a64d76..7f51665e28 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -2195,6 +2195,34 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { return nil } + public func messageInCurrentHistoryView(after messageId: MessageId) -> Message? { + if let historyView = self.historyView { + if let index = historyView.filteredEntries.firstIndex(where: { $0.firstIndex.id == messageId }), index < historyView.filteredEntries.count - 1 { + let nextEntry = historyView.filteredEntries[index + 1] + if case let .MessageEntry(message, _, _, _, _, _) = nextEntry { + return message + } else if case let .MessageGroupEntry(_, messages, _) = nextEntry, let firstMessage = messages.first { + return firstMessage.0 + } + } + } + return nil + } + + public func messageInCurrentHistoryView(before messageId: MessageId) -> Message? { + if let historyView = self.historyView { + if let index = historyView.filteredEntries.firstIndex(where: { $0.firstIndex.id == messageId }), index > 0 { + let nextEntry = historyView.filteredEntries[index - 1] + if case let .MessageEntry(message, _, _, _, _, _) = nextEntry { + return message + } else if case let .MessageGroupEntry(_, messages, _) = nextEntry, let firstMessage = messages.first { + return firstMessage.0 + } + } + } + return nil + } + public func messageInCurrentHistoryView(_ id: MessageId) -> Message? { if let historyView = self.historyView { for entry in historyView.filteredEntries { diff --git a/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift index e8c465c9e4..49900afb1f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift @@ -56,21 +56,21 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.theme = theme self.peerMedia = peerMedia - self.deleteButton = HighlightableButtonNode(pointerStyle: .default) + self.deleteButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0))) self.deleteButton.isEnabled = false self.deleteButton.isAccessibilityElement = true self.deleteButton.accessibilityLabel = strings.VoiceOver_MessageContextDelete - self.reportButton = HighlightableButtonNode(pointerStyle: .default) + self.reportButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0))) self.reportButton.isEnabled = false self.reportButton.isAccessibilityElement = true self.reportButton.accessibilityLabel = strings.VoiceOver_MessageContextReport - self.forwardButton = HighlightableButtonNode(pointerStyle: .default) + self.forwardButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0))) self.forwardButton.isAccessibilityElement = true self.forwardButton.accessibilityLabel = strings.VoiceOver_MessageContextForward - self.shareButton = HighlightableButtonNode(pointerStyle: .default) + self.shareButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0))) self.shareButton.isAccessibilityElement = true self.shareButton.accessibilityLabel = strings.VoiceOver_MessageContextShare diff --git a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift index b6988a7d8c..c142100215 100644 --- a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift @@ -50,12 +50,12 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode { } init(theme: PresentationTheme) { - self.upButton = HighlightableButtonNode(pointerStyle: .default) + self.upButton = HighlightableButtonNode(pointerStyle: .circle(36.0)) self.upButton.isEnabled = false - self.downButton = HighlightableButtonNode(pointerStyle: .default) + self.downButton = HighlightableButtonNode(pointerStyle: .circle(36.0)) self.downButton.isEnabled = false self.calendarButton = HighlightableButtonNode() - self.membersButton = HighlightableButtonNode(pointerStyle: .default) + self.membersButton = HighlightableButtonNode(pointerStyle: .circle(36.0)) self.measureResultsLabel = TextNode() self.measureResultsLabel.displaysAsynchronously = false self.resultsButton = HighlightableButtonNode(pointerStyle: .default) diff --git a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift index dd633334c6..90e3bcdaa5 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift @@ -31,7 +31,8 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode { } } - var micButtonPointerInteraction: PointerInteraction? + private var micButtonPointerInteraction: PointerInteraction? + private var sendButtonPointerInteraction: PointerInteraction? private var validLayout: CGSize? @@ -50,9 +51,9 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode { self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor self.backgroundNode.clipsToBounds = true self.backdropNode = ChatMessageBubbleBackdrop() - self.sendButton = HighlightTrackingButtonNode(pointerStyle: .lift) + self.sendButton = HighlightTrackingButtonNode(pointerStyle: nil) - self.expandMediaInputButton = HighlightableButtonNode(pointerStyle: .default) + self.expandMediaInputButton = HighlightableButtonNode(pointerStyle: .circle(36.0)) super.init() @@ -107,7 +108,8 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode { } } - self.micButtonPointerInteraction = PointerInteraction(view: self.micButton, style: .circle) + self.micButtonPointerInteraction = PointerInteraction(view: self.micButton, style: .circle(36.0)) + self.sendButtonPointerInteraction = PointerInteraction(view: self.sendButton.view, customInteractionView: self.backgroundNode.view, style: .lift) } func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 5d50084126..f49ceeda99 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -51,7 +51,7 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode { self.width = AccessoryItemIconButtonNode.calculateWidth(item: item, image: image, text: text, strings: strings) - super.init() + super.init(pointerStyle: .circle(30.0)) self.isAccessibilityElement = true self.accessibilityTraits = [.button] @@ -754,7 +754,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.sendAsAvatarContainerNode.animateScale = false self.sendAsAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 16.0)) - self.attachmentButton = HighlightableButtonNode(pointerStyle: .circle) + self.attachmentButton = HighlightableButtonNode(pointerStyle: .circle(36.0)) self.attachmentButton.accessibilityLabel = presentationInterfaceState.strings.VoiceOver_AttachMedia self.attachmentButton.accessibilityTraits = [.button] self.attachmentButton.isAccessibilityElement = true diff --git a/submodules/TelegramUI/Sources/IconButtonNode.swift b/submodules/TelegramUI/Sources/IconButtonNode.swift index 917426ff40..0ac254e19e 100644 --- a/submodules/TelegramUI/Sources/IconButtonNode.swift +++ b/submodules/TelegramUI/Sources/IconButtonNode.swift @@ -61,7 +61,7 @@ final class IconButtonNode: HighlightTrackingButtonNode { self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true - super.init(pointerStyle: .circle) + super.init(pointerStyle: .circle(nil)) self.addSubnode(self.iconNode) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index d6ed8b8dea..53effa48c6 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -1054,7 +1054,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true - super.init(pointerStyle: .default) + super.init(pointerStyle: .insetRectangle(-8.0, 2.0)) self.isAccessibilityElement = true self.accessibilityTraits = .button diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 9a18862f15..48638afc37 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -7780,7 +7780,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } } -public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen { +public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortcutResponder { private let context: AccountContext fileprivate let updatedPresentationData: (initial: PresentationData, signal: Signal)? private let peerId: PeerId @@ -8278,6 +8278,37 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen { let controller = ContextController(account: primary.0.account, presentationData: self.presentationData, source: .extracted(SettingsTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) } + + public var keyShortcuts: [KeyShortcut] { + if self.isSettings { + return [ + KeyShortcut( + input: "0", + modifiers: [.command], + action: { [weak self] in + self?.controllerNode.openSettings(section: .savedMessages) + } + ) + ] + } else { + return [ + KeyShortcut( + input: "W", + modifiers: [.command], + action: { [weak self] in + self?.dismiss(animated: true, completion: nil) + } + ), + KeyShortcut( + input: UIKeyCommand.inputEscape, + modifiers: [], + action: { [weak self] in + self?.dismiss(animated: true, completion: nil) + } + ) + ] + } + } } private final class SettingsTabBarContextExtractedContentSource: ContextExtractedContentSource {