iPad trackpad and key shortcuts improvements

This commit is contained in:
Ilya Laktyushin 2022-07-30 03:51:46 +03:00
parent 6c4a730b42
commit 5b8961d02a
46 changed files with 1035 additions and 83 deletions

View File

@ -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";

View File

@ -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) {

View File

@ -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) {

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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()
}
)
]
}
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
})
}

View File

@ -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()
}
)
]
}
}

View File

@ -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

View File

@ -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?

View File

@ -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)

View File

@ -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

View File

@ -18,4 +18,16 @@ open class AlertContentNode: ASDisplayNode {
open func updateTheme(_ theme: AlertControllerTheme) {
}
open func performHighlightedAction() {
}
open func decreaseHighlightedIndex() {
}
open func increaseHighlightedIndex() {
}
}

View File

@ -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()
}
)
]
}
}

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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()

View File

@ -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
}
}

View File

@ -103,4 +103,8 @@ open class GalleryItemNode: ASDisplayNode {
open func contentSize() -> CGSize? {
return nil
}
open var keyShortcuts: [KeyShortcut] {
return []
}
}

View File

@ -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))
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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))

View File

@ -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()

View File

@ -226,6 +226,11 @@ final class AuthorizedApplicationContext {
}
}
strongSelf.mainWindow.forEachViewController(f)
if let globalOverlayController = strongSelf.rootController.globalOverlayControllers.last {
if !f(globalOverlayController) {
return
}
}
})
}
}

View File

@ -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

View File

@ -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):

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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) {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 {