Sticker input rewrite continued

This commit is contained in:
Ali 2022-07-02 00:04:43 +02:00
parent baad43fb1f
commit 087bf3352e
23 changed files with 1299 additions and 187 deletions

View File

@ -192,14 +192,17 @@ public struct Transition {
} }
switch self.animation { switch self.animation {
case .none: 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: "position")
view.layer.removeAnimation(forKey: "bounds") view.layer.removeAnimation(forKey: "bounds")
completion?(true) completion?(true)
case .curve: case .curve:
let previousPosition = view.layer.presentation()?.position ?? view.center let previousPosition = view.layer.presentation()?.position ?? view.center
let previousBounds = view.layer.presentation()?.bounds ?? view.bounds 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.animatePosition(view: view, from: previousPosition, to: view.center, completion: completion)
self.animateBounds(view: view, from: previousBounds, to: view.bounds) self.animateBounds(view: view, from: previousBounds, to: view.bounds)
@ -293,12 +296,17 @@ public struct Transition {
view.layer.sublayerTransform = transform view.layer.sublayerTransform = transform
completion?(true) completion?(true)
case let .curve(duration, curve): 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.sublayerTransform = transform
view.layer.animate( view.layer.animate(
from: NSValue(caTransform3D: previousValue), from: NSValue(caTransform3D: previousValue),
to: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: transform),
keyPath: "transform", keyPath: "sublayerTransform",
duration: duration, duration: duration,
delay: 0.0, delay: 0.0,
curve: curve, curve: curve,

View File

@ -118,3 +118,93 @@ public final class ComponentHostView<EnvironmentType>: UIView {
return findTaggedViewImpl(view: componentView, tag: tag) 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)
}
}

View File

@ -15,7 +15,7 @@ public final class Action<Arguments> {
public final class ActionSlot<Arguments>: Equatable { public final class ActionSlot<Arguments>: Equatable {
private var target: ((Arguments) -> Void)? private var target: ((Arguments) -> Void)?
init() { public init() {
} }
public static func ==(lhs: ActionSlot<Arguments>, rhs: ActionSlot<Arguments>) -> Bool { public static func ==(lhs: ActionSlot<Arguments>, rhs: ActionSlot<Arguments>) -> Bool {

View File

@ -3,19 +3,31 @@ import UIKit
import Display import Display
import ComponentFlow import ComponentFlow
public protocol PagerExpandableScrollView: UIScrollView {
}
public protocol PagerPanGestureRecognizer: UIGestureRecognizer {
}
public final class PagerComponentChildEnvironment: Equatable { public final class PagerComponentChildEnvironment: Equatable {
public struct ContentScrollingUpdate { public struct ContentScrollingUpdate {
public var relativeOffset: CGFloat 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 var transition: Transition
public init( public init(
relativeOffset: CGFloat, relativeOffset: CGFloat,
absoluteOffsetToClosestEdge: CGFloat?, absoluteOffsetToTopEdge: CGFloat?,
absoluteOffsetToBottomEdge: CGFloat?,
isInteracting: Bool,
transition: Transition transition: Transition
) { ) {
self.relativeOffset = relativeOffset self.relativeOffset = relativeOffset
self.absoluteOffsetToClosestEdge = absoluteOffsetToClosestEdge self.absoluteOffsetToTopEdge = absoluteOffsetToTopEdge
self.absoluteOffsetToBottomEdge = absoluteOffsetToBottomEdge
self.isInteracting = isInteracting
self.transition = transition 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 contentOffset: CGFloat
public let contentTopPanels: [AnyComponentWithIdentity<Empty>] public let contentTopPanels: [AnyComponentWithIdentity<TopPanelEnvironment>]
public let contentIcons: [AnyComponentWithIdentity<Empty>] public let contentIcons: [AnyComponentWithIdentity<Empty>]
public let contentAccessoryLeftButtons: [AnyComponentWithIdentity<Empty>]
public let contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>] public let contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>]
public let activeContentId: AnyHashable? public let activeContentId: AnyHashable?
public let navigateToContentId: (AnyHashable) -> Void public let navigateToContentId: (AnyHashable) -> Void
public let visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>
init( init(
contentOffset: CGFloat, contentOffset: CGFloat,
contentTopPanels: [AnyComponentWithIdentity<Empty>], contentTopPanels: [AnyComponentWithIdentity<TopPanelEnvironment>],
contentIcons: [AnyComponentWithIdentity<Empty>], contentIcons: [AnyComponentWithIdentity<Empty>],
contentAccessoryLeftButtons: [AnyComponentWithIdentity<Empty>],
contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>], contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>],
activeContentId: AnyHashable?, activeContentId: AnyHashable?,
navigateToContentId: @escaping (AnyHashable) -> Void navigateToContentId: @escaping (AnyHashable) -> Void,
visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>
) { ) {
self.contentOffset = contentOffset self.contentOffset = contentOffset
self.contentTopPanels = contentTopPanels self.contentTopPanels = contentTopPanels
self.contentIcons = contentIcons self.contentIcons = contentIcons
self.contentAccessoryLeftButtons = contentAccessoryLeftButtons
self.contentAccessoryRightButtons = contentAccessoryRightButtons self.contentAccessoryRightButtons = contentAccessoryRightButtons
self.activeContentId = activeContentId self.activeContentId = activeContentId
self.navigateToContentId = navigateToContentId self.navigateToContentId = navigateToContentId
self.visibilityFractionUpdated = visibilityFractionUpdated
} }
public static func ==(lhs: PagerComponentPanelEnvironment, rhs: PagerComponentPanelEnvironment) -> Bool { public static func ==(lhs: PagerComponentPanelEnvironment, rhs: PagerComponentPanelEnvironment) -> Bool {
@ -74,12 +92,18 @@ public final class PagerComponentPanelEnvironment: Equatable {
if lhs.contentIcons != rhs.contentIcons { if lhs.contentIcons != rhs.contentIcons {
return false return false
} }
if lhs.contentAccessoryLeftButtons != rhs.contentAccessoryLeftButtons {
return false
}
if lhs.contentAccessoryRightButtons != rhs.contentAccessoryRightButtons { if lhs.contentAccessoryRightButtons != rhs.contentAccessoryRightButtons {
return false return false
} }
if lhs.activeContentId != rhs.activeContentId { if lhs.activeContentId != rhs.activeContentId {
return false return false
} }
if lhs.visibilityFractionUpdated !== rhs.visibilityFractionUpdated {
return false
}
return true 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 typealias EnvironmentType = ChildEnvironmentType
public let contentInsets: UIEdgeInsets public let contentInsets: UIEdgeInsets
public let contents: [AnyComponentWithIdentity<(ChildEnvironmentType, PagerComponentChildEnvironment)>] public let contents: [AnyComponentWithIdentity<(ChildEnvironmentType, PagerComponentChildEnvironment)>]
public let contentTopPanels: [AnyComponentWithIdentity<Empty>] public let contentTopPanels: [AnyComponentWithIdentity<TopPanelEnvironment>]
public let contentIcons: [AnyComponentWithIdentity<Empty>] public let contentIcons: [AnyComponentWithIdentity<Empty>]
public let contentAccessoryLeftButtons:[AnyComponentWithIdentity<Empty>]
public let contentAccessoryRightButtons:[AnyComponentWithIdentity<Empty>] public let contentAccessoryRightButtons:[AnyComponentWithIdentity<Empty>]
public let defaultId: AnyHashable? public let defaultId: AnyHashable?
public let contentBackground: AnyComponent<Empty>? public let contentBackground: AnyComponent<Empty>?
public let topPanel: AnyComponent<PagerComponentPanelEnvironment>? public let topPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?
public let externalTopPanelContainer: UIView? public let externalTopPanelContainer: UIView?
public let bottomPanel: AnyComponent<PagerComponentPanelEnvironment>? public let bottomPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?
public let panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)? public let panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?
public let hidePanels: Bool
public init( public init(
contentInsets: UIEdgeInsets, contentInsets: UIEdgeInsets,
contents: [AnyComponentWithIdentity<(ChildEnvironmentType, PagerComponentChildEnvironment)>], contents: [AnyComponentWithIdentity<(ChildEnvironmentType, PagerComponentChildEnvironment)>],
contentTopPanels: [AnyComponentWithIdentity<Empty>], contentTopPanels: [AnyComponentWithIdentity<TopPanelEnvironment>],
contentIcons: [AnyComponentWithIdentity<Empty>], contentIcons: [AnyComponentWithIdentity<Empty>],
contentAccessoryRightButtons:[AnyComponentWithIdentity<Empty>], contentAccessoryLeftButtons: [AnyComponentWithIdentity<Empty>],
contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>],
defaultId: AnyHashable?, defaultId: AnyHashable?,
contentBackground: AnyComponent<Empty>?, contentBackground: AnyComponent<Empty>?,
topPanel: AnyComponent<PagerComponentPanelEnvironment>?, topPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?,
externalTopPanelContainer: UIView?, externalTopPanelContainer: UIView?,
bottomPanel: AnyComponent<PagerComponentPanelEnvironment>?, bottomPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?,
panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)? panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?,
hidePanels: Bool
) { ) {
self.contentInsets = contentInsets self.contentInsets = contentInsets
self.contents = contents self.contents = contents
self.contentTopPanels = contentTopPanels self.contentTopPanels = contentTopPanels
self.contentIcons = contentIcons self.contentIcons = contentIcons
self.contentAccessoryLeftButtons = contentAccessoryLeftButtons
self.contentAccessoryRightButtons = contentAccessoryRightButtons self.contentAccessoryRightButtons = contentAccessoryRightButtons
self.defaultId = defaultId self.defaultId = defaultId
self.contentBackground = contentBackground self.contentBackground = contentBackground
@ -132,6 +166,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
self.externalTopPanelContainer = externalTopPanelContainer self.externalTopPanelContainer = externalTopPanelContainer
self.bottomPanel = bottomPanel self.bottomPanel = bottomPanel
self.panelStateUpdated = panelStateUpdated self.panelStateUpdated = panelStateUpdated
self.hidePanels = hidePanels
} }
public static func ==(lhs: PagerComponent, rhs: PagerComponent) -> Bool { public static func ==(lhs: PagerComponent, rhs: PagerComponent) -> Bool {
@ -162,43 +197,56 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
if lhs.bottomPanel != rhs.bottomPanel { if lhs.bottomPanel != rhs.bottomPanel {
return false return false
} }
if lhs.hidePanels != rhs.hidePanels {
return false
}
return true return true
} }
public final class View: UIView { public final class View: UIView, ComponentTaggedView {
private final class ContentView { private final class ContentView {
let view: ComponentHostView<(ChildEnvironmentType, PagerComponentChildEnvironment)> 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)>) { init(view: ComponentHostView<(ChildEnvironmentType, PagerComponentChildEnvironment)>) {
self.view = view self.view = view
} }
} }
private final class PagerPanGestureRecognizerImpl: UIPanGestureRecognizer, PagerPanGestureRecognizer {
}
private struct PaneTransitionGestureState { private struct PaneTransitionGestureState {
var fraction: CGFloat = 0.0 var fraction: CGFloat = 0.0
} }
private var contentViews: [AnyHashable: ContentView] = [:] private var contentViews: [AnyHashable: ContentView] = [:]
private var contentBackgroundView: ComponentHostView<Empty>? private var contentBackgroundView: ComponentHostView<Empty>?
private var topPanelView: ComponentHostView<PagerComponentPanelEnvironment>? private let topPanelVisibilityFractionUpdated = ActionSlot<(CGFloat, Transition)>()
private var bottomPanelView: ComponentHostView<PagerComponentPanelEnvironment>? 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 paneTransitionGestureState: PaneTransitionGestureState?
private var component: PagerComponent<ChildEnvironmentType>? private var component: PagerComponent<ChildEnvironmentType, TopPanelEnvironment>?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
private var panRecognizer: UIPanGestureRecognizer? private var panRecognizer: PagerPanGestureRecognizerImpl?
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
self.disablesInteractiveTransitionGestureRecognizer = true 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.panRecognizer = panRecognizer
self.addGestureRecognizer(panRecognizer) self.addGestureRecognizer(panRecognizer)
} }
@ -207,6 +255,14 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
fatalError("init(coder:) has not been implemented") 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) { @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state { switch recognizer.state {
case .began: 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.component = component
self.state = state self.state = state
@ -288,22 +344,22 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
var contentInsets = component.contentInsets var contentInsets = component.contentInsets
let scrollingPanelOffsetToClosestEdge: CGFloat var scrollingPanelOffsetFraction: CGFloat
if let centralId = centralId, let centralContentView = self.contentViews[centralId] { if let centralId = centralId, let centralContentView = self.contentViews[centralId] {
scrollingPanelOffsetToClosestEdge = centralContentView.scrollingPanelOffsetToClosestEdge scrollingPanelOffsetFraction = centralContentView.scrollingPanelOffsetFraction
} else { } else {
scrollingPanelOffsetToClosestEdge = 0.0 scrollingPanelOffsetFraction = 0.0
} }
var topPanelHeight: CGFloat = 0.0 var topPanelHeight: CGFloat = 0.0
if let topPanel = component.topPanel { if let topPanel = component.topPanel {
let topPanelView: ComponentHostView<PagerComponentPanelEnvironment> let topPanelView: ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>
var topPanelTransition = transition var topPanelTransition = transition
if let current = self.topPanelView { if let current = self.topPanelView {
topPanelView = current topPanelView = current
} else { } else {
topPanelTransition = .immediate topPanelTransition = .immediate
topPanelView = ComponentHostView<PagerComponentPanelEnvironment>() topPanelView = ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>()
topPanelView.clipsToBounds = true topPanelView.clipsToBounds = true
self.topPanelView = topPanelView self.topPanelView = topPanelView
} }
@ -321,20 +377,38 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
contentOffset: 0.0, contentOffset: 0.0,
contentTopPanels: component.contentTopPanels, contentTopPanels: component.contentTopPanels,
contentIcons: [], contentIcons: [],
contentAccessoryLeftButtons: [],
contentAccessoryRightButtons: [], contentAccessoryRightButtons: [],
activeContentId: centralId, activeContentId: centralId,
navigateToContentId: navigateToContentId navigateToContentId: navigateToContentId,
visibilityFractionUpdated: self.topPanelVisibilityFractionUpdated
) )
}, },
containerSize: availableSize 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) topPanelHeight = max(0.0, topPanelSize.height - topPanelOffset)
if component.hidePanels {
topPanelOffset = topPanelSize.height
}
if component.externalTopPanelContainer != nil { 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))) transition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(), size: CGSize(width: topPanelSize.width, height: visibleTopPanelHeight)))
} else { } else {
transition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelOffset), size: topPanelSize)) 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 contentInsets.top += topPanelSize.height
} else { } else {
if let bottomPanelView = self.bottomPanelView { if let topPanelView = self.topPanelView {
self.bottomPanelView = nil self.topPanelView = nil
bottomPanelView.removeFromSuperview() topPanelView.removeFromSuperview()
} }
self.topPanelHeight = 0.0
} }
var bottomPanelOffset: CGFloat = 0.0 var bottomPanelOffset: CGFloat = 0.0
if let bottomPanel = component.bottomPanel { if let bottomPanel = component.bottomPanel {
let bottomPanelView: ComponentHostView<PagerComponentPanelEnvironment> let bottomPanelView: ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>
var bottomPanelTransition = transition var bottomPanelTransition = transition
if let current = self.bottomPanelView { if let current = self.bottomPanelView {
bottomPanelView = current bottomPanelView = current
} else { } else {
bottomPanelTransition = .immediate bottomPanelTransition = .immediate
bottomPanelView = ComponentHostView<PagerComponentPanelEnvironment>() bottomPanelView = ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>()
self.bottomPanelView = bottomPanelView self.bottomPanelView = bottomPanelView
self.addSubview(bottomPanelView) self.addSubview(bottomPanelView)
} }
@ -365,19 +441,26 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
transition: bottomPanelTransition, transition: bottomPanelTransition,
component: bottomPanel, component: bottomPanel,
environment: { environment: {
PagerComponentPanelEnvironment( PagerComponentPanelEnvironment<TopPanelEnvironment>(
contentOffset: 0.0, contentOffset: 0.0,
contentTopPanels: [], contentTopPanels: [],
contentIcons: component.contentIcons, contentIcons: component.contentIcons,
contentAccessoryLeftButtons: component.contentAccessoryLeftButtons,
contentAccessoryRightButtons: component.contentAccessoryRightButtons, contentAccessoryRightButtons: component.contentAccessoryRightButtons,
activeContentId: centralId, activeContentId: centralId,
navigateToContentId: navigateToContentId navigateToContentId: navigateToContentId,
visibilityFractionUpdated: self.bottomPanelVisibilityFractionUpdated
) )
}, },
containerSize: availableSize 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)) 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() bottomPanelView.removeFromSuperview()
} }
self.bottomPanelHeight = 0.0
} }
let effectiveTopPanelHeight: CGFloat = component.hidePanels ? 0.0 : topPanelHeight
if let contentBackground = component.contentBackground { if let contentBackground = component.contentBackground {
let contentBackgroundView: ComponentHostView<Empty> let contentBackgroundView: ComponentHostView<Empty>
var contentBackgroundTransition = transition var contentBackgroundTransition = transition
@ -405,9 +492,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
transition: contentBackgroundTransition, transition: contentBackgroundTransition,
component: contentBackground, component: contentBackground,
environment: {}, 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 { } else {
if let contentBackgroundView = self.contentBackgroundView { if let contentBackgroundView = self.contentBackgroundView {
self.contentBackgroundView = nil self.contentBackgroundView = nil
@ -558,13 +645,44 @@ public final class PagerComponent<ChildEnvironmentType: Equatable>: Component {
return return
} }
if let absoluteOffsetToClosestEdge = update.absoluteOffsetToClosestEdge { var offsetDelta: CGFloat?
contentView.scrollingPanelOffsetToClosestEdge = absoluteOffsetToClosestEdge offsetDelta = (update.absoluteOffsetToTopEdge ?? 0.0) - contentView.scrollingPanelOffsetToTopEdge
} else {
contentView.scrollingPanelOffsetToClosestEdge = 1000.0 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)
} }
} }

View File

@ -146,6 +146,8 @@ public extension ContainedViewLayoutTransition {
} else { } else {
switch self { switch self {
case .immediate: case .immediate:
node.layer.removeAnimation(forKey: "position")
node.layer.removeAnimation(forKey: "bounds")
node.frame = frame node.frame = frame
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -173,6 +175,8 @@ public extension ContainedViewLayoutTransition {
} else { } else {
switch self { switch self {
case .immediate: case .immediate:
node.layer.removeAnimation(forKey: "position")
node.layer.removeAnimation(forKey: "bounds")
node.position = frame.center node.position = frame.center
node.bounds = CGRect(origin: CGPoint(), size: frame.size) node.bounds = CGRect(origin: CGPoint(), size: frame.size)
if let completion = completion { if let completion = completion {
@ -206,6 +210,8 @@ public extension ContainedViewLayoutTransition {
} else { } else {
switch self { switch self {
case .immediate: case .immediate:
layer.removeAnimation(forKey: "position")
layer.removeAnimation(forKey: "bounds")
layer.position = frame.center layer.position = frame.center
layer.bounds = CGRect(origin: CGPoint(), size: frame.size) layer.bounds = CGRect(origin: CGPoint(), size: frame.size)
if let completion = completion { if let completion = completion {
@ -277,6 +283,7 @@ public extension ContainedViewLayoutTransition {
} else { } else {
switch self { switch self {
case .immediate: case .immediate:
node.layer.removeAnimation(forKey: "bounds")
node.bounds = bounds node.bounds = bounds
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -304,6 +311,7 @@ public extension ContainedViewLayoutTransition {
} else { } else {
switch self { switch self {
case .immediate: case .immediate:
layer.removeAnimation(forKey: "bounds")
layer.bounds = bounds layer.bounds = bounds
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -326,6 +334,7 @@ public extension ContainedViewLayoutTransition {
} else { } else {
switch self { switch self {
case .immediate: case .immediate:
node.layer.removeAnimation(forKey: "position")
node.position = position node.position = position
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -353,6 +362,7 @@ public extension ContainedViewLayoutTransition {
} else { } else {
switch self { switch self {
case .immediate: case .immediate:
layer.removeAnimation(forKey: "position")
layer.position = position layer.position = position
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -615,6 +625,8 @@ public extension ContainedViewLayoutTransition {
} else { } else {
switch self { switch self {
case .immediate: case .immediate:
view.layer.removeAnimation(forKey: "position")
view.layer.removeAnimation(forKey: "bounds")
view.frame = frame view.frame = frame
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -642,6 +654,8 @@ public extension ContainedViewLayoutTransition {
} else { } else {
switch self { switch self {
case .immediate: case .immediate:
layer.removeAnimation(forKey: "position")
layer.removeAnimation(forKey: "bounds")
layer.frame = frame layer.frame = frame
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -790,6 +804,7 @@ public extension ContainedViewLayoutTransition {
switch self { switch self {
case .immediate: case .immediate:
node.layer.removeAnimation(forKey: "cornerRadius")
node.cornerRadius = cornerRadius node.cornerRadius = cornerRadius
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -815,6 +830,7 @@ public extension ContainedViewLayoutTransition {
switch self { switch self {
case .immediate: case .immediate:
layer.removeAnimation(forKey: "cornerRadius")
layer.cornerRadius = cornerRadius layer.cornerRadius = cornerRadius
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -1084,6 +1100,7 @@ public extension ContainedViewLayoutTransition {
switch self { switch self {
case .immediate: case .immediate:
node.layer.removeAnimation(forKey: "sublayerTransform")
node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0) node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0)
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -1116,6 +1133,7 @@ public extension ContainedViewLayoutTransition {
switch self { switch self {
case .immediate: case .immediate:
node.layer.removeAnimation(forKey: "sublayerTransform")
node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0) node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0)
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -1153,6 +1171,7 @@ public extension ContainedViewLayoutTransition {
switch self { switch self {
case .immediate: case .immediate:
node.layer.removeAnimation(forKey: "sublayerTransform")
node.layer.sublayerTransform = transform node.layer.sublayerTransform = transform
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -1200,6 +1219,7 @@ public extension ContainedViewLayoutTransition {
switch self { switch self {
case .immediate: case .immediate:
layer.removeAnimation(forKey: "sublayerTransform")
layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -1248,6 +1268,7 @@ public extension ContainedViewLayoutTransition {
switch self { switch self {
case .immediate: case .immediate:
layer.removeAnimation(forKey: "transform")
layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -1275,6 +1296,7 @@ public extension ContainedViewLayoutTransition {
switch self { switch self {
case .immediate: case .immediate:
layer.removeAnimation(forKey: "sublayerTransform")
layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0)
if let completion = completion { if let completion = completion {
completion(true) completion(true)

View File

@ -6,12 +6,12 @@ private class GridNodeScrollerLayer: CALayer {
} }
} }
private class GridNodeScrollerView: UIScrollView { public class GridNodeScrollerView: UIScrollView {
override class var layerClass: AnyClass { override public class var layerClass: AnyClass {
return GridNodeScrollerLayer.self return GridNodeScrollerLayer.self
} }
override init(frame: CGRect) { override public init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { 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") fatalError("init(coder:) has not been implemented")
} }
override func touchesShouldCancel(in view: UIView) -> Bool { override public func touchesShouldCancel(in view: UIView) -> Bool {
return true return true
} }
@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { @objc private func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false return false
} }
} }

View File

@ -605,7 +605,7 @@ private func loadItem(path: String) -> AnimationCacheItem? {
} }
let decompressedSize = readUInt32(data: compressedData, offset: 0) let decompressedSize = readUInt32(data: compressedData, offset: 0)
if decompressedSize <= 0 || decompressedSize > 20 * 1024 * 1024 { if decompressedSize <= 0 || decompressedSize > 40 * 1024 * 1024 {
return nil return nil
} }
guard let data = decompressData(data: compressedData, range: 4 ..< compressedData.count, decompressedSize: Int(decompressedSize)) else { guard let data = decompressData(data: compressedData, range: 4 ..< compressedData.count, decompressedSize: Int(decompressedSize)) else {

View File

@ -13,6 +13,7 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display", "//submodules/Display:Display",
"//submodules/Components/PagerComponent:PagerComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -2,37 +2,172 @@ import Foundation
import UIKit import UIKit
import Display import Display
import AsyncDisplayKit import AsyncDisplayKit
import PagerComponent
private final class ExpansionPanRecognizer: UIPanGestureRecognizer, UIGestureRecognizerDelegate { private func traceScrollView(view: UIView, point: CGPoint) -> (UIScrollView?, Bool) {
private var targetScrollView: UIScrollView? 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) super.init(target: target, action: action)
self.delegate = self self.delegate = self
} }
override func reset() { override public func reset() {
self.targetScrollView = nil super.reset()
self.state = .possible
self.currentTranslation = CGPoint()
} }
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
/*if let scrollView = otherGestureRecognizer.view as? UIScrollView { if let _ = otherGestureRecognizer.view as? PagerExpandableScrollView {
if scrollView.bounds.height > 200.0 { return true
self.targetScrollView = scrollView }
scrollView.contentOffset = CGPoint()
}
}*/
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 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) super.touchesMoved(touches, with: event)
if let targetScrollView = self.targetScrollView { guard let touch = touches.first, let view = self.view else {
targetScrollView.contentOffset = CGPoint() 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 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.expansionFraction = max(0.0, min(1.0, self.initialExpansionFraction + delta))
self.expansionUpdated?(.immediate) self.expansionUpdated?(.immediate)
@ -75,7 +210,7 @@ public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate {
return return
} }
let velocity = recognizer.velocity(in: self.view) let velocity = recognizer.velocity()
if abs(self.initialExpansionFraction - self.expansionFraction) > 0.25 { if abs(self.initialExpansionFraction - self.expansionFraction) > 0.25 {
if self.initialExpansionFraction < 0.5 { if self.initialExpansionFraction < 0.5 {
self.expansionFraction = 1.0 self.expansionFraction = 1.0
@ -95,6 +230,11 @@ public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate {
self.expansionFraction = 1.0 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)) self.expansionUpdated?(.animated(duration: 0.4, curve: .spring))
default: default:
break break
@ -107,7 +247,13 @@ public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate {
self.scrollableDistance = scrollableDistance self.scrollableDistance = scrollableDistance
} }
public func expand() {
self.expansionFraction = 1.0
self.expansionRecognizer?.requiredLockDirection = self.expansionFraction == 0.0 ? .up : .down
}
public func collapse() { public func collapse() {
self.expansionFraction = 0.0 self.expansionFraction = 0.0
self.expansionRecognizer?.requiredLockDirection = self.expansionFraction == 0.0 ? .up : .down
} }
} }

View File

@ -25,14 +25,14 @@ import UndoUI
private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white) 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 private let iconLayer: SimpleLayer
init() { init() {
self.iconLayer = SimpleLayer() self.iconLayer = SimpleLayer()
self.iconLayer.contents = premiumBadgeIcon?.cgImage self.iconLayer.contents = premiumBadgeIcon?.cgImage
super.init(color: .clear, enableBlur: true) super.init(frame: CGRect())
self.layer.addSublayer(self.iconLayer) self.layer.addSublayer(self.iconLayer)
} }
@ -42,11 +42,13 @@ private final class PremiumBadgeView: BlurredBackgroundView {
} }
func update(backgroundColor: UIColor, size: CGSize) { 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) 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 case detailed
} }
public let id: AnyHashable
public let context: AccountContext public let context: AccountContext
public let animationCache: AnimationCache public let animationCache: AnimationCache
public let animationRenderer: MultiAnimationRenderer public let animationRenderer: MultiAnimationRenderer
@ -158,6 +161,7 @@ public final class EmojiPagerContentComponent: Component {
public let itemLayoutType: ItemLayoutType public let itemLayoutType: ItemLayoutType
public init( public init(
id: AnyHashable,
context: AccountContext, context: AccountContext,
animationCache: AnimationCache, animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer, animationRenderer: MultiAnimationRenderer,
@ -165,6 +169,7 @@ public final class EmojiPagerContentComponent: Component {
itemGroups: [ItemGroup], itemGroups: [ItemGroup],
itemLayoutType: ItemLayoutType itemLayoutType: ItemLayoutType
) { ) {
self.id = id
self.context = context self.context = context
self.animationCache = animationCache self.animationCache = animationCache
self.animationRenderer = animationRenderer self.animationRenderer = animationRenderer
@ -174,6 +179,9 @@ public final class EmojiPagerContentComponent: Component {
} }
public static func ==(lhs: EmojiPagerContentComponent, rhs: EmojiPagerContentComponent) -> Bool { public static func ==(lhs: EmojiPagerContentComponent, rhs: EmojiPagerContentComponent) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
@ -196,14 +204,24 @@ public final class EmojiPagerContentComponent: Component {
return true 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 { private struct ItemGroupDescription: Equatable {
let id: AnyHashable
let hasTitle: Bool let hasTitle: Bool
let itemCount: Int let itemCount: Int
} }
private struct ItemGroupLayout: Equatable { private struct ItemGroupLayout: Equatable {
let frame: CGRect let frame: CGRect
let id: AnyHashable
let itemTopOffset: CGFloat let itemTopOffset: CGFloat
let itemCount: Int let itemCount: Int
} }
@ -230,9 +248,9 @@ public final class EmojiPagerContentComponent: Component {
self.verticalSpacing = 9.0 self.verticalSpacing = 9.0
minSpacing = 9.0 minSpacing = 9.0
case .detailed: case .detailed:
self.itemSize = 60.0 self.itemSize = 76.0
self.verticalSpacing = 9.0 self.verticalSpacing = 2.0
minSpacing = 9.0 minSpacing = 2.0
} }
self.verticalGroupSpacing = 18.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) let groupContentSize = CGSize(width: width, height: itemTopOffset + CGFloat(numRowsInGroup) * self.itemSize + CGFloat(max(0, numRowsInGroup - 1)) * self.verticalSpacing)
self.itemGroupLayouts.append(ItemGroupLayout( self.itemGroupLayouts.append(ItemGroupLayout(
frame: CGRect(origin: CGPoint(x: 0.0, y: verticalGroupOrigin), size: groupContentSize), frame: CGRect(origin: CGPoint(x: 0.0, y: verticalGroupOrigin), size: groupContentSize),
id: itemGroup.id,
itemTopOffset: itemTopOffset, itemTopOffset: itemTopOffset,
itemCount: itemGroup.itemCount itemCount: itemGroup.itemCount
)) ))
@ -281,8 +300,8 @@ public final class EmojiPagerContentComponent: Component {
) )
} }
func visibleItems(for rect: CGRect) -> [(groupIndex: Int, groupItems: Range<Int>)] { func visibleItems(for rect: CGRect) -> [(id: AnyHashable, groupIndex: Int, groupItems: Range<Int>)] {
var result: [(groupIndex: Int, groupItems: Range<Int>)] = [] var result: [(id: AnyHashable, groupIndex: Int, groupItems: Range<Int>)] = []
for groupIndex in 0 ..< self.itemGroupLayouts.count { for groupIndex in 0 ..< self.itemGroupLayouts.count {
let group = self.itemGroupLayouts[groupIndex] let group = self.itemGroupLayouts[groupIndex]
@ -300,6 +319,7 @@ public final class EmojiPagerContentComponent: Component {
if maxVisibleIndex >= minVisibleIndex { if maxVisibleIndex >= minVisibleIndex {
result.append(( result.append((
id: group.id,
groupIndex: groupIndex, groupIndex: groupIndex,
groupItems: minVisibleIndex ..< (maxVisibleIndex + 1) 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 visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:]
private var visibleGroupHeaders: [AnyHashable: ComponentHostView<Empty>] = [:] private var visibleGroupHeaders: [AnyHashable: ComponentView<Empty>] = [:]
private var ignoreScrolling: Bool = false private var ignoreScrolling: Bool = false
private var component: EmojiPagerContentComponent? private var component: EmojiPagerContentComponent?
private var pagerEnvironment: PagerComponentChildEnvironment? private var pagerEnvironment: PagerComponentChildEnvironment?
private var theme: PresentationTheme? private var theme: PresentationTheme?
private var activeItemUpdated: ActionSlot<(AnyHashable, Transition)>?
private var itemLayout: ItemLayout? private var itemLayout: ItemLayout?
private var currentContextGestureItemKey: ItemLayer.Key? private var currentContextGestureItemKey: ItemLayer.Key?
@ -508,7 +532,7 @@ public final class EmojiPagerContentComponent: Component {
private weak var peekController: PeekController? private weak var peekController: PeekController?
override init(frame: CGRect) { override init(frame: CGRect) {
self.scrollView = UIScrollView() self.scrollView = ContentScrollView()
super.init(frame: frame) super.init(frame: frame)
@ -703,6 +727,31 @@ public final class EmojiPagerContentComponent: Component {
fatalError("init(coder:) has not been implemented") 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) { @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state { 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] { 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 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) { public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if let presentation = scrollView.layer.presentation() { if let presentation = scrollView.layer.presentation() {
@ -759,21 +813,22 @@ public final class EmojiPagerContentComponent: Component {
} }
private func updateScrollingOffset(transition: Transition) { private func updateScrollingOffset(transition: Transition) {
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
if let previousScrollingOffsetValue = self.previousScrollingOffset { if let previousScrollingOffsetValue = self.previousScrollingOffset {
let currentBounds = scrollView.bounds let currentBounds = scrollView.bounds
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0) let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY) 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( self.pagerEnvironment?.onChildScrollingUpdate(PagerComponentChildEnvironment.ContentScrollingUpdate(
relativeOffset: relativeOffset, relativeOffset: relativeOffset,
absoluteOffsetToClosestEdge: offsetToClosestEdge, absoluteOffsetToTopEdge: offsetToTopEdge,
absoluteOffsetToBottomEdge: offsetToBottomEdge,
isInteracting: isInteracting,
transition: transition 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 { private func snappedContentOffset(proposedOffset: CGFloat) -> CGFloat {
@ -808,22 +863,27 @@ public final class EmojiPagerContentComponent: Component {
return return
} }
var topVisibleGroupId: AnyHashable?
var validIds = Set<ItemLayer.Key>() var validIds = Set<ItemLayer.Key>()
var validGroupHeaderIds = Set<AnyHashable>() var validGroupHeaderIds = Set<AnyHashable>()
for groupItems in itemLayout.visibleItems(for: self.scrollView.bounds) { for groupItems in itemLayout.visibleItems(for: self.scrollView.bounds) {
if topVisibleGroupId == nil {
topVisibleGroupId = groupItems.id
}
let itemGroup = component.itemGroups[groupItems.groupIndex] let itemGroup = component.itemGroups[groupItems.groupIndex]
let itemGroupLayout = itemLayout.itemGroupLayouts[groupItems.groupIndex] let itemGroupLayout = itemLayout.itemGroupLayouts[groupItems.groupIndex]
if let title = itemGroup.title { if let title = itemGroup.title {
validGroupHeaderIds.insert(itemGroup.id) validGroupHeaderIds.insert(itemGroup.id)
let groupHeaderView: ComponentHostView<Empty> let groupHeaderView: ComponentView<Empty>
if let current = self.visibleGroupHeaders[itemGroup.id] { if let current = self.visibleGroupHeaders[itemGroup.id] {
groupHeaderView = current groupHeaderView = current
} else { } else {
groupHeaderView = ComponentHostView<Empty>() groupHeaderView = ComponentView<Empty>()
self.visibleGroupHeaders[itemGroup.id] = groupHeaderView self.visibleGroupHeaders[itemGroup.id] = groupHeaderView
self.scrollView.addSubview(groupHeaderView)
} }
let groupHeaderSize = groupHeaderView.update( let groupHeaderSize = groupHeaderView.update(
transition: .immediate, transition: .immediate,
@ -833,7 +893,12 @@ public final class EmojiPagerContentComponent: Component {
environment: {}, environment: {},
containerSize: CGSize(width: itemLayout.contentSize.width - itemLayout.containerInsets.left - itemLayout.containerInsets.right, height: 100.0) 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 { for index in groupItems.groupItems.lowerBound ..< groupItems.groupItems.upperBound {
@ -848,7 +913,7 @@ public final class EmojiPagerContentComponent: Component {
itemLayer = ItemLayer( itemLayer = ItemLayer(
item: item, item: item,
context: component.context, context: component.context,
groupId: "keyboard", groupId: "keyboard-\(Int(itemLayout.itemSize))",
attemptSynchronousLoad: attemptSynchronousLoads, attemptSynchronousLoad: attemptSynchronousLoads,
file: item.file, file: item.file,
cache: component.animationCache, cache: component.animationCache,
@ -884,17 +949,22 @@ public final class EmojiPagerContentComponent: Component {
for (id, groupHeaderView) in self.visibleGroupHeaders { for (id, groupHeaderView) in self.visibleGroupHeaders {
if !validGroupHeaderIds.contains(id) { if !validGroupHeaderIds.contains(id) {
removedGroupHeaderIds.append(id) removedGroupHeaderIds.append(id)
groupHeaderView.removeFromSuperview() groupHeaderView.view?.removeFromSuperview()
} }
} }
for id in removedGroupHeaderIds { for id in removedGroupHeaderIds {
self.visibleGroupHeaders.removeValue(forKey: id) 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 { func update(component: EmojiPagerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
self.component = component self.component = component
self.theme = environment[EntityKeyboardChildEnvironment.self].value.theme self.theme = environment[EntityKeyboardChildEnvironment.self].value.theme
self.activeItemUpdated = environment[EntityKeyboardChildEnvironment.self].value.getContentActiveItemUpdated(component.id)
let pagerEnvironment = environment[PagerComponentChildEnvironment.self].value let pagerEnvironment = environment[PagerComponentChildEnvironment.self].value
self.pagerEnvironment = pagerEnvironment self.pagerEnvironment = pagerEnvironment
@ -902,6 +972,7 @@ public final class EmojiPagerContentComponent: Component {
var itemGroups: [ItemGroupDescription] = [] var itemGroups: [ItemGroupDescription] = []
for itemGroup in component.itemGroups { for itemGroup in component.itemGroups {
itemGroups.append(ItemGroupDescription( itemGroups.append(ItemGroupDescription(
id: itemGroup.id,
hasTitle: itemGroup.title != nil, hasTitle: itemGroup.title != nil,
itemCount: itemGroup.items.count itemCount: itemGroup.items.count
)) ))
@ -918,7 +989,7 @@ public final class EmojiPagerContentComponent: Component {
if self.scrollView.scrollIndicatorInsets != pagerEnvironment.containerInsets { if self.scrollView.scrollIndicatorInsets != pagerEnvironment.containerInsets {
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.ignoreScrolling = false
self.updateVisibleItems(attemptSynchronousLoads: true) self.updateVisibleItems(attemptSynchronousLoads: true)

View File

@ -11,9 +11,14 @@ import BundleIconComponent
public final class EntityKeyboardChildEnvironment: Equatable { public final class EntityKeyboardChildEnvironment: Equatable {
public let theme: PresentationTheme 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.theme = theme
self.getContentActiveItemUpdated = getContentActiveItemUpdated
} }
public static func ==(lhs: EntityKeyboardChildEnvironment, rhs: EntityKeyboardChildEnvironment) -> Bool { 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 EntityKeyboardComponent: Component {
public final class MarkInputCollapsed {
public init() {
}
}
public let theme: PresentationTheme public let theme: PresentationTheme
public let bottomInset: CGFloat public let bottomInset: CGFloat
public let emojiContent: EmojiPagerContentComponent public let emojiContent: EmojiPagerContentComponent
@ -34,6 +49,9 @@ public final class EntityKeyboardComponent: Component {
public let defaultToEmojiTab: Bool public let defaultToEmojiTab: Bool
public let externalTopPanelContainer: UIView? public let externalTopPanelContainer: UIView?
public let topPanelExtensionUpdated: (CGFloat, Transition) -> Void public let topPanelExtensionUpdated: (CGFloat, Transition) -> Void
public let hideInputUpdated: (Bool, Transition) -> Void
public let makeSearchContainerNode: (EntitySearchContentType) -> EntitySearchContainerNode
public let deviceMetrics: DeviceMetrics
public init( public init(
theme: PresentationTheme, theme: PresentationTheme,
@ -43,7 +61,10 @@ public final class EntityKeyboardComponent: Component {
gifContent: GifPagerContentComponent, gifContent: GifPagerContentComponent,
defaultToEmojiTab: Bool, defaultToEmojiTab: Bool,
externalTopPanelContainer: UIView?, 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.theme = theme
self.bottomInset = bottomInset self.bottomInset = bottomInset
@ -53,6 +74,9 @@ public final class EntityKeyboardComponent: Component {
self.defaultToEmojiTab = defaultToEmojiTab self.defaultToEmojiTab = defaultToEmojiTab
self.externalTopPanelContainer = externalTopPanelContainer self.externalTopPanelContainer = externalTopPanelContainer
self.topPanelExtensionUpdated = topPanelExtensionUpdated self.topPanelExtensionUpdated = topPanelExtensionUpdated
self.hideInputUpdated = hideInputUpdated
self.makeSearchContainerNode = makeSearchContainerNode
self.deviceMetrics = deviceMetrics
} }
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool { public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
@ -77,6 +101,9 @@ public final class EntityKeyboardComponent: Component {
if lhs.externalTopPanelContainer != rhs.externalTopPanelContainer { if lhs.externalTopPanelContainer != rhs.externalTopPanelContainer {
return false return false
} }
if lhs.deviceMetrics != rhs.deviceMetrics {
return false
}
return true return true
} }
@ -85,6 +112,12 @@ public final class EntityKeyboardComponent: Component {
private let pagerView: ComponentHostView<EntityKeyboardChildEnvironment> private let pagerView: ComponentHostView<EntityKeyboardChildEnvironment>
private var component: EntityKeyboardComponent? 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) { override init(frame: CGRect) {
self.pagerView = ComponentHostView<EntityKeyboardChildEnvironment>() self.pagerView = ComponentHostView<EntityKeyboardChildEnvironment>()
@ -92,6 +125,7 @@ public final class EntityKeyboardComponent: Component {
super.init(frame: frame) super.init(frame: frame)
self.clipsToBounds = true self.clipsToBounds = true
self.disablesInteractiveTransitionGestureRecognizer = true
self.addSubview(self.pagerView) 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 { func update(component: EntityKeyboardComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.state = state
var contents: [AnyComponentWithIdentity<(EntityKeyboardChildEnvironment, PagerComponentChildEnvironment)>] = [] var contents: [AnyComponentWithIdentity<(EntityKeyboardChildEnvironment, PagerComponentChildEnvironment)>] = []
var contentTopPanels: [AnyComponentWithIdentity<Empty>] = [] var contentTopPanels: [AnyComponentWithIdentity<EntityKeyboardTopContainerPanelEnvironment>] = []
var contentIcons: [AnyComponentWithIdentity<Empty>] = [] var contentIcons: [AnyComponentWithIdentity<Empty>] = []
var contentAccessoryLeftButtons: [AnyComponentWithIdentity<Empty>] = []
var contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>] = [] var contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>] = []
let gifsContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
contents.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(component.gifContent))) contents.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(component.gifContent)))
var topGifItems: [EntityKeyboardTopPanelComponent.Item] = [] var topGifItems: [EntityKeyboardTopPanelComponent.Item] = []
topGifItems.append(EntityKeyboardTopPanelComponent.Item( topGifItems.append(EntityKeyboardTopPanelComponent.Item(
@ -126,13 +164,24 @@ public final class EntityKeyboardComponent: Component {
)) ))
contentTopPanels.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(EntityKeyboardTopPanelComponent( contentTopPanels.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(EntityKeyboardTopPanelComponent(
theme: component.theme, theme: component.theme,
items: topGifItems items: topGifItems,
activeContentItemIdUpdated: gifsContentItemIdUpdated
)))) ))))
contentIcons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(BundleIconComponent( contentIcons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(BundleIconComponent(
name: "Chat/Input/Media/EntityInputGifsIcon", name: "Chat/Input/Media/EntityInputGifsIcon",
tintColor: component.theme.chat.inputMediaPanel.panelIconColor, tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
maxSize: nil 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( /*contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(Button(
content: AnyComponent(BundleIconComponent( content: AnyComponent(BundleIconComponent(
name: "Chat/Input/Media/EntityInputSettingsIcon", name: "Chat/Input/Media/EntityInputSettingsIcon",
@ -152,38 +201,59 @@ public final class EntityKeyboardComponent: Component {
] ]
if let iconName = iconMapping[id] { if let iconName = iconMapping[id] {
topStickerItems.append(EntityKeyboardTopPanelComponent.Item( topStickerItems.append(EntityKeyboardTopPanelComponent.Item(
id: id, id: itemGroup.id,
content: AnyComponent(BundleIconComponent( content: AnyComponent(Button(
name: iconName, content: AnyComponent(BundleIconComponent(
tintColor: component.theme.chat.inputMediaPanel.panelIconColor, name: iconName,
maxSize: CGSize(width: 30.0, height: 30.0)) 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 { } else {
if !itemGroup.items.isEmpty { if !itemGroup.items.isEmpty {
topStickerItems.append(EntityKeyboardTopPanelComponent.Item( topStickerItems.append(EntityKeyboardTopPanelComponent.Item(
id: AnyHashable(itemGroup.items[0].file.fileId), id: itemGroup.id,
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent( content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
context: component.stickerContent.context, context: component.stickerContent.context,
file: itemGroup.items[0].file, file: itemGroup.items[0].file,
animationCache: component.stickerContent.animationCache, 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))) contents.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(component.stickerContent)))
contentTopPanels.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardTopPanelComponent( contentTopPanels.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardTopPanelComponent(
theme: component.theme, theme: component.theme,
items: topStickerItems items: topStickerItems,
activeContentItemIdUpdated: stickersContentItemIdUpdated
)))) ))))
contentIcons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(BundleIconComponent( contentIcons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(BundleIconComponent(
name: "Chat/Input/Media/EntityInputStickersIcon", name: "Chat/Input/Media/EntityInputStickersIcon",
tintColor: component.theme.chat.inputMediaPanel.panelIconColor, tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
maxSize: nil 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( contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
content: AnyComponent(BundleIconComponent( content: AnyComponent(BundleIconComponent(
name: "Chat/Input/Media/EntityInputSettingsIcon", name: "Chat/Input/Media/EntityInputSettingsIcon",
@ -195,24 +265,29 @@ public final class EntityKeyboardComponent: Component {
} }
).minSize(CGSize(width: 38.0, height: 38.0))))) ).minSize(CGSize(width: 38.0, height: 38.0)))))
let emojiContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
contents.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(component.emojiContent))) contents.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(component.emojiContent)))
var topEmojiItems: [EntityKeyboardTopPanelComponent.Item] = [] var topEmojiItems: [EntityKeyboardTopPanelComponent.Item] = []
for itemGroup in component.emojiContent.itemGroups { for itemGroup in component.emojiContent.itemGroups {
if !itemGroup.items.isEmpty { if !itemGroup.items.isEmpty {
topEmojiItems.append(EntityKeyboardTopPanelComponent.Item( topEmojiItems.append(EntityKeyboardTopPanelComponent.Item(
id: AnyHashable(itemGroup.items[0].file.fileId), id: itemGroup.id,
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent( content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
context: component.emojiContent.context, context: component.emojiContent.context,
file: itemGroup.items[0].file, file: itemGroup.items[0].file,
animationCache: component.emojiContent.animationCache, 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( contentTopPanels.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardTopPanelComponent(
theme: component.theme, theme: component.theme,
items: topEmojiItems items: topEmojiItems,
activeContentItemIdUpdated: emojiContentItemIdUpdated
)))) ))))
contentIcons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(BundleIconComponent( contentIcons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(BundleIconComponent(
name: "Chat/Input/Media/EntityInputEmojiIcon", name: "Chat/Input/Media/EntityInputEmojiIcon",
@ -237,6 +312,7 @@ public final class EntityKeyboardComponent: Component {
contents: contents, contents: contents,
contentTopPanels: contentTopPanels, contentTopPanels: contentTopPanels,
contentIcons: contentIcons, contentIcons: contentIcons,
contentAccessoryLeftButtons: contentAccessoryLeftButtons,
contentAccessoryRightButtons: contentAccessoryRightButtons, contentAccessoryRightButtons: contentAccessoryRightButtons,
defaultId: component.defaultToEmojiTab ? "emoji" : "stickers", defaultId: component.defaultToEmojiTab ? "emoji" : "stickers",
contentBackground: AnyComponent(BlurredBackgroundComponent( contentBackground: AnyComponent(BlurredBackgroundComponent(
@ -253,21 +329,149 @@ public final class EntityKeyboardComponent: Component {
self?.component?.emojiContent.inputInteraction.deleteBackwards() self?.component?.emojiContent.inputInteraction.deleteBackwards()
} }
)), )),
panelStateUpdated: { panelState, transition in panelStateUpdated: { [weak self] panelState, transition in
component.topPanelExtensionUpdated(panelState.topPanelHeight, transition) guard let strongSelf = self else {
} return
}
strongSelf.topPanelExtensionUpdated(height: panelState.topPanelHeight, transition: transition)
},
hidePanels: self.searchComponent != nil
)), )),
environment: { 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 containerSize: availableSize
) )
transition.setFrame(view: self.pagerView, frame: CGRect(origin: CGPoint(), size: pagerSize)) 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 self.component = component
return availableSize 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 { public func makeView() -> View {

View File

@ -81,7 +81,7 @@ private final class BottomPanelIconComponent: Component {
} }
final class EntityKeyboardBottomPanelComponent: Component { final class EntityKeyboardBottomPanelComponent: Component {
typealias EnvironmentType = PagerComponentPanelEnvironment typealias EnvironmentType = PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>
let theme: PresentationTheme let theme: PresentationTheme
let bottomInset: CGFloat let bottomInset: CGFloat
@ -111,16 +111,19 @@ final class EntityKeyboardBottomPanelComponent: Component {
final class View: UIView { final class View: UIView {
private final class AccessoryButtonView { private final class AccessoryButtonView {
let id: AnyHashable let id: AnyHashable
var component: AnyComponent<Empty>
let view: ComponentHostView<Empty> let view: ComponentHostView<Empty>
init(id: AnyHashable, view: ComponentHostView<Empty>) { init(id: AnyHashable, component: AnyComponent<Empty>, view: ComponentHostView<Empty>) {
self.id = id self.id = id
self.component = component
self.view = view self.view = view
} }
} }
private let backgroundView: BlurredBackgroundView private let backgroundView: BlurredBackgroundView
private let separatorView: UIView private let separatorView: UIView
private var leftAccessoryButton: AccessoryButtonView?
private var rightAccessoryButton: AccessoryButtonView? private var rightAccessoryButton: AccessoryButtonView?
private var iconViews: [AnyHashable: ComponentHostView<Empty>] = [:] private var iconViews: [AnyHashable: ComponentHostView<Empty>] = [:]
@ -160,9 +163,61 @@ final class EntityKeyboardBottomPanelComponent: Component {
let intrinsicHeight: CGFloat = 38.0 let intrinsicHeight: CGFloat = 38.0
let height = intrinsicHeight + component.bottomInset let height = intrinsicHeight + component.bottomInset
let panelEnvironment = environment[PagerComponentPanelEnvironment.self].value let panelEnvironment = environment[PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>.self].value
let activeContentId = panelEnvironment.activeContentId 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>? var rightAccessoryButtonComponent: AnyComponentWithIdentity<Empty>?
for contentAccessoryRightButton in panelEnvironment.contentAccessoryRightButtons { for contentAccessoryRightButton in panelEnvironment.contentAccessoryRightButtons {
if contentAccessoryRightButton.id == activeContentId { if contentAccessoryRightButton.id == activeContentId {
@ -175,11 +230,12 @@ final class EntityKeyboardBottomPanelComponent: Component {
if let rightAccessoryButtonComponent = rightAccessoryButtonComponent { if let rightAccessoryButtonComponent = rightAccessoryButtonComponent {
var rightAccessoryButtonTransition = transition var rightAccessoryButtonTransition = transition
let rightAccessoryButton: AccessoryButtonView 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 rightAccessoryButton = current
current.component = rightAccessoryButtonComponent.component
} else { } else {
rightAccessoryButtonTransition = .immediate 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.rightAccessoryButton = rightAccessoryButton
self.addSubview(rightAccessoryButton.view) self.addSubview(rightAccessoryButton.view)
} }
@ -195,7 +251,7 @@ final class EntityKeyboardBottomPanelComponent: Component {
self.rightAccessoryButton = nil self.rightAccessoryButton = nil
} }
if previousRightAccessoryButton !== self.rightAccessoryButton?.view { if previousRightAccessoryButton?.view !== self.rightAccessoryButton?.view {
if case .none = transition.animation { if case .none = transition.animation {
previousRightAccessoryButton?.view.removeFromSuperview() previousRightAccessoryButton?.view.removeFromSuperview()
} else { } else {

View File

@ -7,8 +7,25 @@ import TelegramPresentationData
import TelegramCore import TelegramCore
import Postbox 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 { final class EntityKeyboardTopContainerPanelComponent: Component {
typealias EnvironmentType = PagerComponentPanelEnvironment typealias EnvironmentType = PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>
let theme: PresentationTheme let theme: PresentationTheme
@ -26,13 +43,20 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
return true return true
} }
private final class PanelView {
let view = ComponentHostView<EntityKeyboardTopContainerPanelEnvironment>()
let visibilityFractionUpdated = ActionSlot<(CGFloat, Transition)>()
}
final class View: UIView { final class View: UIView {
private var panelViews: [AnyHashable: ComponentHostView<Empty>] = [:] private var panelViews: [AnyHashable: PanelView] = [:]
private var component: EntityKeyboardTopContainerPanelComponent? private var component: EntityKeyboardTopContainerPanelComponent?
private var panelEnvironment: PagerComponentPanelEnvironment? private var panelEnvironment: PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
private var visibilityFraction: CGFloat = 1.0
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
} }
@ -84,26 +108,30 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
validPanelIds.insert(panel.id) validPanelIds.insert(panel.id)
var panelTransition = transition var panelTransition = transition
let panelView: ComponentHostView<Empty> let panelView: PanelView
if let current = self.panelViews[panel.id] { if let current = self.panelViews[panel.id] {
panelView = current panelView = current
} else { } else {
panelTransition = .immediate panelTransition = .immediate
panelView = ComponentHostView<Empty>() panelView = PanelView()
self.panelViews[panel.id] = panelView self.panelViews[panel.id] = panelView
self.addSubview(panelView) self.addSubview(panelView.view)
} }
let _ = panelView.update( let _ = panelView.view.update(
transition: panelTransition, transition: panelTransition,
component: panel.component, component: panel.component,
environment: {}, environment: {
EntityKeyboardTopContainerPanelEnvironment(
visibilityFractionUpdated: panelView.visibilityFractionUpdated
)
},
containerSize: panelFrame.size containerSize: panelFrame.size
) )
if isInBounds { 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 { if isPartOfTransition && completed {
self?.state?.updated(transition: .immediate) self?.state?.updated(transition: .immediate)
} }
@ -115,15 +143,34 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
for (id, panelView) in self.panelViews { for (id, panelView) in self.panelViews {
if !validPanelIds.contains(id) { if !validPanelIds.contains(id) {
removedPanelIds.append(id) removedPanelIds.append(id)
panelView.removeFromSuperview() panelView.view.removeFromSuperview()
} }
} }
for id in removedPanelIds { for id in removedPanelIds {
self.panelViews.removeValue(forKey: id) 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) 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 { func makeView() -> View {

View File

@ -17,17 +17,20 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
let file: TelegramMediaFile let file: TelegramMediaFile
let animationCache: AnimationCache let animationCache: AnimationCache
let animationRenderer: MultiAnimationRenderer let animationRenderer: MultiAnimationRenderer
let pressed: () -> Void
init( init(
context: AccountContext, context: AccountContext,
file: TelegramMediaFile, file: TelegramMediaFile,
animationCache: AnimationCache, animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer animationRenderer: MultiAnimationRenderer,
pressed: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.file = file self.file = file
self.animationCache = animationCache self.animationCache = animationCache
self.animationRenderer = animationRenderer self.animationRenderer = animationRenderer
self.pressed = pressed
} }
static func ==(lhs: EntityKeyboardAnimationTopPanelComponent, rhs: EntityKeyboardAnimationTopPanelComponent) -> Bool { static func ==(lhs: EntityKeyboardAnimationTopPanelComponent, rhs: EntityKeyboardAnimationTopPanelComponent) -> Bool {
@ -49,16 +52,27 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
final class View: UIView { final class View: UIView {
var itemLayer: EmojiPagerContentComponent.View.ItemLayer? var itemLayer: EmojiPagerContentComponent.View.ItemLayer?
var component: EntityKeyboardAnimationTopPanelComponent?
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") 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 { func update(component: EntityKeyboardAnimationTopPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
self.component = component
if self.itemLayer == nil { if self.itemLayer == nil {
let itemLayer = EmojiPagerContentComponent.View.ItemLayer( let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
item: EmojiPagerContentComponent.Item( item: EmojiPagerContentComponent.Item(
@ -97,7 +111,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
} }
final class EntityKeyboardTopPanelComponent: Component { final class EntityKeyboardTopPanelComponent: Component {
typealias EnvironmentType = Empty typealias EnvironmentType = EntityKeyboardTopContainerPanelEnvironment
final class Item: Equatable { final class Item: Equatable {
let id: AnyHashable let id: AnyHashable
@ -122,13 +136,16 @@ final class EntityKeyboardTopPanelComponent: Component {
let theme: PresentationTheme let theme: PresentationTheme
let items: [Item] let items: [Item]
let activeContentItemIdUpdated: ActionSlot<(AnyHashable, Transition)>
init( init(
theme: PresentationTheme, theme: PresentationTheme,
items: [Item] items: [Item],
activeContentItemIdUpdated: ActionSlot<(AnyHashable, Transition)>
) { ) {
self.theme = theme self.theme = theme
self.items = items self.items = items
self.activeContentItemIdUpdated = activeContentItemIdUpdated
} }
static func ==(lhs: EntityKeyboardTopPanelComponent, rhs: EntityKeyboardTopPanelComponent) -> Bool { static func ==(lhs: EntityKeyboardTopPanelComponent, rhs: EntityKeyboardTopPanelComponent) -> Bool {
@ -138,6 +155,9 @@ final class EntityKeyboardTopPanelComponent: Component {
if lhs.items != rhs.items { if lhs.items != rhs.items {
return false return false
} }
if lhs.activeContentItemIdUpdated !== rhs.activeContentItemIdUpdated {
return false
}
return true return true
} }
@ -188,6 +208,8 @@ final class EntityKeyboardTopPanelComponent: Component {
private var itemLayout: ItemLayout? private var itemLayout: ItemLayout?
private var ignoreScrolling: Bool = false private var ignoreScrolling: Bool = false
private var visibilityFraction: CGFloat = 1.0
private var component: EntityKeyboardTopPanelComponent? private var component: EntityKeyboardTopPanelComponent?
override init(frame: CGRect) { override init(frame: CGRect) {
@ -296,16 +318,63 @@ final class EntityKeyboardTopPanelComponent: Component {
} }
self.ignoreScrolling = false 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) 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) 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 { func makeView() -> View {

View File

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

View File

@ -453,16 +453,18 @@ public final class GifPagerContentComponent: Component {
} }
private func updateScrollingOffset(transition: Transition) { private func updateScrollingOffset(transition: Transition) {
let isInteracting = scrollView.isDragging || scrollView.isTracking || scrollView.isDecelerating
if let previousScrollingOffsetValue = self.previousScrollingOffset { if let previousScrollingOffsetValue = self.previousScrollingOffset {
let currentBounds = scrollView.bounds let currentBounds = scrollView.bounds
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0) let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY) 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
self.pagerEnvironment?.onChildScrollingUpdate(PagerComponentChildEnvironment.ContentScrollingUpdate( self.pagerEnvironment?.onChildScrollingUpdate(PagerComponentChildEnvironment.ContentScrollingUpdate(
relativeOffset: relativeOffset, relativeOffset: relativeOffset,
absoluteOffsetToClosestEdge: offsetToClosestEdge, absoluteOffsetToTopEdge: offsetToTopEdge,
absoluteOffsetToBottomEdge: offsetToBottomEdge,
isInteracting: isInteracting,
transition: transition transition: transition
)) ))
self.previousScrollingOffset = scrollView.contentOffset.y self.previousScrollingOffset = scrollView.contentOffset.y

View File

@ -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 { private final class FrameGroup {
let image: UIImage let image: UIImage
let size: CGSize let size: CGSize

View File

@ -1523,7 +1523,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
if let strongSelf = self { if let strongSelf = self {
strongSelf.chatDisplayNode.inputPanelContainerNode.collapse() strongSelf.chatDisplayNode.collapseInput()
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in
var current = current var current = current
current = current.updatedInterfaceState { interfaceState in current = current.updatedInterfaceState { interfaceState in
@ -1614,6 +1615,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
if let strongSelf = self { if let strongSelf = self {
strongSelf.chatDisplayNode.collapseInput()
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }.updatedInputMode { current in $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }.updatedInputMode { current in
if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
@ -9931,7 +9934,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .media: case .media:
break break
default: default:
self.chatDisplayNode.inputPanelContainerNode.collapse() self.chatDisplayNode.collapseInput()
} }
if self.isNodeLoaded { if self.isNodeLoaded {

View File

@ -108,6 +108,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
private let inputPanelBackgroundNode: NavigationBackgroundNode private let inputPanelBackgroundNode: NavigationBackgroundNode
private var intrinsicInputPanelBackgroundNodeSize: CGSize? private var intrinsicInputPanelBackgroundNodeSize: CGSize?
private let inputPanelBackgroundSeparatorNode: ASDisplayNode private let inputPanelBackgroundSeparatorNode: ASDisplayNode
private var inputPanelBottomBackgroundSeparatorBaseOffset: CGFloat = 0.0
private let inputPanelBottomBackgroundSeparatorNode: ASDisplayNode private let inputPanelBottomBackgroundSeparatorNode: ASDisplayNode
private var plainInputSeparatorAlpha: CGFloat? private var plainInputSeparatorAlpha: CGFloat?
private var usePlainInputSeparator: Bool private var usePlainInputSeparator: Bool
@ -539,7 +540,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.inputPanelContainerNode.addSubnode(self.inputPanelClippingNode) self.inputPanelContainerNode.addSubnode(self.inputPanelClippingNode)
self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundNode) self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundNode)
self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundSeparatorNode) self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundSeparatorNode)
self.inputPanelClippingNode.addSubnode(self.inputPanelBottomBackgroundSeparatorNode) self.inputPanelBackgroundNode.addSubnode(self.inputPanelBottomBackgroundSeparatorNode)
self.contentContainerNode.addSubnode(self.inputContextPanelContainer) self.contentContainerNode.addSubnode(self.inputContextPanelContainer)
@ -773,8 +774,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.insertSubnode(navigationModalFrame, aboveSubnode: self.contentContainerNode) self.insertSubnode(navigationModalFrame, aboveSubnode: self.contentContainerNode)
} }
if transition.isAnimated, let animateFromFraction = animateFromFraction, animateFromFraction != 1.0 - self.inputPanelContainerNode.expansionFraction { 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: {}) navigationModalFrame.updateDismissal(transition: transition, progress: 1.0 - self.inputPanelContainerNode.expansionFraction, additionalProgress: 0.0, completion: {})
self.inputPanelClippingNode.clipsToBounds = true self.inputPanelClippingNode.clipsToBounds = true
@ -786,11 +789,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
navigationModalFrame?.removeFromSupernode() navigationModalFrame?.removeFromSupernode()
}) })
} }
self.inputPanelClippingNode.clipsToBounds = true
transition.updateCornerRadius(node: self.inputPanelClippingNode, cornerRadius: 0.0, completion: { [weak self] completed in transition.updateCornerRadius(node: self.inputPanelClippingNode, cornerRadius: 0.0, completion: { [weak self] completed in
guard let strongSelf = self, completed else { guard let strongSelf = self, completed else {
return 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 insets: UIEdgeInsets
var inputPanelBottomInsetTerm: CGFloat = 0.0
if inputNodeForState != nil { if inputNodeForState != nil {
insets = layout.insets(options: []) insets = layout.insets(options: [])
insets.bottom = max(insets.bottom, layout.standardInputHeight) inputPanelBottomInsetTerm = max(insets.bottom, layout.standardInputHeight)
} else { } else {
insets = layout.insets(options: [.input]) 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 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 let inputPanelNode = inputPanelNodes.primary, !previewing {
if inputPanelNode !== self.inputPanelNode { if inputPanelNode !== self.inputPanelNode {
if let inputTextPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { if let inputTextPanelNode = self.inputPanelNode as? ChatTextInputPanelNode {
if inputTextPanelNode.isFocused { if inputTextPanelNode.isFocused {
self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring)) 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) { if let prevInputPanelNode = self.inputPanelNode, inputPanelNode.canHandleTransition(from: prevInputPanelNode) {
inputPanelNodeHandlesTransition = true inputPanelNodeHandlesTransition = true
@ -1057,7 +1066,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} else { } else {
dismissedInputPanelNode = self.inputPanelNode 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) inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
self.inputPanelNode = inputPanelNode self.inputPanelNode = inputPanelNode
if inputPanelNode.supernode !== self { if inputPanelNode.supernode !== self {
@ -1065,7 +1074,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.inputPanelClippingNode.insertSubnode(inputPanelNode, aboveSubnode: self.inputPanelBackgroundNode) self.inputPanelClippingNode.insertSubnode(inputPanelNode, aboveSubnode: self.inputPanelBackgroundNode)
} }
} else { } 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) inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
} }
} else { } else {
@ -1076,7 +1085,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let secondaryInputPanelNode = inputPanelNodes.secondary, !previewing { if let secondaryInputPanelNode = inputPanelNodes.secondary, !previewing {
if secondaryInputPanelNode !== self.secondaryInputPanelNode { if secondaryInputPanelNode !== self.secondaryInputPanelNode {
dismissedSecondaryInputPanelNode = 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) secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
self.secondaryInputPanelNode = secondaryInputPanelNode self.secondaryInputPanelNode = secondaryInputPanelNode
if secondaryInputPanelNode.supernode == nil { if secondaryInputPanelNode.supernode == nil {
@ -1084,7 +1093,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.inputPanelClippingNode.insertSubnode(secondaryInputPanelNode, aboveSubnode: self.inputPanelBackgroundNode) self.inputPanelClippingNode.insertSubnode(secondaryInputPanelNode, aboveSubnode: self.inputPanelBackgroundNode)
} }
} else { } 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) secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
} }
} else { } else {
@ -1165,6 +1174,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
inputNode.topBackgroundExtensionUpdated = { [weak self] transition in inputNode.topBackgroundExtensionUpdated = { [weak self] transition in
self?.updateInputPanelBackgroundExtension(transition: transition) self?.updateInputPanelBackgroundExtension(transition: transition)
} }
inputNode.hideInputUpdated = { [weak self] transition in
self?.updateInputPanelBackgroundExpansion(transition: transition)
}
inputNode.expansionFractionUpdated = { [weak self] transition in inputNode.expansionFractionUpdated = { [weak self] transition in
self?.updateInputPanelBackgroundExpansion(transition: transition) 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 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) 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 { 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)) 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 { if self.dismissedAsOverlay {
inputPanelFrame!.origin.y = layout.size.height inputPanelFrame!.origin.y = layout.size.height
} }
@ -1362,6 +1383,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
inputPanelsHeight = 0.0 inputPanelsHeight = 0.0
} }
if let inputNode = self.inputNode {
if inputNode.hideInput {
inputPanelsHeight = 0.0
}
}
let inputBackgroundInset: CGFloat let inputBackgroundInset: CGFloat
if cleanInsets.bottom < insets.bottom { if cleanInsets.bottom < insets.bottom {
inputBackgroundInset = 0.0 inputBackgroundInset = 0.0
@ -1557,8 +1584,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
if immediatelyLayoutInputNodeAndAnimateAppearance { if immediatelyLayoutInputNodeAndAnimateAppearance {
inputPanelUpdateTransition = .immediate inputPanelUpdateTransition = .immediate
} }
self.inputPanelBackgroundNode.update(size: CGSize(width: intrinsicInputPanelBackgroundNodeSize.width, height: intrinsicInputPanelBackgroundNodeSize.height + inputPanelBackgroundExtension), transition: inputPanelUpdateTransition) 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.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) transition.updateFrame(node: self.navigateButtons, frame: apparentNavigateButtonsFrame)
@ -1670,9 +1699,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}) })
} }
if let inputPanelNode = self.inputPanelNode, if let inputPanelNode = self.inputPanelNode, let apparentInputPanelFrame = apparentInputPanelFrame, !inputPanelNode.frame.equalTo(apparentInputPanelFrame) {
let apparentInputPanelFrame = apparentInputPanelFrame,
!inputPanelNode.frame.equalTo(apparentInputPanelFrame) {
if immediatelyLayoutInputPanelAndAnimateAppearance { if immediatelyLayoutInputPanelAndAnimateAppearance {
inputPanelNode.frame = apparentInputPanelFrame.offsetBy(dx: 0.0, dy: apparentInputPanelFrame.height + previousInputPanelBackgroundFrame.maxY - apparentInputBackgroundFrame.maxY) inputPanelNode.frame = apparentInputPanelFrame.offsetBy(dx: 0.0, dy: apparentInputPanelFrame.height + previousInputPanelBackgroundFrame.maxY - apparentInputBackgroundFrame.maxY)
inputPanelNode.alpha = 0.0 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) 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) { 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) self.requestLayout(transition)
} }
@ -2222,6 +2265,18 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.view.window?.endEditing(true) 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) { private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) {
let requestId = self.scheduledLayoutTransitionRequestId let requestId = self.scheduledLayoutTransitionRequestId
self.scheduledLayoutTransitionRequestId += 1 self.scheduledLayoutTransitionRequestId += 1
@ -2248,7 +2303,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
let _ = peerId 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 return inputNode
} }

View File

@ -206,6 +206,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
return EmojiPagerContentComponent( return EmojiPagerContentComponent(
id: "emoji",
context: context, context: context,
animationCache: animationCache, animationCache: animationCache,
animationRenderer: animationRenderer, animationRenderer: animationRenderer,
@ -386,6 +387,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
return EmojiPagerContentComponent( return EmojiPagerContentComponent(
id: "stickers",
context: context, context: context,
animationCache: animationCache, animationCache: animationCache,
animationRenderer: animationRenderer, animationRenderer: animationRenderer,
@ -393,7 +395,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
var title: String? var title: String?
if group.id == AnyHashable("saved") { if group.id == AnyHashable("saved") {
title = nil //TODO:localize
title = "Saved".uppercased()
} else if group.id == AnyHashable("recent") { } else if group.id == AnyHashable("recent") {
//TODO:localize //TODO:localize
title = "Recently Used".uppercased() title = "Recently Used".uppercased()
@ -444,18 +447,30 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
} }
private let context: AccountContext
private let entityKeyboardView: ComponentHostView<Empty> private let entityKeyboardView: ComponentHostView<Empty>
private let defaultToEmojiTab: Bool private let defaultToEmojiTab: Bool
private var currentInputData: InputData private var currentInputData: InputData
private var inputDataDisposable: Disposable? 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)? 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.currentInputData = currentInputData
self.defaultToEmojiTab = defaultToEmojiTab self.defaultToEmojiTab = defaultToEmojiTab
self.controllerInteraction = controllerInteraction
self.entityKeyboardView = ComponentHostView<Empty>() self.entityKeyboardView = ComponentHostView<Empty>()
super.init() super.init()
@ -472,12 +487,48 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
strongSelf.currentInputData = inputData strongSelf.currentInputData = inputData
strongSelf.performLayout() 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 { deinit {
self.inputDataDisposable?.dispose() self.inputDataDisposable?.dispose()
} }
func markInputCollapsed() {
self.isMarkInputCollapsed = true
}
private func performLayout() { private func performLayout() {
guard let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.currentState else { guard let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.currentState else {
return 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) { 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) 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 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( let entityKeyboardSize = self.entityKeyboardView.update(
transition: Transition(transition), transition: mappedTransition,
component: AnyComponent(EntityKeyboardComponent( component: AnyComponent(EntityKeyboardComponent(
theme: interfaceState.theme, theme: interfaceState.theme,
bottomInset: bottomInset, bottomInset: bottomInset,
@ -508,7 +573,39 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
strongSelf.topBackgroundExtension = topPanelExtension strongSelf.topBackgroundExtension = topPanelExtension
strongSelf.topBackgroundExtensionUpdated?(transition.containedViewLayoutTransition) 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: {}, environment: {},
containerSize: CGSize(width: width, height: expandedHeight) containerSize: CGSize(width: width, height: expandedHeight)

View File

@ -16,6 +16,9 @@ class ChatInputNode: ASDisplayNode {
var topBackgroundExtension: CGFloat = 41.0 var topBackgroundExtension: CGFloat = 41.0
var topBackgroundExtensionUpdated: ((ContainedViewLayoutTransition) -> Void)? var topBackgroundExtensionUpdated: ((ContainedViewLayoutTransition) -> Void)?
var hideInput: Bool = false
var hideInputUpdated: ((ContainedViewLayoutTransition) -> Void)?
var expansionFraction: CGFloat = 0.0 var expansionFraction: CGFloat = 0.0
var expansionFractionUpdated: ((ContainedViewLayoutTransition) -> Void)? var expansionFractionUpdated: ((ContainedViewLayoutTransition) -> Void)?

View File

@ -2454,10 +2454,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
} }
} else { } else {
switch presentationInterfaceState.inputMode { switch presentationInterfaceState.inputMode {
case .text, .media: case .text:
self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId { _ in self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId { _ in
return (.none, nil) return (.none, nil)
} }
case .media:
break
default: default:
break break
} }

View File

@ -8,6 +8,7 @@ import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import AccountContext import AccountContext
import ChatPresentationInterfaceState import ChatPresentationInterfaceState
import EntityKeyboard
private let searchBarHeight: CGFloat = 52.0 private let searchBarHeight: CGFloat = 52.0
@ -27,7 +28,7 @@ protocol PaneSearchContentNode {
func itemAt(point: CGPoint) -> (ASDisplayNode, Any)? func itemAt(point: CGPoint) -> (ASDisplayNode, Any)?
} }
final class PaneSearchContainerNode: ASDisplayNode { final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainerNode {
private let context: AccountContext private let context: AccountContext
private let mode: ChatMediaInputSearchMode private let mode: ChatMediaInputSearchMode
public private(set) var contentNode: PaneSearchContentNode & ASDisplayNode public private(set) var contentNode: PaneSearchContentNode & ASDisplayNode
@ -39,6 +40,8 @@ final class PaneSearchContainerNode: ASDisplayNode {
private var validLayout: CGSize? private var validLayout: CGSize?
var onCancel: (() -> Void)?
var openGifContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)? var openGifContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)?
var ready: Signal<Void, NoError> { var ready: Signal<Void, NoError> {
@ -75,8 +78,11 @@ final class PaneSearchContainerNode: ASDisplayNode {
self?.searchBar.activity = active self?.searchBar.activity = active
} }
self.searchBar.cancel = { self.searchBar.cancel = { [weak self] in
cancel() cancel()
self?.searchBar.view.endEditing(true)
self?.onCancel?()
} }
self.searchBar.activate() self.searchBar.activate()