Merge commit 'e7fdd24d554c41666b03411df3e57aa2b4e41397'

This commit is contained in:
Ali 2021-05-09 00:38:20 +04:00
commit 100e9902cc
48 changed files with 1176 additions and 1153 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -182,6 +182,39 @@ public extension ContainedViewLayoutTransition {
}
}
func updateFrameAsPositionAndBounds(layer: CALayer, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if layer.frame.equalTo(frame) && !force {
completion?(true)
} else {
switch self {
case .immediate:
layer.position = frame.center
layer.bounds = CGRect(origin: CGPoint(), size: frame.size)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousPosition: CGPoint
let previousBounds: CGRect
if beginWithCurrentState, let presentation = layer.presentation() {
previousPosition = presentation.position
previousBounds = presentation.bounds
} else {
previousPosition = layer.position
previousBounds = layer.bounds
}
layer.position = frame.center
layer.bounds = CGRect(origin: CGPoint(), size: frame.size)
layer.animateFrame(from:
CGRect(origin: CGPoint(x: previousPosition.x - previousBounds.width / 2.0, y: previousPosition.y - previousBounds.height / 2.0), size: previousBounds.size), to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func updateFrameAdditive(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if node.frame.equalTo(frame) && !force {
completion?(true)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -552,7 +552,12 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
let offset = self.contentContainerNode.frame.height
self.wrappingScrollNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
let position = self.wrappingScrollNode.position
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
self.wrappingScrollNode.position = CGPoint(x: position.x, y: position.y + offset)
transition.animateView({
self.wrappingScrollNode.position = position
})
}
func animateOut(completion: (() -> Void)? = nil) {

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import MergeLists
import ActivityIndicator
import TextFormat
import AccountContext
import ContextUI
private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
let index: Int
@ -204,28 +205,26 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self {
var menuItems: [PeekControllerMenuItem] = []
var menuItems: [ContextMenuItem] = []
if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
if strongSelf.sendSticker != nil {
menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in
if let strongSelf = self {
return strongSelf.sendSticker?(.standalone(media: item.file), node, rect) ?? false
} else {
return false
}
}))
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect)
})))
}
menuItems.append(PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
} else {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
menuItems.append(.action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
} else {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
return true
}))
menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true }))
}
})))
}
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else {
@ -237,7 +236,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.presentationData.theme), content: content, sourceNode: {
let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceNode: {
return sourceNode
})
controller.visibilityUpdated = { [weak self] visible in
@ -550,11 +549,17 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
func animateIn() {
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let offset: CGFloat = 510.0
let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
let targetBounds = self.bounds
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
transition.animateView({
self.bounds = targetBounds
self.dimNode.position = dimPosition
})
}
func animateOut(completion: (() -> Void)? = nil) {

View File

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

View File

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

View File

@ -68,6 +68,7 @@ final class VoiceChatCameraPreviewController: ViewController {
}
self.controllerNode.switchCamera = { [weak self] in
self?.switchCamera()
self?.cameraNode.flip(withBackground: true)
}
self.controllerNode.dismiss = { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
@ -287,15 +288,15 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
let targetBounds = self.bounds
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
transition.animateView({
self.bounds = targetBounds
self.dimNode.position = dimPosition
})
self.applicationStateDisposable = (self.context.sharedContext.applicationBindings.applicationIsActive

View File

@ -43,7 +43,7 @@ private let topPanelHeight: CGFloat = 63.0
private let bottomAreaHeight: CGFloat = 205.0
private let fullscreenBottomAreaHeight: CGFloat = 80.0
private func cornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
private func decorationCornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
if !top && !bottom {
return nil
}
@ -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?()
@ -129,7 +178,7 @@ final class GroupVideoNode: ASDisplayNode {
func updateLayout(size: CGSize, isLandscape: Bool, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, isLandscape)
transition.updateFrame(view: self.videoViewContainer, frame: CGRect(origin: CGPoint(), size: size))
transition.updateFrameAsPositionAndBounds(layer: self.videoViewContainer.layer, frame: CGRect(origin: CGPoint(), size: size))
let orientation = self.videoView.getOrientation()
var aspect = self.videoView.getAspect()
@ -160,32 +209,39 @@ final class GroupVideoNode: ASDisplayNode {
}
var rotatedVideoSize = CGSize(width: 100.0, height: rotatedAspect * 100.0)
if isLandscape {
rotatedVideoSize = rotatedVideoSize.aspectFitted(size)
} else {
rotatedVideoSize = rotatedVideoSize.aspectFilled(size)
}
var containerSize = size
if switchOrientation {
rotatedVideoSize = CGSize(width: rotatedVideoSize.height, height: rotatedVideoSize.width)
containerSize = CGSize(width: containerSize.height, height: containerSize.width)
}
if isLandscape {
rotatedVideoSize = rotatedVideoSize.aspectFitted(containerSize)
} else {
rotatedVideoSize = rotatedVideoSize.aspectFilled(containerSize)
}
var rotatedVideoFrame = CGRect(origin: CGPoint(x: floor((size.width - rotatedVideoSize.width) / 2.0), y: floor((size.height - rotatedVideoSize.height) / 2.0)), size: rotatedVideoSize)
rotatedVideoFrame.origin.x = floor(rotatedVideoFrame.origin.x)
rotatedVideoFrame.origin.y = floor(rotatedVideoFrame.origin.y)
rotatedVideoFrame.size.width = ceil(rotatedVideoFrame.size.width)
rotatedVideoFrame.size.height = ceil(rotatedVideoFrame.size.height)
var videoSize = rotatedVideoFrame.size
let videoSize = rotatedVideoFrame.size.aspectFilled(CGSize(width: 1080.0, height: 1080.0))
transition.updatePosition(layer: self.videoView.view.layer, position: rotatedVideoFrame.center)
transition.updateBounds(layer: self.videoView.view.layer, bounds: CGRect(origin: CGPoint(), size: videoSize))
let scale = rotatedVideoFrame.width / videoSize.width
transition.updateTransformScale(layer: self.videoView.view.layer, scale: scale)
let transformScale: CGFloat = rotatedVideoFrame.width / videoSize.width
transition.updateTransformScale(layer: self.videoViewContainer.layer, scale: transformScale)
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
@ -201,8 +257,8 @@ private final class MainVideoContainerNode: ASDisplayNode {
private var currentVideoNode: GroupVideoNode?
private var candidateVideoNode: GroupVideoNode?
fileprivate let otherVideoButtonNode: HighlightTrackingButtonNode
private let otherVideoWrapperNode: ASDisplayNode
private let otherVideoButtonNode: HighlightTrackingButtonNode
fileprivate let otherVideoWrapperNode: ASDisplayNode
private let otherVideoShadowNode: ASImageNode
private var otherVideoNode: GroupVideoNode?
@ -217,17 +273,20 @@ private final class MainVideoContainerNode: ASDisplayNode {
var tapped: (() -> Void)?
var otherVideoTapped: (() -> Void)?
private let videoReadyDisposable = MetaDisposable()
private let otherVideoReadyDisposable = MetaDisposable()
init(context: AccountContext, call: PresentationGroupCall) {
self.context = context
self.call = call
self.topCornersNode = ASImageNode()
self.topCornersNode.displaysAsynchronously = false
self.topCornersNode.image = cornersImage(top: true, bottom: false, dark: true)
self.topCornersNode.image = decorationCornersImage(top: true, bottom: false, dark: true)
self.bottomCornersNode = ASImageNode()
self.bottomCornersNode.displaysAsynchronously = false
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: true)
self.bottomCornersNode.image = decorationCornersImage(top: false, bottom: true, dark: true)
self.bottomEdgeNode = ASDisplayNode()
self.bottomEdgeNode.backgroundColor = UIColor(rgb: 0x000000)
@ -264,6 +323,8 @@ private final class MainVideoContainerNode: ASDisplayNode {
self.otherVideoButtonNode.cornerRadius = 5.5
self.otherVideoWrapperNode = ASDisplayNode()
self.otherVideoWrapperNode.alpha = 0.0
self.otherVideoWrapperNode.transform = CATransform3DMakeScale(0.001, 0.001, 1.0)
super.init()
@ -282,6 +343,11 @@ private final class MainVideoContainerNode: ASDisplayNode {
self.otherVideoButtonNode.addTarget(self, action: #selector(self.otherVideoPressed), forControlEvents: .touchUpInside)
}
deinit {
self.videoReadyDisposable.dispose()
self.otherVideoReadyDisposable.dispose()
}
override func didLoad() {
super.didLoad()
@ -296,13 +362,14 @@ private final class MainVideoContainerNode: ASDisplayNode {
self.otherVideoTapped?()
}
func updatePeer(peer: (peerId: PeerId, endpointId: String, otherEndpointId: String?)?, waitForFullSize: Bool) {
func updatePeer(peer: (peerId: PeerId, endpointId: String, otherEndpointId: String?)?, waitForFullSize: Bool, completion: (() -> Void)? = nil) {
if self.currentPeer?.0 == peer?.0 && self.currentPeer?.1 == peer?.1 && self.currentPeer?.2 == peer?.2 {
return
}
let previousPeer = self.currentPeer
self.currentPeer = peer
if let (_, endpointId, otherEndpointId) = peer {
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
if let otherEndpointId = otherEndpointId {
if otherEndpointId != previousPeer?.2 {
self.call.makeIncomingVideoView(endpointId: otherEndpointId) { [weak self] videoView in
@ -320,12 +387,35 @@ private final class MainVideoContainerNode: ASDisplayNode {
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
}
if strongSelf.otherVideoWrapperNode.alpha.isZero {
strongSelf.otherVideoReadyDisposable.set((videoNode.ready
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
if let strongSelf = self {
transition.updateAlpha(node: strongSelf.otherVideoWrapperNode, alpha: 1.0)
transition.updateTransformScale(node: strongSelf.otherVideoWrapperNode, scale: 1.0)
}
}))
}
}
}
} else {
if let otherVideoNode = self.otherVideoNode {
otherVideoNode.removeFromSupernode()
self.otherVideoNode = nil
self.otherVideoReadyDisposable.set(nil)
let completion = {
otherVideoNode.removeFromSupernode()
}
if !self.otherVideoWrapperNode.alpha.isZero {
transition.updateAlpha(node: self.otherVideoWrapperNode, alpha: 0.0, completion: { finished in
completion()
})
transition.updateTransformScale(node: self.otherVideoWrapperNode, scale: 0.001)
} else {
completion()
}
}
}
if endpointId != previousPeer?.1 {
@ -334,44 +424,38 @@ private final class MainVideoContainerNode: ASDisplayNode {
guard let strongSelf = self, let videoView = videoView else {
return
}
if waitForFullSize {
let candidateVideoNode = GroupVideoNode(videoView: videoView)
strongSelf.candidateVideoNode = candidateVideoNode
Queue.mainQueue().after(0.3, { [weak candidateVideoNode] in
guard let strongSelf = self, let videoNode = candidateVideoNode, videoNode === strongSelf.candidateVideoNode else {
return
}
if let currentVideoNode = strongSelf.currentVideoNode {
currentVideoNode.removeFromSupernode()
strongSelf.currentVideoNode = nil
}
strongSelf.currentVideoNode = videoNode
strongSelf.insertSubnode(videoNode, belowSubnode: strongSelf.topCornersNode)
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
}
let videoNode = GroupVideoNode(videoView: videoView)
if let currentVideoNode = strongSelf.currentVideoNode {
strongSelf.currentVideoNode = nil
currentVideoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak currentVideoNode] _ in
currentVideoNode?.removeFromSupernode()
})
}
strongSelf.currentVideoNode = videoNode
strongSelf.insertSubnode(videoNode, belowSubnode: strongSelf.topCornersNode)
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
}
if waitForFullSize {
strongSelf.videoReadyDisposable.set((videoNode.ready
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
completion?()
}))
} else {
strongSelf.candidateVideoNode = nil
let videoNode = GroupVideoNode(videoView: videoView)
if let currentVideoNode = strongSelf.currentVideoNode {
currentVideoNode.removeFromSupernode()
strongSelf.currentVideoNode = nil
}
strongSelf.currentVideoNode = videoNode
strongSelf.insertSubnode(videoNode, belowSubnode: strongSelf.topCornersNode)
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
}
strongSelf.videoReadyDisposable.set(nil)
completion?()
}
}
})
}
} else {
self.videoReadyDisposable.set(nil)
self.otherVideoReadyDisposable.set(nil)
if let currentVideoNode = self.currentVideoNode {
currentVideoNode.removeFromSupernode()
self.currentVideoNode = nil
@ -384,7 +468,7 @@ private final class MainVideoContainerNode: ASDisplayNode {
if let currentVideoNode = self.currentVideoNode {
transition.updateFrame(node: currentVideoNode, frame: CGRect(origin: CGPoint(), size: size))
currentVideoNode.updateLayout(size: size, isLandscape: isLandscape, transition: transition)
currentVideoNode.updateLayout(size: size, isLandscape: true, transition: transition)
}
let smallVideoSize = CGSize(width: 40.0, height: 40.0)
@ -430,6 +514,7 @@ public final class VoiceChatController: ViewController {
private final class Interaction {
let updateIsMuted: (PeerId, Bool) -> Void
let pinPeer: (PeerId) -> Void
let togglePeerVideo: (PeerId) -> Void
let openInvite: () -> Void
let peerContextAction: (PeerEntry, ASDisplayNode, ContextGesture?) -> Void
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
@ -443,6 +528,7 @@ public final class VoiceChatController: ViewController {
init(
updateIsMuted: @escaping (PeerId, Bool) -> Void,
pinPeer: @escaping (PeerId) -> Void,
togglePeerVideo: @escaping (PeerId) -> Void,
openInvite: @escaping () -> Void,
peerContextAction: @escaping (PeerEntry, ASDisplayNode, ContextGesture?) -> Void,
setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void,
@ -450,6 +536,7 @@ public final class VoiceChatController: ViewController {
) {
self.updateIsMuted = updateIsMuted
self.pinPeer = pinPeer
self.togglePeerVideo = togglePeerVideo
self.openInvite = openInvite
self.peerContextAction = peerContextAction
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
@ -797,13 +884,12 @@ public final class VoiceChatController: ViewController {
}, action: { node in
if case .list = peerEntry.style {
interaction.peerContextAction(peerEntry, node, nil)
} else {
interaction.pinPeer(peer.id)
// if peerEntry.pinned {
// interaction.peerContextAction(peerEntry, node, nil)
// } else if let endpointId = peerEntry.effectiveVideoEndpointId {
// interaction.pinPeer(peer.id)
// }
} else if peerEntry.effectiveVideoEndpointId != nil {
if peerEntry.pinned && peerEntry.videoEndpointId != nil && peerEntry.screencastEndpointId != nil {
interaction.togglePeerVideo(peer.id)
} else {
interaction.pinPeer(peer.id)
}
}
}, contextAction: peerEntry.style == .list ? { node, gesture in
interaction.peerContextAction(peerEntry, node, gesture)
@ -1005,10 +1091,9 @@ public final class VoiceChatController: ViewController {
self.mainVideoClippingNode = ASDisplayNode()
self.mainVideoClippingNode.clipsToBounds = true
self.mainVideoContainerNode = MainVideoContainerNode(context: call.accountContext, call: call)
if sharedContext.immediateExperimentalUISettings.demoVideoChats {
self.mainVideoContainerNode = MainVideoContainerNode(context: call.accountContext, call: call)
}
self.mainParticipantNode = VoiceChatParticipantItemNode()
self.toggleFullscreenButton = HighlightTrackingButtonNode()
@ -1057,7 +1142,7 @@ public final class VoiceChatController: ViewController {
self.topCornersNode = ASImageNode()
self.topCornersNode.displaysAsynchronously = false
self.topCornersNode.displayWithoutProcessing = true
self.topCornersNode.image = cornersImage(top: true, bottom: false, dark: false)
self.topCornersNode.image = decorationCornersImage(top: true, bottom: false, dark: false)
self.bottomPanelCoverNode = ASDisplayNode()
self.bottomPanelCoverNode.backgroundColor = fullscreenBackgroundColor
@ -1072,7 +1157,7 @@ public final class VoiceChatController: ViewController {
self.bottomCornersNode = ASImageNode()
self.bottomCornersNode.displaysAsynchronously = false
self.bottomCornersNode.displayWithoutProcessing = true
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: false)
self.bottomCornersNode.image = decorationCornersImage(top: false, bottom: true, dark: false)
self.bottomCornersNode.isUserInteractionEnabled = false
self.audioButton = CallControllerButtonItemNode()
@ -1168,8 +1253,13 @@ public final class VoiceChatController: ViewController {
} else {
strongSelf.currentForcedSpeakerWithVideo = nil
}
strongSelf.updatePinnedParticipant(waitForFullSize: false)
strongSelf.updateMainParticipant(waitForFullSize: false)
}
}, togglePeerVideo: { [weak self] peerId in
guard let strongSelf = self else {
return
}
strongSelf.mainVideoContainerNode?.otherVideoTapped?()
}, openInvite: { [weak self] in
guard let strongSelf = self else {
return
@ -1973,10 +2063,9 @@ public final class VoiceChatController: ViewController {
}
if let (peerId, _) = maxLevelWithVideo {
if strongSelf.currentDominantSpeakerWithVideo != peerId {
strongSelf.currentDominantSpeakerWithVideo = peerId
strongSelf.updatePinnedParticipant(waitForFullSize: true)
strongSelf.currentDominantSpeakerWithVideo = peerId
if !strongSelf.requestedVideoSources.isEmpty {
strongSelf.updateMainParticipant(waitForFullSize: false)
}
}
@ -2076,9 +2165,7 @@ public final class VoiceChatController: ViewController {
let videoNode = GroupVideoNode(videoView: videoView)
strongSelf.videoNodes.append((endpointId, videoNode))
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
if let _ = strongSelf.validLayout {
loop: for i in 0 ..< strongSelf.currentEntries.count {
let entry = strongSelf.currentEntries[i]
let tileEntry = strongSelf.currentTileEntries[i]
@ -2110,7 +2197,6 @@ public final class VoiceChatController: ViewController {
strongSelf.requestedVideoSources.remove(source)
}
var updated = false
for i in (0 ..< strongSelf.videoNodes.count).reversed() {
if !validSources.contains(strongSelf.videoNodes[i].0) {
let endpointId = strongSelf.videoNodes[i].0
@ -2123,15 +2209,14 @@ public final class VoiceChatController: ViewController {
case let .peer(peerEntry):
if peerEntry.effectiveVideoEndpointId == endpointId {
let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme)
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
strongSelf.tileListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: tileEntry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: j, previousIndex: j, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
strongSelf.tileListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: j, previousIndex: j, item: tileEntry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
break loop
}
default:
break
}
}
updated = true
}
}
@ -2143,13 +2228,12 @@ public final class VoiceChatController: ViewController {
if peerId == strongSelf.currentDominantSpeakerWithVideo {
strongSelf.currentDominantSpeakerWithVideo = nil
}
strongSelf.updatePinnedParticipant(waitForFullSize: false)
strongSelf.updateMainParticipant(waitForFullSize: false)
}
}
if updated {
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
} else if strongSelf.currentDominantSpeakerWithVideo != nil && !strongSelf.requestedVideoSources.isEmpty && !strongSelf.didSetMainParticipant {
strongSelf.didSetMainParticipant = true
Queue.mainQueue().after(0.1) {
strongSelf.updateMainParticipant(waitForFullSize: true)
}
}
}))
@ -2177,7 +2261,7 @@ public final class VoiceChatController: ViewController {
}
self.mainVideoContainerNode?.tapped = { [weak self] in
if let strongSelf = self {
if let strongSelf = self, !strongSelf.animatingExpansion {
var effectiveDisplayMode = strongSelf.displayMode
var isLandscape = false
if let (layout, _) = strongSelf.validLayout, layout.size.width > layout.size.height, case .compact = layout.metrics.widthClass {
@ -2322,7 +2406,7 @@ public final class VoiceChatController: ViewController {
} else {
strongSelf.switchedToCameraPeers.remove(peerId)
}
strongSelf.updatePinnedParticipant(waitForFullSize: false, force: true)
strongSelf.updateMainParticipant(waitForFullSize: false, force: true)
strongSelf.displayToggleVideoSourceTooltip(screencast: !switchingToCamera)
}
}
@ -3332,10 +3416,16 @@ public final class VoiceChatController: ViewController {
}
}
private var animatingButtons = false
@objc private func cameraPressed() {
if self.call.hasVideo || self.call.hasScreencast {
self.call.disableVideo()
self.call.disableScreencast()
if let (layout, navigationHeight) = self.validLayout {
self.animatingButtons = true
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .linear))
}
} else {
self.call.makeOutgoingVideoView { [weak self] view in
guard let strongSelf = self, let view = view else {
@ -3345,6 +3435,11 @@ public final class VoiceChatController: ViewController {
let controller = VoiceChatCameraPreviewController(context: strongSelf.context, cameraNode: cameraNode, shareCamera: { [weak self] videoNode in
if let strongSelf = self {
strongSelf.call.requestVideo()
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.animatingButtons = true
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .linear))
}
}
}, switchCamera: { [weak self] in
self?.call.switchVideoCamera()
@ -3374,6 +3469,10 @@ public final class VoiceChatController: ViewController {
return controlsHidden ? 0.0 : fullscreenBottomAreaHeight
}
}
private var hasMainVideo: Bool {
return self.mainVideoContainerNode != nil && self.effectiveSpeakerWithVideo != nil
}
private var bringVideoToBackOnCompletion = false
private func updateDecorationsLayout(transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
@ -3411,7 +3510,7 @@ public final class VoiceChatController: ViewController {
let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - bottomPanelHeight)
let topInset: CGFloat
if let (panInitialTopInset, panOffset) = self.panGestureArguments {
if self.isExpanded {
if self.isExpanded && !self.hasMainVideo {
topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset))
} else {
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
@ -3473,7 +3572,7 @@ public final class VoiceChatController: ViewController {
videoY = layout.statusBarHeight ?? 20.0
isFullscreen = true
}
videoClippingFrame = CGRect(origin: CGPoint(x: videoInset, y: videoY), size: CGSize(width: layout.size.width - videoInset * 2.0, height: self.isFullscreen ? videoHeight : 0.0))
videoClippingFrame = CGRect(origin: CGPoint(x: videoInset, y: videoY), size: CGSize(width: layout.size.width - videoInset * 2.0, height: self.hasMainVideo ? videoHeight : 0.0))
videoContainerFrame = CGRect(origin: CGPoint(x: -videoInset, y: 0.0), size: CGSize(width: layout.size.width, height: videoHeight))
}
@ -3578,7 +3677,7 @@ public final class VoiceChatController: ViewController {
let previousBottomCornersFrame = self.bottomCornersNode.frame
if !bottomCornersFrame.equalTo(previousBottomCornersFrame) {
self.bottomCornersNode.frame = bottomCornersFrame
self.bottomPanelBackgroundNode.frame = CGRect(x: 0.0, y: bottomOffset, width: size.width, height: 2000.0)
self.bottomPanelBackgroundNode.frame = CGRect(x: 0.0, y: bottomOffset + bottomDelta, width: size.width, height: 2000.0)
let positionDelta = CGPoint(x: 0.0, y: previousBottomCornersFrame.minY - bottomCornersFrame.minY)
transition.animatePositionAdditive(node: self.bottomCornersNode, offset: positionDelta)
@ -3654,7 +3753,7 @@ public final class VoiceChatController: ViewController {
snapshotView?.removeFromSuperview()
})
}
self.topCornersNode.image = cornersImage(top: true, bottom: false, dark: isFullscreen)
self.topCornersNode.image = decorationCornersImage(top: true, bottom: false, dark: isFullscreen)
if let snapshotView = self.bottomCornersNode.view.snapshotContentTree() {
snapshotView.frame = self.bottomCornersNode.bounds
@ -3664,7 +3763,7 @@ public final class VoiceChatController: ViewController {
snapshotView?.removeFromSuperview()
})
}
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: isFullscreen)
self.bottomCornersNode.image = decorationCornersImage(top: false, bottom: true, dark: isFullscreen)
if !self.optionsButtonIsAvatar {
self.optionsButton.setContent(.more(optionsCircleImage(dark: isFullscreen)), animated: transition.isAnimated)
@ -3803,8 +3902,14 @@ public final class VoiceChatController: ViewController {
let hasVideo = self.call.hasVideo || self.call.hasScreencast
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)
self.cameraButton.update(size: hasVideo ? sideButtonSize : videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: hasVideo ? activeButtonAppearance : normalButtonAppearance, image: hasVideo ? .cameraOn : .cameraOff), text: self.presentationData.strings.VoiceChat_Video, transition: transition)
transition.updateAlpha(node: self.switchCameraButton, alpha: hasVideo ? 1.0 : 0.0)
transition.updateTransformScale(node: self.switchCameraButton, scale: hasVideo ? 1.0 : 0.0)
transition.updateAlpha(node: self.audioButton, alpha: hasVideo ? 0.0 : 1.0)
transition.updateTransformScale(node: self.audioButton, scale: 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)
@ -3812,7 +3917,7 @@ public final class VoiceChatController: ViewController {
self.leaveButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .cancel), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
transition.updateAlpha(node: self.cameraButton.textNode, alpha: 0.0)
transition.updateAlpha(node: self.cameraButton.textNode, alpha: hasVideo ? buttonsTitleAlpha : 0.0)
transition.updateAlpha(node: self.switchCameraButton.textNode, alpha: buttonsTitleAlpha)
transition.updateAlpha(node: self.audioButton.textNode, alpha: buttonsTitleAlpha)
transition.updateAlpha(node: self.leaveButton.textNode, alpha: buttonsTitleAlpha)
@ -3847,7 +3952,9 @@ public final class VoiceChatController: ViewController {
if !self.isFullscreen {
self.isExpanded = true
self.updateIsFullscreen(true)
// self.tileListNode.isHidden = false
}
if self.hasMainVideo {
self.tileListNode.isHidden = false
}
if case .fullscreen = effectiveDisplayMode {
} else {
@ -3913,8 +4020,7 @@ public final class VoiceChatController: ViewController {
var topCornersY = topPanelHeight
if isLandscape {
listTopInset = topPanelHeight
// topCornersY = -50.0
} else if self.mainVideoContainerNode != nil && self.isFullscreen {
} else if self.hasMainVideo && self.isExpanded {
let videoContainerHeight = min(mainVideoHeight, layout.size.width)
listTopInset += videoContainerHeight
topCornersY += videoContainerHeight
@ -3923,7 +4029,7 @@ public final class VoiceChatController: ViewController {
let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - (isLandscape ? layout.intrinsicInsets.bottom : bottomPanelHeight))
let topInset: CGFloat
if let (panInitialTopInset, panOffset) = self.panGestureArguments {
if self.isExpanded {
if self.isExpanded && !self.hasMainVideo {
topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset))
} else {
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
@ -4163,10 +4269,21 @@ public final class VoiceChatController: ViewController {
self.updateButtons(animated: !isFirstTime)
if self.audioButton.supernode === self.bottomPanelNode {
transition.updateFrame(node: self.cameraButton, frame: firstButtonFrame)
transition.updateFrame(node: self.switchCameraButton, frame: firstButtonFrame)
transition.updateFrame(node: self.audioButton, frame: secondButtonFrame)
transition.updateFrame(node: self.leaveButton, frame: forthButtonFrame)
transition.updateFrameAsPositionAndBounds(node: self.switchCameraButton, frame: firstButtonFrame)
if !self.animatingButtons || transition.isAnimated {
if self.call.hasVideo {
transition.updateFrameAsPositionAndBounds(node: self.cameraButton, frame: secondButtonFrame, completion: { [weak self] _ in
self?.animatingButtons = false
})
} else {
transition.updateFrameAsPositionAndBounds(node: self.cameraButton, frame: firstButtonFrame, completion: { [weak self] _ in
self?.animatingButtons = false
})
}
}
transition.updateFrameAsPositionAndBounds(node: self.audioButton, frame: secondButtonFrame)
transition.updateFrameAsPositionAndBounds(node: self.leaveButton, frame: forthButtonFrame)
}
if isFirstTime {
while !self.enqueuedTransitions.isEmpty {
@ -4182,6 +4299,11 @@ public final class VoiceChatController: ViewController {
guard let (layout, navigationHeight) = self.validLayout else {
return
}
if self.hasMainVideo && !self.isFullscreen {
self.updateIsFullscreen(true)
}
self.updateDecorationsLayout(transition: .immediate)
self.animatingAppearance = true
@ -4376,7 +4498,7 @@ public final class VoiceChatController: ViewController {
})
}
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: ([GroupCallParticipantsContext.Participant], String?), invitedPeers: [Peer], speakingPeers: Set<PeerId>) {
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: ([GroupCallParticipantsContext.Participant], String?), invitedPeers: [Peer], speakingPeers: Set<PeerId>, updatePinnedPeer: Bool = true) {
var disableAnimation = false
if self.currentCallMembers?.1 != callMembers.1 {
disableAnimation = true
@ -4532,7 +4654,7 @@ public final class VoiceChatController: ViewController {
volume: member.volume,
raisedHand: member.hasRaiseHand,
displayRaisedHandStatus: self.displayedRaisedHands.contains(member.peer.id),
pinned: true,
pinned: memberPeer.id == self.currentForcedSpeakerWithVideo,
style: .list
))
}
@ -4572,8 +4694,27 @@ public final class VoiceChatController: ViewController {
self.endpointToPeerId = endpointIdToPeerId
self.peerIdToEndpoint = peerIdToEndpointId
let previousPinnedEntry = self.pinnedEntry
self.pinnedEntry = pinnedEntry
var previousPinnedPeerEntry: PeerEntry?
var pinnedPeerEntry: PeerEntry?
if let previousPinnedEntry = previousPinnedEntry, case let .peer(previousPeerEntry) = previousPinnedEntry {
previousPinnedPeerEntry = previousPeerEntry
}
if let pinnedEntry = pinnedEntry, case let .peer(peerEntry) = pinnedEntry {
pinnedPeerEntry = peerEntry
}
if previousPinnedPeerEntry?.peer.id != pinnedPeerEntry?.peer.id {
self.updateDecorationsLayout(transition: .animated(duration: 0.2, curve: .easeInOut))
}
if updatePinnedPeer && (previousPinnedPeerEntry?.videoEndpointId != pinnedPeerEntry?.videoEndpointId || previousPinnedPeerEntry?.screencastEndpointId != pinnedPeerEntry?.screencastEndpointId) {
self.updateMainParticipant(waitForFullSize: false, currentEntries: entries, updateMembers: true, force: true)
return
}
let previousEntries = self.currentEntries
let previousTileEntries = self.currentTileEntries
self.currentEntries = entries
@ -4612,20 +4753,24 @@ public final class VoiceChatController: ViewController {
self.enqueueTileTransition(tileTransition)
}
private func updatePinnedParticipant(waitForFullSize: Bool, force: Bool = false) {
let effectivePinnedParticipant = self.currentForcedSpeakerWithVideo ?? self.currentDominantSpeakerWithVideo
guard effectivePinnedParticipant != self.effectiveSpeakerWithVideo?.0 || force else {
private var didSetMainParticipant = false
private func updateMainParticipant(waitForFullSize: Bool, currentEntries: [ListEntry]? = nil, updateMembers: Bool = true, force: Bool = false) {
let effectiveMainParticipant = self.currentForcedSpeakerWithVideo ?? self.currentDominantSpeakerWithVideo
guard effectiveMainParticipant != self.effectiveSpeakerWithVideo?.0 || force else {
return
}
var hasVideo = false
if let peerId = effectivePinnedParticipant {
for entry in self.currentEntries {
let currentEntries = currentEntries ?? self.currentEntries
var effectivePeer: (PeerId, String, String?)? = nil
var anyPeer: (PeerId, String, String?)? = nil
if let peerId = effectiveMainParticipant {
for entry in currentEntries {
switch entry {
case let .peer(peer):
if peer.peer.id == peerId {
var effectiveEndpointId = peer.effectiveVideoEndpointId
if self.switchedToCameraPeers.contains(peerId), let videoEndpointId = peer.videoEndpointId {
if self.switchedToCameraPeers.contains(peer.peer.id), let videoEndpointId = peer.videoEndpointId {
effectiveEndpointId = videoEndpointId
}
@ -4637,10 +4782,23 @@ public final class VoiceChatController: ViewController {
}
if let endpointId = effectiveEndpointId {
hasVideo = true
self.effectiveSpeakerWithVideo = (peerId, endpointId)
self.call.setFullSizeVideo(endpointId: endpointId)
self.mainVideoContainerNode?.updatePeer(peer: (peerId: peerId, endpointId: endpointId, otherEndpointId: otherEndpointId), waitForFullSize: waitForFullSize)
effectivePeer = (peer.peer.id, endpointId, otherEndpointId)
}
} else if anyPeer == nil && peer.effectiveVideoEndpointId != nil {
var effectiveEndpointId = peer.effectiveVideoEndpointId
if self.switchedToCameraPeers.contains(peer.peer.id), let videoEndpointId = peer.videoEndpointId {
effectiveEndpointId = videoEndpointId
}
var otherEndpointId: String?
if effectiveEndpointId != peer.videoEndpointId {
otherEndpointId = peer.videoEndpointId
} else if effectiveEndpointId != peer.screencastEndpointId {
otherEndpointId = peer.screencastEndpointId
}
if let endpointId = effectiveEndpointId {
anyPeer = (peer.peer.id, endpointId, otherEndpointId)
}
}
default:
@ -4648,33 +4806,57 @@ public final class VoiceChatController: ViewController {
}
}
}
if !hasVideo {
self.effectiveSpeakerWithVideo = nil
self.call.setFullSizeVideo(endpointId: nil)
self.mainVideoContainerNode?.updatePeer(peer: nil, waitForFullSize: false)
if effectivePeer == nil {
effectivePeer = anyPeer
}
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
var updateLayout = false
if self.effectiveSpeakerWithVideo != nil && !self.isExpanded {
self.isExpanded = true
updateLayout = true
} else if self.effectiveSpeakerWithVideo == nil && self.isExpanded {
self.isExpanded = false
updateLayout = true
}
if updateLayout {
self.updateIsFullscreen(self.isExpanded)
self.animatingExpansion = true
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
let completion = {
var updateLayout = false
if self.effectiveSpeakerWithVideo != nil && !self.isExpanded {
self.isExpanded = true
updateLayout = true
} else if self.effectiveSpeakerWithVideo == nil && self.isExpanded {
self.isExpanded = false
updateLayout = true
}
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
if updateLayout {
self.updateIsFullscreen(self.isExpanded)
self.animatingExpansion = true
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)
}
self.updateDecorationsLayout(transition: transition, completion: {
self.animatingExpansion = false
})
}
}
var waitForFullSize = waitForFullSize
if !self.isExpanded {
waitForFullSize = true
self.mainVideoClippingNode.alpha = 0.0
}
self.effectiveSpeakerWithVideo = effectivePeer.flatMap { ($0.0, $0.1) }
if updateMembers {
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set(), updatePinnedPeer: false)
}
self.call.setFullSizeVideo(endpointId: effectivePeer?.1)
self.mainVideoContainerNode?.updatePeer(peer: effectivePeer, waitForFullSize: waitForFullSize, completion: { [weak self] in
if waitForFullSize {
completion()
if let strongSelf = self, strongSelf.mainVideoClippingNode.alpha.isZero {
strongSelf.mainVideoClippingNode.alpha = 1.0
strongSelf.mainVideoClippingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
}
})
if !waitForFullSize {
completion()
}
}
@ -4732,6 +4914,7 @@ public final class VoiceChatController: ViewController {
if isScheduling && translation < 0.0 {
return
}
var topInset: CGFloat = 0.0
if let (currentTopInset, currentPanOffset) = self.panGestureArguments {
topInset = currentTopInset
@ -4754,7 +4937,7 @@ public final class VoiceChatController: ViewController {
self.updateIsFullscreen(false)
}
if self.isExpanded {
if self.isExpanded && !self.hasMainVideo {
} else {
if currentOffset > 0.0 {
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
@ -4766,7 +4949,7 @@ public final class VoiceChatController: ViewController {
self.updateDecorationsLayout(transition: .immediate)
}
if !self.isExpanded {
if !self.isExpanded || self.hasMainVideo {
var bounds = self.contentContainer.bounds
bounds.origin.y = -translation
bounds.origin.y = min(0.0, bounds.origin.y)
@ -4800,7 +4983,7 @@ public final class VoiceChatController: ViewController {
topInset = self.listNode.frame.height
}
if self.isExpanded {
if self.isExpanded && !self.hasMainVideo {
self.panGestureArguments = nil
if velocity.y > 300.0 || offset > topInset / 2.0 {
self.isExpanded = false
@ -4852,7 +5035,7 @@ public final class VoiceChatController: ViewController {
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
} else if !isScheduling {
} else if !isScheduling && !self.hasMainVideo {
self.updateIsFullscreen(false)
self.animatingExpansion = true
self.listNode.scroller.setContentOffset(CGPoint(), animated: false)
@ -4864,7 +5047,7 @@ public final class VoiceChatController: ViewController {
self.animatingExpansion = false
})
}
if !dismissing {
if !dismissing && self.hasMainVideo {
var bounds = self.contentContainer.bounds
let previousBounds = bounds
bounds.origin.y = 0.0
@ -5222,7 +5405,7 @@ public final class VoiceChatController: ViewController {
switch self.displayMode {
case .default:
let location = videoContainerNode.view.convert(videoContainerNode.otherVideoButtonNode.frame, to: nil)
let location = videoContainerNode.view.convert(videoContainerNode.otherVideoWrapperNode.frame, to: nil)
self.controller?.present(TooltipScreen(text: screencast ? self.presentationData.strings.VoiceChat_TapToViewCameraVideo : self.presentationData.strings.VoiceChat_TapToViewScreenVideo, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}), in: .window(.root))

View File

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

View File

@ -814,6 +814,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
if item.pinned {
self.avatarNode.alpha = 1.0
videoNode.alpha = 0.0
startContainerPosition = startContainerPosition.offsetBy(dx: 0.0, dy: 9.0)
} else {
self.avatarNode.alpha = 0.0
}
@ -971,6 +972,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
let currentItem = self.layoutParams?.0
let currentTitle = self.currentTitle
let hasVideo = self.videoNode != nil
return { item, params, first, last in
var updatedTheme: PresentationTheme?
@ -988,14 +990,16 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
if case .list = item.style, item.transparent{
titleFont = Font.semibold(17.0)
titleColor = UIColor(rgb: 0xffffff, alpha: 0.65)
} else if case .tile = item.style {
} else if case .tile = item.style, !hasVideo {
switch item.text {
case let .text(_, _, textColor):
switch textColor {
case .generic:
titleColor = item.presentationData.theme.list.itemPrimaryTextColor
case .accent:
titleColor = item.presentationData.theme.list.itemAccentColor
if item.peer.id != item.context.account.peerId {
titleColor = item.presentationData.theme.list.itemAccentColor
}
case .constructive:
titleColor = constructiveColor
case .destructive:
@ -1550,28 +1554,36 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
strongSelf.borderImageNode.isHidden = !item.pinned || item.style == .list
let canUpdateAvatarVisibility = !strongSelf.isExtracted && !strongSelf.animatingExtraction
if let videoNode = videoNode {
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
if !strongSelf.isExtracted && !strongSelf.animatingExtraction {
if currentItem != nil {
if case .tile = item.style {
if item.pinned {
if strongSelf.avatarNode.alpha.isZero {
strongSelf.videoContainerNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2)
strongSelf.avatarNode.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2)
}
transition.updateAlpha(node: videoNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.videoFadeNode, alpha: 0.0)
strongSelf.videoContainerNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2)
transition.updateAlpha(node: strongSelf.avatarNode, alpha: 1.0)
strongSelf.avatarNode.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2)
} else {
if !strongSelf.avatarNode.alpha.isZero {
strongSelf.videoContainerNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)
strongSelf.avatarNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2)
}
transition.updateAlpha(node: videoNode, alpha: 1.0)
transition.updateAlpha(node: strongSelf.videoFadeNode, alpha: 1.0)
strongSelf.videoContainerNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)
transition.updateAlpha(node: strongSelf.avatarNode, alpha: 0.0)
strongSelf.avatarNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2)
}
} else {
if item.pinned {
videoNode.alpha = 0.0
strongSelf.avatarNode.alpha = 1.0
if canUpdateAvatarVisibility {
strongSelf.avatarNode.alpha = 1.0
}
} else if strongSelf.videoReady {
videoNode.alpha = 1.0
strongSelf.avatarNode.alpha = 0.0
@ -1580,7 +1592,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} else {
if item.pinned {
videoNode.alpha = 0.0
strongSelf.avatarNode.alpha = 1.0
if canUpdateAvatarVisibility {
strongSelf.avatarNode.alpha = 1.0
}
} else if strongSelf.videoReady {
videoNode.alpha = 1.0
strongSelf.avatarNode.alpha = 0.0
@ -1598,7 +1612,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
videoNode.position = CGPoint(x: videoSize.width / 2.0, y: videoSize.height / 2.0)
videoNode.bounds = CGRect(origin: CGPoint(), size: videoSize)
}
if videoNodeUpdated {
strongSelf.videoReadyDelayed = false
strongSelf.videoReadyDisposable.set((videoNode.ready
@ -1608,13 +1622,18 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
strongSelf.videoReadyDelayed = true
}
strongSelf.videoReady = ready
if let videoNode = strongSelf.videoNode, ready && (strongSelf.item?.transparent != true) {
if let videoNode = strongSelf.videoNode, ready && !item.transparent {
if strongSelf.videoReadyDelayed {
Queue.mainQueue().after(0.15) {
switch item.style {
guard let currentItem = strongSelf.item else {
return
}
switch currentItem.style {
case .list:
if item.pinned {
strongSelf.avatarNode.alpha = 1.0
if currentItem.pinned {
if canUpdateAvatarVisibility {
strongSelf.avatarNode.alpha = 1.0
}
videoNode.alpha = 0.0
} else {
strongSelf.avatarNode.alpha = 0.0
@ -1623,8 +1642,10 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
videoNode.alpha = 1.0
}
case .tile:
if item.pinned {
strongSelf.avatarNode.alpha = 1.0
if currentItem.pinned {
if canUpdateAvatarVisibility {
strongSelf.avatarNode.alpha = 1.0
}
videoNode.alpha = 0.0
} else {
strongSelf.avatarNode.alpha = 0.0
@ -1636,7 +1657,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
}
} else {
if item.pinned {
strongSelf.avatarNode.alpha = 1.0
if canUpdateAvatarVisibility {
strongSelf.avatarNode.alpha = 1.0
}
videoNode.alpha = 0.0
} else {
strongSelf.avatarNode.alpha = 0.0
@ -1647,18 +1670,18 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
}
}))
}
} else {
} else if canUpdateAvatarVisibility {
strongSelf.avatarNode.alpha = 1.0
}
switch item.style {
case .list:
strongSelf.audioLevelView?.alpha = item.transparent ? 0.0 : 1.0
strongSelf.avatarNode.isHidden = item.transparent
strongSelf.avatarNode.isHidden = item.transparent || strongSelf.isExtracted
strongSelf.videoContainerNode.isHidden = item.transparent
strongSelf.pinIconNode.isHidden = !item.transparent
if item.transparent && currentItem?.pinned != item.pinned {
strongSelf.pinIconNode.image = generateTintedImage(image: UIImage(bundleImageName: item.pinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: UIColor(rgb: 0xffffff))
strongSelf.pinIconNode.image = generateTintedImage(image: UIImage(bundleImageName: item.pinned ? "Chat/Context Menu/Pin" : "Chat/Context Menu/Unpin"), color: UIColor(rgb: 0xffffff))
}
case .tile:
strongSelf.pinIconNode.isHidden = true

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 911 B

View File

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

View File

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

View File

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

View File

@ -1023,6 +1023,33 @@ final class ChatMediaInputNode: ChatInputNode {
let _ = self?.controllerInteraction.sendBotContextResultAsGif(collection, result, sourceNode, sourceRect)
}
})))
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
var isScheduledMessages = false
if case .scheduledMessages = interfaceState.subject {
isScheduledMessages = true
}
if !isScheduledMessages {
if case let .peer(peerId) = interfaceState.chatLocation {
if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat {
items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
}
}
if isSaved || isGifSaved {
items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
@ -1096,16 +1123,37 @@ final class ChatMediaInputNode: ChatInputNode {
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self {
var menuItems: [PeekControllerMenuItem] = []
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
var menuItems: [ContextMenuItem] = []
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
var isScheduledMessages = false
if case .scheduledMessages = interfaceState.subject {
isScheduledMessages = true
}
if !isScheduledMessages {
if case let .peer(peerId) = interfaceState.chatLocation {
if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat {
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
}),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
}
}
menuItems.append(
.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()
@ -1113,9 +1161,13 @@ 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
}))
)
menuItems.append(.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 {
@ -1138,10 +1190,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
@ -1180,16 +1231,37 @@ final class ChatMediaInputNode: ChatInputNode {
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self {
var menuItems: [PeekControllerMenuItem] = []
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
var menuItems: [ContextMenuItem] = []
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
var isScheduledMessages = false
if case .scheduledMessages = interfaceState.subject {
isScheduledMessages = true
}
if !isScheduledMessages {
if case let .peer(peerId) = interfaceState.chatLocation {
if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat {
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
}),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
}
}
menuItems.append(
.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()
@ -1197,35 +1269,38 @@ 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
}))
)
menuItems.append(
.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 {
return nil
@ -1239,7 +1314,8 @@ final class ChatMediaInputNode: ChatInputNode {
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.theme), content: content, sourceNode: {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = PeekController(presentationData: presentationData, content: content, sourceNode: {
return sourceNode
})
controller.visibilityUpdated = { [weak self] visible in

View File

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

View File

@ -314,10 +314,16 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
let targetBounds = self.bounds
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
transition.animateView({
self.bounds = targetBounds
self.dimNode.position = dimPosition
})
}
func animateOut(completion: (() -> Void)? = nil) {

View File

@ -401,10 +401,16 @@ class ChatTimerScreenNode: ViewControllerTracingNode, UIScrollViewDelegate, UIPi
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
let targetBounds = self.bounds
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
transition.animateView({
self.bounds = targetBounds
self.dimNode.position = dimPosition
})
}
func animateOut(completion: (() -> Void)? = nil) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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