mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Upgrade peek controller to modern context menu implementation
This commit is contained in:
parent
b1e91b3c72
commit
4a465a5893
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
@ -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?
|
@ -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, *) {
|
346
submodules/ContextUI/Sources/PeekControllerNode.swift
Normal file
346
submodules/ContextUI/Sources/PeekControllerNode.swift
Normal 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()
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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 }) {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -28,6 +28,7 @@ swift_library(
|
||||
"//submodules/ArchivedStickerPacksNotice:ArchivedStickerPacksNotice",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 |
@ -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 |
@ -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
|
||||
}
|
||||
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user