mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Sticker input rewrite continued
This commit is contained in:
parent
baad43fb1f
commit
087bf3352e
@ -192,14 +192,17 @@ public struct Transition {
|
||||
}
|
||||
switch self.animation {
|
||||
case .none:
|
||||
view.frame = frame
|
||||
view.bounds = CGRect(origin: view.bounds.origin, size: frame.size)
|
||||
view.layer.position = CGPoint(x: frame.midX, y: frame.midY)
|
||||
view.layer.removeAnimation(forKey: "position")
|
||||
view.layer.removeAnimation(forKey: "bounds")
|
||||
completion?(true)
|
||||
case .curve:
|
||||
let previousPosition = view.layer.presentation()?.position ?? view.center
|
||||
let previousBounds = view.layer.presentation()?.bounds ?? view.bounds
|
||||
view.frame = frame
|
||||
|
||||
view.bounds = CGRect(origin: previousBounds.origin, size: frame.size)
|
||||
view.center = CGPoint(x: frame.midX, y: frame.midY)
|
||||
|
||||
self.animatePosition(view: view, from: previousPosition, to: view.center, completion: completion)
|
||||
self.animateBounds(view: view, from: previousBounds, to: view.bounds)
|
||||
@ -293,12 +296,17 @@ public struct Transition {
|
||||
view.layer.sublayerTransform = transform
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
let previousValue = view.layer.sublayerTransform
|
||||
let previousValue: CATransform3D
|
||||
if let presentation = view.layer.presentation() {
|
||||
previousValue = presentation.sublayerTransform
|
||||
} else {
|
||||
previousValue = view.layer.sublayerTransform
|
||||
}
|
||||
view.layer.sublayerTransform = transform
|
||||
view.layer.animate(
|
||||
from: NSValue(caTransform3D: previousValue),
|
||||
to: NSValue(caTransform3D: transform),
|
||||
keyPath: "transform",
|
||||
keyPath: "sublayerTransform",
|
||||
duration: duration,
|
||||
delay: 0.0,
|
||||
curve: curve,
|
||||
|
@ -118,3 +118,93 @@ public final class ComponentHostView<EnvironmentType>: UIView {
|
||||
return findTaggedViewImpl(view: componentView, tag: tag)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ComponentView<EnvironmentType> {
|
||||
private var currentComponent: AnyComponent<EnvironmentType>?
|
||||
private var currentContainerSize: CGSize?
|
||||
private var currentSize: CGSize?
|
||||
public private(set) var view: UIView?
|
||||
private(set) var isUpdating: Bool = false
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func update(transition: Transition, component: AnyComponent<EnvironmentType>, @EnvironmentBuilder environment: () -> Environment<EnvironmentType>, forceUpdate: Bool = false, containerSize: CGSize) -> CGSize {
|
||||
let size = self._update(transition: transition, component: component, maybeEnvironment: environment, updateEnvironment: true, forceUpdate: forceUpdate, containerSize: containerSize)
|
||||
self.currentSize = size
|
||||
return size
|
||||
}
|
||||
|
||||
private func _update(transition: Transition, component: AnyComponent<EnvironmentType>, maybeEnvironment: () -> Environment<EnvironmentType>, updateEnvironment: Bool, forceUpdate: Bool, containerSize: CGSize) -> CGSize {
|
||||
precondition(!self.isUpdating)
|
||||
self.isUpdating = true
|
||||
|
||||
precondition(containerSize.width.isFinite)
|
||||
precondition(containerSize.height.isFinite)
|
||||
|
||||
let componentView: UIView
|
||||
if let current = self.view {
|
||||
componentView = current
|
||||
} else {
|
||||
componentView = component._makeView()
|
||||
self.view = componentView
|
||||
}
|
||||
|
||||
let context = componentView.context(component: component)
|
||||
|
||||
let componentState: ComponentState = context.erasedState
|
||||
|
||||
if updateEnvironment {
|
||||
EnvironmentBuilder._environment = context.erasedEnvironment
|
||||
let environmentResult = maybeEnvironment()
|
||||
EnvironmentBuilder._environment = nil
|
||||
context.erasedEnvironment = environmentResult
|
||||
}
|
||||
|
||||
let isEnvironmentUpdated = context.erasedEnvironment.calculateIsUpdated()
|
||||
|
||||
|
||||
if !forceUpdate, !isEnvironmentUpdated, let currentComponent = self.currentComponent, let currentContainerSize = self.currentContainerSize, let currentSize = self.currentSize {
|
||||
if currentContainerSize == containerSize && currentComponent == component {
|
||||
self.isUpdating = false
|
||||
return currentSize
|
||||
}
|
||||
}
|
||||
self.currentComponent = component
|
||||
self.currentContainerSize = containerSize
|
||||
|
||||
componentState._updated = { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = strongSelf._update(transition: transition, component: component, maybeEnvironment: {
|
||||
preconditionFailure()
|
||||
} as () -> Environment<EnvironmentType>, updateEnvironment: false, forceUpdate: true, containerSize: containerSize)
|
||||
}
|
||||
|
||||
let updatedSize = component._update(view: componentView, availableSize: containerSize, environment: context.erasedEnvironment, transition: transition)
|
||||
if transition.userData(ComponentHostViewSkipSettingFrame.self) == nil {
|
||||
transition.setFrame(view: componentView, frame: CGRect(origin: CGPoint(), size: updatedSize))
|
||||
}
|
||||
|
||||
if isEnvironmentUpdated {
|
||||
context.erasedEnvironment._isUpdated = false
|
||||
}
|
||||
|
||||
self.isUpdating = false
|
||||
|
||||
return updatedSize
|
||||
}
|
||||
|
||||
public func findTaggedView(tag: Any) -> UIView? {
|
||||
guard let view = self.view else {
|
||||
return nil
|
||||
}
|
||||
return findTaggedViewImpl(view: view, tag: tag)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ public final class Action<Arguments> {
|
||||
public final class ActionSlot<Arguments>: Equatable {
|
||||
private var target: ((Arguments) -> Void)?
|
||||
|
||||
init() {
|
||||
public init() {
|
||||
}
|
||||
|
||||
public static func ==(lhs: ActionSlot<Arguments>, rhs: ActionSlot<Arguments>) -> Bool {
|
||||
|
@ -3,19 +3,31 @@ import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
|
||||
public protocol PagerExpandableScrollView: UIScrollView {
|
||||
}
|
||||
|
||||
public protocol PagerPanGestureRecognizer: UIGestureRecognizer {
|
||||
}
|
||||
|
||||
public final class PagerComponentChildEnvironment: Equatable {
|
||||
public struct ContentScrollingUpdate {
|
||||
public var relativeOffset: CGFloat
|
||||
public var absoluteOffsetToClosestEdge: CGFloat?
|
||||
public var absoluteOffsetToTopEdge: CGFloat?
|
||||
public var absoluteOffsetToBottomEdge: CGFloat?
|
||||
public var isInteracting: Bool
|
||||
public var transition: Transition
|
||||
|
||||
public init(
|
||||
relativeOffset: CGFloat,
|
||||
absoluteOffsetToClosestEdge: CGFloat?,
|
||||
absoluteOffsetToTopEdge: CGFloat?,
|
||||
absoluteOffsetToBottomEdge: CGFloat?,
|
||||
isInteracting: Bool,
|
||||
transition: Transition
|
||||
) {
|
||||
self.relativeOffset = relativeOffset
|
||||
self.absoluteOffsetToClosestEdge = absoluteOffsetToClosestEdge
|
||||
self.absoluteOffsetToTopEdge = absoluteOffsetToTopEdge
|
||||
self.absoluteOffsetToBottomEdge = absoluteOffsetToBottomEdge
|
||||
self.isInteracting = isInteracting
|
||||
self.transition = transition
|
||||
}
|
||||
}
|
||||
@ -40,28 +52,34 @@ public final class PagerComponentChildEnvironment: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public final class PagerComponentPanelEnvironment: Equatable {
|
||||
public final class PagerComponentPanelEnvironment<TopPanelEnvironment>: Equatable {
|
||||
public let contentOffset: CGFloat
|
||||
public let contentTopPanels: [AnyComponentWithIdentity<Empty>]
|
||||
public let contentTopPanels: [AnyComponentWithIdentity<TopPanelEnvironment>]
|
||||
public let contentIcons: [AnyComponentWithIdentity<Empty>]
|
||||
public let contentAccessoryLeftButtons: [AnyComponentWithIdentity<Empty>]
|
||||
public let contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>]
|
||||
public let activeContentId: AnyHashable?
|
||||
public let navigateToContentId: (AnyHashable) -> Void
|
||||
public let visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>
|
||||
|
||||
init(
|
||||
contentOffset: CGFloat,
|
||||
contentTopPanels: [AnyComponentWithIdentity<Empty>],
|
||||
contentTopPanels: [AnyComponentWithIdentity<TopPanelEnvironment>],
|
||||
contentIcons: [AnyComponentWithIdentity<Empty>],
|
||||
contentAccessoryLeftButtons: [AnyComponentWithIdentity<Empty>],
|
||||
contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>],
|
||||
activeContentId: AnyHashable?,
|
||||
navigateToContentId: @escaping (AnyHashable) -> Void
|
||||
navigateToContentId: @escaping (AnyHashable) -> Void,
|
||||
visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>
|
||||
) {
|
||||
self.contentOffset = contentOffset
|
||||
self.contentTopPanels = contentTopPanels
|
||||
self.contentIcons = contentIcons
|
||||
self.contentAccessoryLeftButtons = contentAccessoryLeftButtons
|
||||
self.contentAccessoryRightButtons = contentAccessoryRightButtons
|
||||
self.activeContentId = activeContentId
|
||||
self.navigateToContentId = navigateToContentId
|
||||
self.visibilityFractionUpdated = visibilityFractionUpdated
|
||||
}
|
||||
|
||||
public static func ==(lhs: PagerComponentPanelEnvironment, rhs: PagerComponentPanelEnvironment) -> Bool {
|
||||
@ -74,12 +92,18 @@ public final class PagerComponentPanelEnvironment: Equatable {
|
||||
if lhs.contentIcons != rhs.contentIcons {
|
||||
return false
|
||||
}
|
||||
if lhs.contentAccessoryLeftButtons != rhs.contentAccessoryLeftButtons {
|
||||
return false
|
||||
}
|
||||
if lhs.contentAccessoryRightButtons != rhs.contentAccessoryRightButtons {
|
||||
return false
|
||||
}
|
||||
if lhs.activeContentId != rhs.activeContentId {
|
||||
return false
|
||||
}
|
||||
if lhs.visibilityFractionUpdated !== rhs.visibilityFractionUpdated {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -93,38 +117,48 @@ public struct PagerComponentPanelState {
|
||||
}
|
||||
}
|
||||
|
||||
public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
public final class PagerComponentViewTag {
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
|
||||
public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvironment: Equatable>: Component {
|
||||
public typealias EnvironmentType = ChildEnvironmentType
|
||||
|
||||
public let contentInsets: UIEdgeInsets
|
||||
public let contents: [AnyComponentWithIdentity<(ChildEnvironmentType, PagerComponentChildEnvironment)>]
|
||||
public let contentTopPanels: [AnyComponentWithIdentity<Empty>]
|
||||
public let contentTopPanels: [AnyComponentWithIdentity<TopPanelEnvironment>]
|
||||
public let contentIcons: [AnyComponentWithIdentity<Empty>]
|
||||
public let contentAccessoryLeftButtons:[AnyComponentWithIdentity<Empty>]
|
||||
public let contentAccessoryRightButtons:[AnyComponentWithIdentity<Empty>]
|
||||
public let defaultId: AnyHashable?
|
||||
public let contentBackground: AnyComponent<Empty>?
|
||||
public let topPanel: AnyComponent<PagerComponentPanelEnvironment>?
|
||||
public let topPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?
|
||||
public let externalTopPanelContainer: UIView?
|
||||
public let bottomPanel: AnyComponent<PagerComponentPanelEnvironment>?
|
||||
public let bottomPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?
|
||||
public let panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?
|
||||
public let hidePanels: Bool
|
||||
|
||||
public init(
|
||||
contentInsets: UIEdgeInsets,
|
||||
contents: [AnyComponentWithIdentity<(ChildEnvironmentType, PagerComponentChildEnvironment)>],
|
||||
contentTopPanels: [AnyComponentWithIdentity<Empty>],
|
||||
contentTopPanels: [AnyComponentWithIdentity<TopPanelEnvironment>],
|
||||
contentIcons: [AnyComponentWithIdentity<Empty>],
|
||||
contentAccessoryRightButtons:[AnyComponentWithIdentity<Empty>],
|
||||
contentAccessoryLeftButtons: [AnyComponentWithIdentity<Empty>],
|
||||
contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>],
|
||||
defaultId: AnyHashable?,
|
||||
contentBackground: AnyComponent<Empty>?,
|
||||
topPanel: AnyComponent<PagerComponentPanelEnvironment>?,
|
||||
topPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?,
|
||||
externalTopPanelContainer: UIView?,
|
||||
bottomPanel: AnyComponent<PagerComponentPanelEnvironment>?,
|
||||
panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?
|
||||
bottomPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?,
|
||||
panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?,
|
||||
hidePanels: Bool
|
||||
) {
|
||||
self.contentInsets = contentInsets
|
||||
self.contents = contents
|
||||
self.contentTopPanels = contentTopPanels
|
||||
self.contentIcons = contentIcons
|
||||
self.contentAccessoryLeftButtons = contentAccessoryLeftButtons
|
||||
self.contentAccessoryRightButtons = contentAccessoryRightButtons
|
||||
self.defaultId = defaultId
|
||||
self.contentBackground = contentBackground
|
||||
@ -132,6 +166,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
self.externalTopPanelContainer = externalTopPanelContainer
|
||||
self.bottomPanel = bottomPanel
|
||||
self.panelStateUpdated = panelStateUpdated
|
||||
self.hidePanels = hidePanels
|
||||
}
|
||||
|
||||
public static func ==(lhs: PagerComponent, rhs: PagerComponent) -> Bool {
|
||||
@ -162,43 +197,56 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
if lhs.bottomPanel != rhs.bottomPanel {
|
||||
return false
|
||||
}
|
||||
if lhs.hidePanels != rhs.hidePanels {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
public final class View: UIView, ComponentTaggedView {
|
||||
private final class ContentView {
|
||||
let view: ComponentHostView<(ChildEnvironmentType, PagerComponentChildEnvironment)>
|
||||
var scrollingPanelOffsetToClosestEdge: CGFloat = 0.0
|
||||
var scrollingPanelOffsetToTopEdge: CGFloat = 0.0
|
||||
var scrollingPanelOffsetToBottomEdge: CGFloat = .greatestFiniteMagnitude
|
||||
var scrollingPanelOffsetFraction: CGFloat = 0.0
|
||||
|
||||
init(view: ComponentHostView<(ChildEnvironmentType, PagerComponentChildEnvironment)>) {
|
||||
self.view = view
|
||||
}
|
||||
}
|
||||
|
||||
private final class PagerPanGestureRecognizerImpl: UIPanGestureRecognizer, PagerPanGestureRecognizer {
|
||||
}
|
||||
|
||||
private struct PaneTransitionGestureState {
|
||||
var fraction: CGFloat = 0.0
|
||||
}
|
||||
|
||||
private var contentViews: [AnyHashable: ContentView] = [:]
|
||||
private var contentBackgroundView: ComponentHostView<Empty>?
|
||||
private var topPanelView: ComponentHostView<PagerComponentPanelEnvironment>?
|
||||
private var bottomPanelView: ComponentHostView<PagerComponentPanelEnvironment>?
|
||||
private let topPanelVisibilityFractionUpdated = ActionSlot<(CGFloat, Transition)>()
|
||||
private var topPanelView: ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>?
|
||||
private let bottomPanelVisibilityFractionUpdated = ActionSlot<(CGFloat, Transition)>()
|
||||
private var bottomPanelView: ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>?
|
||||
|
||||
private var centralId: AnyHashable?
|
||||
private var topPanelHeight: CGFloat?
|
||||
private var bottomPanelHeight: CGFloat?
|
||||
|
||||
public private(set) var centralId: AnyHashable?
|
||||
private var paneTransitionGestureState: PaneTransitionGestureState?
|
||||
|
||||
private var component: PagerComponent<ChildEnvironmentType>?
|
||||
private var component: PagerComponent<ChildEnvironmentType, TopPanelEnvironment>?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private var panRecognizer: UIPanGestureRecognizer?
|
||||
private var panRecognizer: PagerPanGestureRecognizerImpl?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.disablesInteractiveTransitionGestureRecognizer = true
|
||||
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||
let panRecognizer = PagerPanGestureRecognizerImpl(target: self, action: #selector(self.panGesture(_:)))
|
||||
self.panRecognizer = panRecognizer
|
||||
self.addGestureRecognizer(panRecognizer)
|
||||
}
|
||||
@ -207,6 +255,14 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func matches(tag: Any) -> Bool {
|
||||
if tag is PagerComponentViewTag {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
@ -252,7 +308,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: PagerComponent<ChildEnvironmentType>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
func update(component: PagerComponent<ChildEnvironmentType, TopPanelEnvironment>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
@ -288,22 +344,22 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
|
||||
var contentInsets = component.contentInsets
|
||||
|
||||
let scrollingPanelOffsetToClosestEdge: CGFloat
|
||||
var scrollingPanelOffsetFraction: CGFloat
|
||||
if let centralId = centralId, let centralContentView = self.contentViews[centralId] {
|
||||
scrollingPanelOffsetToClosestEdge = centralContentView.scrollingPanelOffsetToClosestEdge
|
||||
scrollingPanelOffsetFraction = centralContentView.scrollingPanelOffsetFraction
|
||||
} else {
|
||||
scrollingPanelOffsetToClosestEdge = 0.0
|
||||
scrollingPanelOffsetFraction = 0.0
|
||||
}
|
||||
|
||||
var topPanelHeight: CGFloat = 0.0
|
||||
if let topPanel = component.topPanel {
|
||||
let topPanelView: ComponentHostView<PagerComponentPanelEnvironment>
|
||||
let topPanelView: ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>
|
||||
var topPanelTransition = transition
|
||||
if let current = self.topPanelView {
|
||||
topPanelView = current
|
||||
} else {
|
||||
topPanelTransition = .immediate
|
||||
topPanelView = ComponentHostView<PagerComponentPanelEnvironment>()
|
||||
topPanelView = ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>()
|
||||
topPanelView.clipsToBounds = true
|
||||
self.topPanelView = topPanelView
|
||||
}
|
||||
@ -321,20 +377,38 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
contentOffset: 0.0,
|
||||
contentTopPanels: component.contentTopPanels,
|
||||
contentIcons: [],
|
||||
contentAccessoryLeftButtons: [],
|
||||
contentAccessoryRightButtons: [],
|
||||
activeContentId: centralId,
|
||||
navigateToContentId: navigateToContentId
|
||||
navigateToContentId: navigateToContentId,
|
||||
visibilityFractionUpdated: self.topPanelVisibilityFractionUpdated
|
||||
)
|
||||
},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let topPanelOffset = max(0.0, min(topPanelSize.height, scrollingPanelOffsetToClosestEdge))
|
||||
self.topPanelHeight = topPanelSize.height
|
||||
|
||||
var topPanelOffset = topPanelSize.height * scrollingPanelOffsetFraction
|
||||
|
||||
var topPanelVisibilityFraction: CGFloat = 1.0 - scrollingPanelOffsetFraction
|
||||
if component.hidePanels {
|
||||
topPanelVisibilityFraction = 0.0
|
||||
}
|
||||
|
||||
self.topPanelVisibilityFractionUpdated.invoke((topPanelVisibilityFraction, topPanelTransition))
|
||||
|
||||
topPanelHeight = max(0.0, topPanelSize.height - topPanelOffset)
|
||||
|
||||
if component.hidePanels {
|
||||
topPanelOffset = topPanelSize.height
|
||||
}
|
||||
|
||||
if component.externalTopPanelContainer != nil {
|
||||
let visibleTopPanelHeight = max(0.0, topPanelSize.height - topPanelOffset)
|
||||
var visibleTopPanelHeight = max(0.0, topPanelSize.height - topPanelOffset)
|
||||
if component.hidePanels {
|
||||
visibleTopPanelHeight = 0.0
|
||||
}
|
||||
transition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(), size: CGSize(width: topPanelSize.width, height: visibleTopPanelHeight)))
|
||||
} else {
|
||||
transition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelOffset), size: topPanelSize))
|
||||
@ -342,22 +416,24 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
|
||||
contentInsets.top += topPanelSize.height
|
||||
} else {
|
||||
if let bottomPanelView = self.bottomPanelView {
|
||||
self.bottomPanelView = nil
|
||||
if let topPanelView = self.topPanelView {
|
||||
self.topPanelView = nil
|
||||
|
||||
bottomPanelView.removeFromSuperview()
|
||||
topPanelView.removeFromSuperview()
|
||||
}
|
||||
|
||||
self.topPanelHeight = 0.0
|
||||
}
|
||||
|
||||
var bottomPanelOffset: CGFloat = 0.0
|
||||
if let bottomPanel = component.bottomPanel {
|
||||
let bottomPanelView: ComponentHostView<PagerComponentPanelEnvironment>
|
||||
let bottomPanelView: ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>
|
||||
var bottomPanelTransition = transition
|
||||
if let current = self.bottomPanelView {
|
||||
bottomPanelView = current
|
||||
} else {
|
||||
bottomPanelTransition = .immediate
|
||||
bottomPanelView = ComponentHostView<PagerComponentPanelEnvironment>()
|
||||
bottomPanelView = ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>()
|
||||
self.bottomPanelView = bottomPanelView
|
||||
self.addSubview(bottomPanelView)
|
||||
}
|
||||
@ -365,19 +441,26 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
transition: bottomPanelTransition,
|
||||
component: bottomPanel,
|
||||
environment: {
|
||||
PagerComponentPanelEnvironment(
|
||||
PagerComponentPanelEnvironment<TopPanelEnvironment>(
|
||||
contentOffset: 0.0,
|
||||
contentTopPanels: [],
|
||||
contentIcons: component.contentIcons,
|
||||
contentAccessoryLeftButtons: component.contentAccessoryLeftButtons,
|
||||
contentAccessoryRightButtons: component.contentAccessoryRightButtons,
|
||||
activeContentId: centralId,
|
||||
navigateToContentId: navigateToContentId
|
||||
navigateToContentId: navigateToContentId,
|
||||
visibilityFractionUpdated: self.bottomPanelVisibilityFractionUpdated
|
||||
)
|
||||
},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
bottomPanelOffset = max(0.0, min(bottomPanelSize.height, scrollingPanelOffsetToClosestEdge))
|
||||
self.bottomPanelHeight = bottomPanelSize.height
|
||||
|
||||
bottomPanelOffset = bottomPanelSize.height * scrollingPanelOffsetFraction
|
||||
if component.hidePanels {
|
||||
bottomPanelOffset = bottomPanelSize.height
|
||||
}
|
||||
|
||||
transition.setFrame(view: bottomPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelSize.height + bottomPanelOffset), size: bottomPanelSize))
|
||||
|
||||
@ -388,8 +471,12 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
|
||||
bottomPanelView.removeFromSuperview()
|
||||
}
|
||||
|
||||
self.bottomPanelHeight = 0.0
|
||||
}
|
||||
|
||||
let effectiveTopPanelHeight: CGFloat = component.hidePanels ? 0.0 : topPanelHeight
|
||||
|
||||
if let contentBackground = component.contentBackground {
|
||||
let contentBackgroundView: ComponentHostView<Empty>
|
||||
var contentBackgroundTransition = transition
|
||||
@ -405,9 +492,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
transition: contentBackgroundTransition,
|
||||
component: contentBackground,
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: availableSize.height - topPanelHeight - contentInsets.bottom + bottomPanelOffset)
|
||||
containerSize: CGSize(width: availableSize.width, height: availableSize.height - effectiveTopPanelHeight - contentInsets.bottom + bottomPanelOffset)
|
||||
)
|
||||
contentBackgroundTransition.setFrame(view: contentBackgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: contentBackgroundSize))
|
||||
contentBackgroundTransition.setFrame(view: contentBackgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: effectiveTopPanelHeight), size: contentBackgroundSize))
|
||||
} else {
|
||||
if let contentBackgroundView = self.contentBackgroundView {
|
||||
self.contentBackgroundView = nil
|
||||
@ -558,13 +645,44 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
|
||||
return
|
||||
}
|
||||
|
||||
if let absoluteOffsetToClosestEdge = update.absoluteOffsetToClosestEdge {
|
||||
contentView.scrollingPanelOffsetToClosestEdge = absoluteOffsetToClosestEdge
|
||||
} else {
|
||||
contentView.scrollingPanelOffsetToClosestEdge = 1000.0
|
||||
var offsetDelta: CGFloat?
|
||||
offsetDelta = (update.absoluteOffsetToTopEdge ?? 0.0) - contentView.scrollingPanelOffsetToTopEdge
|
||||
|
||||
contentView.scrollingPanelOffsetToTopEdge = update.absoluteOffsetToTopEdge ?? 0.0
|
||||
contentView.scrollingPanelOffsetToBottomEdge = update.absoluteOffsetToBottomEdge ?? .greatestFiniteMagnitude
|
||||
|
||||
if let topPanelHeight = self.topPanelHeight, let bottomPanelHeight = self.bottomPanelHeight {
|
||||
var scrollingPanelOffsetFraction = contentView.scrollingPanelOffsetFraction
|
||||
|
||||
if topPanelHeight > 0.0, let offsetDelta = offsetDelta {
|
||||
let fractionDelta = -offsetDelta / topPanelHeight
|
||||
scrollingPanelOffsetFraction = max(0.0, min(1.0, contentView.scrollingPanelOffsetFraction - fractionDelta))
|
||||
}
|
||||
|
||||
if bottomPanelHeight > 0.0 && contentView.scrollingPanelOffsetToBottomEdge < bottomPanelHeight {
|
||||
scrollingPanelOffsetFraction = min(scrollingPanelOffsetFraction, contentView.scrollingPanelOffsetToBottomEdge / bottomPanelHeight)
|
||||
} else if topPanelHeight > 0.0 && contentView.scrollingPanelOffsetToTopEdge < topPanelHeight {
|
||||
scrollingPanelOffsetFraction = min(scrollingPanelOffsetFraction, contentView.scrollingPanelOffsetToTopEdge / topPanelHeight)
|
||||
}
|
||||
|
||||
var transition = update.transition
|
||||
if !update.isInteracting {
|
||||
if scrollingPanelOffsetFraction < 0.5 {
|
||||
scrollingPanelOffsetFraction = 0.0
|
||||
} else {
|
||||
scrollingPanelOffsetFraction = 1.0
|
||||
}
|
||||
if case .none = transition.animation {
|
||||
} else {
|
||||
transition = transition.withAnimation(.curve(duration: 0.25, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
if scrollingPanelOffsetFraction != contentView.scrollingPanelOffsetFraction {
|
||||
contentView.scrollingPanelOffsetFraction = scrollingPanelOffsetFraction
|
||||
self.state?.updated(transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
state?.updated(transition: update.transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,6 +146,8 @@ public extension ContainedViewLayoutTransition {
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
node.layer.removeAnimation(forKey: "position")
|
||||
node.layer.removeAnimation(forKey: "bounds")
|
||||
node.frame = frame
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -173,6 +175,8 @@ public extension ContainedViewLayoutTransition {
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
node.layer.removeAnimation(forKey: "position")
|
||||
node.layer.removeAnimation(forKey: "bounds")
|
||||
node.position = frame.center
|
||||
node.bounds = CGRect(origin: CGPoint(), size: frame.size)
|
||||
if let completion = completion {
|
||||
@ -206,6 +210,8 @@ public extension ContainedViewLayoutTransition {
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.removeAnimation(forKey: "position")
|
||||
layer.removeAnimation(forKey: "bounds")
|
||||
layer.position = frame.center
|
||||
layer.bounds = CGRect(origin: CGPoint(), size: frame.size)
|
||||
if let completion = completion {
|
||||
@ -277,6 +283,7 @@ public extension ContainedViewLayoutTransition {
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
node.layer.removeAnimation(forKey: "bounds")
|
||||
node.bounds = bounds
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -304,6 +311,7 @@ public extension ContainedViewLayoutTransition {
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.removeAnimation(forKey: "bounds")
|
||||
layer.bounds = bounds
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -326,6 +334,7 @@ public extension ContainedViewLayoutTransition {
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
node.layer.removeAnimation(forKey: "position")
|
||||
node.position = position
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -353,6 +362,7 @@ public extension ContainedViewLayoutTransition {
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.removeAnimation(forKey: "position")
|
||||
layer.position = position
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -615,6 +625,8 @@ public extension ContainedViewLayoutTransition {
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
view.layer.removeAnimation(forKey: "position")
|
||||
view.layer.removeAnimation(forKey: "bounds")
|
||||
view.frame = frame
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -642,6 +654,8 @@ public extension ContainedViewLayoutTransition {
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.removeAnimation(forKey: "position")
|
||||
layer.removeAnimation(forKey: "bounds")
|
||||
layer.frame = frame
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -790,6 +804,7 @@ public extension ContainedViewLayoutTransition {
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
node.layer.removeAnimation(forKey: "cornerRadius")
|
||||
node.cornerRadius = cornerRadius
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -815,6 +830,7 @@ public extension ContainedViewLayoutTransition {
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.removeAnimation(forKey: "cornerRadius")
|
||||
layer.cornerRadius = cornerRadius
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -1084,6 +1100,7 @@ public extension ContainedViewLayoutTransition {
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
node.layer.removeAnimation(forKey: "sublayerTransform")
|
||||
node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -1116,6 +1133,7 @@ public extension ContainedViewLayoutTransition {
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
node.layer.removeAnimation(forKey: "sublayerTransform")
|
||||
node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -1153,6 +1171,7 @@ public extension ContainedViewLayoutTransition {
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
node.layer.removeAnimation(forKey: "sublayerTransform")
|
||||
node.layer.sublayerTransform = transform
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -1200,6 +1219,7 @@ public extension ContainedViewLayoutTransition {
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.removeAnimation(forKey: "sublayerTransform")
|
||||
layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -1248,6 +1268,7 @@ public extension ContainedViewLayoutTransition {
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.removeAnimation(forKey: "transform")
|
||||
layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
@ -1275,6 +1296,7 @@ public extension ContainedViewLayoutTransition {
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.removeAnimation(forKey: "sublayerTransform")
|
||||
layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0)
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
|
@ -6,12 +6,12 @@ private class GridNodeScrollerLayer: CALayer {
|
||||
}
|
||||
}
|
||||
|
||||
private class GridNodeScrollerView: UIScrollView {
|
||||
override class var layerClass: AnyClass {
|
||||
public class GridNodeScrollerView: UIScrollView {
|
||||
override public class var layerClass: AnyClass {
|
||||
return GridNodeScrollerLayer.self
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
@ -19,15 +19,15 @@ private class GridNodeScrollerView: UIScrollView {
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
override public func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
@objc private func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -605,7 +605,7 @@ private func loadItem(path: String) -> AnimationCacheItem? {
|
||||
}
|
||||
let decompressedSize = readUInt32(data: compressedData, offset: 0)
|
||||
|
||||
if decompressedSize <= 0 || decompressedSize > 20 * 1024 * 1024 {
|
||||
if decompressedSize <= 0 || decompressedSize > 40 * 1024 * 1024 {
|
||||
return nil
|
||||
}
|
||||
guard let data = decompressData(data: compressedData, range: 4 ..< compressedData.count, decompressedSize: Int(decompressedSize)) else {
|
||||
|
@ -13,6 +13,7 @@ swift_library(
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/Components/PagerComponent:PagerComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -2,37 +2,172 @@ import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import PagerComponent
|
||||
|
||||
private final class ExpansionPanRecognizer: UIPanGestureRecognizer, UIGestureRecognizerDelegate {
|
||||
private var targetScrollView: UIScrollView?
|
||||
private func traceScrollView(view: UIView, point: CGPoint) -> (UIScrollView?, Bool) {
|
||||
for subview in view.subviews.reversed() {
|
||||
let subviewPoint = view.convert(point, to: subview)
|
||||
if subview.frame.contains(point) {
|
||||
let (result, shouldContinue) = traceScrollView(view: subview, point: subviewPoint)
|
||||
if let result = result {
|
||||
return (result, false)
|
||||
} else if subview.backgroundColor != nil {
|
||||
return (nil, false)
|
||||
} else if !shouldContinue{
|
||||
return (nil, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let scrollView = view as? UIScrollView {
|
||||
if scrollView is ListViewScroller || scrollView is GridNodeScrollerView {
|
||||
return (nil, false)
|
||||
}
|
||||
return (scrollView, false)
|
||||
}
|
||||
return (nil, true)
|
||||
}
|
||||
|
||||
private final class ExpansionPanRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate {
|
||||
enum LockDirection {
|
||||
case up
|
||||
case down
|
||||
}
|
||||
|
||||
override init(target: Any?, action: Selector?) {
|
||||
var requiredLockDirection: LockDirection = .up
|
||||
|
||||
private var beginPosition = CGPoint()
|
||||
private var currentTranslation = CGPoint()
|
||||
|
||||
override public init(target: Any?, action: Selector?) {
|
||||
super.init(target: target, action: action)
|
||||
|
||||
self.delegate = self
|
||||
}
|
||||
|
||||
override func reset() {
|
||||
self.targetScrollView = nil
|
||||
override public func reset() {
|
||||
super.reset()
|
||||
|
||||
self.state = .possible
|
||||
self.currentTranslation = CGPoint()
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
/*if let scrollView = otherGestureRecognizer.view as? UIScrollView {
|
||||
if scrollView.bounds.height > 200.0 {
|
||||
self.targetScrollView = scrollView
|
||||
scrollView.contentOffset = CGPoint()
|
||||
}
|
||||
}*/
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let _ = otherGestureRecognizer.view as? PagerExpandableScrollView {
|
||||
return true
|
||||
}
|
||||
|
||||
if let _ = gestureRecognizer as? PagerPanGestureRecognizer {
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let _ = otherGestureRecognizer.view as? PagerExpandableScrollView {
|
||||
return true
|
||||
}
|
||||
if otherGestureRecognizer is UIPanGestureRecognizer {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
guard let touch = touches.first, let view = self.view else {
|
||||
self.state = .failed
|
||||
return
|
||||
}
|
||||
|
||||
var found = false
|
||||
let point = touch.location(in: self.view)
|
||||
if let _ = view.hitTest(point, with: event) as? UIButton {
|
||||
} else if let scrollView = traceScrollView(view: view, point: point).0 {
|
||||
let contentOffset = scrollView.contentOffset
|
||||
let contentInset = scrollView.contentInset
|
||||
if contentOffset.y.isLessThanOrEqualTo(contentInset.top) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if found {
|
||||
self.beginPosition = point
|
||||
} else {
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
if let targetScrollView = self.targetScrollView {
|
||||
targetScrollView.contentOffset = CGPoint()
|
||||
guard let touch = touches.first, let view = self.view else {
|
||||
self.state = .failed
|
||||
return
|
||||
}
|
||||
|
||||
let point = touch.location(in: self.view)
|
||||
|
||||
let translation = CGPoint(x: point.x - self.beginPosition.x, y: point.y - self.beginPosition.y)
|
||||
self.currentTranslation = translation
|
||||
|
||||
if self.state == .possible {
|
||||
if abs(translation.x) > 8.0 {
|
||||
self.state = .failed
|
||||
return
|
||||
}
|
||||
var lockDirection: LockDirection?
|
||||
let point = touch.location(in: self.view)
|
||||
if let scrollView = traceScrollView(view: view, point: point).0 {
|
||||
let contentOffset = scrollView.contentOffset
|
||||
let contentInset = scrollView.contentInset
|
||||
if contentOffset.y <= contentInset.top {
|
||||
lockDirection = self.requiredLockDirection
|
||||
}
|
||||
}
|
||||
if let lockDirection = lockDirection {
|
||||
if abs(translation.y) > 2.0 {
|
||||
switch lockDirection {
|
||||
case .up:
|
||||
if translation.y < 0.0 {
|
||||
self.state = .began
|
||||
} else {
|
||||
self.state = .failed
|
||||
}
|
||||
case .down:
|
||||
if translation.y > 0.0 {
|
||||
self.state = .began
|
||||
} else {
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.state = .failed
|
||||
}
|
||||
} else {
|
||||
self.state = .changed
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
self.state = .ended
|
||||
}
|
||||
|
||||
override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
|
||||
self.state = .cancelled
|
||||
}
|
||||
|
||||
func translation() -> CGPoint {
|
||||
return self.currentTranslation
|
||||
}
|
||||
|
||||
func velocity() -> CGPoint {
|
||||
return CGPoint()
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +201,7 @@ public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let delta = -recognizer.translation(in: self.view).y / scrollableDistance
|
||||
let delta = -recognizer.translation().y / scrollableDistance
|
||||
|
||||
self.expansionFraction = max(0.0, min(1.0, self.initialExpansionFraction + delta))
|
||||
self.expansionUpdated?(.immediate)
|
||||
@ -75,7 +210,7 @@ public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let velocity = recognizer.velocity(in: self.view)
|
||||
let velocity = recognizer.velocity()
|
||||
if abs(self.initialExpansionFraction - self.expansionFraction) > 0.25 {
|
||||
if self.initialExpansionFraction < 0.5 {
|
||||
self.expansionFraction = 1.0
|
||||
@ -95,6 +230,11 @@ public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate {
|
||||
self.expansionFraction = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
if let expansionRecognizer = self.expansionRecognizer {
|
||||
expansionRecognizer.requiredLockDirection = self.expansionFraction == 0.0 ? .up : .down
|
||||
}
|
||||
|
||||
self.expansionUpdated?(.animated(duration: 0.4, curve: .spring))
|
||||
default:
|
||||
break
|
||||
@ -107,7 +247,13 @@ public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate {
|
||||
self.scrollableDistance = scrollableDistance
|
||||
}
|
||||
|
||||
public func expand() {
|
||||
self.expansionFraction = 1.0
|
||||
self.expansionRecognizer?.requiredLockDirection = self.expansionFraction == 0.0 ? .up : .down
|
||||
}
|
||||
|
||||
public func collapse() {
|
||||
self.expansionFraction = 0.0
|
||||
self.expansionRecognizer?.requiredLockDirection = self.expansionFraction == 0.0 ? .up : .down
|
||||
}
|
||||
}
|
||||
|
@ -25,14 +25,14 @@ import UndoUI
|
||||
|
||||
private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white)
|
||||
|
||||
private final class PremiumBadgeView: BlurredBackgroundView {
|
||||
private final class PremiumBadgeView: UIView {
|
||||
private let iconLayer: SimpleLayer
|
||||
|
||||
init() {
|
||||
self.iconLayer = SimpleLayer()
|
||||
self.iconLayer.contents = premiumBadgeIcon?.cgImage
|
||||
|
||||
super.init(color: .clear, enableBlur: true)
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.layer.addSublayer(self.iconLayer)
|
||||
}
|
||||
@ -42,11 +42,13 @@ private final class PremiumBadgeView: BlurredBackgroundView {
|
||||
}
|
||||
|
||||
func update(backgroundColor: UIColor, size: CGSize) {
|
||||
self.updateColor(color: backgroundColor, transition: .immediate)
|
||||
//self.updateColor(color: backgroundColor, transition: .immediate)
|
||||
self.backgroundColor = backgroundColor
|
||||
self.layer.cornerRadius = size.width / 2.0
|
||||
|
||||
self.iconLayer.frame = CGRect(origin: CGPoint(), size: size).insetBy(dx: 2.0, dy: 2.0)
|
||||
|
||||
super.update(size: size, cornerRadius: min(size.width / 2.0, size.height / 2.0), transition: .immediate)
|
||||
//super.update(size: size, cornerRadius: min(size.width / 2.0, size.height / 2.0), transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,6 +152,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
case detailed
|
||||
}
|
||||
|
||||
public let id: AnyHashable
|
||||
public let context: AccountContext
|
||||
public let animationCache: AnimationCache
|
||||
public let animationRenderer: MultiAnimationRenderer
|
||||
@ -158,6 +161,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
public let itemLayoutType: ItemLayoutType
|
||||
|
||||
public init(
|
||||
id: AnyHashable,
|
||||
context: AccountContext,
|
||||
animationCache: AnimationCache,
|
||||
animationRenderer: MultiAnimationRenderer,
|
||||
@ -165,6 +169,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemGroups: [ItemGroup],
|
||||
itemLayoutType: ItemLayoutType
|
||||
) {
|
||||
self.id = id
|
||||
self.context = context
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
@ -174,6 +179,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
public static func ==(lhs: EmojiPagerContentComponent, rhs: EmojiPagerContentComponent) -> Bool {
|
||||
if lhs.id != rhs.id {
|
||||
return false
|
||||
}
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
@ -196,14 +204,24 @@ public final class EmojiPagerContentComponent: Component {
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView, UIScrollViewDelegate {
|
||||
public final class Tag {
|
||||
public let id: AnyHashable
|
||||
|
||||
public init(id: AnyHashable) {
|
||||
self.id = id
|
||||
}
|
||||
}
|
||||
|
||||
public final class View: UIView, UIScrollViewDelegate, ComponentTaggedView {
|
||||
private struct ItemGroupDescription: Equatable {
|
||||
let id: AnyHashable
|
||||
let hasTitle: Bool
|
||||
let itemCount: Int
|
||||
}
|
||||
|
||||
private struct ItemGroupLayout: Equatable {
|
||||
let frame: CGRect
|
||||
let id: AnyHashable
|
||||
let itemTopOffset: CGFloat
|
||||
let itemCount: Int
|
||||
}
|
||||
@ -230,9 +248,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.verticalSpacing = 9.0
|
||||
minSpacing = 9.0
|
||||
case .detailed:
|
||||
self.itemSize = 60.0
|
||||
self.verticalSpacing = 9.0
|
||||
minSpacing = 9.0
|
||||
self.itemSize = 76.0
|
||||
self.verticalSpacing = 2.0
|
||||
minSpacing = 2.0
|
||||
}
|
||||
|
||||
self.verticalGroupSpacing = 18.0
|
||||
@ -254,6 +272,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let groupContentSize = CGSize(width: width, height: itemTopOffset + CGFloat(numRowsInGroup) * self.itemSize + CGFloat(max(0, numRowsInGroup - 1)) * self.verticalSpacing)
|
||||
self.itemGroupLayouts.append(ItemGroupLayout(
|
||||
frame: CGRect(origin: CGPoint(x: 0.0, y: verticalGroupOrigin), size: groupContentSize),
|
||||
id: itemGroup.id,
|
||||
itemTopOffset: itemTopOffset,
|
||||
itemCount: itemGroup.itemCount
|
||||
))
|
||||
@ -281,8 +300,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
)
|
||||
}
|
||||
|
||||
func visibleItems(for rect: CGRect) -> [(groupIndex: Int, groupItems: Range<Int>)] {
|
||||
var result: [(groupIndex: Int, groupItems: Range<Int>)] = []
|
||||
func visibleItems(for rect: CGRect) -> [(id: AnyHashable, groupIndex: Int, groupItems: Range<Int>)] {
|
||||
var result: [(id: AnyHashable, groupIndex: Int, groupItems: Range<Int>)] = []
|
||||
|
||||
for groupIndex in 0 ..< self.itemGroupLayouts.count {
|
||||
let group = self.itemGroupLayouts[groupIndex]
|
||||
@ -300,6 +319,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
if maxVisibleIndex >= minVisibleIndex {
|
||||
result.append((
|
||||
id: group.id,
|
||||
groupIndex: groupIndex,
|
||||
groupItems: minVisibleIndex ..< (maxVisibleIndex + 1)
|
||||
))
|
||||
@ -492,15 +512,19 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private let scrollView: UIScrollView
|
||||
private final class ContentScrollView: UIScrollView, PagerExpandableScrollView {
|
||||
}
|
||||
|
||||
private let scrollView: ContentScrollView
|
||||
|
||||
private var visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:]
|
||||
private var visibleGroupHeaders: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||
private var visibleGroupHeaders: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
private var component: EmojiPagerContentComponent?
|
||||
private var pagerEnvironment: PagerComponentChildEnvironment?
|
||||
private var theme: PresentationTheme?
|
||||
private var activeItemUpdated: ActionSlot<(AnyHashable, Transition)>?
|
||||
private var itemLayout: ItemLayout?
|
||||
|
||||
private var currentContextGestureItemKey: ItemLayer.Key?
|
||||
@ -508,7 +532,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
private weak var peekController: PeekController?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.scrollView = UIScrollView()
|
||||
self.scrollView = ContentScrollView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
@ -703,6 +727,31 @@ public final class EmojiPagerContentComponent: Component {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func matches(tag: Any) -> Bool {
|
||||
if let tag = tag as? Tag {
|
||||
if tag.id == self.component?.id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func scrollToItemGroup(groupId: AnyHashable) {
|
||||
guard let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
for group in itemLayout.itemGroupLayouts {
|
||||
if group.id == groupId {
|
||||
let wasIgnoringScrollingEvents = self.ignoreScrolling
|
||||
self.ignoreScrolling = true
|
||||
self.scrollView.setContentOffset(self.scrollView.contentOffset, animated: false)
|
||||
self.ignoreScrolling = wasIgnoringScrollingEvents
|
||||
|
||||
self.scrollView.scrollRectToVisible(CGRect(origin: group.frame.origin.offsetBy(dx: 0.0, dy: floor(-itemLayout.verticalGroupSpacing / 2.0)), size: CGSize(width: 1.0, height: self.scrollView.bounds.height)), animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
if let component = self.component, let (item, itemKey) = self.item(atPoint: recognizer.location(in: self)), let itemLayer = self.visibleItemLayers[itemKey] {
|
||||
@ -723,7 +772,12 @@ public final class EmojiPagerContentComponent: Component {
|
||||
return nil
|
||||
}
|
||||
|
||||
private var previousScrollingOffset: CGFloat?
|
||||
private struct ScrollingOffsetState: Equatable {
|
||||
var value: CGFloat
|
||||
var isDraggingOrDecelerating: Bool
|
||||
}
|
||||
|
||||
private var previousScrollingOffset: ScrollingOffsetState?
|
||||
|
||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
if let presentation = scrollView.layer.presentation() {
|
||||
@ -759,21 +813,22 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
private func updateScrollingOffset(transition: Transition) {
|
||||
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
|
||||
if let previousScrollingOffsetValue = self.previousScrollingOffset {
|
||||
let currentBounds = scrollView.bounds
|
||||
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
|
||||
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY)
|
||||
let offsetToClosestEdge = min(offsetToTopEdge, offsetToBottomEdge)
|
||||
|
||||
let relativeOffset = scrollView.contentOffset.y - previousScrollingOffsetValue
|
||||
let relativeOffset = scrollView.contentOffset.y - previousScrollingOffsetValue.value
|
||||
self.pagerEnvironment?.onChildScrollingUpdate(PagerComponentChildEnvironment.ContentScrollingUpdate(
|
||||
relativeOffset: relativeOffset,
|
||||
absoluteOffsetToClosestEdge: offsetToClosestEdge,
|
||||
absoluteOffsetToTopEdge: offsetToTopEdge,
|
||||
absoluteOffsetToBottomEdge: offsetToBottomEdge,
|
||||
isInteracting: isInteracting,
|
||||
transition: transition
|
||||
))
|
||||
self.previousScrollingOffset = scrollView.contentOffset.y
|
||||
}
|
||||
self.previousScrollingOffset = scrollView.contentOffset.y
|
||||
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: isInteracting)
|
||||
}
|
||||
|
||||
private func snappedContentOffset(proposedOffset: CGFloat) -> CGFloat {
|
||||
@ -808,22 +863,27 @@ public final class EmojiPagerContentComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
var topVisibleGroupId: AnyHashable?
|
||||
|
||||
var validIds = Set<ItemLayer.Key>()
|
||||
var validGroupHeaderIds = Set<AnyHashable>()
|
||||
|
||||
for groupItems in itemLayout.visibleItems(for: self.scrollView.bounds) {
|
||||
if topVisibleGroupId == nil {
|
||||
topVisibleGroupId = groupItems.id
|
||||
}
|
||||
|
||||
let itemGroup = component.itemGroups[groupItems.groupIndex]
|
||||
let itemGroupLayout = itemLayout.itemGroupLayouts[groupItems.groupIndex]
|
||||
|
||||
if let title = itemGroup.title {
|
||||
validGroupHeaderIds.insert(itemGroup.id)
|
||||
let groupHeaderView: ComponentHostView<Empty>
|
||||
let groupHeaderView: ComponentView<Empty>
|
||||
if let current = self.visibleGroupHeaders[itemGroup.id] {
|
||||
groupHeaderView = current
|
||||
} else {
|
||||
groupHeaderView = ComponentHostView<Empty>()
|
||||
groupHeaderView = ComponentView<Empty>()
|
||||
self.visibleGroupHeaders[itemGroup.id] = groupHeaderView
|
||||
self.scrollView.addSubview(groupHeaderView)
|
||||
}
|
||||
let groupHeaderSize = groupHeaderView.update(
|
||||
transition: .immediate,
|
||||
@ -833,7 +893,12 @@ public final class EmojiPagerContentComponent: Component {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: itemLayout.contentSize.width - itemLayout.containerInsets.left - itemLayout.containerInsets.right, height: 100.0)
|
||||
)
|
||||
groupHeaderView.frame = CGRect(origin: CGPoint(x: itemLayout.containerInsets.left, y: itemGroupLayout.frame.minY + 1.0), size: groupHeaderSize)
|
||||
if let view = groupHeaderView.view {
|
||||
if view.superview == nil {
|
||||
self.scrollView.addSubview(view)
|
||||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: itemLayout.containerInsets.left, y: itemGroupLayout.frame.minY + 1.0), size: groupHeaderSize)
|
||||
}
|
||||
}
|
||||
|
||||
for index in groupItems.groupItems.lowerBound ..< groupItems.groupItems.upperBound {
|
||||
@ -848,7 +913,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemLayer = ItemLayer(
|
||||
item: item,
|
||||
context: component.context,
|
||||
groupId: "keyboard",
|
||||
groupId: "keyboard-\(Int(itemLayout.itemSize))",
|
||||
attemptSynchronousLoad: attemptSynchronousLoads,
|
||||
file: item.file,
|
||||
cache: component.animationCache,
|
||||
@ -884,17 +949,22 @@ public final class EmojiPagerContentComponent: Component {
|
||||
for (id, groupHeaderView) in self.visibleGroupHeaders {
|
||||
if !validGroupHeaderIds.contains(id) {
|
||||
removedGroupHeaderIds.append(id)
|
||||
groupHeaderView.removeFromSuperview()
|
||||
groupHeaderView.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
for id in removedGroupHeaderIds {
|
||||
self.visibleGroupHeaders.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
if let topVisibleGroupId = topVisibleGroupId {
|
||||
self.activeItemUpdated?.invoke((topVisibleGroupId, .immediate))
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: EmojiPagerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.theme = environment[EntityKeyboardChildEnvironment.self].value.theme
|
||||
self.activeItemUpdated = environment[EntityKeyboardChildEnvironment.self].value.getContentActiveItemUpdated(component.id)
|
||||
|
||||
let pagerEnvironment = environment[PagerComponentChildEnvironment.self].value
|
||||
self.pagerEnvironment = pagerEnvironment
|
||||
@ -902,6 +972,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var itemGroups: [ItemGroupDescription] = []
|
||||
for itemGroup in component.itemGroups {
|
||||
itemGroups.append(ItemGroupDescription(
|
||||
id: itemGroup.id,
|
||||
hasTitle: itemGroup.title != nil,
|
||||
itemCount: itemGroup.items.count
|
||||
))
|
||||
@ -918,7 +989,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if self.scrollView.scrollIndicatorInsets != pagerEnvironment.containerInsets {
|
||||
self.scrollView.scrollIndicatorInsets = pagerEnvironment.containerInsets
|
||||
}
|
||||
self.previousScrollingOffset = self.scrollView.contentOffset.y
|
||||
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: scrollView.isDragging || scrollView.isDecelerating)
|
||||
self.ignoreScrolling = false
|
||||
|
||||
self.updateVisibleItems(attemptSynchronousLoads: true)
|
||||
|
@ -11,9 +11,14 @@ import BundleIconComponent
|
||||
|
||||
public final class EntityKeyboardChildEnvironment: Equatable {
|
||||
public let theme: PresentationTheme
|
||||
public let getContentActiveItemUpdated: (AnyHashable) -> ActionSlot<(AnyHashable, Transition)>?
|
||||
|
||||
public init(theme: PresentationTheme) {
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
getContentActiveItemUpdated: @escaping (AnyHashable) -> ActionSlot<(AnyHashable, Transition)>?
|
||||
) {
|
||||
self.theme = theme
|
||||
self.getContentActiveItemUpdated = getContentActiveItemUpdated
|
||||
}
|
||||
|
||||
public static func ==(lhs: EntityKeyboardChildEnvironment, rhs: EntityKeyboardChildEnvironment) -> Bool {
|
||||
@ -25,7 +30,17 @@ public final class EntityKeyboardChildEnvironment: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum EntitySearchContentType {
|
||||
case stickers
|
||||
case gifs
|
||||
}
|
||||
|
||||
public final class EntityKeyboardComponent: Component {
|
||||
public final class MarkInputCollapsed {
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
|
||||
public let theme: PresentationTheme
|
||||
public let bottomInset: CGFloat
|
||||
public let emojiContent: EmojiPagerContentComponent
|
||||
@ -34,6 +49,9 @@ public final class EntityKeyboardComponent: Component {
|
||||
public let defaultToEmojiTab: Bool
|
||||
public let externalTopPanelContainer: UIView?
|
||||
public let topPanelExtensionUpdated: (CGFloat, Transition) -> Void
|
||||
public let hideInputUpdated: (Bool, Transition) -> Void
|
||||
public let makeSearchContainerNode: (EntitySearchContentType) -> EntitySearchContainerNode
|
||||
public let deviceMetrics: DeviceMetrics
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
@ -43,7 +61,10 @@ public final class EntityKeyboardComponent: Component {
|
||||
gifContent: GifPagerContentComponent,
|
||||
defaultToEmojiTab: Bool,
|
||||
externalTopPanelContainer: UIView?,
|
||||
topPanelExtensionUpdated: @escaping (CGFloat, Transition) -> Void
|
||||
topPanelExtensionUpdated: @escaping (CGFloat, Transition) -> Void,
|
||||
hideInputUpdated: @escaping (Bool, Transition) -> Void,
|
||||
makeSearchContainerNode: @escaping (EntitySearchContentType) -> EntitySearchContainerNode,
|
||||
deviceMetrics: DeviceMetrics
|
||||
) {
|
||||
self.theme = theme
|
||||
self.bottomInset = bottomInset
|
||||
@ -53,6 +74,9 @@ public final class EntityKeyboardComponent: Component {
|
||||
self.defaultToEmojiTab = defaultToEmojiTab
|
||||
self.externalTopPanelContainer = externalTopPanelContainer
|
||||
self.topPanelExtensionUpdated = topPanelExtensionUpdated
|
||||
self.hideInputUpdated = hideInputUpdated
|
||||
self.makeSearchContainerNode = makeSearchContainerNode
|
||||
self.deviceMetrics = deviceMetrics
|
||||
}
|
||||
|
||||
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
|
||||
@ -77,6 +101,9 @@ public final class EntityKeyboardComponent: Component {
|
||||
if lhs.externalTopPanelContainer != rhs.externalTopPanelContainer {
|
||||
return false
|
||||
}
|
||||
if lhs.deviceMetrics != rhs.deviceMetrics {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -85,6 +112,12 @@ public final class EntityKeyboardComponent: Component {
|
||||
private let pagerView: ComponentHostView<EntityKeyboardChildEnvironment>
|
||||
|
||||
private var component: EntityKeyboardComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private var searchView: ComponentHostView<EntitySearchContentEnvironment>?
|
||||
private var searchComponent: EntitySearchContentComponent?
|
||||
|
||||
private var topPanelExtension: CGFloat?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.pagerView = ComponentHostView<EntityKeyboardChildEnvironment>()
|
||||
@ -92,6 +125,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.disablesInteractiveTransitionGestureRecognizer = true
|
||||
|
||||
self.addSubview(self.pagerView)
|
||||
}
|
||||
@ -101,11 +135,15 @@ public final class EntityKeyboardComponent: Component {
|
||||
}
|
||||
|
||||
func update(component: EntityKeyboardComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.state = state
|
||||
|
||||
var contents: [AnyComponentWithIdentity<(EntityKeyboardChildEnvironment, PagerComponentChildEnvironment)>] = []
|
||||
var contentTopPanels: [AnyComponentWithIdentity<Empty>] = []
|
||||
var contentTopPanels: [AnyComponentWithIdentity<EntityKeyboardTopContainerPanelEnvironment>] = []
|
||||
var contentIcons: [AnyComponentWithIdentity<Empty>] = []
|
||||
var contentAccessoryLeftButtons: [AnyComponentWithIdentity<Empty>] = []
|
||||
var contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>] = []
|
||||
|
||||
let gifsContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
|
||||
contents.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(component.gifContent)))
|
||||
var topGifItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||
topGifItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
@ -126,13 +164,24 @@ public final class EntityKeyboardComponent: Component {
|
||||
))
|
||||
contentTopPanels.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
theme: component.theme,
|
||||
items: topGifItems
|
||||
items: topGifItems,
|
||||
activeContentItemIdUpdated: gifsContentItemIdUpdated
|
||||
))))
|
||||
contentIcons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputGifsIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
))))
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSearchIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
)),
|
||||
action: { [weak self] in
|
||||
self?.openSearch()
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
/*contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSettingsIcon",
|
||||
@ -152,38 +201,59 @@ public final class EntityKeyboardComponent: Component {
|
||||
]
|
||||
if let iconName = iconMapping[id] {
|
||||
topStickerItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: id,
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: iconName,
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: CGSize(width: 30.0, height: 30.0))
|
||||
id: itemGroup.id,
|
||||
content: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: iconName,
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: CGSize(width: 30.0, height: 30.0)
|
||||
)),
|
||||
action: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "stickers", groupId: itemGroup.id)
|
||||
}
|
||||
).minSize(CGSize(width: 30.0, height: 30.0))
|
||||
)
|
||||
))
|
||||
}
|
||||
} else {
|
||||
if !itemGroup.items.isEmpty {
|
||||
topStickerItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: AnyHashable(itemGroup.items[0].file.fileId),
|
||||
id: itemGroup.id,
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: component.stickerContent.context,
|
||||
file: itemGroup.items[0].file,
|
||||
animationCache: component.stickerContent.animationCache,
|
||||
animationRenderer: component.stickerContent.animationRenderer
|
||||
animationRenderer: component.stickerContent.animationRenderer,
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "stickers", groupId: itemGroup.id)
|
||||
}
|
||||
))
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
let stickersContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
|
||||
contents.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(component.stickerContent)))
|
||||
contentTopPanels.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
theme: component.theme,
|
||||
items: topStickerItems
|
||||
items: topStickerItems,
|
||||
activeContentItemIdUpdated: stickersContentItemIdUpdated
|
||||
))))
|
||||
contentIcons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputStickersIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
))))
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSearchIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
)),
|
||||
action: { [weak self] in
|
||||
self?.openSearch()
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSettingsIcon",
|
||||
@ -195,24 +265,29 @@ public final class EntityKeyboardComponent: Component {
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
|
||||
let emojiContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
|
||||
contents.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(component.emojiContent)))
|
||||
var topEmojiItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||
for itemGroup in component.emojiContent.itemGroups {
|
||||
if !itemGroup.items.isEmpty {
|
||||
topEmojiItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: AnyHashable(itemGroup.items[0].file.fileId),
|
||||
id: itemGroup.id,
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: component.emojiContent.context,
|
||||
file: itemGroup.items[0].file,
|
||||
animationCache: component.emojiContent.animationCache,
|
||||
animationRenderer: component.emojiContent.animationRenderer
|
||||
animationRenderer: component.emojiContent.animationRenderer,
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "emoji", groupId: itemGroup.id)
|
||||
}
|
||||
))
|
||||
))
|
||||
}
|
||||
}
|
||||
contentTopPanels.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
theme: component.theme,
|
||||
items: topEmojiItems
|
||||
items: topEmojiItems,
|
||||
activeContentItemIdUpdated: emojiContentItemIdUpdated
|
||||
))))
|
||||
contentIcons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputEmojiIcon",
|
||||
@ -237,6 +312,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
contents: contents,
|
||||
contentTopPanels: contentTopPanels,
|
||||
contentIcons: contentIcons,
|
||||
contentAccessoryLeftButtons: contentAccessoryLeftButtons,
|
||||
contentAccessoryRightButtons: contentAccessoryRightButtons,
|
||||
defaultId: component.defaultToEmojiTab ? "emoji" : "stickers",
|
||||
contentBackground: AnyComponent(BlurredBackgroundComponent(
|
||||
@ -253,21 +329,149 @@ public final class EntityKeyboardComponent: Component {
|
||||
self?.component?.emojiContent.inputInteraction.deleteBackwards()
|
||||
}
|
||||
)),
|
||||
panelStateUpdated: { panelState, transition in
|
||||
component.topPanelExtensionUpdated(panelState.topPanelHeight, transition)
|
||||
}
|
||||
panelStateUpdated: { [weak self] panelState, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.topPanelExtensionUpdated(height: panelState.topPanelHeight, transition: transition)
|
||||
},
|
||||
hidePanels: self.searchComponent != nil
|
||||
)),
|
||||
environment: {
|
||||
EntityKeyboardChildEnvironment(theme: component.theme)
|
||||
EntityKeyboardChildEnvironment(
|
||||
theme: component.theme,
|
||||
getContentActiveItemUpdated: { id in
|
||||
if id == AnyHashable("gifs") {
|
||||
return gifsContentItemIdUpdated
|
||||
} else if id == AnyHashable("stickers") {
|
||||
return stickersContentItemIdUpdated
|
||||
} else if id == AnyHashable("emoji") {
|
||||
return emojiContentItemIdUpdated
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
},
|
||||
containerSize: availableSize
|
||||
)
|
||||
transition.setFrame(view: self.pagerView, frame: CGRect(origin: CGPoint(), size: pagerSize))
|
||||
|
||||
if transition.userData(MarkInputCollapsed.self) != nil {
|
||||
self.searchComponent = nil
|
||||
}
|
||||
|
||||
if let searchComponent = self.searchComponent {
|
||||
var animateIn = false
|
||||
let searchView: ComponentHostView<EntitySearchContentEnvironment>
|
||||
var searchViewTransition = transition
|
||||
if let current = self.searchView {
|
||||
searchView = current
|
||||
} else {
|
||||
searchViewTransition = .immediate
|
||||
searchView = ComponentHostView<EntitySearchContentEnvironment>()
|
||||
self.searchView = searchView
|
||||
self.addSubview(searchView)
|
||||
|
||||
animateIn = true
|
||||
component.topPanelExtensionUpdated(0.0, transition)
|
||||
}
|
||||
|
||||
let _ = searchView.update(
|
||||
transition: searchViewTransition,
|
||||
component: AnyComponent(searchComponent),
|
||||
environment: {
|
||||
EntitySearchContentEnvironment(
|
||||
context: component.stickerContent.context,
|
||||
theme: component.theme,
|
||||
deviceMetrics: component.deviceMetrics
|
||||
)
|
||||
},
|
||||
containerSize: availableSize
|
||||
)
|
||||
searchViewTransition.setFrame(view: searchView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
if animateIn {
|
||||
transition.animateAlpha(view: searchView, from: 0.0, to: 1.0)
|
||||
transition.animatePosition(view: searchView, from: CGPoint(x: 0.0, y: self.topPanelExtension ?? 0.0), to: CGPoint(), additive: true, completion: nil)
|
||||
}
|
||||
} else {
|
||||
if let searchView = self.searchView {
|
||||
self.searchView = nil
|
||||
|
||||
transition.setFrame(view: searchView, frame: CGRect(origin: CGPoint(x: 0.0, y: self.topPanelExtension ?? 0.0), size: availableSize))
|
||||
transition.setAlpha(view: searchView, alpha: 0.0, completion: { [weak searchView] _ in
|
||||
searchView?.removeFromSuperview()
|
||||
})
|
||||
|
||||
if let topPanelExtension = self.topPanelExtension {
|
||||
component.topPanelExtensionUpdated(topPanelExtension, transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
||||
private func topPanelExtensionUpdated(height: CGFloat, transition: Transition) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if self.topPanelExtension != height {
|
||||
self.topPanelExtension = height
|
||||
}
|
||||
if self.searchComponent == nil {
|
||||
component.topPanelExtensionUpdated(height, transition)
|
||||
}
|
||||
}
|
||||
|
||||
private func openSearch() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if self.searchComponent != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if let pagerView = self.pagerView.findTaggedView(tag: PagerComponentViewTag()) as? PagerComponent<EntityKeyboardChildEnvironment, EntityKeyboardTopContainerPanelEnvironment>.View, let centralId = pagerView.centralId {
|
||||
let contentType: EntitySearchContentType
|
||||
if centralId == AnyHashable("gifs") {
|
||||
contentType = .gifs
|
||||
} else {
|
||||
contentType = .stickers
|
||||
}
|
||||
|
||||
self.searchComponent = EntitySearchContentComponent(
|
||||
makeContainerNode: {
|
||||
return component.makeSearchContainerNode(contentType)
|
||||
},
|
||||
dismissSearch: { [weak self] in
|
||||
self?.closeSearch()
|
||||
}
|
||||
)
|
||||
//self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
component.hideInputUpdated(true, Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
}
|
||||
}
|
||||
|
||||
private func closeSearch() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if self.searchComponent == nil {
|
||||
return
|
||||
}
|
||||
self.searchComponent = nil
|
||||
//self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
component.hideInputUpdated(false, Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
}
|
||||
|
||||
private func scrollToItemGroup(contentId: String, groupId: AnyHashable) {
|
||||
if let pagerView = self.pagerView.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: contentId)) as? EmojiPagerContentComponent.View {
|
||||
pagerView.scrollToItemGroup(groupId: groupId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
|
@ -81,7 +81,7 @@ private final class BottomPanelIconComponent: Component {
|
||||
}
|
||||
|
||||
final class EntityKeyboardBottomPanelComponent: Component {
|
||||
typealias EnvironmentType = PagerComponentPanelEnvironment
|
||||
typealias EnvironmentType = PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>
|
||||
|
||||
let theme: PresentationTheme
|
||||
let bottomInset: CGFloat
|
||||
@ -111,16 +111,19 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
||||
final class View: UIView {
|
||||
private final class AccessoryButtonView {
|
||||
let id: AnyHashable
|
||||
var component: AnyComponent<Empty>
|
||||
let view: ComponentHostView<Empty>
|
||||
|
||||
init(id: AnyHashable, view: ComponentHostView<Empty>) {
|
||||
init(id: AnyHashable, component: AnyComponent<Empty>, view: ComponentHostView<Empty>) {
|
||||
self.id = id
|
||||
self.component = component
|
||||
self.view = view
|
||||
}
|
||||
}
|
||||
|
||||
private let backgroundView: BlurredBackgroundView
|
||||
private let separatorView: UIView
|
||||
private var leftAccessoryButton: AccessoryButtonView?
|
||||
private var rightAccessoryButton: AccessoryButtonView?
|
||||
|
||||
private var iconViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||
@ -160,9 +163,61 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
||||
let intrinsicHeight: CGFloat = 38.0
|
||||
let height = intrinsicHeight + component.bottomInset
|
||||
|
||||
let panelEnvironment = environment[PagerComponentPanelEnvironment.self].value
|
||||
let panelEnvironment = environment[PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>.self].value
|
||||
let activeContentId = panelEnvironment.activeContentId
|
||||
|
||||
var leftAccessoryButtonComponent: AnyComponentWithIdentity<Empty>?
|
||||
for contentAccessoryLeftButton in panelEnvironment.contentAccessoryLeftButtons {
|
||||
if contentAccessoryLeftButton.id == activeContentId {
|
||||
leftAccessoryButtonComponent = contentAccessoryLeftButton
|
||||
break
|
||||
}
|
||||
}
|
||||
let previousLeftAccessoryButton = self.leftAccessoryButton
|
||||
|
||||
if let leftAccessoryButtonComponent = leftAccessoryButtonComponent {
|
||||
var leftAccessoryButtonTransition = transition
|
||||
let leftAccessoryButton: AccessoryButtonView
|
||||
if let current = self.leftAccessoryButton, (current.id == leftAccessoryButtonComponent.id || current.component == leftAccessoryButtonComponent.component) {
|
||||
leftAccessoryButton = current
|
||||
leftAccessoryButton.component = leftAccessoryButtonComponent.component
|
||||
} else {
|
||||
leftAccessoryButtonTransition = .immediate
|
||||
leftAccessoryButton = AccessoryButtonView(id: leftAccessoryButtonComponent.id, component: leftAccessoryButtonComponent.component, view: ComponentHostView<Empty>())
|
||||
self.leftAccessoryButton = leftAccessoryButton
|
||||
self.addSubview(leftAccessoryButton.view)
|
||||
}
|
||||
|
||||
let leftAccessoryButtonSize = leftAccessoryButton.view.update(
|
||||
transition: leftAccessoryButtonTransition,
|
||||
component: leftAccessoryButtonComponent.component,
|
||||
environment: {},
|
||||
containerSize: CGSize(width: .greatestFiniteMagnitude, height: intrinsicHeight)
|
||||
)
|
||||
leftAccessoryButtonTransition.setFrame(view: leftAccessoryButton.view, frame: CGRect(origin: CGPoint(x: 2.0, y: 2.0), size: leftAccessoryButtonSize))
|
||||
} else {
|
||||
self.leftAccessoryButton = nil
|
||||
}
|
||||
|
||||
if previousLeftAccessoryButton?.view !== self.leftAccessoryButton?.view {
|
||||
if case .none = transition.animation {
|
||||
previousLeftAccessoryButton?.view.removeFromSuperview()
|
||||
} else {
|
||||
if let previousLeftAccessoryButton = previousLeftAccessoryButton {
|
||||
let previousLeftAccessoryButtonView = previousLeftAccessoryButton.view
|
||||
previousLeftAccessoryButtonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
previousLeftAccessoryButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousLeftAccessoryButtonView] _ in
|
||||
previousLeftAccessoryButtonView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
if let leftAccessoryButtonView = self.leftAccessoryButton?.view {
|
||||
leftAccessoryButtonView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
leftAccessoryButtonView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rightAccessoryButtonComponent: AnyComponentWithIdentity<Empty>?
|
||||
for contentAccessoryRightButton in panelEnvironment.contentAccessoryRightButtons {
|
||||
if contentAccessoryRightButton.id == activeContentId {
|
||||
@ -175,11 +230,12 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
||||
if let rightAccessoryButtonComponent = rightAccessoryButtonComponent {
|
||||
var rightAccessoryButtonTransition = transition
|
||||
let rightAccessoryButton: AccessoryButtonView
|
||||
if let current = self.rightAccessoryButton, current.id == rightAccessoryButtonComponent.id {
|
||||
if let current = self.rightAccessoryButton, (current.id == rightAccessoryButtonComponent.id || current.component == rightAccessoryButtonComponent.component) {
|
||||
rightAccessoryButton = current
|
||||
current.component = rightAccessoryButtonComponent.component
|
||||
} else {
|
||||
rightAccessoryButtonTransition = .immediate
|
||||
rightAccessoryButton = AccessoryButtonView(id: rightAccessoryButtonComponent.id, view: ComponentHostView<Empty>())
|
||||
rightAccessoryButton = AccessoryButtonView(id: rightAccessoryButtonComponent.id, component: rightAccessoryButtonComponent.component, view: ComponentHostView<Empty>())
|
||||
self.rightAccessoryButton = rightAccessoryButton
|
||||
self.addSubview(rightAccessoryButton.view)
|
||||
}
|
||||
@ -195,7 +251,7 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
||||
self.rightAccessoryButton = nil
|
||||
}
|
||||
|
||||
if previousRightAccessoryButton !== self.rightAccessoryButton?.view {
|
||||
if previousRightAccessoryButton?.view !== self.rightAccessoryButton?.view {
|
||||
if case .none = transition.animation {
|
||||
previousRightAccessoryButton?.view.removeFromSuperview()
|
||||
} else {
|
||||
|
@ -7,8 +7,25 @@ import TelegramPresentationData
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
|
||||
final class EntityKeyboardTopContainerPanelEnvironment: Equatable {
|
||||
let visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>
|
||||
|
||||
init(
|
||||
visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>
|
||||
) {
|
||||
self.visibilityFractionUpdated = visibilityFractionUpdated
|
||||
}
|
||||
|
||||
static func ==(lhs: EntityKeyboardTopContainerPanelEnvironment, rhs: EntityKeyboardTopContainerPanelEnvironment) -> Bool {
|
||||
if lhs.visibilityFractionUpdated !== rhs.visibilityFractionUpdated {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
typealias EnvironmentType = PagerComponentPanelEnvironment
|
||||
typealias EnvironmentType = PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>
|
||||
|
||||
let theme: PresentationTheme
|
||||
|
||||
@ -26,13 +43,20 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
return true
|
||||
}
|
||||
|
||||
private final class PanelView {
|
||||
let view = ComponentHostView<EntityKeyboardTopContainerPanelEnvironment>()
|
||||
let visibilityFractionUpdated = ActionSlot<(CGFloat, Transition)>()
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var panelViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||
private var panelViews: [AnyHashable: PanelView] = [:]
|
||||
|
||||
private var component: EntityKeyboardTopContainerPanelComponent?
|
||||
private var panelEnvironment: PagerComponentPanelEnvironment?
|
||||
private var panelEnvironment: PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private var visibilityFraction: CGFloat = 1.0
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
@ -84,26 +108,30 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
validPanelIds.insert(panel.id)
|
||||
|
||||
var panelTransition = transition
|
||||
let panelView: ComponentHostView<Empty>
|
||||
let panelView: PanelView
|
||||
if let current = self.panelViews[panel.id] {
|
||||
panelView = current
|
||||
} else {
|
||||
panelTransition = .immediate
|
||||
panelView = ComponentHostView<Empty>()
|
||||
panelView = PanelView()
|
||||
self.panelViews[panel.id] = panelView
|
||||
self.addSubview(panelView)
|
||||
self.addSubview(panelView.view)
|
||||
}
|
||||
|
||||
let _ = panelView.update(
|
||||
let _ = panelView.view.update(
|
||||
transition: panelTransition,
|
||||
component: panel.component,
|
||||
environment: {},
|
||||
environment: {
|
||||
EntityKeyboardTopContainerPanelEnvironment(
|
||||
visibilityFractionUpdated: panelView.visibilityFractionUpdated
|
||||
)
|
||||
},
|
||||
containerSize: panelFrame.size
|
||||
)
|
||||
if isInBounds {
|
||||
transition.animatePosition(view: panelView, from: CGPoint(x: transitionOffsetFraction * availableSize.width, y: 0.0), to: CGPoint(), additive: true, completion: nil)
|
||||
transition.animatePosition(view: panelView.view, from: CGPoint(x: transitionOffsetFraction * availableSize.width, y: 0.0), to: CGPoint(), additive: true, completion: nil)
|
||||
}
|
||||
panelTransition.setFrame(view: panelView, frame: panelFrame, completion: { [weak self] completed in
|
||||
panelTransition.setFrame(view: panelView.view, frame: panelFrame, completion: { [weak self] completed in
|
||||
if isPartOfTransition && completed {
|
||||
self?.state?.updated(transition: .immediate)
|
||||
}
|
||||
@ -115,15 +143,34 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
for (id, panelView) in self.panelViews {
|
||||
if !validPanelIds.contains(id) {
|
||||
removedPanelIds.append(id)
|
||||
panelView.removeFromSuperview()
|
||||
panelView.view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
for id in removedPanelIds {
|
||||
self.panelViews.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
environment[PagerComponentPanelEnvironment.self].value.visibilityFractionUpdated.connect { [weak self] (fraction, transition) in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateVisibilityFraction(value: fraction, transition: transition)
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: height)
|
||||
}
|
||||
|
||||
private func updateVisibilityFraction(value: CGFloat, transition: Transition) {
|
||||
if self.visibilityFraction == value {
|
||||
return
|
||||
}
|
||||
|
||||
self.visibilityFraction = value
|
||||
for (_, panelView) in self.panelViews {
|
||||
panelView.visibilityFractionUpdated.invoke((value, transition))
|
||||
transition.setSublayerTransform(view: panelView.view, transform: CATransform3DMakeTranslation(0.0, -panelView.view.bounds.height / 2.0 * (1.0 - value), 0.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
|
@ -17,17 +17,20 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
let file: TelegramMediaFile
|
||||
let animationCache: AnimationCache
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
let pressed: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
file: TelegramMediaFile,
|
||||
animationCache: AnimationCache,
|
||||
animationRenderer: MultiAnimationRenderer
|
||||
animationRenderer: MultiAnimationRenderer,
|
||||
pressed: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.file = file
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
self.pressed = pressed
|
||||
}
|
||||
|
||||
static func ==(lhs: EntityKeyboardAnimationTopPanelComponent, rhs: EntityKeyboardAnimationTopPanelComponent) -> Bool {
|
||||
@ -49,16 +52,27 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
|
||||
final class View: UIView {
|
||||
var itemLayer: EmojiPagerContentComponent.View.ItemLayer?
|
||||
var component: EntityKeyboardAnimationTopPanelComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.component?.pressed()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: EntityKeyboardAnimationTopPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
if self.itemLayer == nil {
|
||||
let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
|
||||
item: EmojiPagerContentComponent.Item(
|
||||
@ -97,7 +111,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
}
|
||||
|
||||
final class EntityKeyboardTopPanelComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
typealias EnvironmentType = EntityKeyboardTopContainerPanelEnvironment
|
||||
|
||||
final class Item: Equatable {
|
||||
let id: AnyHashable
|
||||
@ -122,13 +136,16 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
|
||||
let theme: PresentationTheme
|
||||
let items: [Item]
|
||||
let activeContentItemIdUpdated: ActionSlot<(AnyHashable, Transition)>
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
items: [Item]
|
||||
items: [Item],
|
||||
activeContentItemIdUpdated: ActionSlot<(AnyHashable, Transition)>
|
||||
) {
|
||||
self.theme = theme
|
||||
self.items = items
|
||||
self.activeContentItemIdUpdated = activeContentItemIdUpdated
|
||||
}
|
||||
|
||||
static func ==(lhs: EntityKeyboardTopPanelComponent, rhs: EntityKeyboardTopPanelComponent) -> Bool {
|
||||
@ -138,6 +155,9 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
if lhs.activeContentItemIdUpdated !== rhs.activeContentItemIdUpdated {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -188,6 +208,8 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
private var itemLayout: ItemLayout?
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
private var visibilityFraction: CGFloat = 1.0
|
||||
|
||||
private var component: EntityKeyboardTopPanelComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
@ -296,16 +318,63 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
}
|
||||
self.ignoreScrolling = false
|
||||
|
||||
if let _ = component.items.first {
|
||||
self.highlightedIconBackgroundView.isHidden = false
|
||||
let itemFrame = itemLayout.containerFrame(at: 0)
|
||||
transition.setFrame(view: self.highlightedIconBackgroundView, frame: itemFrame)
|
||||
}
|
||||
|
||||
self.updateVisibleItems(attemptSynchronousLoads: true)
|
||||
|
||||
environment[EntityKeyboardTopContainerPanelEnvironment.self].value.visibilityFractionUpdated.connect { [weak self] (fraction, transition) in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.visibilityFractionUpdated(value: fraction, transition: transition)
|
||||
}
|
||||
|
||||
component.activeContentItemIdUpdated.connect { [weak self] (itemId, transition) in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.activeContentItemIdUpdated(itemId: itemId, transition: transition)
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: height)
|
||||
}
|
||||
|
||||
private func visibilityFractionUpdated(value: CGFloat, transition: Transition) {
|
||||
if self.visibilityFraction == value {
|
||||
return
|
||||
}
|
||||
|
||||
self.visibilityFraction = value
|
||||
|
||||
let scale = max(0.01, self.visibilityFraction)
|
||||
|
||||
transition.setScale(view: self.highlightedIconBackgroundView, scale: scale)
|
||||
transition.setAlpha(view: self.highlightedIconBackgroundView, alpha: self.visibilityFraction)
|
||||
|
||||
for (_, itemView) in self.itemViews {
|
||||
transition.setSublayerTransform(view: itemView, transform: CATransform3DMakeScale(scale, scale, 1.0))
|
||||
transition.setAlpha(view: itemView, alpha: self.visibilityFraction)
|
||||
}
|
||||
}
|
||||
|
||||
private func activeContentItemIdUpdated(itemId: AnyHashable, transition: Transition) {
|
||||
guard let component = self.component, let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
var found = false
|
||||
for i in 0 ..< component.items.count {
|
||||
if component.items[i].id == itemId {
|
||||
found = true
|
||||
self.highlightedIconBackgroundView.isHidden = false
|
||||
let itemFrame = itemLayout.containerFrame(at: i)
|
||||
transition.setFrame(view: self.highlightedIconBackgroundView, frame: itemFrame)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
self.highlightedIconBackgroundView.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
|
@ -0,0 +1,118 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import PagerComponent
|
||||
import TelegramPresentationData
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import AccountContext
|
||||
import AsyncDisplayKit
|
||||
import ComponentDisplayAdapters
|
||||
|
||||
public protocol EntitySearchContainerNode: ASDisplayNode {
|
||||
var onCancel: (() -> Void)? { get set }
|
||||
|
||||
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition)
|
||||
}
|
||||
|
||||
final class EntitySearchContentEnvironment: Equatable {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let deviceMetrics: DeviceMetrics
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
deviceMetrics: DeviceMetrics
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.deviceMetrics = deviceMetrics
|
||||
}
|
||||
|
||||
static func ==(lhs: EntitySearchContentEnvironment, rhs: EntitySearchContentEnvironment) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.deviceMetrics != rhs.deviceMetrics {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
final class EntitySearchContentComponent: Component {
|
||||
typealias EnvironmentType = EntitySearchContentEnvironment
|
||||
|
||||
let makeContainerNode: () -> EntitySearchContainerNode
|
||||
let dismissSearch: () -> Void
|
||||
|
||||
init(
|
||||
makeContainerNode: @escaping () -> EntitySearchContainerNode,
|
||||
dismissSearch: @escaping () -> Void
|
||||
) {
|
||||
self.makeContainerNode = makeContainerNode
|
||||
self.dismissSearch = dismissSearch
|
||||
}
|
||||
|
||||
static func ==(lhs: EntitySearchContentComponent, rhs: EntitySearchContentComponent) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var containerNode: EntitySearchContainerNode?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: EntitySearchContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
let containerNode: EntitySearchContainerNode
|
||||
if let current = self.containerNode {
|
||||
containerNode = current
|
||||
} else {
|
||||
containerNode = component.makeContainerNode()
|
||||
self.containerNode = containerNode
|
||||
self.addSubnode(containerNode)
|
||||
}
|
||||
|
||||
let environmentValue = environment[EntitySearchContentEnvironment.self].value
|
||||
|
||||
transition.setFrame(view: containerNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
containerNode.updateLayout(
|
||||
size: availableSize,
|
||||
leftInset: 0.0,
|
||||
rightInset: 0.0,
|
||||
bottomInset: 0.0,
|
||||
inputHeight: 0.0,
|
||||
deviceMetrics: environmentValue.deviceMetrics,
|
||||
transition: transition.containedViewLayoutTransition
|
||||
)
|
||||
|
||||
containerNode.onCancel = {
|
||||
component.dismissSearch()
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -453,16 +453,18 @@ public final class GifPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
private func updateScrollingOffset(transition: Transition) {
|
||||
let isInteracting = scrollView.isDragging || scrollView.isTracking || scrollView.isDecelerating
|
||||
if let previousScrollingOffsetValue = self.previousScrollingOffset {
|
||||
let currentBounds = scrollView.bounds
|
||||
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
|
||||
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY)
|
||||
let offsetToClosestEdge = min(offsetToTopEdge, offsetToBottomEdge)
|
||||
|
||||
let relativeOffset = scrollView.contentOffset.y - previousScrollingOffsetValue
|
||||
self.pagerEnvironment?.onChildScrollingUpdate(PagerComponentChildEnvironment.ContentScrollingUpdate(
|
||||
relativeOffset: relativeOffset,
|
||||
absoluteOffsetToClosestEdge: offsetToClosestEdge,
|
||||
absoluteOffsetToTopEdge: offsetToTopEdge,
|
||||
absoluteOffsetToBottomEdge: offsetToBottomEdge,
|
||||
isInteracting: isInteracting,
|
||||
transition: transition
|
||||
))
|
||||
self.previousScrollingOffset = scrollView.contentOffset.y
|
||||
|
@ -34,18 +34,6 @@ open class MultiAnimationRenderTarget: SimpleLayer {
|
||||
}
|
||||
}
|
||||
|
||||
private func convertFrameToImage(frame: AnimationCacheItemFrame) -> UIImage? {
|
||||
switch frame.format {
|
||||
case let .rgba(width, height, bytesPerRow):
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: false, bytesPerRow: bytesPerRow)
|
||||
let range = frame.range
|
||||
frame.data.withUnsafeBytes { bytes -> Void in
|
||||
memcpy(context.bytes, bytes.baseAddress!.advanced(by: range.lowerBound), min(context.length, range.upperBound - range.lowerBound))
|
||||
}
|
||||
return context.generateImage()
|
||||
}
|
||||
}
|
||||
|
||||
private final class FrameGroup {
|
||||
let image: UIImage
|
||||
let size: CGSize
|
||||
|
@ -1523,7 +1523,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.inputPanelContainerNode.collapse()
|
||||
strongSelf.chatDisplayNode.collapseInput()
|
||||
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in
|
||||
var current = current
|
||||
current = current.updatedInterfaceState { interfaceState in
|
||||
@ -1614,6 +1615,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.collapseInput()
|
||||
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }.updatedInputMode { current in
|
||||
if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
|
||||
@ -9931,7 +9934,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case .media:
|
||||
break
|
||||
default:
|
||||
self.chatDisplayNode.inputPanelContainerNode.collapse()
|
||||
self.chatDisplayNode.collapseInput()
|
||||
}
|
||||
|
||||
if self.isNodeLoaded {
|
||||
|
@ -108,6 +108,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private let inputPanelBackgroundNode: NavigationBackgroundNode
|
||||
private var intrinsicInputPanelBackgroundNodeSize: CGSize?
|
||||
private let inputPanelBackgroundSeparatorNode: ASDisplayNode
|
||||
private var inputPanelBottomBackgroundSeparatorBaseOffset: CGFloat = 0.0
|
||||
private let inputPanelBottomBackgroundSeparatorNode: ASDisplayNode
|
||||
private var plainInputSeparatorAlpha: CGFloat?
|
||||
private var usePlainInputSeparator: Bool
|
||||
@ -539,7 +540,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.inputPanelContainerNode.addSubnode(self.inputPanelClippingNode)
|
||||
self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundNode)
|
||||
self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundSeparatorNode)
|
||||
self.inputPanelClippingNode.addSubnode(self.inputPanelBottomBackgroundSeparatorNode)
|
||||
self.inputPanelBackgroundNode.addSubnode(self.inputPanelBottomBackgroundSeparatorNode)
|
||||
|
||||
self.contentContainerNode.addSubnode(self.inputContextPanelContainer)
|
||||
|
||||
@ -773,8 +774,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.insertSubnode(navigationModalFrame, aboveSubnode: self.contentContainerNode)
|
||||
}
|
||||
if transition.isAnimated, let animateFromFraction = animateFromFraction, animateFromFraction != 1.0 - self.inputPanelContainerNode.expansionFraction {
|
||||
navigationModalFrame.updateDismissal(transition: transition, progress: animateFromFraction, additionalProgress: 0.0, completion: {})
|
||||
navigationModalFrame.update(layout: layout, transition: .immediate)
|
||||
navigationModalFrame.updateDismissal(transition: .immediate, progress: animateFromFraction, additionalProgress: 0.0, completion: {})
|
||||
}
|
||||
navigationModalFrame.update(layout: layout, transition: transition)
|
||||
navigationModalFrame.updateDismissal(transition: transition, progress: 1.0 - self.inputPanelContainerNode.expansionFraction, additionalProgress: 0.0, completion: {})
|
||||
|
||||
self.inputPanelClippingNode.clipsToBounds = true
|
||||
@ -786,11 +789,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
navigationModalFrame?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
self.inputPanelClippingNode.clipsToBounds = true
|
||||
transition.updateCornerRadius(node: self.inputPanelClippingNode, cornerRadius: 0.0, completion: { [weak self] completed in
|
||||
guard let strongSelf = self, completed else {
|
||||
return
|
||||
}
|
||||
strongSelf.inputPanelClippingNode.clipsToBounds = false
|
||||
//strongSelf.inputPanelClippingNode.clipsToBounds = false
|
||||
let _ = strongSelf
|
||||
let _ = completed
|
||||
})
|
||||
}
|
||||
|
||||
@ -1014,9 +1020,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
})
|
||||
|
||||
var insets: UIEdgeInsets
|
||||
var inputPanelBottomInsetTerm: CGFloat = 0.0
|
||||
if inputNodeForState != nil {
|
||||
insets = layout.insets(options: [])
|
||||
insets.bottom = max(insets.bottom, layout.standardInputHeight)
|
||||
inputPanelBottomInsetTerm = max(insets.bottom, layout.standardInputHeight)
|
||||
} else {
|
||||
insets = layout.insets(options: [.input])
|
||||
}
|
||||
@ -1041,13 +1048,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
let inputPanelNodes = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputPanelNode, currentSecondaryPanel: self.secondaryInputPanelNode, textInputPanelNode: self.textInputPanelNode, interfaceInteraction: self.interfaceInteraction)
|
||||
|
||||
let inputPanelBottomInset = max(insets.bottom, inputPanelBottomInsetTerm)
|
||||
|
||||
if let inputPanelNode = inputPanelNodes.primary, !previewing {
|
||||
if inputPanelNode !== self.inputPanelNode {
|
||||
if let inputTextPanelNode = self.inputPanelNode as? ChatTextInputPanelNode {
|
||||
if inputTextPanelNode.isFocused {
|
||||
self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
|
||||
let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
|
||||
}
|
||||
if let prevInputPanelNode = self.inputPanelNode, inputPanelNode.canHandleTransition(from: prevInputPanelNode) {
|
||||
inputPanelNodeHandlesTransition = true
|
||||
@ -1057,7 +1066,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
} else {
|
||||
dismissedInputPanelNode = self.inputPanelNode
|
||||
}
|
||||
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: false, transition: inputPanelNode.supernode !== self ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
|
||||
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: inputPanelNode.supernode !== self ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
|
||||
inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
|
||||
self.inputPanelNode = inputPanelNode
|
||||
if inputPanelNode.supernode !== self {
|
||||
@ -1065,7 +1074,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.inputPanelClippingNode.insertSubnode(inputPanelNode, aboveSubnode: self.inputPanelBackgroundNode)
|
||||
}
|
||||
} else {
|
||||
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - insets.bottom - 120.0, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
|
||||
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset - 120.0, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
|
||||
inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
|
||||
}
|
||||
} else {
|
||||
@ -1076,7 +1085,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if let secondaryInputPanelNode = inputPanelNodes.secondary, !previewing {
|
||||
if secondaryInputPanelNode !== self.secondaryInputPanelNode {
|
||||
dismissedSecondaryInputPanelNode = self.secondaryInputPanelNode
|
||||
let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: true, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
|
||||
let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
|
||||
secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
|
||||
self.secondaryInputPanelNode = secondaryInputPanelNode
|
||||
if secondaryInputPanelNode.supernode == nil {
|
||||
@ -1084,7 +1093,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.inputPanelClippingNode.insertSubnode(secondaryInputPanelNode, aboveSubnode: self.inputPanelBackgroundNode)
|
||||
}
|
||||
} else {
|
||||
let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
|
||||
let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
|
||||
secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
|
||||
}
|
||||
} else {
|
||||
@ -1165,6 +1174,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
inputNode.topBackgroundExtensionUpdated = { [weak self] transition in
|
||||
self?.updateInputPanelBackgroundExtension(transition: transition)
|
||||
}
|
||||
inputNode.hideInputUpdated = { [weak self] transition in
|
||||
self?.updateInputPanelBackgroundExpansion(transition: transition)
|
||||
}
|
||||
inputNode.expansionFractionUpdated = { [weak self] transition in
|
||||
self?.updateInputPanelBackgroundExpansion(transition: transition)
|
||||
}
|
||||
@ -1198,6 +1210,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if inputNode.hideInput, let inputPanelSize = inputPanelSize {
|
||||
maximumInputNodeHeight += inputPanelSize.height
|
||||
}
|
||||
|
||||
let inputHeight = layout.standardInputHeight + self.inputPanelContainerNode.expansionFraction * (maximumInputNodeHeight - layout.standardInputHeight)
|
||||
|
||||
let heightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: inputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: self.isInFocus)
|
||||
@ -1334,6 +1350,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
if self.inputPanelNode != nil {
|
||||
inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight - inputPanelSize!.height), size: CGSize(width: layout.size.width, height: inputPanelSize!.height))
|
||||
if let inputNode = self.inputNode {
|
||||
if inputNode.hideInput {
|
||||
inputPanelFrame = inputPanelFrame!.offsetBy(dx: 0.0, dy: -inputPanelFrame!.height)
|
||||
}
|
||||
}
|
||||
if self.dismissedAsOverlay {
|
||||
inputPanelFrame!.origin.y = layout.size.height
|
||||
}
|
||||
@ -1362,6 +1383,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
inputPanelsHeight = 0.0
|
||||
}
|
||||
|
||||
if let inputNode = self.inputNode {
|
||||
if inputNode.hideInput {
|
||||
inputPanelsHeight = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
let inputBackgroundInset: CGFloat
|
||||
if cleanInsets.bottom < insets.bottom {
|
||||
inputBackgroundInset = 0.0
|
||||
@ -1557,8 +1584,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if immediatelyLayoutInputNodeAndAnimateAppearance {
|
||||
inputPanelUpdateTransition = .immediate
|
||||
}
|
||||
|
||||
self.inputPanelBackgroundNode.update(size: CGSize(width: intrinsicInputPanelBackgroundNodeSize.width, height: intrinsicInputPanelBackgroundNodeSize.height + inputPanelBackgroundExtension), transition: inputPanelUpdateTransition)
|
||||
transition.updateFrame(node: self.inputPanelBottomBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.inputPanelBackgroundNode.frame.maxY + inputPanelBackgroundExtension), size: CGSize(width: self.inputPanelBackgroundNode.bounds.width, height: UIScreenPixel)))
|
||||
self.inputPanelBottomBackgroundSeparatorBaseOffset = intrinsicInputPanelBackgroundNodeSize.height
|
||||
inputPanelUpdateTransition.updateFrame(node: self.inputPanelBottomBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: intrinsicInputPanelBackgroundNodeSize.height + inputPanelBackgroundExtension), size: CGSize(width: intrinsicInputPanelBackgroundNodeSize.width, height: UIScreenPixel)), beginWithCurrentState: true)
|
||||
|
||||
transition.updateFrame(node: self.inputPanelBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y), size: CGSize(width: apparentInputBackgroundFrame.size.width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: self.navigateButtons, frame: apparentNavigateButtonsFrame)
|
||||
@ -1670,9 +1699,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
if let inputPanelNode = self.inputPanelNode,
|
||||
let apparentInputPanelFrame = apparentInputPanelFrame,
|
||||
!inputPanelNode.frame.equalTo(apparentInputPanelFrame) {
|
||||
if let inputPanelNode = self.inputPanelNode, let apparentInputPanelFrame = apparentInputPanelFrame, !inputPanelNode.frame.equalTo(apparentInputPanelFrame) {
|
||||
if immediatelyLayoutInputPanelAndAnimateAppearance {
|
||||
inputPanelNode.frame = apparentInputPanelFrame.offsetBy(dx: 0.0, dy: apparentInputPanelFrame.height + previousInputPanelBackgroundFrame.maxY - apparentInputBackgroundFrame.maxY)
|
||||
inputPanelNode.alpha = 0.0
|
||||
@ -1929,10 +1956,26 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
self.inputPanelBackgroundNode.update(size: CGSize(width: intrinsicInputPanelBackgroundNodeSize.width, height: intrinsicInputPanelBackgroundNodeSize.height + extensionValue), transition: transition)
|
||||
transition.updateFrame(node: self.inputPanelBottomBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.inputPanelBackgroundNode.frame.maxY + extensionValue), size: CGSize(width: self.inputPanelBackgroundNode.bounds.width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: self.inputPanelBottomBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.inputPanelBottomBackgroundSeparatorBaseOffset + extensionValue), size: CGSize(width: self.inputPanelBottomBackgroundSeparatorNode.bounds.width, height: UIScreenPixel)), beginWithCurrentState: true)
|
||||
}
|
||||
|
||||
private var storedHideInputExpanded: Bool?
|
||||
|
||||
private func updateInputPanelBackgroundExpansion(transition: ContainedViewLayoutTransition) {
|
||||
if let inputNode = self.inputNode {
|
||||
if inputNode.hideInput {
|
||||
self.storedHideInputExpanded = self.inputPanelContainerNode.expansionFraction == 1.0
|
||||
self.inputPanelContainerNode.expand()
|
||||
} else {
|
||||
if let storedHideInputExpanded = self.storedHideInputExpanded {
|
||||
self.storedHideInputExpanded = nil
|
||||
if !storedHideInputExpanded {
|
||||
self.inputPanelContainerNode.collapse()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.requestLayout(transition)
|
||||
}
|
||||
|
||||
@ -2222,6 +2265,18 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.view.window?.endEditing(true)
|
||||
}
|
||||
|
||||
func collapseInput() {
|
||||
if self.inputPanelContainerNode.expansionFraction != 0.0 {
|
||||
self.inputPanelContainerNode.collapse()
|
||||
if let inputNode = self.inputNode {
|
||||
inputNode.hideInput = false
|
||||
if let inputNode = inputNode as? ChatEntityKeyboardInputNode {
|
||||
inputNode.markInputCollapsed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) {
|
||||
let requestId = self.scheduledLayoutTransitionRequestId
|
||||
self.scheduledLayoutTransitionRequestId += 1
|
||||
@ -2248,7 +2303,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
let _ = peerId
|
||||
|
||||
let inputNode = ChatEntityKeyboardInputNode(context: self.context, currentInputData: inputMediaNodeData, updatedInputData: self.inputMediaNodeDataPromise.get(), defaultToEmojiTab: !self.chatPresentationInterfaceState.interfaceState.effectiveInputState.inputText.string.isEmpty)
|
||||
let inputNode = ChatEntityKeyboardInputNode(
|
||||
context: self.context,
|
||||
currentInputData: inputMediaNodeData,
|
||||
updatedInputData: self.inputMediaNodeDataPromise.get(),
|
||||
defaultToEmojiTab: !self.chatPresentationInterfaceState.interfaceState.effectiveInputState.inputText.string.isEmpty,
|
||||
controllerInteraction: self.controllerInteraction
|
||||
)
|
||||
|
||||
return inputNode
|
||||
}
|
||||
|
@ -206,6 +206,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
return EmojiPagerContentComponent(
|
||||
id: "emoji",
|
||||
context: context,
|
||||
animationCache: animationCache,
|
||||
animationRenderer: animationRenderer,
|
||||
@ -386,6 +387,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
return EmojiPagerContentComponent(
|
||||
id: "stickers",
|
||||
context: context,
|
||||
animationCache: animationCache,
|
||||
animationRenderer: animationRenderer,
|
||||
@ -393,7 +395,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
|
||||
var title: String?
|
||||
if group.id == AnyHashable("saved") {
|
||||
title = nil
|
||||
//TODO:localize
|
||||
title = "Saved".uppercased()
|
||||
} else if group.id == AnyHashable("recent") {
|
||||
//TODO:localize
|
||||
title = "Recently Used".uppercased()
|
||||
@ -444,18 +447,30 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let entityKeyboardView: ComponentHostView<Empty>
|
||||
|
||||
private let defaultToEmojiTab: Bool
|
||||
private var currentInputData: InputData
|
||||
private var inputDataDisposable: Disposable?
|
||||
|
||||
private let controllerInteraction: ChatControllerInteraction
|
||||
|
||||
private var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||
|
||||
private let trendingGifsPromise = Promise<ChatMediaInputGifPaneTrendingState?>(nil)
|
||||
|
||||
private var isMarkInputCollapsed: Bool = false
|
||||
|
||||
private var currentState: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool)?
|
||||
|
||||
init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal<InputData, NoError>, defaultToEmojiTab: Bool) {
|
||||
init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal<InputData, NoError>, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction) {
|
||||
self.context = context
|
||||
self.currentInputData = currentInputData
|
||||
self.defaultToEmojiTab = defaultToEmojiTab
|
||||
|
||||
self.controllerInteraction = controllerInteraction
|
||||
|
||||
self.entityKeyboardView = ComponentHostView<Empty>()
|
||||
|
||||
super.init()
|
||||
@ -472,12 +487,48 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
strongSelf.currentInputData = inputData
|
||||
strongSelf.performLayout()
|
||||
})
|
||||
|
||||
self.inputNodeInteraction = ChatMediaInputNodeInteraction(
|
||||
navigateToCollectionId: { _ in
|
||||
},
|
||||
navigateBackToStickers: {
|
||||
},
|
||||
setGifMode: { _ in
|
||||
},
|
||||
openSettings: {
|
||||
},
|
||||
openTrending: { _ in
|
||||
},
|
||||
dismissTrendingPacks: { _ in
|
||||
},
|
||||
toggleSearch: { _, _, _ in
|
||||
},
|
||||
openPeerSpecificSettings: {
|
||||
},
|
||||
dismissPeerSpecificSettings: {
|
||||
},
|
||||
clearRecentlyUsedStickers: {
|
||||
}
|
||||
)
|
||||
|
||||
self.trendingGifsPromise.set(paneGifSearchForQuery(context: context, query: "", offset: nil, incompleteResults: true, delayRequest: false, updateActivity: nil)
|
||||
|> map { items -> ChatMediaInputGifPaneTrendingState? in
|
||||
if let items = items {
|
||||
return ChatMediaInputGifPaneTrendingState(files: items.files, nextOffset: items.nextOffset)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.inputDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
func markInputCollapsed() {
|
||||
self.isMarkInputCollapsed = true
|
||||
}
|
||||
|
||||
private func performLayout() {
|
||||
guard let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.currentState else {
|
||||
return
|
||||
@ -488,10 +539,24 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) {
|
||||
self.currentState = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible)
|
||||
|
||||
let wasMarkedInputCollapsed = self.isMarkInputCollapsed
|
||||
self.isMarkInputCollapsed = false
|
||||
|
||||
let expandedHeight = standardInputHeight + self.expansionFraction * (maximumHeight - standardInputHeight)
|
||||
|
||||
let context = self.context
|
||||
let controllerInteraction = self.controllerInteraction
|
||||
let inputNodeInteraction = self.inputNodeInteraction!
|
||||
let trendingGifsPromise = self.trendingGifsPromise
|
||||
|
||||
var mappedTransition = Transition(transition)
|
||||
|
||||
if wasMarkedInputCollapsed {
|
||||
mappedTransition = mappedTransition.withUserData(EntityKeyboardComponent.MarkInputCollapsed())
|
||||
}
|
||||
|
||||
let entityKeyboardSize = self.entityKeyboardView.update(
|
||||
transition: Transition(transition),
|
||||
transition: mappedTransition,
|
||||
component: AnyComponent(EntityKeyboardComponent(
|
||||
theme: interfaceState.theme,
|
||||
bottomInset: bottomInset,
|
||||
@ -508,7 +573,39 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
strongSelf.topBackgroundExtension = topPanelExtension
|
||||
strongSelf.topBackgroundExtensionUpdated?(transition.containedViewLayoutTransition)
|
||||
}
|
||||
}
|
||||
},
|
||||
hideInputUpdated: { [weak self] hideInput, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.hideInput != hideInput {
|
||||
strongSelf.hideInput = hideInput
|
||||
strongSelf.hideInputUpdated?(transition.containedViewLayoutTransition)
|
||||
}
|
||||
},
|
||||
makeSearchContainerNode: { content in
|
||||
let mappedMode: ChatMediaInputSearchMode
|
||||
switch content {
|
||||
case .stickers:
|
||||
mappedMode = .sticker
|
||||
case .gifs:
|
||||
mappedMode = .gif
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
return PaneSearchContainerNode(
|
||||
context: context,
|
||||
theme: presentationData.theme,
|
||||
strings: presentationData.strings,
|
||||
controllerInteraction: controllerInteraction,
|
||||
inputNodeInteraction: inputNodeInteraction,
|
||||
mode: mappedMode,
|
||||
trendingGifsPromise: trendingGifsPromise,
|
||||
cancel: {
|
||||
}
|
||||
)
|
||||
},
|
||||
deviceMetrics: deviceMetrics
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: width, height: expandedHeight)
|
||||
|
@ -16,6 +16,9 @@ class ChatInputNode: ASDisplayNode {
|
||||
var topBackgroundExtension: CGFloat = 41.0
|
||||
var topBackgroundExtensionUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
var hideInput: Bool = false
|
||||
var hideInputUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
var expansionFraction: CGFloat = 0.0
|
||||
var expansionFractionUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
|
@ -2454,10 +2454,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
} else {
|
||||
switch presentationInterfaceState.inputMode {
|
||||
case .text, .media:
|
||||
case .text:
|
||||
self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId { _ in
|
||||
return (.none, nil)
|
||||
}
|
||||
case .media:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import ChatPresentationInterfaceState
|
||||
import EntityKeyboard
|
||||
|
||||
private let searchBarHeight: CGFloat = 52.0
|
||||
|
||||
@ -27,7 +28,7 @@ protocol PaneSearchContentNode {
|
||||
func itemAt(point: CGPoint) -> (ASDisplayNode, Any)?
|
||||
}
|
||||
|
||||
final class PaneSearchContainerNode: ASDisplayNode {
|
||||
final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainerNode {
|
||||
private let context: AccountContext
|
||||
private let mode: ChatMediaInputSearchMode
|
||||
public private(set) var contentNode: PaneSearchContentNode & ASDisplayNode
|
||||
@ -39,6 +40,8 @@ final class PaneSearchContainerNode: ASDisplayNode {
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
var onCancel: (() -> Void)?
|
||||
|
||||
var openGifContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)?
|
||||
|
||||
var ready: Signal<Void, NoError> {
|
||||
@ -75,8 +78,11 @@ final class PaneSearchContainerNode: ASDisplayNode {
|
||||
self?.searchBar.activity = active
|
||||
}
|
||||
|
||||
self.searchBar.cancel = {
|
||||
self.searchBar.cancel = { [weak self] in
|
||||
cancel()
|
||||
|
||||
self?.searchBar.view.endEditing(true)
|
||||
self?.onCancel?()
|
||||
}
|
||||
self.searchBar.activate()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user