mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
iPad trackpad and key shortcuts improvements
This commit is contained in:
parent
6c4a730b42
commit
5b8961d02a
@ -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";
|
||||
|
||||
@ -26,6 +26,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
private var sendButtonPointerInteraction: PointerInteraction?
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) {
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -18,4 +18,16 @@ open class AlertContentNode: ASDisplayNode {
|
||||
open func updateTheme(_ theme: AlertControllerTheme) {
|
||||
|
||||
}
|
||||
|
||||
open func performHighlightedAction() {
|
||||
|
||||
}
|
||||
|
||||
open func decreaseHighlightedIndex() {
|
||||
|
||||
}
|
||||
|
||||
open func increaseHighlightedIndex() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ 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
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,4 +103,8 @@ open class GalleryItemNode: ASDisplayNode {
|
||||
open func contentSize() -> CGSize? {
|
||||
return nil
|
||||
}
|
||||
|
||||
open var keyShortcuts: [KeyShortcut] {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -775,6 +775,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private var scrubbingFrames = false
|
||||
private var scrubbingFrameDisposable: Disposable?
|
||||
|
||||
private var isPlaying = false
|
||||
private let isPlayingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let isInteractingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let controlsVisiblePromise = ValuePromise<Bool>(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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -226,6 +226,11 @@ final class AuthorizedApplicationContext {
|
||||
}
|
||||
}
|
||||
strongSelf.mainWindow.forEachViewController(f)
|
||||
if let globalOverlayController = strongSelf.rootController.globalOverlayControllers.last {
|
||||
if !f(globalOverlayController) {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,6 +15933,102 @@ 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
|
||||
|
||||
@ -89,6 +89,23 @@ enum ChatHistoryEntry: Identifiable, Comparable {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
case let .MessageEntry(lhsMessage, lhsPresentationData, lhsRead, _, lhsSelection, lhsAttributes):
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<PresentationData, NoError>)?
|
||||
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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user