Upgrade peek controller to modern context menu implementation

This commit is contained in:
Ilya Laktyushin 2021-05-07 21:16:24 +04:00
parent b1e91b3c72
commit 4a465a5893
42 changed files with 728 additions and 981 deletions

View File

@ -17,7 +17,7 @@ public protocol ContextActionNodeProtocol: ASDisplayNode {
final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
private let action: ContextMenuActionItem
private let getController: () -> ContextController?
private let getController: () -> ContextControllerProtocol?
private let actionSelected: (ContextMenuActionResult) -> Void
private let backgroundNode: ASDisplayNode
@ -33,7 +33,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
private var pointerInteraction: PointerInteraction?
init(presentationData: PresentationData, action: ContextMenuActionItem, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
init(presentationData: PresentationData, action: ContextMenuActionItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
self.action = action
self.getController = getController
self.actionSelected = actionSelected

View File

@ -69,7 +69,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
}
}
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
self.presentationData = presentationData
self.feedbackTap = feedbackTap
self.blurBackground = blurBackground
@ -460,7 +460,7 @@ final class ContextActionsContainerNode: ASDisplayNode {
return self.additionalActionsNode != nil
}
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, displayTextSelectionTip: Bool, blurBackground: Bool) {
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, displayTextSelectionTip: Bool, blurBackground: Bool) {
self.blurBackground = blurBackground
self.shadowNode = ASImageNode()
self.shadowNode.displaysAsynchronously = false

View File

@ -11,6 +11,13 @@ import SwiftSignalKit
private let animationDurationFactor: Double = 1.0
public protocol ContextControllerProtocol {
var useComplexItemsTransitionAnimation: Bool { get set }
func setItems(_ items: Signal<[ContextMenuItem], NoError>)
func dismiss(completion: (() -> Void)?)
}
public enum ContextMenuActionItemTextLayout {
case singleLine
case twoLinesMax
@ -66,9 +73,9 @@ public final class ContextMenuActionItem {
public let badge: ContextMenuActionBadge?
public let icon: (PresentationTheme) -> UIImage?
public let iconSource: ContextMenuActionItemIconSource?
public let action: (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void
public let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void
public init(text: String, textColor: ContextMenuActionItemTextColor = .primary, textLayout: ContextMenuActionItemTextLayout = .twoLinesMax, textFont: ContextMenuActionItemFont = .regular, badge: ContextMenuActionBadge? = nil, icon: @escaping (PresentationTheme) -> UIImage?, iconSource: ContextMenuActionItemIconSource? = nil, action: @escaping (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void) {
public init(text: String, textColor: ContextMenuActionItemTextColor = .primary, textLayout: ContextMenuActionItemTextLayout = .twoLinesMax, textFont: ContextMenuActionItemFont = .regular, badge: ContextMenuActionBadge? = nil, icon: @escaping (PresentationTheme) -> UIImage?, iconSource: ContextMenuActionItemIconSource? = nil, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) {
self.text = text
self.textColor = textColor
self.textFont = textFont
@ -86,7 +93,7 @@ public protocol ContextMenuCustomNode: ASDisplayNode {
}
public protocol ContextMenuCustomItem {
func node(presentationData: PresentationData, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode
}
public enum ContextMenuItem {
@ -113,7 +120,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
private let reactionSelected: (ReactionContextItem.Reaction) -> Void
private let beganAnimatingOut: () -> Void
private let attemptTransitionControllerIntoNavigation: () -> Void
private let getController: () -> ContextController?
private let getController: () -> ContextControllerProtocol?
private weak var gesture: ContextGesture?
private var displayTextSelectionTip: Bool
@ -1159,7 +1166,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
if let layout = self.validLayout {
self.updateLayout(layout: layout, transition: .animated(duration: 0.3, curve: .spring), previousActionsContainerNode: previousActionsContainerNode)
} else {
previousActionsContainerNode.removeFromSupernode()
}
@ -1748,7 +1754,7 @@ public enum ContextContentSource {
case controller(ContextControllerContentSource)
}
public final class ContextController: ViewController, StandalonePresentableController {
public final class ContextController: ViewController, StandalonePresentableController, ContextControllerProtocol {
private let account: Account
private var presentationData: PresentationData
private let source: ContextContentSource

View File

@ -1,6 +1,9 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramPresentationData
public final class PeekControllerTheme {
public let isDark: Bool
@ -20,12 +23,25 @@ public final class PeekControllerTheme {
}
}
public final class PeekController: ViewController {
extension PeekControllerTheme {
convenience public init(presentationTheme: PresentationTheme) {
let actionSheet = presentationTheme.actionSheet
self.init(isDark: actionSheet.backgroundType == .dark, menuBackgroundColor: actionSheet.opaqueItemBackgroundColor, menuItemHighligtedColor: actionSheet.opaqueItemHighlightedBackgroundColor, menuItemSeparatorColor: actionSheet.opaqueItemSeparatorColor, accentColor: actionSheet.controlAccentColor, destructiveColor: actionSheet.destructiveActionTextColor)
}
}
public final class PeekController: ViewController, ContextControllerProtocol {
public var useComplexItemsTransitionAnimation: Bool = false
public func setItems(_ items: Signal<[ContextMenuItem], NoError>) {
}
private var controllerNode: PeekControllerNode {
return self.displayNode as! PeekControllerNode
}
private let theme: PeekControllerTheme
private let presentationData: PresentationData
private let content: PeekControllerContent
var sourceNode: () -> ASDisplayNode?
@ -33,8 +49,8 @@ public final class PeekController: ViewController {
private var animatedIn = false
public init(theme: PeekControllerTheme, content: PeekControllerContent, sourceNode: @escaping () -> ASDisplayNode?) {
self.theme = theme
public init(presentationData: PresentationData, content: PeekControllerContent, sourceNode: @escaping () -> ASDisplayNode?) {
self.presentationData = presentationData
self.content = content
self.sourceNode = sourceNode
@ -48,7 +64,7 @@ public final class PeekController: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = PeekControllerNode(theme: self.theme, content: self.content, requestDismiss: { [weak self] in
self.displayNode = PeekControllerNode(presentationData: self.presentationData, controller: self, content: self.content, requestDismiss: { [weak self] in
self?.dismiss()
})
self.displayNodeDidLoad()

View File

@ -1,21 +1,22 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
public enum PeekControllerContentPresentation {
case contained
case freeform
}
public enum PeerkControllerMenuActivation {
public enum PeerControllerMenuActivation {
case drag
case press
}
public protocol PeekControllerContent {
func presentation() -> PeekControllerContentPresentation
func menuActivation() -> PeerkControllerMenuActivation
func menuItems() -> [PeekControllerMenuItem]
func menuActivation() -> PeerControllerMenuActivation
func menuItems() -> [ContextMenuItem]
func node() -> PeekControllerContentNode & ASDisplayNode
func topAccessoryNode() -> ASDisplayNode?

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display
private func traceDeceleratingScrollView(_ view: UIView, at point: CGPoint) -> Bool {
if view.bounds.contains(point), let view = view as? UIScrollView, view.isDecelerating {
@ -33,7 +34,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
}
}
private var menuActivation: PeerkControllerMenuActivation?
private var menuActivation: PeerControllerMenuActivation?
private weak var presentedController: PeekController?
public init(contentAtPoint: @escaping (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?, present: @escaping (PeekControllerContent, ASDisplayNode) -> ViewController?, updateContent: @escaping (PeekControllerContent?) -> Void = { _ in }, activateBySingleTap: Bool = false) {
@ -105,8 +106,8 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
(presentedController.displayNode as? PeekControllerNode)?.activateMenu()
}
self.menuActivation = nil
self.presentedController = nil
self.state = .ended
// self.presentedController = nil
// self.state = .ended
}
}
}
@ -136,10 +137,8 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
}
self.state = .ended
} else {
let velocity = self.velocity(in: self.view)
if let presentedController = self.presentedController, presentedController.isNodeLoaded {
(presentedController.displayNode as? PeekControllerNode)?.endDraggingWithVelocity(velocity.y)
if let presentedController = self.presentedController, presentedController.isNodeLoaded, let location = touches.first?.location(in: presentedController.view) {
(presentedController.displayNode as? PeekControllerNode)?.endDragging(location)
self.presentedController = nil
self.menuActivation = nil
}
@ -172,7 +171,12 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
if let touch = touches.first, let initialTapLocation = self.tapLocation {
let touchLocation = touch.location(in: self.view)
if let menuActivation = self.menuActivation, let presentedController = self.presentedController {
if let presentedController = self.presentedController, self.menuActivation == nil {
if presentedController.isNodeLoaded {
let touchLocation = touch.location(in: presentedController.view)
(presentedController.displayNode as? PeekControllerNode)?.applyDraggingOffset(touchLocation)
}
} else if let menuActivation = self.menuActivation, let presentedController = self.presentedController {
switch menuActivation {
case .drag:
var offset = touchLocation.y - initialTapLocation.y
@ -181,7 +185,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
offset = (-((1.0 - (1.0 / (((delta) * 0.55 / (factor)) + 1.0))) * factor)) * (offset < 0.0 ? 1.0 : -1.0)
if presentedController.isNodeLoaded {
(presentedController.displayNode as? PeekControllerNode)?.applyDraggingOffset(offset)
// (presentedController.displayNode as? PeekControllerNode)?.applyDraggingOffset(offset)
}
case .press:
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {

View File

@ -0,0 +1,346 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
private let animationDurationFactor: Double = 1.0
final class PeekControllerNode: ViewControllerTracingNode {
private let requestDismiss: () -> Void
private let presentationData: PresentationData
private let theme: PeekControllerTheme
private weak var controller: PeekController?
private let blurView: UIView
private let dimNode: ASDisplayNode
private let containerBackgroundNode: ASImageNode
private let containerNode: ASDisplayNode
private let darkDimNode: ASDisplayNode
private var validLayout: ContainerViewLayout?
private var content: PeekControllerContent
private var contentNode: PeekControllerContentNode & ASDisplayNode
private var contentNodeHasValidLayout = false
private var topAccessoryNode: ASDisplayNode?
private var actionsContainerNode: ContextActionsContainerNode
private var hapticFeedback = HapticFeedback()
private var initialContinueGesturePoint: CGPoint?
private var didMoveFromInitialGesturePoint = false
private var highlightedActionNode: ContextActionNodeProtocol?
init(presentationData: PresentationData, controller: PeekController, content: PeekControllerContent, requestDismiss: @escaping () -> Void) {
self.presentationData = presentationData
self.requestDismiss = requestDismiss
self.theme = PeekControllerTheme(presentationTheme: presentationData.theme)
self.controller = controller
self.dimNode = ASDisplayNode()
self.blurView = UIVisualEffectView(effect: UIBlurEffect(style: self.theme.isDark ? .dark : .light))
self.blurView.isUserInteractionEnabled = false
self.darkDimNode = ASDisplayNode()
self.darkDimNode.alpha = 0.0
self.darkDimNode.backgroundColor = presentationData.theme.contextMenu.dimColor
self.darkDimNode.isUserInteractionEnabled = false
switch content.menuActivation() {
case .drag:
self.dimNode.backgroundColor = nil
self.blurView.alpha = 1.0
case .press:
self.dimNode.backgroundColor = UIColor(white: self.theme.isDark ? 0.0 : 1.0, alpha: 0.5)
self.blurView.alpha = 0.0
}
self.containerBackgroundNode = ASImageNode()
self.containerBackgroundNode.isLayerBacked = true
self.containerBackgroundNode.displaysAsynchronously = false
self.containerNode = ASDisplayNode()
self.content = content
self.contentNode = content.node()
self.topAccessoryNode = content.topAccessoryNode()
var feedbackTapImpl: (() -> Void)?
var activatedActionImpl: (() -> Void)?
self.actionsContainerNode = ContextActionsContainerNode(presentationData: presentationData, items: content.menuItems(), getController: { [weak controller] in
return controller
}, actionSelected: { result in
activatedActionImpl?()
}, feedbackTap: {
feedbackTapImpl?()
}, displayTextSelectionTip: false, blurBackground: true)
self.actionsContainerNode.alpha = 0.0
super.init()
feedbackTapImpl = { [weak self] in
self?.hapticFeedback.tap()
}
if content.presentation() == .freeform {
self.containerNode.isUserInteractionEnabled = false
} else {
self.containerNode.clipsToBounds = true
self.containerNode.cornerRadius = 16.0
}
self.addSubnode(self.dimNode)
self.view.addSubview(self.blurView)
self.addSubnode(self.darkDimNode)
self.containerNode.addSubnode(self.contentNode)
self.addSubnode(self.actionsContainerNode)
self.addSubnode(self.containerNode)
activatedActionImpl = { [weak self] in
self?.requestDismiss()
}
self.hapticFeedback.prepareTap()
}
deinit {
}
override func didLoad() {
super.didLoad()
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTap(_:))))
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.darkDimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(view: self.blurView, frame: CGRect(origin: CGPoint(), size: layout.size))
var layoutInsets = layout.insets(options: [])
let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
layoutInsets.left = floor((layout.size.width - containerWidth) / 2.0)
layoutInsets.right = layoutInsets.left
if !layoutInsets.bottom.isZero {
layoutInsets.bottom -= 12.0
}
let maxContainerSize = CGSize(width: layout.size.width - 14.0 * 2.0, height: layout.size.height - layoutInsets.top - layoutInsets.bottom - 90.0)
let contentSize = self.contentNode.updateLayout(size: maxContainerSize, transition: self.contentNodeHasValidLayout ? transition : .immediate)
if self.contentNodeHasValidLayout {
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: contentSize))
} else {
self.contentNode.frame = CGRect(origin: CGPoint(), size: contentSize)
}
let actionsSideInset: CGFloat = layout.safeInsets.left + 11.0
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: .immediate)
let containerFrame: CGRect
let actionsFrame: CGRect
if layout.size.width > layout.size.height {
if self.actionsContainerNode.alpha.isZero {
containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize)
} else {
containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 3.0), y: floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize)
}
actionsFrame = CGRect(origin: CGPoint(x: containerFrame.maxX + 32.0, y: floor((layout.size.height - actionsSize.height) / 2.0)), size: actionsSize)
} else {
switch self.content.presentation() {
case .contained:
containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize)
case .freeform:
containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) / 3.0)), size: contentSize)
}
actionsFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - actionsSize.width) / 2.0), y: containerFrame.maxY + 32.0), size: actionsSize)
}
transition.updateFrame(node: self.containerNode, frame: containerFrame)
self.actionsContainerNode.updateSize(containerSize: actionsSize, contentSize: actionsSize)
transition.updateFrame(node: self.actionsContainerNode, frame: actionsFrame)
self.contentNodeHasValidLayout = true
}
func animateIn(from rect: CGRect) {
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.blurView.layer.animateAlpha(from: 0.0, to: self.blurView.alpha, duration: 0.3)
let offset = CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y)
self.containerNode.layer.animateSpring(from: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true)
self.containerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0)
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
if let topAccessoryNode = self.topAccessoryNode {
topAccessoryNode.layer.animateSpring(from: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true)
topAccessoryNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0)
topAccessoryNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
if case .press = self.content.menuActivation() {
self.hapticFeedback.tap()
} else {
self.hapticFeedback.impact()
}
}
func animateOut(to rect: CGRect, completion: @escaping () -> Void) {
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.blurView.layer.animateAlpha(from: self.blurView.alpha, to: 0.0, duration: 0.25, removeOnCompletion: false)
self.darkDimNode.layer.animateAlpha(from: self.darkDimNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
let springDuration: Double = 0.42 * animationDurationFactor
let springDamping: CGFloat = 104.0
let offset = CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y)
self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint()), to: NSValue(cgPoint: offset), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true, completion: { _ in
completion()
})
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.containerNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false)
if !self.actionsContainerNode.alpha.isZero {
let actionsOffset = CGPoint(x: rect.midX - self.actionsContainerNode.position.x, y: rect.midY - self.actionsContainerNode.position.y)
self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false)
self.actionsContainerNode.layer.animateSpring(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false)
self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint()), to: NSValue(cgPoint: actionsOffset), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
}
}
@objc func dimNodeTap(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.requestDismiss()
}
}
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
guard case .drag = self.content.menuActivation() else {
return
}
let location = recognizer.location(in: self.view)
switch recognizer.state {
case .began:
break
case .changed:
self.applyDraggingOffset(location)
case .cancelled, .ended:
self.endDragging(location)
default:
break
}
}
func applyDraggingOffset(_ offset: CGPoint) {
let localPoint = offset
let initialPoint: CGPoint
if let current = self.initialContinueGesturePoint {
initialPoint = current
} else {
initialPoint = localPoint
self.initialContinueGesturePoint = localPoint
}
if !self.actionsContainerNode.alpha.isZero {
if !self.didMoveFromInitialGesturePoint {
let distance = abs(localPoint.y - initialPoint.y)
if distance > 12.0 {
self.didMoveFromInitialGesturePoint = true
}
}
if self.didMoveFromInitialGesturePoint {
let actionPoint = self.view.convert(localPoint, to: self.actionsContainerNode.view)
let actionNode = self.actionsContainerNode.actionNode(at: actionPoint)
if self.highlightedActionNode !== actionNode {
self.highlightedActionNode?.setIsHighlighted(false)
self.highlightedActionNode = actionNode
if let actionNode = actionNode {
actionNode.setIsHighlighted(true)
self.hapticFeedback.tap()
}
}
}
}
}
func activateMenu() {
if case .press = self.content.menuActivation() {
self.hapticFeedback.impact()
}
let springDuration: Double = 0.42 * animationDurationFactor
let springDamping: CGFloat = 104.0
let previousBlurAlpha = self.blurView.alpha
self.blurView.alpha = 1.0
self.blurView.layer.animateAlpha(from: previousBlurAlpha, to: self.blurView.alpha, duration: 0.3)
let previousDarkDimAlpha = self.darkDimNode.alpha
self.darkDimNode.alpha = 1.0
self.darkDimNode.layer.animateAlpha(from: previousDarkDimAlpha, to: 1.0, duration: 0.3)
self.actionsContainerNode.alpha = 1.0
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
self.actionsContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
let localContentSourceFrame = self.containerNode.frame
self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localContentSourceFrame.center.x - self.actionsContainerNode.position.x, y: localContentSourceFrame.center.y - self.actionsContainerNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .animated(duration: springDuration, curve: .spring))
}
}
func endDragging(_ location: CGPoint) {
if self.didMoveFromInitialGesturePoint {
if let highlightedActionNode = self.highlightedActionNode {
self.highlightedActionNode = nil
highlightedActionNode.performAction()
}
}
}
func updateContent(content: PeekControllerContent) {
let contentNode = self.contentNode
contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak contentNode] _ in
contentNode?.removeFromSupernode()
})
contentNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.15, removeOnCompletion: false)
self.content = content
self.contentNode = content.node()
self.containerNode.addSubnode(self.contentNode)
self.contentNodeHasValidLayout = false
let previousActionsContainerNode = self.actionsContainerNode
self.actionsContainerNode = ContextActionsContainerNode(presentationData: self.presentationData, items: content.menuItems(), getController: { [weak self] in
return self?.controller
}, actionSelected: { [weak self] result in
self?.requestDismiss()
}, feedbackTap: { [weak self] in
self?.hapticFeedback.tap()
}, displayTextSelectionTip: false, blurBackground: true)
self.actionsContainerNode.alpha = 0.0
self.insertSubnode(self.actionsContainerNode, aboveSubnode: previousActionsContainerNode)
previousActionsContainerNode.removeFromSupernode()
self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
self.contentNode.layer.animateSpring(from: 0.35 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.15, curve: .easeInOut))
}
self.hapticFeedback.tap()
}
}

View File

@ -1,108 +0,0 @@
import Foundation
import UIKit
import AsyncDisplayKit
public enum PeekControllerMenuItemColor {
case accent
case destructive
}
public enum PeekControllerMenuItemFont {
case `default`
case bold
}
public struct PeekControllerMenuItem {
public let title: String
public let color: PeekControllerMenuItemColor
public let font: PeekControllerMenuItemFont
public let action: (ASDisplayNode, CGRect) -> Bool
public init(title: String, color: PeekControllerMenuItemColor, font: PeekControllerMenuItemFont = .default, action: @escaping (ASDisplayNode, CGRect) -> Bool) {
self.title = title
self.color = color
self.font = font
self.action = action
}
}
final class PeekControllerMenuItemNode: HighlightTrackingButtonNode {
private let item: PeekControllerMenuItem
private let activatedAction: () -> Void
private let separatorNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let textNode: ImmediateTextNode
init(theme: PeekControllerTheme, item: PeekControllerMenuItem, activatedAction: @escaping () -> Void) {
self.item = item
self.activatedAction = activatedAction
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
self.separatorNode.backgroundColor = theme.menuItemSeparatorColor
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
self.highlightedBackgroundNode.backgroundColor = theme.menuItemHighligtedColor
self.highlightedBackgroundNode.alpha = 0.0
self.textNode = ImmediateTextNode()
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = false
let textColor: UIColor
let textFont: UIFont
switch item.color {
case .accent:
textColor = theme.accentColor
case .destructive:
textColor = theme.destructiveColor
}
switch item.font {
case .default:
textFont = Font.regular(20.0)
case .bold:
textFont = Font.medium(20.0)
}
self.textNode.attributedText = NSAttributedString(string: item.title, font: textFont, textColor: textColor)
super.init()
self.addSubnode(self.separatorNode)
self.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.textNode)
self.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.view.superview?.bringSubviewToFront(strongSelf.view)
strongSelf.highlightedBackgroundNode.alpha = 1.0
} else {
strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
}
self.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let height: CGFloat = 57.0
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: height)))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: height), size: CGSize(width: width, height: UIScreenPixel)))
let textSize = self.textNode.updateLayout(CGSize(width: width - 10.0, height: height))
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: floor((height - textSize.height) / 2.0)), size: textSize))
return height
}
@objc func buttonPressed() {
self.activatedAction()
if self.item.action(self, self.bounds) {
}
}
}

View File

@ -1,31 +0,0 @@
import Foundation
import UIKit
import AsyncDisplayKit
final class PeekControllerMenuNode: ASDisplayNode {
private let itemNodes: [PeekControllerMenuItemNode]
init(theme: PeekControllerTheme, items: [PeekControllerMenuItem], activatedAction: @escaping () -> Void) {
self.itemNodes = items.map { PeekControllerMenuItemNode(theme: theme, item: $0, activatedAction: activatedAction) }
super.init()
self.backgroundColor = theme.menuBackgroundColor
self.cornerRadius = 16.0
self.clipsToBounds = true
for itemNode in self.itemNodes {
self.addSubnode(itemNode)
}
}
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
var verticalOffset: CGFloat = 0.0
for itemNode in self.itemNodes {
let itemHeight = itemNode.updateLayout(width: width, transition: transition)
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: width, height: itemHeight)))
verticalOffset += itemHeight
}
return verticalOffset - UIScreenPixel
}
}

View File

@ -1,357 +0,0 @@
import Foundation
import UIKit
import AsyncDisplayKit
final class PeekControllerNode: ViewControllerTracingNode {
private let requestDismiss: () -> Void
private let theme: PeekControllerTheme
private let blurView: UIView
private let dimNode: ASDisplayNode
private let containerBackgroundNode: ASImageNode
private let containerNode: ASDisplayNode
private var validLayout: ContainerViewLayout?
private var containerOffset: CGFloat = 0.0
private var panInitialContainerOffset: CGFloat?
private var content: PeekControllerContent
private var contentNode: PeekControllerContentNode & ASDisplayNode
private var contentNodeHasValidLayout = false
private var topAccessoryNode: ASDisplayNode?
private var menuNode: PeekControllerMenuNode?
private var displayingMenu = false
private var hapticFeedback: HapticFeedback?
init(theme: PeekControllerTheme, content: PeekControllerContent, requestDismiss: @escaping () -> Void) {
self.theme = theme
self.requestDismiss = requestDismiss
self.dimNode = ASDisplayNode()
self.blurView = UIVisualEffectView(effect: UIBlurEffect(style: theme.isDark ? .dark : .light))
self.blurView.isUserInteractionEnabled = false
switch content.menuActivation() {
case .drag:
self.dimNode.backgroundColor = nil
self.blurView.alpha = 1.0
case .press:
self.dimNode.backgroundColor = UIColor(white: theme.isDark ? 0.0 : 1.0, alpha: 0.5)
self.blurView.alpha = 0.0
}
self.containerBackgroundNode = ASImageNode()
self.containerBackgroundNode.isLayerBacked = true
self.containerBackgroundNode.displaysAsynchronously = false
self.containerNode = ASDisplayNode()
self.content = content
self.contentNode = content.node()
self.topAccessoryNode = content.topAccessoryNode()
var activatedActionImpl: (() -> Void)?
let menuItems = content.menuItems()
if menuItems.isEmpty {
self.menuNode = nil
} else {
self.menuNode = PeekControllerMenuNode(theme: theme, items: menuItems, activatedAction: {
activatedActionImpl?()
})
}
super.init()
if content.presentation() == .freeform {
self.containerNode.isUserInteractionEnabled = false
} else {
self.containerNode.clipsToBounds = true
self.containerNode.cornerRadius = 16.0
}
self.addSubnode(self.dimNode)
self.view.addSubview(self.blurView)
self.containerNode.addSubnode(self.contentNode)
self.addSubnode(self.containerNode)
if let topAccessoryNode = self.topAccessoryNode {
self.addSubnode(topAccessoryNode)
}
if let menuNode = self.menuNode {
self.addSubnode(menuNode)
}
activatedActionImpl = { [weak self] in
self?.requestDismiss()
}
self.hapticFeedback = HapticFeedback()
self.hapticFeedback?.prepareTap()
}
deinit {
}
override func didLoad() {
super.didLoad()
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTap(_:))))
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(view: self.blurView, frame: CGRect(origin: CGPoint(), size: layout.size))
var layoutInsets = layout.insets(options: [])
let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
layoutInsets.left = floor((layout.size.width - containerWidth) / 2.0)
layoutInsets.right = layoutInsets.left
if !layoutInsets.bottom.isZero {
layoutInsets.bottom -= 12.0
}
let maxContainerSize = CGSize(width: layout.size.width - 14.0 * 2.0, height: layout.size.height - layoutInsets.top - layoutInsets.bottom - 90.0)
var menuSize: CGSize?
let contentSize = self.contentNode.updateLayout(size: maxContainerSize, transition: self.contentNodeHasValidLayout ? transition : .immediate)
if self.contentNodeHasValidLayout {
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: contentSize))
} else {
self.contentNode.frame = CGRect(origin: CGPoint(), size: contentSize)
}
var containerFrame: CGRect
switch self.content.presentation() {
case .contained:
containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize)
case .freeform:
containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) / 4.0)), size: contentSize)
}
if let menuNode = self.menuNode {
let menuWidth = layout.size.width - layoutInsets.left - layoutInsets.right - 14.0 * 2.0
let menuHeight = menuNode.updateLayout(width: menuWidth, transition: transition)
menuSize = CGSize(width: menuWidth, height: menuHeight)
if self.displayingMenu {
let upperBound = layout.size.height - layoutInsets.bottom - menuHeight - 14.0 * 2.0 - containerFrame.height
if containerFrame.origin.y > upperBound {
containerFrame.origin.y = upperBound
}
transition.updateAlpha(layer: self.blurView.layer, alpha: 1.0)
}
}
if self.displayingMenu {
var offset = self.containerOffset
let delta = abs(offset)
let factor: CGFloat = 60.0
offset = (-((1.0 - (1.0 / (((delta) * 0.55 / (factor)) + 1.0))) * factor)) * (offset < 0.0 ? 1.0 : -1.0)
containerFrame = containerFrame.offsetBy(dx: 0.0, dy: offset)
} else {
containerFrame = containerFrame.offsetBy(dx: 0.0, dy: self.containerOffset)
}
transition.updateFrame(node: self.containerNode, frame: containerFrame)
if let topAccessoryNode = self.topAccessoryNode {
let accessorySize = topAccessoryNode.frame.size
let accessoryFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(containerFrame.midX - accessorySize.width / 2.0), y: containerFrame.minY - accessorySize.height - 16.0), size: accessorySize)
transition.updateFrame(node: topAccessoryNode, frame: accessoryFrame)
transition.updateAlpha(node: topAccessoryNode, alpha: self.displayingMenu ? 0.0 : 1.0)
}
if let menuNode = self.menuNode, let menuSize = menuSize {
let menuY: CGFloat
if self.displayingMenu {
menuY = max(containerFrame.maxY + 14.0, layout.size.height - layoutInsets.bottom - 14.0 - menuSize.height)
} else {
menuY = layout.size.height + 14.0
}
let menuFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - menuSize.width) / 2.0), y: menuY), size: menuSize)
if self.contentNodeHasValidLayout {
transition.updateFrame(node: menuNode, frame: menuFrame)
} else {
menuNode.frame = menuFrame
}
}
self.contentNodeHasValidLayout = true
}
func animateIn(from rect: CGRect) {
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.blurView.layer.animateAlpha(from: 0.0, to: self.blurView.alpha, duration: 0.3)
let offset = CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y)
self.containerNode.layer.animateSpring(from: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true)
self.containerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0)
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
if let topAccessoryNode = self.topAccessoryNode {
topAccessoryNode.layer.animateSpring(from: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true)
topAccessoryNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0)
topAccessoryNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
if case .press = self.content.menuActivation() {
self.hapticFeedback?.tap()
} else {
self.hapticFeedback?.impact()
}
}
func animateOut(to rect: CGRect, completion: @escaping () -> Void) {
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.blurView.layer.animateAlpha(from: self.blurView.alpha, to: 0.0, duration: 0.25, removeOnCompletion: false)
let offset = CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y)
self.containerNode.layer.animatePosition(from: CGPoint(), to: offset, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, force: true, completion: { _ in
completion()
})
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.containerNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false)
if let topAccessoryNode = self.topAccessoryNode {
topAccessoryNode.layer.animatePosition(from: CGPoint(), to: offset, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, force: true, completion: { _ in
completion()
})
topAccessoryNode.layer.animateAlpha(from: topAccessoryNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
topAccessoryNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false)
}
if let menuNode = self.menuNode {
menuNode.layer.animatePosition(from: menuNode.position, to: CGPoint(x: menuNode.position.x, y: self.bounds.size.height + menuNode.bounds.size.height / 2.0), duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false)
}
}
@objc func dimNodeTap(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.requestDismiss()
}
}
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
guard case .drag = self.content.menuActivation() else {
return
}
switch recognizer.state {
case .began:
self.panInitialContainerOffset = self.containerOffset
case .changed:
if let panInitialContainerOffset = self.panInitialContainerOffset {
let translation = recognizer.translation(in: self.view)
var offset = panInitialContainerOffset + translation.y
if offset < 0.0 {
let delta = abs(offset)
let factor: CGFloat = 60.0
offset = (-((1.0 - (1.0 / (((delta) * 0.55 / (factor)) + 1.0))) * factor)) * (offset < 0.0 ? 1.0 : -1.0)
}
self.applyDraggingOffset(offset)
}
case .cancelled, .ended:
if let _ = self.panInitialContainerOffset {
self.panInitialContainerOffset = nil
if self.containerOffset < 0.0 {
self.activateMenu()
} else {
self.requestDismiss()
}
}
default:
break
}
}
func applyDraggingOffset(_ offset: CGFloat) {
self.containerOffset = offset
if self.containerOffset < -25.0 {
//self.displayingMenu = true
} else {
//self.displayingMenu = false
}
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .immediate)
}
}
func activateMenu() {
if case .press = self.content.menuActivation() {
self.hapticFeedback?.impact()
}
if let layout = self.validLayout {
self.displayingMenu = true
self.containerOffset = 0.0
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.18, curve: .spring))
}
}
func endDraggingWithVelocity(_ velocity: CGFloat) {
if let _ = self.menuNode, velocity < -600.0 || self.containerOffset < -38.0 {
if let layout = self.validLayout {
self.displayingMenu = true
self.containerOffset = 0.0
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.18, curve: .spring))
}
} else {
self.requestDismiss()
}
}
func updateContent(content: PeekControllerContent) {
let contentNode = self.contentNode
contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak contentNode] _ in
contentNode?.removeFromSupernode()
})
contentNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.15, removeOnCompletion: false)
self.menuNode?.removeFromSupernode()
self.menuNode = nil
self.content = content
self.contentNode = content.node()
self.containerNode.addSubnode(self.contentNode)
self.contentNodeHasValidLayout = false
var activatedActionImpl: (() -> Void)?
let menuItems = content.menuItems()
if menuItems.isEmpty {
self.menuNode = nil
} else {
self.menuNode = PeekControllerMenuNode(theme: self.theme, items: menuItems, activatedAction: {
activatedActionImpl?()
})
}
if let menuNode = self.menuNode {
self.addSubnode(menuNode)
}
activatedActionImpl = { [weak self] in
self?.requestDismiss()
}
self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
self.contentNode.layer.animateSpring(from: 0.35 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.15, curve: .easeInOut))
}
self.hapticFeedback?.tap()
}
}

View File

@ -183,8 +183,6 @@ public enum TabBarItemContextActionType {
public let navigationBar: NavigationBar?
private(set) var toolbar: Toolbar?
private var previewingContext: Any?
public var displayNavigationBar = true
open var navigationBarRequiresEntireLayoutUpdate: Bool {
return true
@ -612,33 +610,6 @@ public enum TabBarItemContextActionType {
}
}
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
open func registerForPreviewing(with delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, theme: PeekControllerTheme, onlyNative: Bool) {
}
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
public func registerForPreviewingNonNative(with delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, theme: PeekControllerTheme) {
if true || self.traitCollection.forceTouchCapability != .available {
if self.previewingContext == nil {
let previewingContext = SimulatedViewControllerPreviewing(theme: theme, delegate: delegate, sourceView: sourceView, node: self.displayNode, present: { [weak self] c, a in
self?.presentInGlobalOverlay(c, with: a)
}, customPresent: { [weak self] c, n in
return self?.customPresentPreviewingController?(c, n)
})
self.previewingContext = previewingContext
}
}
}
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
open override func unregisterForPreviewing(withContext previewing: UIViewControllerPreviewing) {
if self.previewingContext != nil {
self.previewingContext = nil
} else {
super.unregisterForPreviewing(withContext: previewing)
}
}
public final func navigationNextSibling() -> UIViewController? {
if let navigationController = self.navigationController as? NavigationController {
if let index = navigationController.viewControllers.firstIndex(where: { $0 === self }) {

View File

@ -1,137 +0,0 @@
import Foundation
import UIKit
import AsyncDisplayKit
import SwiftSignalKit
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
private final class ViewControllerPeekContent: PeekControllerContent {
let controller: ViewController
private let menu: [PeekControllerMenuItem]
init(controller: ViewController) {
self.controller = controller
var menu: [PeekControllerMenuItem] = []
for item in controller.previewActionItems {
menu.append(PeekControllerMenuItem(title: item.title, color: .accent, action: { [weak controller] _, _ in
if let controller = controller, let item = item as? UIPreviewAction {
item.handler(item, controller)
}
return true
}))
}
self.menu = menu
}
func presentation() -> PeekControllerContentPresentation {
return .contained
}
func menuActivation() -> PeerkControllerMenuActivation {
return .drag
}
func menuItems() -> [PeekControllerMenuItem] {
return self.menu
}
func node() -> PeekControllerContentNode & ASDisplayNode {
return ViewControllerPeekContentNode(controller: self.controller)
}
func topAccessoryNode() -> ASDisplayNode? {
return nil
}
func isEqual(to: PeekControllerContent) -> Bool {
if let to = to as? ViewControllerPeekContent {
return self.controller === to.controller
} else {
return false
}
}
}
private final class ViewControllerPeekContentNode: ASDisplayNode, PeekControllerContentNode {
private let controller: ViewController
private var hasValidLayout = false
init(controller: ViewController) {
self.controller = controller
super.init()
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
if !self.hasValidLayout {
self.hasValidLayout = true
self.controller.view.frame = CGRect(origin: CGPoint(), size: size)
self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), deviceMetrics: .unknown(screenSize: size, statusBarHeight: 20.0, onScreenNavigationHeight: nil), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate)
self.controller.setIgnoreAppearanceMethodInvocations(true)
self.view.addSubview(self.controller.view)
self.controller.setIgnoreAppearanceMethodInvocations(false)
self.controller.viewWillAppear(false)
self.controller.viewDidAppear(false)
} else {
self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), deviceMetrics: .unknown(screenSize: size, statusBarHeight: 20.0, onScreenNavigationHeight: nil), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition)
}
return size
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.bounds.contains(point) {
return self.view
}
return nil
}
}
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
final class SimulatedViewControllerPreviewing: NSObject, UIViewControllerPreviewing {
weak var delegateImpl: UIViewControllerPreviewingDelegate?
var delegate: UIViewControllerPreviewingDelegate {
return self.delegateImpl!
}
let recognizer: PeekControllerGestureRecognizer
var previewingGestureRecognizerForFailureRelationship: UIGestureRecognizer {
return self.recognizer
}
let sourceView: UIView
let node: ASDisplayNode
var sourceRect: CGRect = CGRect()
init(theme: PeekControllerTheme, delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, node: ASDisplayNode, present: @escaping (ViewController, Any?) -> Void, customPresent: ((ViewController, ASDisplayNode) -> ViewController?)?) {
self.delegateImpl = delegate
self.sourceView = sourceView
self.node = node
var contentAtPointImpl: ((CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?)?
self.recognizer = PeekControllerGestureRecognizer(contentAtPoint: { point in
return contentAtPointImpl?(point)
}, present: { content, sourceNode in
if let content = content as? ViewControllerPeekContent, let controller = customPresent?(content.controller, sourceNode) {
present(controller, nil)
return controller
} else {
let controller = PeekController(theme: theme, content: content, sourceNode: {
return sourceNode
})
present(controller, nil)
return controller
}
})
node.view.addGestureRecognizer(self.recognizer)
super.init()
contentAtPointImpl = { [weak self] point in
if let strongSelf = self, let delegate = strongSelf.delegateImpl {
if let controller = delegate.previewingContext(strongSelf, viewControllerForLocation: point) as? ViewController {
return .single((strongSelf.node, ViewControllerPeekContent(controller: controller)))
}
}
return nil
}
}
}

View File

@ -17,7 +17,7 @@ swift_library(
"//submodules/AccountContext:AccountContext",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/ShareController:ShareController",
"//submodules/ItemListUI:ItemListUI",
"//submodules/ItemListUI:ItemListUI",
"//submodules/StickerResources:StickerResources",
"//submodules/AlertUI:AlertUI",
"//submodules/PresentationDataUtils:PresentationDataUtils",
@ -28,6 +28,7 @@ swift_library(
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/ShimmerEffect:ShimmerEffect",
"//submodules/UndoUI:UndoUI",
"//submodules/ContextUI:ContextUI",
],
visibility = [
"//visibility:public",

View File

@ -12,6 +12,7 @@ import MergeLists
import ActivityIndicator
import TextFormat
import AccountContext
import ContextUI
private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
let index: Int
@ -189,7 +190,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.presentationData.theme), content: content, sourceNode: {
let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceNode: {
return sourceNode
})
controller.visibilityUpdated = { [weak self] visible in

View File

@ -9,13 +9,14 @@ import SwiftSignalKit
import StickerResources
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ContextUI
public final class StickerPreviewPeekContent: PeekControllerContent {
let account: Account
public let item: ImportStickerPack.Sticker
let menu: [PeekControllerMenuItem]
let menu: [ContextMenuItem]
public init(account: Account, item: ImportStickerPack.Sticker, menu: [PeekControllerMenuItem]) {
public init(account: Account, item: ImportStickerPack.Sticker, menu: [ContextMenuItem]) {
self.account = account
self.item = item
self.menu = menu
@ -25,11 +26,11 @@ public final class StickerPreviewPeekContent: PeekControllerContent {
return .freeform
}
public func menuActivation() -> PeerkControllerMenuActivation {
public func menuActivation() -> PeerControllerMenuActivation {
return .press
}
public func menuItems() -> [PeekControllerMenuItem] {
public func menuItems() -> [ContextMenuItem] {
return self.menu
}

View File

@ -33,7 +33,7 @@ public enum PeerReportOption {
case other
}
public func presentPeerReportOptions(context: AccountContext, parent: ViewController, contextController: ContextController?, backAction: ((ContextController) -> Void)? = nil, subject: PeerReportSubject, options: [PeerReportOption] = [.spam, .violence, .pornography, .childAbuse, .copyright, .other], passthrough: Bool = false, completion: @escaping (ReportReason?, Bool) -> Void) {
public func presentPeerReportOptions(context: AccountContext, parent: ViewController, contextController: ContextControllerProtocol?, backAction: ((ContextControllerProtocol) -> Void)? = nil, subject: PeerReportSubject, options: [PeerReportOption] = [.spam, .violence, .pornography, .childAbuse, .copyright, .other], passthrough: Bool = false, completion: @escaping (ReportReason?, Bool) -> Void) {
if let contextController = contextController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var items: [ContextMenuItem] = []
@ -163,7 +163,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
}
contextController.setItems(.single(items))
} else {
contextController?.dismiss()
contextController?.dismiss(completion: nil)
parent.view.endEditing(true)
parent.present(peerReportOptionsController(context: context, subject: subject, passthrough: passthrough, present: { [weak parent] c, a in
parent?.present(c, in: .window(.root), with: a)

View File

@ -28,6 +28,7 @@ swift_library(
"//submodules/ArchivedStickerPacksNotice:ArchivedStickerPacksNotice",
"//submodules/ShimmerEffect:ShimmerEffect",
"//submodules/UndoUI:UndoUI",
"//submodules/ContextUI:ContextUI",
],
visibility = [
"//visibility:public",

View File

@ -12,6 +12,7 @@ import MergeLists
import ActivityIndicator
import TextFormat
import AccountContext
import ContextUI
private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
let index: Int
@ -204,28 +205,26 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self {
var menuItems: [PeekControllerMenuItem] = []
var menuItems: [ContextMenuItem] = []
if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
if strongSelf.sendSticker != nil {
menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in
if let strongSelf = self {
return strongSelf.sendSticker?(.standalone(media: item.file), node, rect) ?? false
} else {
return false
}
}))
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect)
})))
}
menuItems.append(PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
} else {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
menuItems.append(.action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
} else {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
return true
}))
menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true }))
}
})))
}
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else {
@ -237,7 +236,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.presentationData.theme), content: content, sourceNode: {
let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceNode: {
return sourceNode
})
controller.visibilityUpdated = { [weak self] visible in

View File

@ -11,6 +11,7 @@ import TelegramPresentationData
import TelegramUIPreferences
import MergeLists
import ShimmerEffect
import ContextUI
private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
let index: Int
@ -276,28 +277,26 @@ private final class StickerPackContainer: ASDisplayNode {
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self {
var menuItems: [PeekControllerMenuItem] = []
var menuItems: [ContextMenuItem] = []
if let (info, _, _) = strongSelf.currentStickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
if strongSelf.sendSticker != nil {
menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in
if let strongSelf = self {
return strongSelf.sendSticker?(.standalone(media: item.file), node, rect) ?? false
} else {
return false
}
}))
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect)
})))
}
menuItems.append(PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
} else {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
menuItems.append(.action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
} else {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
return true
}))
menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true }))
}
})))
}
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else {
@ -309,7 +308,7 @@ private final class StickerPackContainer: ASDisplayNode {
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.presentationData.theme), content: content, sourceNode: {
let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceNode: {
return sourceNode
})
strongSelf.presentInGlobalOverlay(controller, nil)

View File

@ -9,6 +9,7 @@ import SwiftSignalKit
import StickerResources
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ContextUI
public enum StickerPreviewPeekItem: Equatable {
case pack(StickerPackItem)
@ -27,9 +28,9 @@ public enum StickerPreviewPeekItem: Equatable {
public final class StickerPreviewPeekContent: PeekControllerContent {
let account: Account
public let item: StickerPreviewPeekItem
let menu: [PeekControllerMenuItem]
let menu: [ContextMenuItem]
public init(account: Account, item: StickerPreviewPeekItem, menu: [PeekControllerMenuItem]) {
public init(account: Account, item: StickerPreviewPeekItem, menu: [ContextMenuItem]) {
self.account = account
self.item = item
self.menu = menu
@ -39,11 +40,11 @@ public final class StickerPreviewPeekContent: PeekControllerContent {
return .freeform
}
public func menuActivation() -> PeerkControllerMenuActivation {
public func menuActivation() -> PeerControllerMenuActivation {
return .press
}
public func menuItems() -> [PeekControllerMenuItem] {
public func menuItems() -> [ContextMenuItem] {
return self.menu
}
@ -123,7 +124,7 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
let imageSize = dimensitons.cgSize.aspectFitted(boundingSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: textSize.height + textSpacing), size: imageSize)
let imageFrame = CGRect(origin: CGPoint(x: 0.0, y: textSize.height + textSpacing), size: imageSize)
self.imageNode.frame = imageFrame
if let animationNode = self.animationNode {
animationNode.frame = imageFrame
@ -132,7 +133,7 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
self.textNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - textSize.width) / 2.0), y: -textSize.height - textSpacing), size: textSize)
return CGSize(width: size.width, height: imageFrame.height + textSize.height + textSpacing)
return CGSize(width: imageFrame.width, height: imageFrame.height + textSize.height + textSpacing)
} else {
return CGSize(width: size.width, height: 10.0)
}

View File

@ -73,6 +73,9 @@ final class GroupVideoNode: ASDisplayNode {
private let videoViewContainer: UIView
private let videoView: PresentationCallVideoView
private var effectView: UIVisualEffectView?
private var isBlurred: Bool = false
private var validLayout: (CGSize, Bool)?
var tapped: (() -> Void)?
@ -121,6 +124,52 @@ final class GroupVideoNode: ASDisplayNode {
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
func updateIsBlurred(isBlurred: Bool, light: Bool = false, animated: Bool = true) {
if self.isBlurred == isBlurred {
return
}
self.isBlurred = isBlurred
if isBlurred {
if self.effectView == nil {
let effectView = UIVisualEffectView()
self.effectView = effectView
effectView.frame = self.videoViewContainer.bounds
self.videoViewContainer.addSubview(effectView)
}
if animated {
UIView.animate(withDuration: 0.3, animations: {
self.effectView?.effect = UIBlurEffect(style: light ? .light : .dark)
})
} else {
self.effectView?.effect = UIBlurEffect(style: light ? .light : .dark)
}
} else if let effectView = self.effectView {
self.effectView = nil
UIView.animate(withDuration: 0.3, animations: {
effectView.effect = nil
}, completion: { [weak effectView] _ in
effectView?.removeFromSuperview()
})
}
}
func flip(withBackground: Bool) {
if withBackground {
self.backgroundColor = .black
}
UIView.transition(with: withBackground ? self.videoViewContainer : self.view, duration: 0.4, options: [.transitionFlipFromLeft, .curveEaseOut], animations: {
UIView.performWithoutAnimation {
self.updateIsBlurred(isBlurred: true, light: true, animated: false)
}
}) { finished in
self.backgroundColor = nil
Queue.mainQueue().after(0.5) {
self.updateIsBlurred(isBlurred: false)
}
}
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.tapped?()
@ -186,6 +235,10 @@ final class GroupVideoNode: ASDisplayNode {
let transition: ContainedViewLayoutTransition = .immediate
transition.updateTransformRotation(view: self.videoView.view, angle: angle)
if let effectView = self.effectView {
transition.updateFrame(view: effectView, frame: self.videoViewContainer.bounds)
}
// TODO: properly fix the issue
// On iOS 13 and later metal layer transformation is broken if the layer does not require compositing
self.videoView.view.alpha = 0.995
@ -1989,8 +2042,9 @@ public final class VoiceChatController: ViewController {
if let (peerId, _) = maxLevelWithVideo {
if strongSelf.currentDominantSpeakerWithVideo != peerId {
strongSelf.currentDominantSpeakerWithVideo = peerId
strongSelf.updateMainParticipant(waitForFullSize: true)
if !strongSelf.requestedVideoSources.isEmpty {
strongSelf.updateMainParticipant(waitForFullSize: true)
}
}
}
@ -2159,6 +2213,8 @@ public final class VoiceChatController: ViewController {
}
strongSelf.updateMainParticipant(waitForFullSize: false)
}
} else if strongSelf.currentDominantSpeakerWithVideo != nil && !strongSelf.requestedVideoSources.isEmpty {
strongSelf.updateMainParticipant(waitForFullSize: true)
}
if updated {
@ -3819,6 +3875,9 @@ public final class VoiceChatController: ViewController {
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
self.cameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: hasVideo ? .cameraOn : .cameraOff), text: self.presentationData.strings.VoiceChat_Video, transition: transition)
transition.updateAlpha(node: self.switchCameraButton, alpha: self.call.hasVideo ? 1.0 : 0.0)
transition.updateAlpha(node: self.audioButton, alpha: self.call.hasVideo ? 0.0 : 1.0)
self.switchCameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: .flipCamera), text: "", transition: transition)
self.audioButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage, isEnabled: soundEnabled), text: soundTitle, transition: transition)

View File

@ -17,7 +17,7 @@ public final class VoiceChatInfoContextItem: ContextMenuCustomItem {
self.icon = icon
}
public func node(presentationData: PresentationData, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
return VoiceChatInfoContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
}
}
@ -25,14 +25,14 @@ public final class VoiceChatInfoContextItem: ContextMenuCustomItem {
private final class VoiceChatInfoContextItemNode: ASDisplayNode, ContextMenuCustomNode {
private let item: VoiceChatInfoContextItem
private let presentationData: PresentationData
private let getController: () -> ContextController?
private let getController: () -> ContextControllerProtocol?
private let actionSelected: (ContextMenuActionResult) -> Void
private let backgroundNode: ASDisplayNode
private let textNode: ImmediateTextNode
private let iconNode: ASImageNode
init(presentationData: PresentationData, item: VoiceChatInfoContextItem, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
init(presentationData: PresentationData, item: VoiceChatInfoContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
self.item = item
self.presentationData = presentationData
self.getController = getController

View File

@ -22,14 +22,14 @@ func generateStartRecordingIcon(color: UIColor) -> UIImage? {
final class VoiceChatRecordingContextItem: ContextMenuCustomItem {
fileprivate let timestamp: Int32
fileprivate let action: (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void
fileprivate let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void
init(timestamp: Int32, action: @escaping (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void) {
init(timestamp: Int32, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) {
self.timestamp = timestamp
self.action = action
}
func node(presentationData: PresentationData, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
return VoiceChatRecordingContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
}
}
@ -94,7 +94,7 @@ class VoiceChatRecordingIconNode: ASDisplayNode {
private final class VoiceChatRecordingContextItemNode: ASDisplayNode, ContextMenuCustomNode {
private let item: VoiceChatRecordingContextItem
private let presentationData: PresentationData
private let getController: () -> ContextController?
private let getController: () -> ContextControllerProtocol?
private let actionSelected: (ContextMenuActionResult) -> Void
private let backgroundNode: ASDisplayNode
@ -108,7 +108,7 @@ private final class VoiceChatRecordingContextItemNode: ASDisplayNode, ContextMen
private var pointerInteraction: PointerInteraction?
init(presentationData: PresentationData, item: VoiceChatRecordingContextItem, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
init(presentationData: PresentationData, item: VoiceChatRecordingContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
self.item = item
self.presentationData = presentationData
self.getController = getController

View File

@ -17,7 +17,7 @@ final class VoiceChatVolumeContextItem: ContextMenuCustomItem {
self.valueChanged = valueChanged
}
func node(presentationData: PresentationData, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
return VoiceChatVolumeContextItemNode(presentationData: presentationData, getController: getController, minValue: self.minValue, value: self.value, valueChanged: self.valueChanged)
}
}
@ -45,7 +45,7 @@ private final class VoiceChatVolumeContextItemNode: ASDisplayNode, ContextMenuCu
private let hapticFeedback = HapticFeedback()
init(presentationData: PresentationData, getController: @escaping () -> ContextController?, minValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
init(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, minValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
self.presentationData = presentationData
self.minValue = minValue
self.value = value

View File

@ -104,13 +104,6 @@ public extension AlertControllerTheme {
}
}
extension PeekControllerTheme {
convenience public init(presentationTheme: PresentationTheme) {
let actionSheet = presentationTheme.actionSheet
self.init(isDark: actionSheet.backgroundType == .dark, menuBackgroundColor: actionSheet.opaqueItemBackgroundColor, menuItemHighligtedColor: actionSheet.opaqueItemHighlightedBackgroundColor, menuItemSeparatorColor: actionSheet.opaqueItemSeparatorColor, accentColor: actionSheet.controlAccentColor, destructiveColor: actionSheet.destructiveActionTextColor)
}
}
public extension NavigationControllerTheme {
convenience init(presentationTheme: PresentationTheme) {
let navigationStatusBar: NavigationStatusBarStyle

View File

@ -1,22 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_favesticker@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_favesticker@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,22 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_favedsticker@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_favedsticker@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 911 B

View File

@ -9,13 +9,14 @@ import SwiftSignalKit
import AVFoundation
import PhotoResources
import AppBundle
import ContextUI
final class ChatContextResultPeekContent: PeekControllerContent {
let account: Account
let contextResult: ChatContextResult
let menu: [PeekControllerMenuItem]
let menu: [ContextMenuItem]
init(account: Account, contextResult: ChatContextResult, menu: [PeekControllerMenuItem]) {
init(account: Account, contextResult: ChatContextResult, menu: [ContextMenuItem]) {
self.account = account
self.contextResult = contextResult
self.menu = menu
@ -25,11 +26,11 @@ final class ChatContextResultPeekContent: PeekControllerContent {
return .contained
}
func menuActivation() -> PeerkControllerMenuActivation {
func menuActivation() -> PeerControllerMenuActivation {
return .drag
}
func menuItems() -> [PeekControllerMenuItem] {
func menuItems() -> [ContextMenuItem] {
return self.menu
}

View File

@ -11673,7 +11673,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
private func presentDeleteMessageOptions(messageIds: Set<MessageId>, options: ChatAvailableMessageActionOptions, contextController: ContextController?, completion: @escaping (ContextMenuActionResult) -> Void) {
private func presentDeleteMessageOptions(messageIds: Set<MessageId>, options: ChatAvailableMessageActionOptions, contextController: ContextControllerProtocol?, completion: @escaping (ContextMenuActionResult) -> Void) {
let actionSheet = ActionSheetController(presentationData: self.presentationData)
var items: [ActionSheetItem] = []
var personalPeerName: String?

View File

@ -137,10 +137,6 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: LimitsCo
return false
}
private let starIconEmpty = UIImage(bundleImageName: "Chat/Context Menu/StarIconEmpty")?.precomposed()
private let starIconFilled = UIImage(bundleImageName: "Chat/Context Menu/StarIconFilled")?.precomposed()
func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState) -> Bool {
guard let peer = chatPresentationInterfaceState.renderedPeer?.peer else {
return false
@ -1264,14 +1260,14 @@ func chatAvailableMessageActionsImpl(postbox: Postbox, accountPeerId: PeerId, me
final class ChatDeleteMessageContextItem: ContextMenuCustomItem {
fileprivate let timestamp: Double
fileprivate let action: (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void
fileprivate let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void
init(timestamp: Double, action: @escaping (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void) {
init(timestamp: Double, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) {
self.timestamp = timestamp
self.action = action
}
func node(presentationData: PresentationData, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
return ChatDeleteMessageContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
}
}
@ -1281,7 +1277,7 @@ private let textFont = Font.regular(17.0)
private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenuCustomNode, ContextActionNodeProtocol {
private let item: ChatDeleteMessageContextItem
private let presentationData: PresentationData
private let getController: () -> ContextController?
private let getController: () -> ContextControllerProtocol?
private let actionSelected: (ContextMenuActionResult) -> Void
private let backgroundNode: ASDisplayNode
@ -1296,7 +1292,7 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
private var pointerInteraction: PointerInteraction?
init(presentationData: PresentationData, item: ChatDeleteMessageContextItem, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
init(presentationData: PresentationData, item: ChatDeleteMessageContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
self.item = item
self.presentationData = presentationData
self.getController = getController

View File

@ -1098,16 +1098,16 @@ final class ChatMediaInputNode: ChatInputNode {
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self {
var menuItems: [PeekControllerMenuItem] = []
var menuItems: [ContextMenuItem] = []
menuItems = [
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { node, rect in
if let strongSelf = self {
return strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), nil, false, node, rect)
} else {
return false
}
}),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect)
})),
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -1115,9 +1115,12 @@ final class ChatMediaInputNode: ChatInputNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
})),
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
if let strongSelf = self {
loop: for attribute in item.file.attributes {
switch attribute {
@ -1140,10 +1143,9 @@ final class ChatMediaInputNode: ChatInputNode {
}
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true })
}))
]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems))
} else {
return nil
@ -1182,16 +1184,16 @@ final class ChatMediaInputNode: ChatInputNode {
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self {
var menuItems: [PeekControllerMenuItem] = []
var menuItems: [ContextMenuItem] = []
menuItems = [
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { node, rect in
if let strongSelf = self {
return strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), nil, false, node, rect)
} else {
return false
}
}),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect)
})),
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -1199,34 +1201,35 @@ final class ChatMediaInputNode: ChatInputNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
})),
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
if let strongSelf = self {
loop: for attribute in item.file.attributes {
switch attribute {
case let .Sticker(_, packReference, _):
if let packReference = packReference {
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.controllerInteraction.navigationController(), sendSticker: { file, sourceNode, sourceRect in
if let strongSelf = self {
return strongSelf.controllerInteraction.sendSticker(file, nil, false, sourceNode, sourceRect)
} else {
return false
}
})
strongSelf.controllerInteraction.navigationController()?.view.window?.endEditing(true)
strongSelf.controllerInteraction.presentController(controller, nil)
}
break loop
default:
break
case let .Sticker(_, packReference, _):
if let packReference = packReference {
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.controllerInteraction.navigationController(), sendSticker: { file, sourceNode, sourceRect in
if let strongSelf = self {
return strongSelf.controllerInteraction.sendSticker(file, nil, false, sourceNode, sourceRect)
} else {
return false
}
})
strongSelf.controllerInteraction.navigationController()?.view.window?.endEditing(true)
strongSelf.controllerInteraction.presentController(controller, nil)
}
break loop
default:
break
}
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true })
}))
]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else {
@ -1241,7 +1244,8 @@ final class ChatMediaInputNode: ChatInputNode {
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.theme), content: content, sourceNode: {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = PeekController(presentationData: presentationData, content: content, sourceNode: {
return sourceNode
})
controller.visibilityUpdated = { [weak self] visible in

View File

@ -55,9 +55,9 @@ final class ChatPanelInterfaceInteraction {
let beginMessageSelection: ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void
let deleteSelectedMessages: () -> Void
let reportSelectedMessages: () -> Void
let reportMessages: ([Message], ContextController?) -> Void
let blockMessageAuthor: (Message, ContextController?) -> Void
let deleteMessages: ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void
let reportMessages: ([Message], ContextControllerProtocol?) -> Void
let blockMessageAuthor: (Message, ContextControllerProtocol?) -> Void
let deleteMessages: ([Message], ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void
let forwardSelectedMessages: () -> Void
let forwardCurrentForwardMessages: () -> Void
let forwardMessages: ([Message]) -> Void
@ -94,8 +94,8 @@ final class ChatPanelInterfaceInteraction {
let setupMessageAutoremoveTimeout: () -> Void
let sendSticker: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
let unblockPeer: () -> Void
let pinMessage: (MessageId, ContextController?) -> Void
let unpinMessage: (MessageId, Bool, ContextController?) -> Void
let pinMessage: (MessageId, ContextControllerProtocol?) -> Void
let unpinMessage: (MessageId, Bool, ContextControllerProtocol?) -> Void
let unpinAllMessages: () -> Void
let openPinnedList: (MessageId) -> Void
let shareAccountContact: () -> Void
@ -138,9 +138,9 @@ final class ChatPanelInterfaceInteraction {
beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void,
deleteSelectedMessages: @escaping () -> Void,
reportSelectedMessages: @escaping () -> Void,
reportMessages: @escaping ([Message], ContextController?) -> Void,
blockMessageAuthor: @escaping (Message, ContextController?) -> Void,
deleteMessages: @escaping ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void,
reportMessages: @escaping ([Message], ContextControllerProtocol?) -> Void,
blockMessageAuthor: @escaping (Message, ContextControllerProtocol?) -> Void,
deleteMessages: @escaping ([Message], ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void,
forwardSelectedMessages: @escaping () -> Void,
forwardCurrentForwardMessages: @escaping () -> Void,
forwardMessages: @escaping ([Message]) -> Void,
@ -177,8 +177,8 @@ final class ChatPanelInterfaceInteraction {
setupMessageAutoremoveTimeout: @escaping () -> Void,
sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool,
unblockPeer: @escaping () -> Void,
pinMessage: @escaping (MessageId, ContextController?) -> Void,
unpinMessage: @escaping (MessageId, Bool, ContextController?) -> Void,
pinMessage: @escaping (MessageId, ContextControllerProtocol?) -> Void,
unpinMessage: @escaping (MessageId, Bool, ContextControllerProtocol?) -> Void,
unpinAllMessages: @escaping () -> Void,
openPinnedList: @escaping (MessageId) -> Void,
shareAccountContact: @escaping () -> Void,

View File

@ -15,6 +15,7 @@ import OverlayStatusController
import PresentationDataUtils
import SearchBarNode
import UndoUI
import ContextUI
private final class FeaturedInteraction {
let installPack: (ItemCollectionInfo, Bool) -> Void
@ -460,16 +461,16 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self {
var menuItems: [PeekControllerMenuItem] = []
var menuItems: [ContextMenuItem] = []
menuItems = [
PeekControllerMenuItem(title: strongSelf.presentationData.strings.StickerPack_Send, color: .accent, font: .bold, action: { node, rect in
if let strongSelf = self {
return strongSelf.sendSticker?(.standalone(media: item.file), node, rect) ?? false
} else {
return false
}
}),
PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect)
})),
.action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -477,9 +478,10 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.presentationData.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
})),
.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_ViewPack, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
loop: for attribute in item.file.attributes {
switch attribute {
@ -502,9 +504,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
}
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true })
}))
]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems))
} else {
@ -524,16 +524,16 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self {
var menuItems: [PeekControllerMenuItem] = []
var menuItems: [ContextMenuItem] = []
menuItems = [
PeekControllerMenuItem(title: strongSelf.presentationData.strings.StickerPack_Send, color: .accent, font: .bold, action: { node, rect in
if let strongSelf = self {
return strongSelf.sendSticker?(.standalone(media: item.file), node, rect) ?? false
} else {
return false
}
}),
PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect)
})),
.action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -541,34 +541,33 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.presentationData.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
})),
.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_ViewPack, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
loop: for attribute in item.file.attributes {
switch attribute {
case let .Sticker(_, packReference, _):
if let packReference = packReference {
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.controller?.navigationController as? NavigationController, sendSticker: { file, sourceNode, sourceRect in
if let strongSelf = self {
return strongSelf.sendSticker?(file, sourceNode, sourceRect) ?? false
} else {
return false
}
})
strongSelf.controller?.view.endEditing(true)
strongSelf.controller?.present(controller, in: .window(.root))
}
break loop
default:
break
case let .Sticker(_, packReference, _):
if let packReference = packReference {
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.controller?.navigationController as? NavigationController, sendSticker: { file, sourceNode, sourceRect in
if let strongSelf = self {
return strongSelf.sendSticker?(file, sourceNode, sourceRect) ?? false
} else {
return false
}
})
strongSelf.controller?.view.endEditing(true)
strongSelf.controller?.present(controller, in: .window(.root))
}
break loop
default:
break
}
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true })
}))
]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else {
@ -579,7 +578,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.presentationData.theme), content: content, sourceNode: {
let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceNode: {
return sourceNode
})
strongSelf.controller?.presentInGlobalOverlay(controller)

View File

@ -11,6 +11,7 @@ import TelegramUIPreferences
import MergeLists
import AccountContext
import StickerPackPreviewUI
import ContextUI
private struct ChatContextResultStableId: Hashable {
let result: ChatContextResult
@ -145,15 +146,19 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
strongSelf.listView.forEachItemNode { itemNode in
if itemNode.frame.contains(convertedPoint), let itemNode = itemNode as? HorizontalListContextResultsChatInputPanelItemNode, let item = itemNode.item {
if case let .internalReference(internalReference) = item.result, let file = internalReference.file, file.isSticker {
var menuItems: [PeekControllerMenuItem] = []
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { _, _ in
return item.resultSelected(item.result, itemNode, itemNode.bounds)
}))
var menuItems: [ContextMenuItem] = []
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
let _ = item.resultSelected(item.result, itemNode, itemNode.bounds)
})))
for case let .Sticker(_, packReference, _) in file.attributes {
guard let packReference = packReference else {
continue
}
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.interfaceInteraction?.getNavigationController(), sendSticker: { file, sourceNode, sourceRect in
if let strongSelf = self {
@ -166,21 +171,29 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
strongSelf.interfaceInteraction?.getNavigationController()?.view.window?.endEditing(true)
strongSelf.interfaceInteraction?.presentController(controller, nil)
}
return true
}))
})))
}
selectedItemNodeAndContent = (itemNode, StickerPreviewPeekContent(account: item.account, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: menuItems))
} else {
var menuItems: [PeekControllerMenuItem] = []
var menuItems: [ContextMenuItem] = []
if case let .internalReference(internalReference) = item.result, let file = internalReference.file, file.isAnimated {
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.Preview_SaveGif, color: .accent, action: { _, _ in
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Preview_SaveGif, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
guard let strongSelf = self else {
return
}
let _ = addSavedGif(postbox: strongSelf.context.account.postbox, fileReference: .standalone(media: file)).start()
return true
}))
})))
}
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { _, _ in
return item.resultSelected(item.result, itemNode, itemNode.bounds)
}))
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.ShareMenu_Send, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
item.resultSelected(item.result, itemNode, itemNode.bounds)
})))
selectedItemNodeAndContent = (itemNode, ChatContextResultPeekContent(account: item.account, contextResult: item.result, menu: menuItems))
}
}
@ -190,7 +203,8 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.theme), content: content, sourceNode: {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = PeekController(presentationData: presentationData, content: content, sourceNode: {
return sourceNode
})
strongSelf.interfaceInteraction?.presentGlobalOverlayController(controller, nil)

View File

@ -11,6 +11,7 @@ import TelegramUIPreferences
import MergeLists
import AccountContext
import StickerPackPreviewUI
import ContextUI
final class HorizontalStickersChatContextPanelInteraction {
var previewedStickerItem: StickerPackItem?
@ -174,12 +175,16 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
var menuItems: [PeekControllerMenuItem] = []
var menuItems: [ContextMenuItem] = []
menuItems = [
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { _, _ in
return controllerInteraction.sendSticker(.standalone(media: item.file), nil, true, itemNode, itemNode.bounds)
}),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
let _ = controllerInteraction.sendSticker(.standalone(media: item.file), nil, true, itemNode, itemNode.bounds)
})),
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -187,9 +192,10 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
})),
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
loop: for attribute in item.file.attributes {
switch attribute {
@ -211,11 +217,8 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
break
}
}
return true
}
return true
}),
PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true })
}))
]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else {
@ -227,7 +230,8 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.theme), content: content, sourceNode: {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = PeekController(presentationData: presentationData, content: content, sourceNode: {
return sourceNode
})
strongSelf.interfaceInteraction?.presentGlobalOverlayController(controller, nil)

View File

@ -10,6 +10,7 @@ import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import StickerPackPreviewUI
import ContextUI
private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollViewDelegate {
private final class DisplayItem {
@ -88,12 +89,16 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self, let controllerInteraction = strongSelf.getControllerInteraction?() {
var menuItems: [PeekControllerMenuItem] = []
var menuItems: [ContextMenuItem] = []
menuItems = [
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { _, _ in
return controllerInteraction.sendSticker(.standalone(media: item.file), nil, true, itemNode, itemNode.bounds)
}),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
let _ = controllerInteraction.sendSticker(.standalone(media: item.file), nil, true, itemNode, itemNode.bounds)
})),
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -101,9 +106,10 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
})),
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self, let controllerInteraction = strongSelf.getControllerInteraction?() {
loop: for attribute in item.file.attributes {
switch attribute {
@ -125,12 +131,8 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
break
}
}
return true
}
return true
}),
PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true })
]
}))]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else {
return nil
@ -141,7 +143,8 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.theme), content: content, sourceNode: {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = PeekController(presentationData: presentationData, content: content, sourceNode: {
return sourceNode
})
strongSelf.getControllerInteraction?()?.presentGlobalOverlayController(controller, nil)

View File

@ -3911,7 +3911,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.controller?.present(shareController, in: .window(.root))
}
private func requestCall(isVideo: Bool, gesture: ContextGesture? = nil, contextController: ContextController? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextController) -> Void)? = nil) {
private func requestCall(isVideo: Bool, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextControllerProtocol) -> Void)? = nil) {
let peerId = self.peerId
let requestCall: (PeerId?, CachedChannelData.ActiveCall?) -> Void = { [weak self] defaultJoinAsPeerId, activeCall in
if let activeCall = activeCall {
@ -4310,7 +4310,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
controller.push(statsController)
}
private func openVoiceChatOptions(defaultJoinAsPeerId: PeerId?, gesture: ContextGesture? = nil, contextController: ContextController? = nil) {
private func openVoiceChatOptions(defaultJoinAsPeerId: PeerId?, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil) {
let context = self.context
let peerId = self.peerId
let defaultJoinAsPeerId = defaultJoinAsPeerId ?? self.context.account.peerId
@ -4392,7 +4392,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
})
}
private func openVoiceChatDisplayAsPeerSelection(completion: @escaping (PeerId) -> Void, gesture: ContextGesture? = nil, contextController: ContextController? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextController) -> Void)? = nil) {
private func openVoiceChatDisplayAsPeerSelection(completion: @escaping (PeerId) -> Void, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextControllerProtocol) -> Void)? = nil) {
let dismissOnSelection = contextController == nil
let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(context.account.peerId)
|> map { peer in
@ -4489,7 +4489,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
})
}
private func openReport(user: Bool, contextController: ContextController?, backAction: ((ContextController) -> Void)?) {
private func openReport(user: Bool, contextController: ContextControllerProtocol?, backAction: ((ContextControllerProtocol) -> Void)?) {
guard let controller = self.controller else {
return
}

View File

@ -11,6 +11,7 @@ import TelegramUIPreferences
import MergeLists
import AccountContext
import StickerPackPreviewUI
import ContextUI
private struct StickersChatInputContextPanelEntryStableId: Hashable {
let ids: [MediaId]
@ -130,12 +131,16 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
var menuItems: [PeekControllerMenuItem] = []
var menuItems: [ContextMenuItem] = []
menuItems = [
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { _, _ in
return controllerInteraction.sendSticker(.standalone(media: item.file), nil, true, itemNode, itemNode.bounds)
}),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
let _ = controllerInteraction.sendSticker(.standalone(media: item.file), nil, true, itemNode, itemNode.bounds)
})),
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -143,9 +148,10 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
})),
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
loop: for attribute in item.file.attributes {
switch attribute {
@ -157,7 +163,6 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
} else {
return false
}
})
controllerInteraction.navigationController()?.view.window?.endEditing(true)
@ -169,9 +174,7 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
}
}
}
return true
}),
PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true })
}))
]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else {
@ -184,7 +187,8 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.theme), content: content, sourceNode: {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = PeekController(presentationData: presentationData, content: content, sourceNode: {
return sourceNode
})
strongSelf.interfaceInteraction?.presentGlobalOverlayController(controller, nil)