mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-29 19:35:08 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
907918cead
@ -352,7 +352,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
||||
guard let presentationInterfaceState = self.presentationInterfaceState else {
|
||||
return 0.0
|
||||
}
|
||||
return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: .immediate, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact))
|
||||
return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: .immediate, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), isMediaInputExpanded: false)
|
||||
}
|
||||
|
||||
public func setCaption(_ caption: NSAttributedString?) {
|
||||
@ -496,7 +496,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
||||
return minimalHeight
|
||||
}
|
||||
|
||||
public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
let hadLayout = self.validLayout != nil
|
||||
let previousAdditionalSideInsets = self.validLayout?.3
|
||||
self.validLayout = (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, isSecondary)
|
||||
@ -1110,7 +1110,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
||||
self.focusUpdated?(true)
|
||||
|
||||
if self.isCaption, let (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, isSecondary) = self.validLayout, let presentationInterfaceState = self.presentationInterfaceState {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: 0.0, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .animated(duration: 0.3, curve: .easeInOut), interfaceState: presentationInterfaceState, metrics: metrics)
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: 0.0, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .animated(duration: 0.3, curve: .easeInOut), interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1121,7 +1121,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
||||
self.focusUpdated?(false)
|
||||
|
||||
if self.isCaption, let (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, isSecondary) = self.validLayout, let presentationInterfaceState = self.presentationInterfaceState {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: 0.0, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .animated(duration: 0.3, curve: .easeInOut), interfaceState: presentationInterfaceState, metrics: metrics)
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: 0.0, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .animated(duration: 0.3, curve: .easeInOut), interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1098,7 +1098,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
if textInputPanelNode.frame.width.isZero {
|
||||
panelTransition = .immediate
|
||||
}
|
||||
let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left + layout.safeInsets.left, rightInset: insets.right + layout.safeInsets.right, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics)
|
||||
let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left + layout.safeInsets.left, rightInset: insets.right + layout.safeInsets.right, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false)
|
||||
let panelFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: panelHeight)
|
||||
if textInputPanelNode.frame.width.isZero {
|
||||
textInputPanelNode.frame = panelFrame
|
||||
|
||||
@ -1312,6 +1312,8 @@ public final class ChatListNode: ListView {
|
||||
switch activity {
|
||||
case .interactingWithEmoji:
|
||||
return true
|
||||
case .speakingInGroupCall:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@ -1343,9 +1345,9 @@ public final class ChatListNode: ListView {
|
||||
return engine.data.get(EngineDataMap(
|
||||
activitiesByPeerId.keys.filter { key in
|
||||
if case .global = key.category {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}.map { key in
|
||||
return TelegramEngine.EngineData.Item.Peer.Peer(id: key.peerId)
|
||||
|
||||
@ -3,13 +3,42 @@ import AccountContext
|
||||
import SwiftSignalKit
|
||||
|
||||
public enum ChatTextInputAccessoryItem: Equatable {
|
||||
public enum Key: Hashable {
|
||||
case keyboard
|
||||
case stickers(Bool)
|
||||
case stickers
|
||||
case inputButtons
|
||||
case commands
|
||||
case silentPost
|
||||
case messageAutoremoveTimeout
|
||||
case scheduledMessages
|
||||
}
|
||||
|
||||
case keyboard
|
||||
case stickers(isEnabled: Bool, isEmoji: Bool)
|
||||
case inputButtons
|
||||
case commands
|
||||
case silentPost(Bool)
|
||||
case messageAutoremoveTimeout(Int32?)
|
||||
case scheduledMessages
|
||||
|
||||
public var key: Key {
|
||||
switch self {
|
||||
case .keyboard:
|
||||
return .keyboard
|
||||
case .stickers:
|
||||
return .stickers
|
||||
case .inputButtons:
|
||||
return .inputButtons
|
||||
case .commands:
|
||||
return .commands
|
||||
case .silentPost:
|
||||
return .silentPost
|
||||
case .messageAutoremoveTimeout:
|
||||
return .messageAutoremoveTimeout
|
||||
case .scheduledMessages:
|
||||
return .scheduledMessages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class InstantVideoControllerRecordingStatus {
|
||||
|
||||
@ -128,6 +128,16 @@ private extension Transition.Animation.Curve {
|
||||
}
|
||||
}
|
||||
|
||||
public extension Transition.Animation {
|
||||
var isImmediate: Bool {
|
||||
if case .none = self {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Transition {
|
||||
public enum Animation {
|
||||
public enum Curve {
|
||||
@ -245,6 +255,61 @@ public struct Transition {
|
||||
}
|
||||
}
|
||||
|
||||
public func setBounds(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.bounds == bounds {
|
||||
completion?(true)
|
||||
return
|
||||
}
|
||||
switch self.animation {
|
||||
case .none:
|
||||
layer.bounds = bounds
|
||||
layer.removeAnimation(forKey: "bounds")
|
||||
completion?(true)
|
||||
case .curve:
|
||||
let previousBounds = layer.presentation()?.bounds ?? layer.bounds
|
||||
layer.bounds = bounds
|
||||
|
||||
self.animateBounds(layer: layer, from: previousBounds, to: layer.bounds, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
public func setPosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.position == position {
|
||||
completion?(true)
|
||||
return
|
||||
}
|
||||
switch self.animation {
|
||||
case .none:
|
||||
layer.position = position
|
||||
layer.removeAnimation(forKey: "position")
|
||||
completion?(true)
|
||||
case .curve:
|
||||
let previousPosition = layer.presentation()?.position ?? layer.position
|
||||
layer.position = position
|
||||
|
||||
self.animatePosition(layer: layer, from: previousPosition, to: layer.position, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
public func attachAnimation(view: UIView, completion: @escaping (Bool) -> Void) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
completion(true)
|
||||
case let .curve(duration, curve):
|
||||
view.layer.animate(
|
||||
from: 0.0 as NSNumber,
|
||||
to: 1.0 as NSNumber,
|
||||
keyPath: "attached\(UInt32.random(in: 0 ... UInt32.max))",
|
||||
duration: duration,
|
||||
delay: 0.0,
|
||||
curve: curve,
|
||||
removeOnCompletion: true,
|
||||
additive: true,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func setAlpha(view: UIView, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) {
|
||||
if view.alpha == alpha {
|
||||
completion?(true)
|
||||
@ -356,11 +421,19 @@ public struct Transition {
|
||||
}
|
||||
|
||||
public func animatePosition(view: UIView, from fromValue: CGPoint, to toValue: CGPoint, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
self.animatePosition(layer: view.layer, from: fromValue, to: toValue, additive: additive, completion: completion)
|
||||
}
|
||||
|
||||
public func animateBounds(view: UIView, from fromValue: CGRect, to toValue: CGRect, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
self.animateBounds(layer: view.layer, from: fromValue, to: toValue, additive: additive, completion: completion)
|
||||
}
|
||||
|
||||
public func animatePosition(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
view.layer.animate(
|
||||
layer.animate(
|
||||
from: NSValue(cgPoint: fromValue),
|
||||
to: NSValue(cgPoint: toValue),
|
||||
keyPath: "position",
|
||||
@ -374,12 +447,12 @@ public struct Transition {
|
||||
}
|
||||
}
|
||||
|
||||
public func animateBounds(view: UIView, from fromValue: CGRect, to toValue: CGRect, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
public func animateBounds(layer: CALayer, from fromValue: CGRect, to toValue: CGRect, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
break
|
||||
case let .curve(duration, curve):
|
||||
view.layer.animate(
|
||||
layer.animate(
|
||||
from: NSValue(cgRect: fromValue),
|
||||
to: NSValue(cgRect: toValue),
|
||||
keyPath: "bounds",
|
||||
|
||||
@ -7,6 +7,7 @@ public final class Button: Component {
|
||||
public let tag: AnyObject?
|
||||
public let automaticHighlight: Bool
|
||||
public let action: () -> Void
|
||||
public let holdAction: (() -> Void)?
|
||||
|
||||
convenience public init(
|
||||
content: AnyComponent<Empty>,
|
||||
@ -17,7 +18,8 @@ public final class Button: Component {
|
||||
minSize: nil,
|
||||
tag: nil,
|
||||
automaticHighlight: true,
|
||||
action: action
|
||||
action: action,
|
||||
holdAction: nil
|
||||
)
|
||||
}
|
||||
|
||||
@ -26,13 +28,15 @@ public final class Button: Component {
|
||||
minSize: CGSize? = nil,
|
||||
tag: AnyObject? = nil,
|
||||
automaticHighlight: Bool = true,
|
||||
action: @escaping () -> Void
|
||||
action: @escaping () -> Void,
|
||||
holdAction: (() -> Void)?
|
||||
) {
|
||||
self.content = content
|
||||
self.minSize = minSize
|
||||
self.tag = tag
|
||||
self.automaticHighlight = automaticHighlight
|
||||
self.action = action
|
||||
self.holdAction = holdAction
|
||||
}
|
||||
|
||||
public func minSize(_ minSize: CGSize?) -> Button {
|
||||
@ -41,7 +45,19 @@ public final class Button: Component {
|
||||
minSize: minSize,
|
||||
tag: self.tag,
|
||||
automaticHighlight: self.automaticHighlight,
|
||||
action: self.action
|
||||
action: self.action,
|
||||
holdAction: self.holdAction
|
||||
)
|
||||
}
|
||||
|
||||
public func withHoldAction(_ holdAction: (() -> Void)?) -> Button {
|
||||
return Button(
|
||||
content: self.content,
|
||||
minSize: self.minSize,
|
||||
tag: self.tag,
|
||||
automaticHighlight: self.automaticHighlight,
|
||||
action: self.action,
|
||||
holdAction: holdAction
|
||||
)
|
||||
}
|
||||
|
||||
@ -51,7 +67,8 @@ public final class Button: Component {
|
||||
minSize: self.minSize,
|
||||
tag: tag,
|
||||
automaticHighlight: self.automaticHighlight,
|
||||
action: self.action
|
||||
action: self.action,
|
||||
holdAction: self.holdAction
|
||||
)
|
||||
}
|
||||
|
||||
@ -86,6 +103,9 @@ public final class Button: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private var holdActionTriggerred: Bool = false
|
||||
private var holdActionTimer: Timer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.contentView = ComponentHostView<Empty>()
|
||||
self.contentView.isUserInteractionEnabled = false
|
||||
@ -101,6 +121,10 @@ public final class Button: Component {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.holdActionTimer?.invalidate()
|
||||
}
|
||||
|
||||
public func matches(tag: Any) -> Bool {
|
||||
if let component = self.component, let componentTag = component.tag {
|
||||
let tag = tag as AnyObject
|
||||
@ -112,24 +136,69 @@ public final class Button: Component {
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
if self.holdActionTriggerred {
|
||||
self.holdActionTriggerred = false
|
||||
} else {
|
||||
self.component?.action()
|
||||
}
|
||||
}
|
||||
|
||||
override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
self.currentIsHighlighted = true
|
||||
|
||||
self.holdActionTriggerred = false
|
||||
|
||||
if self.component?.holdAction != nil {
|
||||
self.holdActionTriggerred = true
|
||||
self.component?.action()
|
||||
|
||||
self.holdActionTimer?.invalidate()
|
||||
if #available(iOS 10.0, *) {
|
||||
let holdActionTimer = Timer(timeInterval: 1.0, repeats: false, block: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.holdActionTimer?.invalidate()
|
||||
strongSelf.component?.holdAction?()
|
||||
strongSelf.beginExecuteHoldActionTimer()
|
||||
})
|
||||
self.holdActionTimer = holdActionTimer
|
||||
RunLoop.main.add(holdActionTimer, forMode: .common)
|
||||
}
|
||||
}
|
||||
|
||||
return super.beginTracking(touch, with: event)
|
||||
}
|
||||
|
||||
private func beginExecuteHoldActionTimer() {
|
||||
self.holdActionTimer?.invalidate()
|
||||
if #available(iOS 10.0, *) {
|
||||
let holdActionTimer = Timer(timeInterval: 0.2, repeats: true, block: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.component?.holdAction?()
|
||||
})
|
||||
self.holdActionTimer = holdActionTimer
|
||||
RunLoop.main.add(holdActionTimer, forMode: .common)
|
||||
}
|
||||
}
|
||||
|
||||
override public func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||
self.currentIsHighlighted = false
|
||||
|
||||
self.holdActionTimer?.invalidate()
|
||||
self.holdActionTimer = nil
|
||||
|
||||
super.endTracking(touch, with: event)
|
||||
}
|
||||
|
||||
override public func cancelTracking(with event: UIEvent?) {
|
||||
self.currentIsHighlighted = false
|
||||
|
||||
self.holdActionTimer?.invalidate()
|
||||
self.holdActionTimer = nil
|
||||
|
||||
super.cancelTracking(with: event)
|
||||
}
|
||||
|
||||
|
||||
@ -106,9 +106,22 @@ public final class ComponentHostView<EnvironmentType>: UIView {
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let result = super.hitTest(point, with: event)
|
||||
if self.alpha.isZero {
|
||||
return nil
|
||||
}
|
||||
for view in self.subviews.reversed() {
|
||||
if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
let result = super.hitTest(point, with: event)
|
||||
if result != self {
|
||||
return result
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func findTaggedView(tag: Any) -> UIView? {
|
||||
guard let componentView = self.componentView else {
|
||||
|
||||
@ -157,9 +157,6 @@ public final class LottieAnimationComponent: Component {
|
||||
|
||||
for (key, value) in component.animation.colors {
|
||||
view.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: "\(key).Color"))
|
||||
/*let colorCallback = LOTColorValueCallback(color: value.cgColor)
|
||||
self.colorCallbacks.append(colorCallback)
|
||||
view.setValueDelegate(colorCallback, for: LOTKeypath(string: "\(key).Color"))*/
|
||||
}
|
||||
|
||||
self.animationView = view
|
||||
|
||||
@ -9,11 +9,15 @@ public protocol PagerExpandableScrollView: UIScrollView {
|
||||
public protocol PagerPanGestureRecognizer: UIGestureRecognizer {
|
||||
}
|
||||
|
||||
open class PagerExternalTopPanelContainer: SparseContainerView {
|
||||
}
|
||||
|
||||
public final class PagerComponentChildEnvironment: Equatable {
|
||||
public struct ContentScrollingUpdate {
|
||||
public var relativeOffset: CGFloat
|
||||
public var absoluteOffsetToTopEdge: CGFloat?
|
||||
public var absoluteOffsetToBottomEdge: CGFloat?
|
||||
public var isReset: Bool
|
||||
public var isInteracting: Bool
|
||||
public var transition: Transition
|
||||
|
||||
@ -21,12 +25,14 @@ public final class PagerComponentChildEnvironment: Equatable {
|
||||
relativeOffset: CGFloat,
|
||||
absoluteOffsetToTopEdge: CGFloat?,
|
||||
absoluteOffsetToBottomEdge: CGFloat?,
|
||||
isReset: Bool,
|
||||
isInteracting: Bool,
|
||||
transition: Transition
|
||||
) {
|
||||
self.relativeOffset = relativeOffset
|
||||
self.absoluteOffsetToTopEdge = absoluteOffsetToTopEdge
|
||||
self.absoluteOffsetToBottomEdge = absoluteOffsetToBottomEdge
|
||||
self.isReset = isReset
|
||||
self.isInteracting = isInteracting
|
||||
self.transition = transition
|
||||
}
|
||||
@ -61,6 +67,7 @@ public final class PagerComponentPanelEnvironment<TopPanelEnvironment>: Equatabl
|
||||
public let activeContentId: AnyHashable?
|
||||
public let navigateToContentId: (AnyHashable) -> Void
|
||||
public let visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>
|
||||
public let isExpandedUpdated: (Bool, Transition) -> Void
|
||||
|
||||
init(
|
||||
contentOffset: CGFloat,
|
||||
@ -70,7 +77,8 @@ public final class PagerComponentPanelEnvironment<TopPanelEnvironment>: Equatabl
|
||||
contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>],
|
||||
activeContentId: AnyHashable?,
|
||||
navigateToContentId: @escaping (AnyHashable) -> Void,
|
||||
visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>
|
||||
visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>,
|
||||
isExpandedUpdated: @escaping (Bool, Transition) -> Void
|
||||
) {
|
||||
self.contentOffset = contentOffset
|
||||
self.contentTopPanels = contentTopPanels
|
||||
@ -80,6 +88,7 @@ public final class PagerComponentPanelEnvironment<TopPanelEnvironment>: Equatabl
|
||||
self.activeContentId = activeContentId
|
||||
self.navigateToContentId = navigateToContentId
|
||||
self.visibilityFractionUpdated = visibilityFractionUpdated
|
||||
self.isExpandedUpdated = isExpandedUpdated
|
||||
}
|
||||
|
||||
public static func ==(lhs: PagerComponentPanelEnvironment, rhs: PagerComponentPanelEnvironment) -> Bool {
|
||||
@ -122,6 +131,12 @@ public final class PagerComponentViewTag {
|
||||
}
|
||||
}
|
||||
|
||||
public enum PagerComponentPanelHideBehavior {
|
||||
case hideOnScroll
|
||||
case show
|
||||
case hide
|
||||
}
|
||||
|
||||
public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvironment: Equatable>: Component {
|
||||
public typealias EnvironmentType = ChildEnvironmentType
|
||||
|
||||
@ -134,10 +149,11 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
public let defaultId: AnyHashable?
|
||||
public let contentBackground: AnyComponent<Empty>?
|
||||
public let topPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?
|
||||
public let externalTopPanelContainer: UIView?
|
||||
public let externalTopPanelContainer: PagerExternalTopPanelContainer?
|
||||
public let bottomPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?
|
||||
public let panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?
|
||||
public let hidePanels: Bool
|
||||
public let isTopPanelExpandedUpdated: (Bool, Transition) -> Void
|
||||
public let panelHideBehavior: PagerComponentPanelHideBehavior
|
||||
|
||||
public init(
|
||||
contentInsets: UIEdgeInsets,
|
||||
@ -149,10 +165,11 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
defaultId: AnyHashable?,
|
||||
contentBackground: AnyComponent<Empty>?,
|
||||
topPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?,
|
||||
externalTopPanelContainer: UIView?,
|
||||
externalTopPanelContainer: PagerExternalTopPanelContainer?,
|
||||
bottomPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?,
|
||||
panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?,
|
||||
hidePanels: Bool
|
||||
isTopPanelExpandedUpdated: @escaping (Bool, Transition) -> Void,
|
||||
panelHideBehavior: PagerComponentPanelHideBehavior
|
||||
) {
|
||||
self.contentInsets = contentInsets
|
||||
self.contents = contents
|
||||
@ -166,7 +183,8 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
self.externalTopPanelContainer = externalTopPanelContainer
|
||||
self.bottomPanel = bottomPanel
|
||||
self.panelStateUpdated = panelStateUpdated
|
||||
self.hidePanels = hidePanels
|
||||
self.isTopPanelExpandedUpdated = isTopPanelExpandedUpdated
|
||||
self.panelHideBehavior = panelHideBehavior
|
||||
}
|
||||
|
||||
public static func ==(lhs: PagerComponent, rhs: PagerComponent) -> Bool {
|
||||
@ -197,7 +215,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
if lhs.bottomPanel != rhs.bottomPanel {
|
||||
return false
|
||||
}
|
||||
if lhs.hidePanels != rhs.hidePanels {
|
||||
if lhs.panelHideBehavior != rhs.panelHideBehavior {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -241,6 +259,8 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
|
||||
private var panRecognizer: PagerPanGestureRecognizerImpl?
|
||||
|
||||
private var isTopPanelExpanded: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
@ -309,6 +329,13 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
}
|
||||
|
||||
func update(component: PagerComponent<ChildEnvironmentType, TopPanelEnvironment>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
let previousPanelHideBehavior = self.component?.panelHideBehavior
|
||||
|
||||
var panelStateTransition = transition
|
||||
if let previousPanelHideBehavior = previousPanelHideBehavior, previousPanelHideBehavior != component.panelHideBehavior, panelStateTransition.animation.isImmediate {
|
||||
panelStateTransition = Transition(animation: .curve(duration: 0.3, curve: .spring))
|
||||
}
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
@ -345,7 +372,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
var contentInsets = component.contentInsets
|
||||
|
||||
var scrollingPanelOffsetFraction: CGFloat
|
||||
if let centralId = centralId, let centralContentView = self.contentViews[centralId] {
|
||||
if case .show = component.panelHideBehavior {
|
||||
scrollingPanelOffsetFraction = 0.0
|
||||
} else if let centralId = centralId, let centralContentView = self.contentViews[centralId] {
|
||||
scrollingPanelOffsetFraction = centralContentView.scrollingPanelOffsetFraction
|
||||
} else {
|
||||
scrollingPanelOffsetFraction = 0.0
|
||||
@ -354,13 +383,12 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
var topPanelHeight: CGFloat = 0.0
|
||||
if let topPanel = component.topPanel {
|
||||
let topPanelView: ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>
|
||||
var topPanelTransition = transition
|
||||
var topPanelTransition = panelStateTransition
|
||||
if let current = self.topPanelView {
|
||||
topPanelView = current
|
||||
} else {
|
||||
topPanelTransition = .immediate
|
||||
topPanelView = ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>()
|
||||
topPanelView.clipsToBounds = true
|
||||
self.topPanelView = topPanelView
|
||||
}
|
||||
|
||||
@ -381,7 +409,13 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
contentAccessoryRightButtons: [],
|
||||
activeContentId: centralId,
|
||||
navigateToContentId: navigateToContentId,
|
||||
visibilityFractionUpdated: self.topPanelVisibilityFractionUpdated
|
||||
visibilityFractionUpdated: self.topPanelVisibilityFractionUpdated,
|
||||
isExpandedUpdated: { [weak self] isExpanded, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isTopPanelExpandedUpdated(isExpanded: isExpanded, transition: transition)
|
||||
}
|
||||
)
|
||||
},
|
||||
containerSize: availableSize
|
||||
@ -392,26 +426,31 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
var topPanelOffset = topPanelSize.height * scrollingPanelOffsetFraction
|
||||
|
||||
var topPanelVisibilityFraction: CGFloat = 1.0 - scrollingPanelOffsetFraction
|
||||
if component.hidePanels {
|
||||
switch component.panelHideBehavior {
|
||||
case .hide:
|
||||
topPanelVisibilityFraction = 0.0
|
||||
case .show:
|
||||
topPanelVisibilityFraction = 1.0
|
||||
case .hideOnScroll:
|
||||
break
|
||||
}
|
||||
|
||||
self.topPanelVisibilityFractionUpdated.invoke((topPanelVisibilityFraction, topPanelTransition))
|
||||
|
||||
topPanelHeight = max(0.0, topPanelSize.height - topPanelOffset)
|
||||
|
||||
if component.hidePanels {
|
||||
if case .hide = component.panelHideBehavior {
|
||||
topPanelOffset = topPanelSize.height
|
||||
}
|
||||
|
||||
if component.externalTopPanelContainer != nil {
|
||||
var visibleTopPanelHeight = max(0.0, topPanelSize.height - topPanelOffset)
|
||||
if component.hidePanels {
|
||||
if case .hide = component.panelHideBehavior {
|
||||
visibleTopPanelHeight = 0.0
|
||||
}
|
||||
transition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(), size: CGSize(width: topPanelSize.width, height: visibleTopPanelHeight)))
|
||||
panelStateTransition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(), size: CGSize(width: topPanelSize.width, height: visibleTopPanelHeight)))
|
||||
} else {
|
||||
transition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelOffset), size: topPanelSize))
|
||||
panelStateTransition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelOffset), size: topPanelSize))
|
||||
}
|
||||
|
||||
contentInsets.top += topPanelSize.height
|
||||
@ -428,7 +467,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
var bottomPanelOffset: CGFloat = 0.0
|
||||
if let bottomPanel = component.bottomPanel {
|
||||
let bottomPanelView: ComponentHostView<PagerComponentPanelEnvironment<TopPanelEnvironment>>
|
||||
var bottomPanelTransition = transition
|
||||
var bottomPanelTransition = panelStateTransition
|
||||
if let current = self.bottomPanelView {
|
||||
bottomPanelView = current
|
||||
} else {
|
||||
@ -449,7 +488,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
contentAccessoryRightButtons: component.contentAccessoryRightButtons,
|
||||
activeContentId: centralId,
|
||||
navigateToContentId: navigateToContentId,
|
||||
visibilityFractionUpdated: self.bottomPanelVisibilityFractionUpdated
|
||||
visibilityFractionUpdated: self.bottomPanelVisibilityFractionUpdated,
|
||||
isExpandedUpdated: { _, _ in
|
||||
}
|
||||
)
|
||||
},
|
||||
containerSize: availableSize
|
||||
@ -458,11 +499,11 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
self.bottomPanelHeight = bottomPanelSize.height
|
||||
|
||||
bottomPanelOffset = bottomPanelSize.height * scrollingPanelOffsetFraction
|
||||
if component.hidePanels {
|
||||
if case .hide = component.panelHideBehavior {
|
||||
bottomPanelOffset = bottomPanelSize.height
|
||||
}
|
||||
|
||||
transition.setFrame(view: bottomPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelSize.height + bottomPanelOffset), size: bottomPanelSize))
|
||||
panelStateTransition.setFrame(view: bottomPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelSize.height + bottomPanelOffset), size: bottomPanelSize))
|
||||
|
||||
contentInsets.bottom += bottomPanelSize.height
|
||||
} else {
|
||||
@ -475,11 +516,17 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
self.bottomPanelHeight = 0.0
|
||||
}
|
||||
|
||||
let effectiveTopPanelHeight: CGFloat = component.hidePanels ? 0.0 : topPanelHeight
|
||||
let effectiveTopPanelHeight: CGFloat
|
||||
switch component.panelHideBehavior {
|
||||
case .hide:
|
||||
effectiveTopPanelHeight = 0.0
|
||||
case .show, .hideOnScroll:
|
||||
effectiveTopPanelHeight = topPanelHeight
|
||||
}
|
||||
|
||||
if let contentBackground = component.contentBackground {
|
||||
let contentBackgroundView: ComponentHostView<Empty>
|
||||
var contentBackgroundTransition = transition
|
||||
var contentBackgroundTransition = panelStateTransition
|
||||
if let current = self.contentBackgroundView {
|
||||
contentBackgroundView = current
|
||||
} else {
|
||||
@ -633,7 +680,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
PagerComponentPanelState(
|
||||
topPanelHeight: topPanelHeight
|
||||
),
|
||||
transition
|
||||
panelStateTransition
|
||||
)
|
||||
}
|
||||
|
||||
@ -647,6 +694,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
|
||||
var offsetDelta: CGFloat?
|
||||
offsetDelta = (update.absoluteOffsetToTopEdge ?? 0.0) - contentView.scrollingPanelOffsetToTopEdge
|
||||
if update.isReset {
|
||||
offsetDelta = 0.0
|
||||
}
|
||||
|
||||
contentView.scrollingPanelOffsetToTopEdge = update.absoluteOffsetToTopEdge ?? 0.0
|
||||
contentView.scrollingPanelOffsetToBottomEdge = update.absoluteOffsetToBottomEdge ?? .greatestFiniteMagnitude
|
||||
@ -682,10 +732,24 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
contentView.scrollingPanelOffsetFraction = scrollingPanelOffsetFraction
|
||||
self.state?.updated(transition: transition)
|
||||
}
|
||||
|
||||
if self.isTopPanelExpanded {
|
||||
self.isTopPanelExpanded = false
|
||||
self.component?.isTopPanelExpandedUpdated(self.isTopPanelExpanded, Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func isTopPanelExpandedUpdated(isExpanded: Bool, transition: Transition) {
|
||||
if self.isTopPanelExpanded == isExpanded {
|
||||
return
|
||||
}
|
||||
self.isTopPanelExpanded = isExpanded
|
||||
|
||||
self.component?.isTopPanelExpandedUpdated(self.isTopPanelExpanded, transition)
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ public func cancelParentGestures(view: UIView, ignore: [UIGestureRecognizer] = [
|
||||
node.highligthedChanged(false)
|
||||
}
|
||||
if let superview = view.superview {
|
||||
cancelParentGestures(view: superview)
|
||||
cancelParentGestures(view: superview, ignore: ignore)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,8 @@ public class PortalView {
|
||||
|
||||
if let portalSuperview = self.view.superview, let index = portalSuperview.subviews.firstIndex(of: self.view) {
|
||||
portalSuperview.insertSubview(self.view, at: index)
|
||||
} else if let portalSuperlayer = self.view.layer.superlayer, let index = portalSuperlayer.sublayers?.firstIndex(of: self.view.layer) {
|
||||
portalSuperlayer.insertSublayer(self.view.layer, at: UInt32(index))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1435,6 +1435,10 @@ public class TextNode: ASDisplayNode {
|
||||
for run in glyphRuns {
|
||||
let run = run as! CTRun
|
||||
let glyphCount = CTRunGetGlyphCount(run)
|
||||
let attributes = CTRunGetAttributes(run) as NSDictionary
|
||||
if attributes["Attribute__EmbeddedItem"] != nil {
|
||||
continue
|
||||
}
|
||||
CTRunDraw(run, context, CFRangeMake(0, glyphCount))
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +51,9 @@ private func chatInputStateString(attributedString: NSAttributedString) -> NSAtt
|
||||
if let _ = attributes[.underlineStyle] {
|
||||
string.addAttribute(ChatTextInputAttributes.underline, value: true as NSNumber, range: range)
|
||||
}
|
||||
if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
|
||||
string.addAttribute(ChatTextInputAttributes.customEmoji, value: value, range: range)
|
||||
}
|
||||
})
|
||||
return string
|
||||
}
|
||||
|
||||
@ -430,3 +430,69 @@ public final class ShimmerEffectNode: ASDisplayNode {
|
||||
self.effectNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
}
|
||||
|
||||
public final class StandaloneShimmerEffect {
|
||||
private var image: UIImage?
|
||||
|
||||
private var background: UIColor?
|
||||
private var foreground: UIColor?
|
||||
|
||||
public var layer: CALayer? {
|
||||
didSet {
|
||||
if self.layer !== oldValue {
|
||||
self.updateLayer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
public func update(background: UIColor, foreground: UIColor) {
|
||||
if self.background == background && self.foreground == foreground {
|
||||
return
|
||||
}
|
||||
self.background = background
|
||||
self.foreground = foreground
|
||||
|
||||
self.image = generateImage(CGSize(width: 1.0, height: 320.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(background.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let transparentColor = foreground.withAlphaComponent(0.0).cgColor
|
||||
let peakColor = foreground.cgColor
|
||||
|
||||
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
})
|
||||
|
||||
self.updateLayer()
|
||||
}
|
||||
|
||||
public func updateLayer() {
|
||||
guard let layer = self.layer, let image = self.image else {
|
||||
return
|
||||
}
|
||||
|
||||
layer.contents = image.cgImage
|
||||
|
||||
if layer.animation(forKey: "shimmer") == nil {
|
||||
let animation = CABasicAnimation(keyPath: "contentsRect.origin.y")
|
||||
animation.fromValue = 1.0 as NSNumber
|
||||
animation.toValue = -1.0 as NSNumber
|
||||
animation.isAdditive = true
|
||||
animation.repeatCount = .infinity
|
||||
animation.duration = 0.8
|
||||
animation.beginTime = layer.convertTime(1.0, from: nil)
|
||||
layer.add(animation, forKey: "shimmer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,8 +21,8 @@ private func decodeStickerThumbnailData(_ data: Data) -> String {
|
||||
return string
|
||||
}
|
||||
|
||||
public func generateStickerPlaceholderImage(data: Data?, size: CGSize, imageSize: CGSize, backgroundColor: UIColor?, foregroundColor: UIColor) -> UIImage? {
|
||||
return generateImage(size, rotatedContext: { size, context in
|
||||
public func generateStickerPlaceholderImage(data: Data?, size: CGSize, scale: CGFloat? = nil, imageSize: CGSize, backgroundColor: UIColor?, foregroundColor: UIColor) -> UIImage? {
|
||||
return generateImage(size, scale: scale, rotatedContext: { size, context in
|
||||
if let backgroundColor = backgroundColor {
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
|
||||
@ -291,6 +291,8 @@ swift_library(
|
||||
"//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters",
|
||||
"//submodules/Media/ConvertOpusToAAC:ConvertOpusToAAC",
|
||||
"//submodules/Media/LocalAudioTranscription:LocalAudioTranscription",
|
||||
"//submodules/Components/PagerComponent:PagerComponent",
|
||||
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
|
||||
@ -7,21 +7,18 @@ import PagerComponent
|
||||
private func traceScrollView(view: UIView, point: CGPoint) -> (UIScrollView?, Bool) {
|
||||
for subview in view.subviews.reversed() {
|
||||
let subviewPoint = view.convert(point, to: subview)
|
||||
if subview.frame.contains(point) {
|
||||
if subview.frame.contains(point) || subview is PagerExternalTopPanelContainer {
|
||||
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{
|
||||
} 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)
|
||||
@ -31,6 +28,7 @@ private final class ExpansionPanRecognizer: UIGestureRecognizer, UIGestureRecogn
|
||||
enum LockDirection {
|
||||
case up
|
||||
case down
|
||||
case any
|
||||
}
|
||||
|
||||
var requiredLockDirection: LockDirection = .up
|
||||
@ -83,14 +81,23 @@ private final class ExpansionPanRecognizer: UIGestureRecognizer, UIGestureRecogn
|
||||
|
||||
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) {
|
||||
|
||||
let hitView = view.hitTest(point, with: event)
|
||||
|
||||
if let _ = hitView as? UIButton {
|
||||
} else if let hitView = hitView, hitView.asyncdisplaykit_node is ASButtonNode {
|
||||
} else {
|
||||
if let scrollView = traceScrollView(view: view, point: point).0 {
|
||||
if scrollView is ListViewScroller || scrollView is GridNodeScrollerView {
|
||||
found = false
|
||||
} else {
|
||||
found = true
|
||||
}
|
||||
} else {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
self.beginPosition = point
|
||||
} else {
|
||||
@ -118,28 +125,46 @@ private final class ExpansionPanRecognizer: UIGestureRecognizer, UIGestureRecogn
|
||||
}
|
||||
var lockDirection: LockDirection?
|
||||
let point = touch.location(in: self.view)
|
||||
let tracedView = view.hitTest(point, with: event)
|
||||
if let scrollView = traceScrollView(view: view, point: point).0 {
|
||||
if !(scrollView is PagerExpandableScrollView) {
|
||||
lockDirection = .any
|
||||
} else {
|
||||
let contentOffset = scrollView.contentOffset
|
||||
let contentInset = scrollView.contentInset
|
||||
if contentOffset.y <= contentInset.top {
|
||||
lockDirection = self.requiredLockDirection
|
||||
lockDirection = .down
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lockDirection = .any
|
||||
}
|
||||
if let lockDirection = lockDirection {
|
||||
if abs(translation.y) > 2.0 {
|
||||
switch lockDirection {
|
||||
case .up:
|
||||
if translation.y < 0.0 {
|
||||
if let tracedView = tracedView {
|
||||
cancelParentGestures(view: tracedView, ignore: [self])
|
||||
}
|
||||
self.state = .began
|
||||
} else {
|
||||
self.state = .failed
|
||||
}
|
||||
case .down:
|
||||
if translation.y > 0.0 {
|
||||
if let tracedView = tracedView {
|
||||
cancelParentGestures(view: tracedView, ignore: [self])
|
||||
}
|
||||
self.state = .began
|
||||
} else {
|
||||
self.state = .failed
|
||||
}
|
||||
case .any:
|
||||
if let tracedView = tracedView {
|
||||
cancelParentGestures(view: tracedView, ignore: [self])
|
||||
}
|
||||
self.state = .began
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -179,6 +204,7 @@ public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate {
|
||||
private var scrollableDistance: CGFloat?
|
||||
public private(set) var initialExpansionFraction: CGFloat = 0.0
|
||||
public private(set) var expansionFraction: CGFloat = 0.0
|
||||
public private(set) var stableIsExpanded: Bool = false
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
@ -231,6 +257,8 @@ public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
self.stableIsExpanded = self.expansionFraction == 1.0
|
||||
|
||||
if let expansionRecognizer = self.expansionRecognizer {
|
||||
expansionRecognizer.requiredLockDirection = self.expansionFraction == 0.0 ? .up : .down
|
||||
}
|
||||
@ -250,10 +278,24 @@ public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate {
|
||||
public func expand() {
|
||||
self.expansionFraction = 1.0
|
||||
self.expansionRecognizer?.requiredLockDirection = self.expansionFraction == 0.0 ? .up : .down
|
||||
self.stableIsExpanded = self.expansionFraction == 1.0
|
||||
}
|
||||
|
||||
public func collapse() {
|
||||
self.expansionFraction = 0.0
|
||||
self.expansionRecognizer?.requiredLockDirection = self.expansionFraction == 0.0 ? .up : .down
|
||||
self.stableIsExpanded = self.expansionFraction == 1.0
|
||||
}
|
||||
|
||||
public func toggleIfEnabled() {
|
||||
if let expansionRecognizer = self.expansionRecognizer, expansionRecognizer.isEnabled {
|
||||
if self.expansionFraction == 0.0 {
|
||||
self.expansionFraction = 1.0
|
||||
} else {
|
||||
self.expansionFraction = 0.0
|
||||
}
|
||||
self.stableIsExpanded = self.expansionFraction == 1.0
|
||||
self.expansionUpdated?(.animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@ swift_library(
|
||||
"//submodules/PremiumUI:PremiumUI",
|
||||
"//submodules/StickerPackPreviewUI:StickerPackPreviewUI",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -230,7 +230,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var width: CGFloat
|
||||
var containerInsets: UIEdgeInsets
|
||||
var itemGroupLayouts: [ItemGroupLayout]
|
||||
var itemSize: CGFloat
|
||||
var nativeItemSize: CGFloat
|
||||
let visibleItemSize: CGFloat
|
||||
var horizontalSpacing: CGFloat
|
||||
var verticalSpacing: CGFloat
|
||||
var verticalGroupSpacing: CGFloat
|
||||
@ -241,14 +242,17 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.width = width
|
||||
self.containerInsets = containerInsets
|
||||
|
||||
let minItemsPerRow: Int
|
||||
let minSpacing: CGFloat
|
||||
switch itemLayoutType {
|
||||
case .compact:
|
||||
self.itemSize = 36.0
|
||||
minItemsPerRow = 8
|
||||
self.nativeItemSize = 36.0
|
||||
self.verticalSpacing = 9.0
|
||||
minSpacing = 9.0
|
||||
case .detailed:
|
||||
self.itemSize = 76.0
|
||||
minItemsPerRow = 5
|
||||
self.nativeItemSize = 76.0
|
||||
self.verticalSpacing = 2.0
|
||||
minSpacing = 2.0
|
||||
}
|
||||
@ -257,8 +261,11 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
let itemHorizontalSpace = width - self.containerInsets.left - self.containerInsets.right
|
||||
|
||||
self.itemsPerRow = Int((itemHorizontalSpace + minSpacing) / (self.itemSize + minSpacing))
|
||||
self.horizontalSpacing = floor((itemHorizontalSpace - self.itemSize * CGFloat(self.itemsPerRow)) / CGFloat(self.itemsPerRow - 1))
|
||||
self.itemsPerRow = max(minItemsPerRow, Int((itemHorizontalSpace + minSpacing) / (self.nativeItemSize + minSpacing)))
|
||||
|
||||
self.visibleItemSize = floor((itemHorizontalSpace - CGFloat(self.itemsPerRow - 1) * minSpacing) / CGFloat(self.itemsPerRow))
|
||||
|
||||
self.horizontalSpacing = floor((itemHorizontalSpace - self.visibleItemSize * CGFloat(self.itemsPerRow)) / CGFloat(self.itemsPerRow - 1))
|
||||
|
||||
var verticalGroupOrigin: CGFloat = self.containerInsets.top
|
||||
self.itemGroupLayouts = []
|
||||
@ -269,7 +276,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
let numRowsInGroup = (itemGroup.itemCount + (self.itemsPerRow - 1)) / self.itemsPerRow
|
||||
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.visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * self.verticalSpacing)
|
||||
self.itemGroupLayouts.append(ItemGroupLayout(
|
||||
frame: CGRect(origin: CGPoint(x: 0.0, y: verticalGroupOrigin), size: groupContentSize),
|
||||
id: itemGroup.id,
|
||||
@ -290,12 +297,12 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
return CGRect(
|
||||
origin: CGPoint(
|
||||
x: self.containerInsets.left + CGFloat(column) * (self.itemSize + self.horizontalSpacing),
|
||||
y: groupLayout.frame.minY + groupLayout.itemTopOffset + CGFloat(row) * (self.itemSize + self.verticalSpacing)
|
||||
x: self.containerInsets.left + CGFloat(column) * (self.visibleItemSize + self.horizontalSpacing),
|
||||
y: groupLayout.frame.minY + groupLayout.itemTopOffset + CGFloat(row) * (self.visibleItemSize + self.verticalSpacing)
|
||||
),
|
||||
size: CGSize(
|
||||
width: self.itemSize,
|
||||
height: self.itemSize
|
||||
width: self.visibleItemSize,
|
||||
height: self.visibleItemSize
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -310,9 +317,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
continue
|
||||
}
|
||||
let offsetRect = rect.offsetBy(dx: -self.containerInsets.left, dy: -group.frame.minY - group.itemTopOffset)
|
||||
var minVisibleRow = Int(floor((offsetRect.minY - self.verticalSpacing) / (self.itemSize + self.verticalSpacing)))
|
||||
var minVisibleRow = Int(floor((offsetRect.minY - self.verticalSpacing) / (self.visibleItemSize + self.verticalSpacing)))
|
||||
minVisibleRow = max(0, minVisibleRow)
|
||||
let maxVisibleRow = Int(ceil((offsetRect.maxY - self.verticalSpacing) / (self.itemSize + self.verticalSpacing)))
|
||||
let maxVisibleRow = Int(ceil((offsetRect.maxY - self.verticalSpacing) / (self.visibleItemSize + self.verticalSpacing)))
|
||||
|
||||
let minVisibleIndex = minVisibleRow * self.itemsPerRow
|
||||
let maxVisibleIndex = min(group.itemCount - 1, (maxVisibleRow + 1) * self.itemsPerRow - 1)
|
||||
@ -330,6 +337,60 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
final class ItemPlaceholderView: UIView {
|
||||
private let shimmerView: PortalSourceView?
|
||||
private var placeholderView: PortalView?
|
||||
private let placeholderMaskLayer: SimpleLayer
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
file: TelegramMediaFile,
|
||||
shimmerView: PortalSourceView?,
|
||||
color: UIColor?,
|
||||
size: CGSize
|
||||
) {
|
||||
self.shimmerView = shimmerView
|
||||
self.placeholderView = PortalView()
|
||||
self.placeholderMaskLayer = SimpleLayer()
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
if let placeholderView = self.placeholderView, let shimmerView = self.shimmerView {
|
||||
placeholderView.view.clipsToBounds = true
|
||||
placeholderView.view.layer.mask = self.placeholderMaskLayer
|
||||
self.addSubview(placeholderView.view)
|
||||
shimmerView.addPortal(view: placeholderView)
|
||||
}
|
||||
|
||||
Queue.concurrentDefaultQueue().async { [weak self] in
|
||||
if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: size, scale: min(2.0, UIScreenScale), imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: color ?? .black) {
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let _ = color {
|
||||
strongSelf.layer.contents = image.cgImage
|
||||
} else {
|
||||
strongSelf.placeholderMaskLayer.contents = image.cgImage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
if let placeholderView = self.placeholderView {
|
||||
placeholderView.view.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
self.placeholderMaskLayer.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
}
|
||||
|
||||
final class ItemLayer: MultiAnimationRenderTarget {
|
||||
struct Key: Hashable {
|
||||
var groupId: AnyHashable
|
||||
@ -353,7 +414,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
private var displayPlaceholder: Bool = false
|
||||
private(set) var displayPlaceholder: Bool = false
|
||||
let onUpdateDisplayPlaceholder: (Bool) -> Void
|
||||
|
||||
init(
|
||||
item: Item,
|
||||
@ -366,11 +428,13 @@ public final class EmojiPagerContentComponent: Component {
|
||||
placeholderColor: UIColor,
|
||||
blurredBadgeColor: UIColor,
|
||||
displayPremiumBadgeIfAvailable: Bool,
|
||||
pointSize: CGSize
|
||||
pointSize: CGSize,
|
||||
onUpdateDisplayPlaceholder: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.item = item
|
||||
self.file = file
|
||||
self.placeholderColor = placeholderColor
|
||||
self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder
|
||||
|
||||
let scale = min(2.0, UIScreenScale)
|
||||
let pixelSize = CGSize(width: pointSize.width * scale, height: pointSize.height * scale)
|
||||
@ -414,17 +478,21 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
if attemptSynchronousLoad {
|
||||
if !renderer.loadFirstFrameSynchronously(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize) {
|
||||
self.displayPlaceholder = true
|
||||
|
||||
if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: self.size, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: placeholderColor) {
|
||||
self.contents = image.cgImage
|
||||
}
|
||||
self.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
}
|
||||
|
||||
loadAnimation()
|
||||
} else {
|
||||
let _ = renderer.loadFirstFrame(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, completion: { _ in
|
||||
let _ = renderer.loadFirstFrame(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, completion: { [weak self] success in
|
||||
loadAnimation()
|
||||
|
||||
if !success {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if let dimensions = file.dimensions {
|
||||
@ -458,9 +526,21 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
override public init(layer: Any) {
|
||||
guard let layer = layer as? ItemLayer else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
self.item = layer.item
|
||||
|
||||
self.file = layer.file
|
||||
self.placeholderColor = layer.placeholderColor
|
||||
self.size = layer.size
|
||||
|
||||
self.onUpdateDisplayPlaceholder = { _ in }
|
||||
|
||||
super.init(layer: layer)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
@ -492,34 +572,65 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
self.displayPlaceholder = displayPlaceholder
|
||||
self.onUpdateDisplayPlaceholder(displayPlaceholder)
|
||||
|
||||
/*if displayPlaceholder {
|
||||
if self.placeholderView == nil {
|
||||
self.placeholderView = PortalView()
|
||||
if let placeholderView = self.placeholderView, let shimmerView = self.shimmerView {
|
||||
self.addSublayer(placeholderView.view.layer)
|
||||
placeholderView.view.frame = self.bounds
|
||||
shimmerView.addPortal(view: placeholderView)
|
||||
}
|
||||
}
|
||||
if self.placeholderMaskLayer == nil {
|
||||
self.placeholderMaskLayer = SimpleLayer()
|
||||
self.placeholderView?.view.layer.mask = self.placeholderMaskLayer
|
||||
}
|
||||
let file = self.file
|
||||
let size = self.size
|
||||
let placeholderColor = self.placeholderColor
|
||||
//let placeholderColor = self.placeholderColor
|
||||
|
||||
Queue.concurrentDefaultQueue().async { [weak self] in
|
||||
if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: size, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: placeholderColor) {
|
||||
if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: size, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: .black) {
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if strongSelf.displayPlaceholder {
|
||||
strongSelf.contents = image.cgImage
|
||||
strongSelf.placeholderMaskLayer?.contents = image.cgImage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let placeholderView = self.placeholderView {
|
||||
self.placeholderView = nil
|
||||
placeholderView.view.layer.removeFromSuperlayer()
|
||||
}
|
||||
if let _ = self.placeholderMaskLayer {
|
||||
self.placeholderMaskLayer = nil
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContentScrollView: UIScrollView, PagerExpandableScrollView {
|
||||
}
|
||||
|
||||
private let scrollView: ContentScrollView
|
||||
private let shimmerHostView: PortalSourceView
|
||||
private let standaloneShimmerEffect: StandaloneShimmerEffect
|
||||
|
||||
private let scrollView: ContentScrollView
|
||||
private let boundsChangeTrackerLayer = SimpleLayer()
|
||||
private var effectiveVisibleSize: CGSize = CGSize()
|
||||
|
||||
private var visibleItemPlaceholderViews: [ItemLayer.Key: ItemPlaceholderView] = [:]
|
||||
private var visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:]
|
||||
private var visibleGroupHeaders: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
private var ignoreScrolling: Bool = false
|
||||
private var keepTopPanelVisibleUntilScrollingInput: Bool = false
|
||||
|
||||
private var component: EmojiPagerContentComponent?
|
||||
private var pagerEnvironment: PagerComponentChildEnvironment?
|
||||
@ -532,10 +643,24 @@ public final class EmojiPagerContentComponent: Component {
|
||||
private weak var peekController: PeekController?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.shimmerHostView = PortalSourceView()
|
||||
|
||||
self.standaloneShimmerEffect = StandaloneShimmerEffect()
|
||||
|
||||
self.scrollView = ContentScrollView()
|
||||
self.scrollView.layer.anchorPoint = CGPoint()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.shimmerHostView.alpha = 0.0
|
||||
self.addSubview(self.shimmerHostView)
|
||||
|
||||
self.boundsChangeTrackerLayer.opacity = 0.0
|
||||
self.layer.addSublayer(self.boundsChangeTrackerLayer)
|
||||
self.boundsChangeTrackerLayer.didEnterHierarchy = { [weak self] in
|
||||
self?.standaloneShimmerEffect.updateLayer()
|
||||
}
|
||||
|
||||
self.scrollView.delaysContentTouches = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
@ -546,8 +671,11 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.delegate = self
|
||||
self.scrollView.clipsToBounds = false
|
||||
self.addSubview(self.scrollView)
|
||||
|
||||
//self.clipsToBounds = true
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
|
||||
/*self.useSublayerTransformForActivation = false
|
||||
@ -747,7 +875,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
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)
|
||||
self.keepTopPanelVisibleUntilScrollingInput = true
|
||||
|
||||
self.scrollView.scrollRectToVisible(CGRect(origin: group.frame.origin.offsetBy(dx: 0.0, dy: floor(-itemLayout.verticalGroupSpacing / 2.0) - 41.0), size: CGSize(width: 1.0, height: self.scrollView.bounds.height)), animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -780,6 +910,11 @@ public final class EmojiPagerContentComponent: Component {
|
||||
private var previousScrollingOffset: ScrollingOffsetState?
|
||||
|
||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
if self.keepTopPanelVisibleUntilScrollingInput {
|
||||
self.keepTopPanelVisibleUntilScrollingInput = false
|
||||
|
||||
self.updateScrollingOffset(isReset: true, transition: .immediate)
|
||||
}
|
||||
if let presentation = scrollView.layer.presentation() {
|
||||
scrollView.bounds = presentation.bounds
|
||||
scrollView.layer.removeAllAnimations()
|
||||
@ -793,7 +928,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
self.updateVisibleItems(attemptSynchronousLoads: false)
|
||||
|
||||
self.updateScrollingOffset(transition: .immediate)
|
||||
self.updateScrollingOffset(isReset: false, transition: .immediate)
|
||||
}
|
||||
|
||||
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
@ -812,22 +947,29 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.snapScrollingOffsetToInsets()
|
||||
}
|
||||
|
||||
private func updateScrollingOffset(transition: Transition) {
|
||||
private func updateScrollingOffset(isReset: Bool, transition: Transition) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
|
||||
if let previousScrollingOffsetValue = self.previousScrollingOffset {
|
||||
if let previousScrollingOffsetValue = self.previousScrollingOffset, !self.keepTopPanelVisibleUntilScrollingInput {
|
||||
let currentBounds = scrollView.bounds
|
||||
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
|
||||
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY)
|
||||
|
||||
let relativeOffset = scrollView.contentOffset.y - previousScrollingOffsetValue.value
|
||||
if case .detailed = component.itemLayoutType {
|
||||
self.pagerEnvironment?.onChildScrollingUpdate(PagerComponentChildEnvironment.ContentScrollingUpdate(
|
||||
relativeOffset: relativeOffset,
|
||||
absoluteOffsetToTopEdge: offsetToTopEdge,
|
||||
absoluteOffsetToBottomEdge: offsetToBottomEdge,
|
||||
isReset: isReset,
|
||||
isInteracting: isInteracting,
|
||||
transition: transition
|
||||
))
|
||||
}
|
||||
}
|
||||
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: isInteracting)
|
||||
}
|
||||
|
||||
@ -855,7 +997,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
currentBounds.origin.y = self.snappedContentOffset(proposedOffset: currentBounds.minY)
|
||||
transition.setBounds(view: self.scrollView, bounds: currentBounds)
|
||||
|
||||
self.updateScrollingOffset(transition: transition)
|
||||
self.updateScrollingOffset(isReset: false, transition: transition)
|
||||
}
|
||||
|
||||
private func updateVisibleItems(attemptSynchronousLoads: Bool) {
|
||||
@ -868,14 +1010,17 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var validIds = Set<ItemLayer.Key>()
|
||||
var validGroupHeaderIds = Set<AnyHashable>()
|
||||
|
||||
for groupItems in itemLayout.visibleItems(for: self.scrollView.bounds) {
|
||||
if topVisibleGroupId == nil {
|
||||
topVisibleGroupId = groupItems.id
|
||||
}
|
||||
let effectiveVisibleBounds = CGRect(origin: self.scrollView.bounds.origin, size: self.effectiveVisibleSize)
|
||||
let topVisibleDetectionBounds = effectiveVisibleBounds.offsetBy(dx: 0.0, dy: 41.0)
|
||||
|
||||
for groupItems in itemLayout.visibleItems(for: effectiveVisibleBounds) {
|
||||
let itemGroup = component.itemGroups[groupItems.groupIndex]
|
||||
let itemGroupLayout = itemLayout.itemGroupLayouts[groupItems.groupIndex]
|
||||
|
||||
if topVisibleGroupId == nil && itemGroupLayout.frame.intersects(topVisibleDetectionBounds) {
|
||||
topVisibleGroupId = groupItems.id
|
||||
}
|
||||
|
||||
if let title = itemGroup.title {
|
||||
validGroupHeaderIds.insert(itemGroup.id)
|
||||
let groupHeaderView: ComponentView<Empty>
|
||||
@ -888,7 +1033,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let groupHeaderSize = groupHeaderView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Text(
|
||||
text: title, font: Font.medium(12.0), color: theme.chat.inputMediaPanel.stickersSectionTextColor
|
||||
text: title.uppercased(), font: Font.medium(12.0), color: theme.chat.inputMediaPanel.stickersSectionTextColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: itemLayout.contentSize.width - itemLayout.containerInsets.left - itemLayout.containerInsets.right, height: 100.0)
|
||||
@ -906,14 +1051,21 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let itemId = ItemLayer.Key(groupId: itemGroup.id, fileId: item.file.fileId)
|
||||
validIds.insert(itemId)
|
||||
|
||||
let itemDimensions = item.file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
|
||||
let itemNativeFitSize = itemDimensions.fitted(CGSize(width: itemLayout.nativeItemSize, height: itemLayout.nativeItemSize))
|
||||
let itemVisibleFitSize = itemDimensions.fitted(CGSize(width: itemLayout.visibleItemSize, height: itemLayout.visibleItemSize))
|
||||
|
||||
var updateItemLayerPlaceholder = false
|
||||
let itemLayer: ItemLayer
|
||||
if let current = self.visibleItemLayers[itemId] {
|
||||
itemLayer = current
|
||||
} else {
|
||||
updateItemLayerPlaceholder = true
|
||||
|
||||
itemLayer = ItemLayer(
|
||||
item: item,
|
||||
context: component.context,
|
||||
groupId: "keyboard-\(Int(itemLayout.itemSize))",
|
||||
groupId: "keyboard-\(Int(itemLayout.nativeItemSize))",
|
||||
attemptSynchronousLoad: attemptSynchronousLoads,
|
||||
file: item.file,
|
||||
cache: component.animationCache,
|
||||
@ -921,15 +1073,72 @@ public final class EmojiPagerContentComponent: Component {
|
||||
placeholderColor: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1),
|
||||
blurredBadgeColor: theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(0.5),
|
||||
displayPremiumBadgeIfAvailable: true,
|
||||
pointSize: CGSize(width: itemLayout.itemSize, height: itemLayout.itemSize)
|
||||
pointSize: itemNativeFitSize,
|
||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if displayPlaceholder {
|
||||
if let itemLayer = strongSelf.visibleItemLayers[itemId] {
|
||||
let placeholderView: ItemPlaceholderView
|
||||
if let current = strongSelf.visibleItemPlaceholderViews[itemId] {
|
||||
placeholderView = current
|
||||
} else {
|
||||
placeholderView = ItemPlaceholderView(
|
||||
context: component.context,
|
||||
file: item.file,
|
||||
shimmerView: strongSelf.shimmerHostView,
|
||||
color: nil,
|
||||
size: itemNativeFitSize
|
||||
)
|
||||
strongSelf.visibleItemPlaceholderViews[itemId] = placeholderView
|
||||
strongSelf.scrollView.insertSubview(placeholderView, at: 0)
|
||||
}
|
||||
placeholderView.frame = itemLayer.frame
|
||||
placeholderView.update(size: placeholderView.bounds.size)
|
||||
|
||||
strongSelf.updateShimmerIfNeeded()
|
||||
}
|
||||
} else {
|
||||
if let placeholderView = strongSelf.visibleItemPlaceholderViews[itemId] {
|
||||
strongSelf.visibleItemPlaceholderViews.removeValue(forKey: itemId)
|
||||
placeholderView.removeFromSuperview()
|
||||
|
||||
strongSelf.updateShimmerIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
self.scrollView.layer.addSublayer(itemLayer)
|
||||
self.visibleItemLayers[itemId] = itemLayer
|
||||
}
|
||||
|
||||
let itemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: index)
|
||||
itemLayer.position = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
|
||||
var itemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: index)
|
||||
|
||||
itemFrame.origin.x += floor((itemFrame.width - itemVisibleFitSize.width) / 2.0)
|
||||
itemFrame.origin.y += floor((itemFrame.height - itemVisibleFitSize.height) / 2.0)
|
||||
itemFrame.size = itemVisibleFitSize
|
||||
|
||||
let itemPosition = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
|
||||
let itemBounds = CGRect(origin: CGPoint(), size: itemFrame.size)
|
||||
if itemLayer.position != itemPosition {
|
||||
itemLayer.position = itemPosition
|
||||
}
|
||||
if itemLayer.bounds != itemBounds {
|
||||
itemLayer.bounds = CGRect(origin: CGPoint(), size: itemFrame.size)
|
||||
}
|
||||
|
||||
if let placeholderView = self.visibleItemPlaceholderViews[itemId] {
|
||||
if placeholderView.layer.position != itemPosition || placeholderView.layer.bounds != itemBounds {
|
||||
placeholderView.frame = itemFrame
|
||||
placeholderView.update(size: itemFrame.size)
|
||||
}
|
||||
} else if updateItemLayerPlaceholder {
|
||||
if itemLayer.displayPlaceholder {
|
||||
itemLayer.onUpdateDisplayPlaceholder(true)
|
||||
}
|
||||
}
|
||||
|
||||
itemLayer.isVisibleForAnimations = true
|
||||
}
|
||||
}
|
||||
@ -943,6 +1152,10 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
for id in removedIds {
|
||||
self.visibleItemLayers.removeValue(forKey: id)
|
||||
|
||||
if let view = self.visibleItemPlaceholderViews.removeValue(forKey: id) {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
var removedGroupHeaderIds: [AnyHashable] = []
|
||||
@ -961,14 +1174,31 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private func updateShimmerIfNeeded() {
|
||||
if self.visibleItemPlaceholderViews.isEmpty {
|
||||
self.standaloneShimmerEffect.layer = nil
|
||||
} else {
|
||||
self.standaloneShimmerEffect.layer = self.shimmerHostView.layer
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: EmojiPagerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.theme = environment[EntityKeyboardChildEnvironment.self].value.theme
|
||||
self.activeItemUpdated = environment[EntityKeyboardChildEnvironment.self].value.getContentActiveItemUpdated(component.id)
|
||||
|
||||
let keyboardChildEnvironment = environment[EntityKeyboardChildEnvironment.self].value
|
||||
|
||||
self.theme = keyboardChildEnvironment.theme
|
||||
self.activeItemUpdated = keyboardChildEnvironment.getContentActiveItemUpdated(component.id)
|
||||
|
||||
let pagerEnvironment = environment[PagerComponentChildEnvironment.self].value
|
||||
self.pagerEnvironment = pagerEnvironment
|
||||
|
||||
transition.setFrame(view: self.shimmerHostView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
let shimmerBackgroundColor = keyboardChildEnvironment.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08)
|
||||
let shimmerForegroundColor = keyboardChildEnvironment.theme.list.itemBlocksBackgroundColor.withMultipliedAlpha(0.15)
|
||||
self.standaloneShimmerEffect.update(background: shimmerBackgroundColor, foreground: shimmerForegroundColor)
|
||||
|
||||
var itemGroups: [ItemGroupDescription] = []
|
||||
for itemGroup in component.itemGroups {
|
||||
itemGroups.append(ItemGroupDescription(
|
||||
@ -982,7 +1212,28 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.itemLayout = itemLayout
|
||||
|
||||
self.ignoreScrolling = true
|
||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
transition.setPosition(view: self.scrollView, position: CGPoint())
|
||||
let previousSize = self.scrollView.bounds.size
|
||||
self.scrollView.bounds = CGRect(origin: self.scrollView.bounds.origin, size: availableSize)
|
||||
|
||||
if availableSize.height > previousSize.height || transition.animation.isImmediate {
|
||||
self.boundsChangeTrackerLayer.removeAllAnimations()
|
||||
self.boundsChangeTrackerLayer.bounds = self.scrollView.bounds
|
||||
self.effectiveVisibleSize = self.scrollView.bounds.size
|
||||
} else {
|
||||
self.effectiveVisibleSize = CGSize(width: availableSize.width, height: max(self.effectiveVisibleSize.height, availableSize.height))
|
||||
transition.setBounds(layer: self.boundsChangeTrackerLayer, bounds: self.scrollView.bounds, completion: { [weak self] completed in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let effectiveVisibleSize = strongSelf.scrollView.bounds.size
|
||||
if strongSelf.effectiveVisibleSize != effectiveVisibleSize {
|
||||
strongSelf.effectiveVisibleSize = effectiveVisibleSize
|
||||
strongSelf.updateVisibleItems(attemptSynchronousLoads: false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if self.scrollView.contentSize != itemLayout.contentSize {
|
||||
self.scrollView.contentSize = itemLayout.contentSize
|
||||
}
|
||||
@ -992,7 +1243,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: scrollView.isDragging || scrollView.isDecelerating)
|
||||
self.ignoreScrolling = false
|
||||
|
||||
self.updateVisibleItems(attemptSynchronousLoads: true)
|
||||
self.updateVisibleItems(attemptSynchronousLoads: !(scrollView.isDragging || scrollView.isDecelerating))
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
||||
@ -47,11 +47,14 @@ public final class EntityKeyboardComponent: Component {
|
||||
public let stickerContent: EmojiPagerContentComponent
|
||||
public let gifContent: GifPagerContentComponent
|
||||
public let defaultToEmojiTab: Bool
|
||||
public let externalTopPanelContainer: UIView?
|
||||
public let externalTopPanelContainer: PagerExternalTopPanelContainer?
|
||||
public let topPanelExtensionUpdated: (CGFloat, Transition) -> Void
|
||||
public let hideInputUpdated: (Bool, Transition) -> Void
|
||||
public let hideInputUpdated: (Bool, Bool, Transition) -> Void
|
||||
public let switchToTextInput: () -> Void
|
||||
public let makeSearchContainerNode: (EntitySearchContentType) -> EntitySearchContainerNode
|
||||
public let deviceMetrics: DeviceMetrics
|
||||
public let hiddenInputHeight: CGFloat
|
||||
public let isExpanded: Bool
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
@ -60,11 +63,14 @@ public final class EntityKeyboardComponent: Component {
|
||||
stickerContent: EmojiPagerContentComponent,
|
||||
gifContent: GifPagerContentComponent,
|
||||
defaultToEmojiTab: Bool,
|
||||
externalTopPanelContainer: UIView?,
|
||||
externalTopPanelContainer: PagerExternalTopPanelContainer?,
|
||||
topPanelExtensionUpdated: @escaping (CGFloat, Transition) -> Void,
|
||||
hideInputUpdated: @escaping (Bool, Transition) -> Void,
|
||||
hideInputUpdated: @escaping (Bool, Bool, Transition) -> Void,
|
||||
switchToTextInput: @escaping () -> Void,
|
||||
makeSearchContainerNode: @escaping (EntitySearchContentType) -> EntitySearchContainerNode,
|
||||
deviceMetrics: DeviceMetrics
|
||||
deviceMetrics: DeviceMetrics,
|
||||
hiddenInputHeight: CGFloat,
|
||||
isExpanded: Bool
|
||||
) {
|
||||
self.theme = theme
|
||||
self.bottomInset = bottomInset
|
||||
@ -75,8 +81,11 @@ public final class EntityKeyboardComponent: Component {
|
||||
self.externalTopPanelContainer = externalTopPanelContainer
|
||||
self.topPanelExtensionUpdated = topPanelExtensionUpdated
|
||||
self.hideInputUpdated = hideInputUpdated
|
||||
self.switchToTextInput = switchToTextInput
|
||||
self.makeSearchContainerNode = makeSearchContainerNode
|
||||
self.deviceMetrics = deviceMetrics
|
||||
self.hiddenInputHeight = hiddenInputHeight
|
||||
self.isExpanded = isExpanded
|
||||
}
|
||||
|
||||
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
|
||||
@ -104,6 +113,12 @@ public final class EntityKeyboardComponent: Component {
|
||||
if lhs.deviceMetrics != rhs.deviceMetrics {
|
||||
return false
|
||||
}
|
||||
if lhs.hiddenInputHeight != rhs.hiddenInputHeight {
|
||||
return false
|
||||
}
|
||||
if lhs.isExpanded != rhs.isExpanded {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -118,13 +133,14 @@ public final class EntityKeyboardComponent: Component {
|
||||
private var searchComponent: EntitySearchContentComponent?
|
||||
|
||||
private var topPanelExtension: CGFloat?
|
||||
private var isTopPanelExpanded: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.pagerView = ComponentHostView<EntityKeyboardChildEnvironment>()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.clipsToBounds = true
|
||||
//self.clipsToBounds = true
|
||||
self.disablesInteractiveTransitionGestureRecognizer = true
|
||||
|
||||
self.addSubview(self.pagerView)
|
||||
@ -146,7 +162,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
let gifsContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
|
||||
contents.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(component.gifContent)))
|
||||
var topGifItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||
topGifItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
topGifItems.removeAll()
|
||||
/*topGifItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: "recent",
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/RecentTabIcon",
|
||||
@ -161,7 +178,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: CGSize(width: 30.0, height: 30.0))
|
||||
)
|
||||
))
|
||||
))*/
|
||||
contentTopPanels.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
theme: component.theme,
|
||||
items: topGifItems,
|
||||
@ -199,20 +216,21 @@ public final class EntityKeyboardComponent: Component {
|
||||
"recent": "Chat/Input/Media/RecentTabIcon",
|
||||
"premium": "Chat/Input/Media/PremiumIcon"
|
||||
]
|
||||
if let iconName = iconMapping[id] {
|
||||
let titleMapping: [String: String] = [
|
||||
"recent": "Recent",
|
||||
"premium": "Premium"
|
||||
]
|
||||
if let iconName = iconMapping[id], let title = titleMapping[id] {
|
||||
topStickerItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: itemGroup.id,
|
||||
content: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: iconName,
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: CGSize(width: 30.0, height: 30.0)
|
||||
)),
|
||||
action: { [weak self] in
|
||||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
imageName: iconName,
|
||||
theme: component.theme,
|
||||
title: title,
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "stickers", groupId: itemGroup.id)
|
||||
}
|
||||
).minSize(CGSize(width: 30.0, height: 30.0))
|
||||
)
|
||||
))
|
||||
))
|
||||
}
|
||||
} else {
|
||||
@ -224,6 +242,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
file: itemGroup.items[0].file,
|
||||
animationCache: component.stickerContent.animationCache,
|
||||
animationRenderer: component.stickerContent.animationRenderer,
|
||||
theme: component.theme,
|
||||
title: itemGroup.title ?? "",
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "stickers", groupId: itemGroup.id)
|
||||
}
|
||||
@ -277,6 +297,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
file: itemGroup.items[0].file,
|
||||
animationCache: component.emojiContent.animationCache,
|
||||
animationRenderer: component.emojiContent.animationRenderer,
|
||||
theme: component.theme,
|
||||
title: itemGroup.title ?? "",
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "emoji", groupId: itemGroup.id)
|
||||
}
|
||||
@ -294,6 +316,20 @@ public final class EntityKeyboardComponent: Component {
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
))))
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputGlobeIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
)),
|
||||
action: { [weak self] in
|
||||
guard let strongSelf = self, let component = strongSelf.component else {
|
||||
return
|
||||
}
|
||||
component.switchToTextInput()
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
let deleteBackwards = component.emojiContent.inputInteraction.deleteBackwards
|
||||
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputClearIcon",
|
||||
@ -301,9 +337,20 @@ public final class EntityKeyboardComponent: Component {
|
||||
maxSize: nil
|
||||
)),
|
||||
action: {
|
||||
component.emojiContent.inputInteraction.deleteBackwards()
|
||||
deleteBackwards()
|
||||
}
|
||||
).withHoldAction({
|
||||
deleteBackwards()
|
||||
}).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
|
||||
let panelHideBehavior: PagerComponentPanelHideBehavior
|
||||
if self.searchComponent != nil {
|
||||
panelHideBehavior = .hide
|
||||
} else if component.isExpanded {
|
||||
panelHideBehavior = .show
|
||||
} else {
|
||||
panelHideBehavior = .hideOnScroll
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
|
||||
let pagerSize = self.pagerView.update(
|
||||
transition: transition,
|
||||
@ -319,7 +366,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
color: component.theme.chat.inputMediaPanel.stickersBackgroundColor.withMultipliedAlpha(0.75)
|
||||
)),
|
||||
topPanel: AnyComponent(EntityKeyboardTopContainerPanelComponent(
|
||||
theme: component.theme
|
||||
theme: component.theme,
|
||||
overflowHeight: component.hiddenInputHeight
|
||||
)),
|
||||
externalTopPanelContainer: component.externalTopPanelContainer,
|
||||
bottomPanel: AnyComponent(EntityKeyboardBottomPanelComponent(
|
||||
@ -335,7 +383,13 @@ public final class EntityKeyboardComponent: Component {
|
||||
}
|
||||
strongSelf.topPanelExtensionUpdated(height: panelState.topPanelHeight, transition: transition)
|
||||
},
|
||||
hidePanels: self.searchComponent != nil
|
||||
isTopPanelExpandedUpdated: { [weak self] isExpanded, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isTopPanelExpandedUpdated(isExpanded: isExpanded, transition: transition)
|
||||
},
|
||||
panelHideBehavior: panelHideBehavior
|
||||
)),
|
||||
environment: {
|
||||
EntityKeyboardChildEnvironment(
|
||||
@ -426,6 +480,18 @@ public final class EntityKeyboardComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private func isTopPanelExpandedUpdated(isExpanded: Bool, transition: Transition) {
|
||||
if self.isTopPanelExpanded != isExpanded {
|
||||
self.isTopPanelExpanded = isExpanded
|
||||
}
|
||||
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
component.hideInputUpdated(self.isTopPanelExpanded, false, transition)
|
||||
}
|
||||
|
||||
private func openSearch() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
@ -451,7 +517,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
}
|
||||
)
|
||||
//self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
component.hideInputUpdated(true, Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
component.hideInputUpdated(true, true, Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,7 +530,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
}
|
||||
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)))
|
||||
component.hideInputUpdated(false, false, Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
}
|
||||
|
||||
private func scrollToItemGroup(contentId: String, groupId: AnyHashable) {
|
||||
|
||||
@ -9,11 +9,14 @@ import Postbox
|
||||
|
||||
final class EntityKeyboardTopContainerPanelEnvironment: Equatable {
|
||||
let visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>
|
||||
let isExpandedUpdated: (Bool, Transition) -> Void
|
||||
|
||||
init(
|
||||
visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>
|
||||
visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)>,
|
||||
isExpandedUpdated: @escaping (Bool, Transition) -> Void
|
||||
) {
|
||||
self.visibilityFractionUpdated = visibilityFractionUpdated
|
||||
self.isExpandedUpdated = isExpandedUpdated
|
||||
}
|
||||
|
||||
static func ==(lhs: EntityKeyboardTopContainerPanelEnvironment, rhs: EntityKeyboardTopContainerPanelEnvironment) -> Bool {
|
||||
@ -28,17 +31,23 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
typealias EnvironmentType = PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>
|
||||
|
||||
let theme: PresentationTheme
|
||||
let overflowHeight: CGFloat
|
||||
|
||||
init(
|
||||
theme: PresentationTheme
|
||||
theme: PresentationTheme,
|
||||
overflowHeight: CGFloat
|
||||
) {
|
||||
self.theme = theme
|
||||
self.overflowHeight = overflowHeight
|
||||
}
|
||||
|
||||
static func ==(lhs: EntityKeyboardTopContainerPanelComponent, rhs: EntityKeyboardTopContainerPanelComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.overflowHeight != rhs.overflowHeight {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -46,6 +55,7 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
private final class PanelView {
|
||||
let view = ComponentHostView<EntityKeyboardTopContainerPanelEnvironment>()
|
||||
let visibilityFractionUpdated = ActionSlot<(CGFloat, Transition)>()
|
||||
var isExpanded: Bool = false
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
@ -57,8 +67,13 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
|
||||
private var visibilityFraction: CGFloat = 1.0
|
||||
|
||||
private var hasExpandedPanels: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.disablesInteractiveKeyboardGestureRecognizer = true
|
||||
self.disablesInteractiveTransitionGestureRecognizer = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -94,7 +109,7 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
let panel = panelEnvironment.contentTopPanels[index]
|
||||
let indexOffset = index - centralIndex
|
||||
|
||||
let panelFrame = CGRect(origin: CGPoint(x: CGFloat(indexOffset) * availableSize.width, y: 0.0), size: CGSize(width: availableSize.width, height: intrinsicHeight))
|
||||
let panelFrame = CGRect(origin: CGPoint(x: CGFloat(indexOffset) * availableSize.width, y: -component.overflowHeight), size: CGSize(width: availableSize.width, height: intrinsicHeight + component.overflowHeight))
|
||||
|
||||
let isInBounds = visibleBounds.intersects(panelFrame)
|
||||
let isPartOfTransition: Bool
|
||||
@ -118,12 +133,19 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
self.addSubview(panelView.view)
|
||||
}
|
||||
|
||||
let panelId = panel.id
|
||||
let _ = panelView.view.update(
|
||||
transition: panelTransition,
|
||||
component: panel.component,
|
||||
environment: {
|
||||
EntityKeyboardTopContainerPanelEnvironment(
|
||||
visibilityFractionUpdated: panelView.visibilityFractionUpdated
|
||||
visibilityFractionUpdated: panelView.visibilityFractionUpdated,
|
||||
isExpandedUpdated: { [weak self] isExpanded, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.panelIsExpandedUpdated(id: panelId, isExpanded: isExpanded, transition: transition)
|
||||
}
|
||||
)
|
||||
},
|
||||
containerSize: panelFrame.size
|
||||
@ -171,6 +193,45 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
transition.setSublayerTransform(view: panelView.view, transform: CATransform3DMakeTranslation(0.0, -panelView.view.bounds.height / 2.0 * (1.0 - value), 0.0))
|
||||
}
|
||||
}
|
||||
|
||||
private func panelIsExpandedUpdated(id: AnyHashable, isExpanded: Bool, transition: Transition) {
|
||||
guard let panelView = self.panelViews[id] else {
|
||||
return
|
||||
}
|
||||
panelView.isExpanded = isExpanded
|
||||
|
||||
var hasExpanded = false
|
||||
for (_, panel) in self.panelViews {
|
||||
if panel.isExpanded {
|
||||
hasExpanded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if self.hasExpandedPanels != hasExpanded {
|
||||
self.hasExpandedPanels = hasExpanded
|
||||
|
||||
self.panelEnvironment?.isExpandedUpdated(self.hasExpandedPanels, transition)
|
||||
}
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.alpha.isZero {
|
||||
return nil
|
||||
}
|
||||
for view in self.subviews.reversed() {
|
||||
if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
let result = super.hitTest(point, with: event)
|
||||
if result != self {
|
||||
return result
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
@ -9,14 +10,17 @@ import Postbox
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import AccountContext
|
||||
import MultilineTextComponent
|
||||
|
||||
final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
typealias EnvironmentType = EntityKeyboardTopPanelItemEnvironment
|
||||
|
||||
let context: AccountContext
|
||||
let file: TelegramMediaFile
|
||||
let animationCache: AnimationCache
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let pressed: () -> Void
|
||||
|
||||
init(
|
||||
@ -24,12 +28,16 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
file: TelegramMediaFile,
|
||||
animationCache: AnimationCache,
|
||||
animationRenderer: MultiAnimationRenderer,
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
pressed: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.file = file
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.pressed = pressed
|
||||
}
|
||||
|
||||
@ -46,13 +54,21 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
if lhs.animationRenderer !== rhs.animationRenderer {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
var itemLayer: EmojiPagerContentComponent.View.ItemLayer?
|
||||
var placeholderView: EmojiPagerContentComponent.View.ItemPlaceholderView?
|
||||
var component: EntityKeyboardAnimationTopPanelComponent?
|
||||
var titleView: ComponentView<Empty>?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
@ -73,6 +89,8 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
func update(component: EntityKeyboardAnimationTopPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let itemEnvironment = environment[EntityKeyboardTopPanelItemEnvironment.self].value
|
||||
|
||||
if self.itemLayer == nil {
|
||||
let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
|
||||
item: EmojiPagerContentComponent.Item(
|
||||
@ -89,15 +107,88 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
placeholderColor: .lightGray,
|
||||
blurredBadgeColor: .clear,
|
||||
displayPremiumBadgeIfAvailable: false,
|
||||
pointSize: CGSize(width: 28.0, height: 28.0)
|
||||
pointSize: CGSize(width: 44.0, height: 44.0),
|
||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateDisplayPlaceholder(displayPlaceholder: displayPlaceholder)
|
||||
}
|
||||
)
|
||||
self.itemLayer = itemLayer
|
||||
self.layer.addSublayer(itemLayer)
|
||||
itemLayer.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0))
|
||||
|
||||
if itemLayer.displayPlaceholder {
|
||||
self.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
}
|
||||
}
|
||||
|
||||
let iconSize: CGSize = itemEnvironment.isExpanded ? CGSize(width: 44.0, height: 44.0) : CGSize(width: 28.0, height: 28.0)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) / 2.0), y: 0.0), size: iconSize)
|
||||
|
||||
if let itemLayer = self.itemLayer {
|
||||
transition.setPosition(layer: itemLayer, position: CGPoint(x: iconFrame.midX, y: iconFrame.midY))
|
||||
transition.setBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||
itemLayer.isVisibleForAnimations = true
|
||||
}
|
||||
|
||||
return CGSize(width: 28.0, height: 28.0)
|
||||
if itemEnvironment.isExpanded {
|
||||
let titleView: ComponentView<Empty>
|
||||
if let current = self.titleView {
|
||||
titleView = current
|
||||
} else {
|
||||
titleView = ComponentView<Empty>()
|
||||
self.titleView = titleView
|
||||
}
|
||||
let titleSize = titleView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.regular(10.0), textColor: component.theme.chat.inputPanel.primaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 62.0, height: 100.0)
|
||||
)
|
||||
if let view = titleView.view {
|
||||
if view.superview == nil {
|
||||
view.alpha = 0.0
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: availableSize.height - titleSize.height), size: titleSize)
|
||||
transition.setAlpha(view: view, alpha: 1.0)
|
||||
}
|
||||
} else if let titleView = self.titleView {
|
||||
self.titleView = nil
|
||||
if let view = titleView.view {
|
||||
transition.setAlpha(view: view, alpha: 0.0, completion: { [weak view] _ in
|
||||
view?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
||||
private func updateDisplayPlaceholder(displayPlaceholder: Bool) {
|
||||
if displayPlaceholder {
|
||||
if self.placeholderView == nil, let component = self.component {
|
||||
let placeholderView = EmojiPagerContentComponent.View.ItemPlaceholderView(
|
||||
context: component.context,
|
||||
file: component.file,
|
||||
shimmerView: nil,
|
||||
color: component.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08),
|
||||
size: CGSize(width: 28.0, height: 28.0)
|
||||
)
|
||||
self.placeholderView = placeholderView
|
||||
self.insertSubview(placeholderView, at: 0)
|
||||
placeholderView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0))
|
||||
placeholderView.update(size: CGSize(width: 28.0, height: 28.0))
|
||||
}
|
||||
} else {
|
||||
if let placeholderView = self.placeholderView {
|
||||
self.placeholderView = nil
|
||||
placeholderView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,14 +201,151 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
final class EntityKeyboardIconTopPanelComponent: Component {
|
||||
typealias EnvironmentType = EntityKeyboardTopPanelItemEnvironment
|
||||
|
||||
let imageName: String
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let pressed: () -> Void
|
||||
|
||||
init(
|
||||
imageName: String,
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
pressed: @escaping () -> Void
|
||||
) {
|
||||
self.imageName = imageName
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.pressed = pressed
|
||||
}
|
||||
|
||||
static func ==(lhs: EntityKeyboardIconTopPanelComponent, rhs: EntityKeyboardIconTopPanelComponent) -> Bool {
|
||||
if lhs.imageName != rhs.imageName {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
let iconView: UIImageView
|
||||
var component: EntityKeyboardIconTopPanelComponent?
|
||||
var titleView: ComponentView<Empty>?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.iconView = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.iconView)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.component?.pressed()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: EntityKeyboardIconTopPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
let itemEnvironment = environment[EntityKeyboardTopPanelItemEnvironment.self].value
|
||||
|
||||
if self.component?.imageName != component.imageName {
|
||||
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: component.imageName), color: component.theme.chat.inputMediaPanel.panelIconColor)
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
||||
let nativeIconSize: CGSize = itemEnvironment.isExpanded ? CGSize(width: 44.0, height: 44.0) : CGSize(width: 28.0, height: 28.0)
|
||||
let boundingIconSize: CGSize = itemEnvironment.isExpanded ? CGSize(width: 38.0, height: 38.0) : CGSize(width: 24.0, height: 24.0)
|
||||
|
||||
let iconSize = (self.iconView.image?.size ?? nativeIconSize).aspectFitted(boundingIconSize)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) / 2.0), y: floor((nativeIconSize.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
|
||||
transition.setFrame(view: self.iconView, frame: iconFrame)
|
||||
|
||||
if itemEnvironment.isExpanded {
|
||||
let titleView: ComponentView<Empty>
|
||||
if let current = self.titleView {
|
||||
titleView = current
|
||||
} else {
|
||||
titleView = ComponentView<Empty>()
|
||||
self.titleView = titleView
|
||||
}
|
||||
let titleSize = titleView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.regular(10.0), textColor: component.theme.chat.inputPanel.primaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 62.0, height: 100.0)
|
||||
)
|
||||
if let view = titleView.view {
|
||||
if view.superview == nil {
|
||||
view.alpha = 0.0
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: availableSize.height - titleSize.height), size: titleSize)
|
||||
transition.setAlpha(view: view, alpha: 1.0)
|
||||
}
|
||||
} else if let titleView = self.titleView {
|
||||
self.titleView = nil
|
||||
if let view = titleView.view {
|
||||
transition.setAlpha(view: view, alpha: 0.0, completion: { [weak view] _ in
|
||||
view?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
final class EntityKeyboardTopPanelItemEnvironment: Equatable {
|
||||
let isExpanded: Bool
|
||||
|
||||
init(isExpanded: Bool) {
|
||||
self.isExpanded = isExpanded
|
||||
}
|
||||
|
||||
static func ==(lhs: EntityKeyboardTopPanelItemEnvironment, rhs: EntityKeyboardTopPanelItemEnvironment) -> Bool {
|
||||
if lhs.isExpanded != rhs.isExpanded {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
final class EntityKeyboardTopPanelComponent: Component {
|
||||
typealias EnvironmentType = EntityKeyboardTopContainerPanelEnvironment
|
||||
|
||||
final class Item: Equatable {
|
||||
let id: AnyHashable
|
||||
let content: AnyComponent<Empty>
|
||||
let content: AnyComponent<EntityKeyboardTopPanelItemEnvironment>
|
||||
|
||||
init(id: AnyHashable, content: AnyComponent<Empty>) {
|
||||
init(id: AnyHashable, content: AnyComponent<EntityKeyboardTopPanelItemEnvironment>) {
|
||||
self.id = id
|
||||
self.content = content
|
||||
}
|
||||
@ -165,34 +393,42 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
final class View: UIView, UIScrollViewDelegate {
|
||||
private struct ItemLayout {
|
||||
let sideInset: CGFloat = 7.0
|
||||
let itemSize: CGFloat = 32.0
|
||||
let innerItemSize: CGFloat = 28.0
|
||||
let itemSize: CGSize
|
||||
let innerItemSize: CGSize
|
||||
let itemSpacing: CGFloat = 15.0
|
||||
let itemCount: Int
|
||||
let contentSize: CGSize
|
||||
let isExpanded: Bool
|
||||
|
||||
init(itemCount: Int) {
|
||||
init(itemCount: Int, isExpanded: Bool, height: CGFloat) {
|
||||
self.isExpanded = isExpanded
|
||||
self.itemSize = self.isExpanded ? CGSize(width: 54.0, height: 68.0) : CGSize(width: 32.0, height: 32.0)
|
||||
self.innerItemSize = self.isExpanded ? CGSize(width: 50.0, height: 62.0) : CGSize(width: 28.0, height: 28.0)
|
||||
self.itemCount = itemCount
|
||||
self.contentSize = CGSize(width: sideInset * 2.0 + CGFloat(itemCount) * self.itemSize + CGFloat(max(0, itemCount - 1)) * itemSpacing, height: 41.0)
|
||||
self.contentSize = CGSize(width: sideInset * 2.0 + CGFloat(itemCount) * self.itemSize.width + CGFloat(max(0, itemCount - 1)) * itemSpacing, height: height)
|
||||
}
|
||||
|
||||
func containerFrame(at index: Int) -> CGRect {
|
||||
return CGRect(origin: CGPoint(x: sideInset + CGFloat(index) * (self.itemSize + self.itemSpacing), y: floor((self.contentSize.height - self.itemSize) / 2.0)), size: CGSize(width: self.itemSize, height: self.itemSize))
|
||||
return CGRect(origin: CGPoint(x: sideInset + CGFloat(index) * (self.itemSize.width + self.itemSpacing), y: floor((self.contentSize.height - self.itemSize.height) / 2.0)), size: self.itemSize)
|
||||
}
|
||||
|
||||
func contentFrame(containerFrame: CGRect) -> CGRect {
|
||||
var frame = containerFrame
|
||||
frame.origin.x += floor((self.itemSize.width - self.innerItemSize.width)) / 2.0
|
||||
frame.origin.y += floor((self.itemSize.height - self.innerItemSize.height)) / 2.0
|
||||
frame.size = self.innerItemSize
|
||||
return frame
|
||||
}
|
||||
|
||||
func contentFrame(at index: Int) -> CGRect {
|
||||
var frame = self.containerFrame(at: index)
|
||||
frame.origin.x += floor((self.itemSize - self.innerItemSize)) / 2.0
|
||||
frame.origin.y += floor((self.itemSize - self.innerItemSize)) / 2.0
|
||||
frame.size = CGSize(width: self.innerItemSize, height: self.innerItemSize)
|
||||
return frame
|
||||
return self.contentFrame(containerFrame: self.containerFrame(at: index))
|
||||
}
|
||||
|
||||
func visibleItemRange(for rect: CGRect) -> (minIndex: Int, maxIndex: Int) {
|
||||
let offsetRect = rect.offsetBy(dx: -self.sideInset, dy: 0.0)
|
||||
var minVisibleColumn = Int(floor((offsetRect.minX - self.itemSpacing) / (self.itemSize + self.itemSpacing)))
|
||||
var minVisibleColumn = Int(floor((offsetRect.minX - self.itemSpacing) / (self.itemSize.width + self.itemSpacing)))
|
||||
minVisibleColumn = max(0, minVisibleColumn)
|
||||
let maxVisibleColumn = Int(ceil((offsetRect.maxX - self.itemSpacing) / (self.itemSize + self.itemSpacing)))
|
||||
let maxVisibleColumn = Int(ceil((offsetRect.maxX - self.itemSpacing) / (self.itemSize.width + self.itemSpacing)))
|
||||
|
||||
let minVisibleIndex = minVisibleColumn
|
||||
let maxVisibleIndex = min(maxVisibleColumn, self.itemCount - 1)
|
||||
@ -202,15 +438,23 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
}
|
||||
|
||||
private let scrollView: UIScrollView
|
||||
private var itemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||
private var itemViews: [AnyHashable: ComponentHostView<EntityKeyboardTopPanelItemEnvironment>] = [:]
|
||||
private var highlightedIconBackgroundView: UIView
|
||||
|
||||
private var itemLayout: ItemLayout?
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
private var isDragging: Bool = false
|
||||
private var draggingStoppedTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var isExpanded: Bool = false
|
||||
|
||||
private var visibilityFraction: CGFloat = 1.0
|
||||
|
||||
private var activeContentItemId: AnyHashable?
|
||||
|
||||
private var component: EntityKeyboardTopPanelComponent?
|
||||
private var environment: EntityKeyboardTopContainerPanelEnvironment?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.scrollView = UIScrollView()
|
||||
@ -222,7 +466,9 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.scrollView.layer.anchorPoint = CGPoint()
|
||||
self.scrollView.delaysContentTouches = false
|
||||
self.scrollView.clipsToBounds = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
@ -236,6 +482,8 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
|
||||
self.scrollView.addSubview(self.highlightedIconBackgroundView)
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
@ -253,38 +501,98 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
self.updateVisibleItems(attemptSynchronousLoads: false)
|
||||
self.updateVisibleItems(attemptSynchronousLoads: false, transition: .immediate)
|
||||
}
|
||||
|
||||
private func updateVisibleItems(attemptSynchronousLoads: Bool) {
|
||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
self.updateIsDragging(true)
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
if !decelerate {
|
||||
self.updateIsDragging(false)
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
self.updateIsDragging(false)
|
||||
}
|
||||
|
||||
private func updateIsDragging(_ isDragging: Bool) {
|
||||
if !isDragging {
|
||||
if !self.isDragging {
|
||||
return
|
||||
}
|
||||
|
||||
if self.draggingStoppedTimer == nil {
|
||||
self.draggingStoppedTimer = SwiftSignalKit.Timer(timeout: 0.8, repeat: false, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.draggingStoppedTimer = nil
|
||||
strongSelf.isDragging = false
|
||||
guard let environment = strongSelf.environment else {
|
||||
return
|
||||
}
|
||||
environment.isExpandedUpdated(false, Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
}, queue: .mainQueue())
|
||||
self.draggingStoppedTimer?.start()
|
||||
}
|
||||
} else {
|
||||
self.draggingStoppedTimer?.invalidate()
|
||||
self.draggingStoppedTimer = nil
|
||||
|
||||
if !self.isDragging {
|
||||
self.isDragging = true
|
||||
|
||||
guard let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
environment.isExpandedUpdated(true, Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateVisibleItems(attemptSynchronousLoads: Bool, transition: Transition) {
|
||||
guard let component = self.component, let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
var visibleBounds = self.scrollView.bounds
|
||||
visibleBounds.size.width += 200.0
|
||||
|
||||
var validIds = Set<AnyHashable>()
|
||||
let visibleItemRange = itemLayout.visibleItemRange(for: self.scrollView.bounds)
|
||||
let visibleItemRange = itemLayout.visibleItemRange(for: visibleBounds)
|
||||
if !component.items.isEmpty && visibleItemRange.maxIndex >= visibleItemRange.minIndex {
|
||||
for index in visibleItemRange.minIndex ... visibleItemRange.maxIndex {
|
||||
let item = component.items[index]
|
||||
validIds.insert(item.id)
|
||||
|
||||
let itemView: ComponentHostView<Empty>
|
||||
var itemTransition = transition
|
||||
let itemView: ComponentHostView<EntityKeyboardTopPanelItemEnvironment>
|
||||
if let current = self.itemViews[item.id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemView = ComponentHostView<Empty>()
|
||||
itemTransition = .immediate
|
||||
itemView = ComponentHostView<EntityKeyboardTopPanelItemEnvironment>()
|
||||
self.scrollView.addSubview(itemView)
|
||||
self.itemViews[item.id] = itemView
|
||||
}
|
||||
|
||||
let itemOuterFrame = itemLayout.contentFrame(at: index)
|
||||
let itemSize = itemView.update(
|
||||
transition: .immediate,
|
||||
transition: itemTransition,
|
||||
component: item.content,
|
||||
environment: {},
|
||||
environment: {
|
||||
EntityKeyboardTopPanelItemEnvironment(isExpanded: itemLayout.isExpanded)
|
||||
},
|
||||
containerSize: itemOuterFrame.size
|
||||
)
|
||||
itemView.frame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize)
|
||||
/*if index == visibleItemRange.minIndex, !itemTransition.animation.isImmediate {
|
||||
print("\(index): \(itemView.frame) -> \(itemFrame)")
|
||||
}*/
|
||||
itemTransition.setFrame(view: itemView, frame: itemFrame)
|
||||
}
|
||||
}
|
||||
var removedIds: [AnyHashable] = []
|
||||
@ -305,22 +613,77 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
}
|
||||
self.component = component
|
||||
|
||||
let intrinsicHeight: CGFloat = 41.0
|
||||
let panelEnvironment = environment[EntityKeyboardTopContainerPanelEnvironment.self].value
|
||||
self.environment = panelEnvironment
|
||||
|
||||
let isExpanded = availableSize.height > 41.0
|
||||
let wasExpanded = self.isExpanded
|
||||
self.isExpanded = isExpanded
|
||||
|
||||
let intrinsicHeight: CGFloat = availableSize.height
|
||||
let height = intrinsicHeight
|
||||
|
||||
let itemLayout = ItemLayout(itemCount: component.items.count)
|
||||
let previousItemLayout = self.itemLayout
|
||||
let itemLayout = ItemLayout(itemCount: component.items.count, isExpanded: isExpanded, height: availableSize.height)
|
||||
self.itemLayout = itemLayout
|
||||
|
||||
self.ignoreScrolling = true
|
||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: intrinsicHeight)))
|
||||
|
||||
var updatedBounds: CGRect?
|
||||
if wasExpanded != isExpanded, let previousItemLayout = previousItemLayout {
|
||||
var visibleBounds = self.scrollView.bounds
|
||||
visibleBounds.size.width += 200.0
|
||||
|
||||
let previousVisibleRange = previousItemLayout.visibleItemRange(for: visibleBounds)
|
||||
if previousVisibleRange.minIndex <= previousVisibleRange.maxIndex {
|
||||
let previousItemFrame = previousItemLayout.containerFrame(at: previousVisibleRange.minIndex)
|
||||
let updatedItemFrame = itemLayout.containerFrame(at: previousVisibleRange.minIndex)
|
||||
|
||||
let previousDistanceToItemFraction = (previousItemFrame.minX - self.scrollView.bounds.minX) / previousItemFrame.width
|
||||
let newBounds = CGRect(origin: CGPoint(x: updatedItemFrame.minX - floor(previousDistanceToItemFraction * updatedItemFrame.width), y: 0.0), size: availableSize)
|
||||
updatedBounds = newBounds
|
||||
|
||||
var updatedVisibleBounds = newBounds
|
||||
updatedVisibleBounds.size.width += 200.0
|
||||
let updatedVisibleRange = itemLayout.visibleItemRange(for: updatedVisibleBounds)
|
||||
|
||||
let baseFrame = CGRect(origin: CGPoint(x: updatedItemFrame.minX, y: previousItemFrame.minY), size: previousItemFrame.size)
|
||||
for index in updatedVisibleRange.minIndex ..< updatedVisibleRange.maxIndex {
|
||||
let indexDifference = index - previousVisibleRange.minIndex
|
||||
if let itemView = self.itemViews[component.items[index].id] {
|
||||
let itemContainerOriginX = baseFrame.minX + CGFloat(indexDifference) * (previousItemLayout.itemSize.width + previousItemLayout.itemSpacing)
|
||||
let itemContainerFrame = CGRect(origin: CGPoint(x: itemContainerOriginX, y: baseFrame.minY), size: baseFrame.size)
|
||||
let itemOuterFrame = previousItemLayout.contentFrame(containerFrame: itemContainerFrame)
|
||||
|
||||
let itemSize = itemView.bounds.size
|
||||
itemView.frame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.scrollView.contentSize != itemLayout.contentSize {
|
||||
self.scrollView.contentSize = itemLayout.contentSize
|
||||
}
|
||||
if let updatedBounds = updatedBounds {
|
||||
self.scrollView.bounds = updatedBounds
|
||||
} else {
|
||||
self.scrollView.bounds = CGRect(origin: self.scrollView.bounds.origin, size: availableSize)
|
||||
}
|
||||
self.ignoreScrolling = false
|
||||
|
||||
self.updateVisibleItems(attemptSynchronousLoads: true)
|
||||
self.updateVisibleItems(attemptSynchronousLoads: !(self.scrollView.isDragging || self.scrollView.isDecelerating), transition: transition)
|
||||
|
||||
environment[EntityKeyboardTopContainerPanelEnvironment.self].value.visibilityFractionUpdated.connect { [weak self] (fraction, transition) in
|
||||
if let activeContentItemId = self.activeContentItemId {
|
||||
if let index = component.items.firstIndex(where: { $0.id == activeContentItemId }) {
|
||||
let itemFrame = itemLayout.containerFrame(at: index)
|
||||
transition.setPosition(view: self.highlightedIconBackgroundView, position: CGPoint(x: itemFrame.midX, y: itemFrame.midY))
|
||||
transition.setBounds(view: self.highlightedIconBackgroundView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||
}
|
||||
}
|
||||
transition.setAlpha(view: self.highlightedIconBackgroundView, alpha: isExpanded ? 0.0 : 1.0)
|
||||
|
||||
panelEnvironment.visibilityFractionUpdated.connect { [weak self] (fraction, transition) in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -360,13 +723,18 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
self.activeContentItemId = itemId
|
||||
|
||||
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)
|
||||
transition.setPosition(view: self.highlightedIconBackgroundView, position: CGPoint(x: itemFrame.midX, y: itemFrame.midY))
|
||||
transition.setBounds(view: self.highlightedIconBackgroundView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||
|
||||
self.scrollView.scrollRectToVisible(itemFrame.insetBy(dx: -6.0, dy: 0.0), animated: true)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@ -464,6 +464,7 @@ public final class GifPagerContentComponent: Component {
|
||||
relativeOffset: relativeOffset,
|
||||
absoluteOffsetToTopEdge: offsetToTopEdge,
|
||||
absoluteOffsetToBottomEdge: offsetToBottomEdge,
|
||||
isReset: false,
|
||||
isInteracting: isInteracting,
|
||||
transition: transition
|
||||
))
|
||||
|
||||
@ -3,6 +3,7 @@ import UIKit
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import AnimationCache
|
||||
import Accelerate
|
||||
|
||||
public protocol MultiAnimationRenderer: AnyObject {
|
||||
func add(groupId: String, target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (CGSize, AnimationCacheItemWriter) -> Disposable) -> Disposable
|
||||
@ -36,6 +37,7 @@ open class MultiAnimationRenderTarget: SimpleLayer {
|
||||
|
||||
private final class FrameGroup {
|
||||
let image: UIImage
|
||||
let badgeImage: UIImage?
|
||||
let size: CGSize
|
||||
let timestamp: Double
|
||||
|
||||
@ -50,6 +52,27 @@ private final class FrameGroup {
|
||||
|
||||
firstFrame.data.withUnsafeBytes { bytes -> Void in
|
||||
memcpy(context.bytes, bytes.baseAddress!.advanced(by: firstFrame.range.lowerBound), height * bytesPerRow)
|
||||
|
||||
/*var sourceBuffer = vImage_Buffer()
|
||||
sourceBuffer.width = UInt(width)
|
||||
sourceBuffer.height = UInt(height)
|
||||
sourceBuffer.data = UnsafeMutableRawPointer(mutating: bytes.baseAddress!.advanced(by: firstFrame.range.lowerBound))
|
||||
sourceBuffer.rowBytes = bytesPerRow
|
||||
|
||||
var destinationBuffer = vImage_Buffer()
|
||||
destinationBuffer.width = UInt(32)
|
||||
destinationBuffer.height = UInt(32)
|
||||
destinationBuffer.data = context.bytes
|
||||
destinationBuffer.rowBytes = bytesPerRow
|
||||
|
||||
vImageBoxConvolve_ARGB8888(&sourceBuffer,
|
||||
&destinationBuffer,
|
||||
nil,
|
||||
UInt(width - 32 - 16), UInt(height - 32 - 16),
|
||||
UInt32(31),
|
||||
UInt32(31),
|
||||
nil,
|
||||
vImage_Flags(kvImageEdgeExtend))*/
|
||||
}
|
||||
|
||||
guard let image = context.generateImage() else {
|
||||
@ -59,6 +82,7 @@ private final class FrameGroup {
|
||||
self.image = image
|
||||
self.size = CGSize(width: CGFloat(width), height: CGFloat(height))
|
||||
self.timestamp = timestamp
|
||||
self.badgeImage = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "planet.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
255
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/planet.pdf
vendored
Normal file
255
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/planet.pdf
vendored
Normal file
@ -0,0 +1,255 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.500000 1.839844 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
22.170000 13.160156 m
|
||||
22.170000 7.267279 17.392878 2.490156 11.500000 2.490156 c
|
||||
11.500000 0.830156 l
|
||||
18.309671 0.830156 23.830000 6.350485 23.830000 13.160156 c
|
||||
22.170000 13.160156 l
|
||||
h
|
||||
11.500000 2.490156 m
|
||||
5.607122 2.490156 0.830000 7.267279 0.830000 13.160156 c
|
||||
-0.830000 13.160156 l
|
||||
-0.830000 6.350485 4.690329 0.830156 11.500000 0.830156 c
|
||||
11.500000 2.490156 l
|
||||
h
|
||||
0.830000 13.160156 m
|
||||
0.830000 19.053034 5.607122 23.830156 11.500000 23.830156 c
|
||||
11.500000 25.490156 l
|
||||
4.690329 25.490156 -0.830000 19.969828 -0.830000 13.160156 c
|
||||
0.830000 13.160156 l
|
||||
h
|
||||
11.500000 23.830156 m
|
||||
17.392878 23.830156 22.170000 19.053034 22.170000 13.160156 c
|
||||
23.830000 13.160156 l
|
||||
23.830000 19.969828 18.309671 25.490156 11.500000 25.490156 c
|
||||
11.500000 23.830156 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 8.500000 1.839844 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
12.170000 13.160156 m
|
||||
12.170000 10.100716 11.467313 7.371819 10.373627 5.436836 c
|
||||
9.265684 3.476629 7.863927 2.490156 6.500000 2.490156 c
|
||||
6.500000 0.830156 l
|
||||
8.725924 0.830156 10.574167 2.418047 11.818761 4.620020 c
|
||||
13.077613 6.847219 13.830000 9.868322 13.830000 13.160156 c
|
||||
12.170000 13.160156 l
|
||||
h
|
||||
6.500000 2.490156 m
|
||||
5.136073 2.490156 3.734316 3.476629 2.626373 5.436836 c
|
||||
1.532687 7.371819 0.830000 10.100716 0.830000 13.160156 c
|
||||
-0.830000 13.160156 l
|
||||
-0.830000 9.868322 -0.077613 6.847219 1.181239 4.620020 c
|
||||
2.425833 2.418047 4.274076 0.830156 6.500000 0.830156 c
|
||||
6.500000 2.490156 l
|
||||
h
|
||||
0.830000 13.160156 m
|
||||
0.830000 16.219597 1.532687 18.948494 2.626373 20.883476 c
|
||||
3.734316 22.843683 5.136073 23.830156 6.500000 23.830156 c
|
||||
6.500000 25.490156 l
|
||||
4.274076 25.490156 2.425833 23.902266 1.181239 21.700291 c
|
||||
-0.077613 19.473093 -0.830000 16.451990 -0.830000 13.160156 c
|
||||
0.830000 13.160156 l
|
||||
h
|
||||
6.500000 23.830156 m
|
||||
7.863927 23.830156 9.265684 22.843683 10.373627 20.883476 c
|
||||
11.467313 18.948494 12.170000 16.219597 12.170000 13.160156 c
|
||||
13.830000 13.160156 l
|
||||
13.830000 16.451990 13.077613 19.473093 11.818761 21.700291 c
|
||||
10.574167 23.902266 8.725924 25.490156 6.500000 25.490156 c
|
||||
6.500000 23.830156 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 15.000000 3.500000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
-0.830000 23.000000 m
|
||||
-0.830000 0.000000 l
|
||||
0.830000 0.000000 l
|
||||
0.830000 23.000000 l
|
||||
-0.830000 23.000000 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
0.000000 1.000000 -1.000000 0.000000 5.160156 15.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
-0.830000 1.660156 m
|
||||
-0.830000 -21.339844 l
|
||||
0.830000 -21.339844 l
|
||||
0.830000 1.660156 l
|
||||
-0.830000 1.660156 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
0.000000 1.000000 -1.000000 0.000000 9.019043 8.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 3.519043 m
|
||||
-0.686377 3.052367 -0.686606 3.052704 -0.686827 3.053029 c
|
||||
-0.686890 3.053123 -0.687103 3.053437 -0.687230 3.053624 c
|
||||
-0.687484 3.053999 -0.687707 3.054330 -0.687901 3.054617 c
|
||||
-0.688287 3.055190 -0.688552 3.055586 -0.688699 3.055805 c
|
||||
-0.688991 3.056243 -0.688808 3.055973 -0.688163 3.054991 c
|
||||
-0.686874 3.053027 -0.683741 3.048217 -0.678884 3.040542 c
|
||||
-0.669169 3.025192 -0.652556 2.998377 -0.630008 2.959931 c
|
||||
-0.584915 2.883047 -0.516061 2.759615 -0.431147 2.588317 c
|
||||
-0.261366 2.245815 -0.027114 1.711416 0.209833 0.974417 c
|
||||
0.683340 -0.498374 1.170000 -2.787649 1.170000 -5.980957 c
|
||||
2.830000 -5.980957 l
|
||||
2.830000 -2.615824 2.316660 -0.155099 1.790166 1.482499 c
|
||||
1.527114 2.300695 1.261366 2.911589 1.056147 3.325581 c
|
||||
0.953561 3.532529 0.866165 3.690144 0.801883 3.799746 c
|
||||
0.769744 3.854543 0.743388 3.897330 0.723805 3.928271 c
|
||||
0.714015 3.943742 0.705917 3.956251 0.699638 3.965818 c
|
||||
0.696498 3.970602 0.693813 3.974651 0.691598 3.977967 c
|
||||
0.690490 3.979625 0.689500 3.981099 0.688629 3.982391 c
|
||||
0.688194 3.983037 0.687788 3.983638 0.687413 3.984192 c
|
||||
0.687225 3.984469 0.686966 3.984851 0.686872 3.984990 c
|
||||
0.686621 3.985360 0.686377 3.985719 0.000000 3.519043 c
|
||||
h
|
||||
1.170000 -5.980957 m
|
||||
1.170000 -9.174265 0.683340 -11.463540 0.209833 -12.936331 c
|
||||
-0.027114 -13.673330 -0.261366 -14.207729 -0.431147 -14.550232 c
|
||||
-0.516061 -14.721529 -0.584915 -14.844961 -0.630008 -14.921844 c
|
||||
-0.652556 -14.960291 -0.669169 -14.987106 -0.678884 -15.002457 c
|
||||
-0.683741 -15.010132 -0.686874 -15.014940 -0.688163 -15.016905 c
|
||||
-0.688808 -15.017887 -0.688991 -15.018158 -0.688699 -15.017719 c
|
||||
-0.688552 -15.017500 -0.688287 -15.017103 -0.687901 -15.016531 c
|
||||
-0.687707 -15.016245 -0.687484 -15.015913 -0.687230 -15.015539 c
|
||||
-0.687103 -15.015350 -0.686890 -15.015036 -0.686827 -15.014942 c
|
||||
-0.686606 -15.014618 -0.686377 -15.014280 0.000000 -15.480957 c
|
||||
0.686377 -15.947634 0.686621 -15.947273 0.686872 -15.946903 c
|
||||
0.686966 -15.946766 0.687225 -15.946383 0.687413 -15.946106 c
|
||||
0.687788 -15.945551 0.688194 -15.944952 0.688629 -15.944305 c
|
||||
0.689500 -15.943014 0.690490 -15.941538 0.691598 -15.939880 c
|
||||
0.693813 -15.936565 0.696498 -15.932516 0.699638 -15.927732 c
|
||||
0.705917 -15.918165 0.714015 -15.905657 0.723805 -15.890184 c
|
||||
0.743388 -15.859243 0.769744 -15.816458 0.801883 -15.761660 c
|
||||
0.866165 -15.652058 0.953561 -15.494444 1.056147 -15.287495 c
|
||||
1.261366 -14.873503 1.527114 -14.262609 1.790166 -13.444414 c
|
||||
2.316660 -11.806815 2.830000 -9.346090 2.830000 -5.980957 c
|
||||
1.170000 -5.980957 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
0.000000 -1.000000 1.000000 0.000000 20.980957 22.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 3.519043 m
|
||||
-0.686377 3.052367 -0.686606 3.052704 -0.686827 3.053029 c
|
||||
-0.686890 3.053123 -0.687103 3.053437 -0.687230 3.053624 c
|
||||
-0.687484 3.053999 -0.687707 3.054330 -0.687901 3.054617 c
|
||||
-0.688287 3.055190 -0.688552 3.055586 -0.688699 3.055805 c
|
||||
-0.688991 3.056243 -0.688808 3.055973 -0.688163 3.054991 c
|
||||
-0.686874 3.053027 -0.683741 3.048217 -0.678884 3.040542 c
|
||||
-0.669169 3.025192 -0.652556 2.998377 -0.630008 2.959931 c
|
||||
-0.584915 2.883047 -0.516061 2.759615 -0.431147 2.588317 c
|
||||
-0.261366 2.245815 -0.027114 1.711416 0.209833 0.974417 c
|
||||
0.683340 -0.498374 1.170000 -2.787649 1.170000 -5.980957 c
|
||||
2.830000 -5.980957 l
|
||||
2.830000 -2.615824 2.316660 -0.155099 1.790166 1.482499 c
|
||||
1.527114 2.300695 1.261366 2.911589 1.056147 3.325581 c
|
||||
0.953561 3.532529 0.866165 3.690144 0.801883 3.799746 c
|
||||
0.769744 3.854543 0.743388 3.897330 0.723805 3.928271 c
|
||||
0.714015 3.943742 0.705917 3.956251 0.699638 3.965818 c
|
||||
0.696498 3.970602 0.693813 3.974651 0.691598 3.977967 c
|
||||
0.690490 3.979625 0.689500 3.981099 0.688629 3.982391 c
|
||||
0.688194 3.983037 0.687788 3.983638 0.687413 3.984192 c
|
||||
0.687225 3.984469 0.686966 3.984851 0.686872 3.984990 c
|
||||
0.686621 3.985360 0.686377 3.985719 0.000000 3.519043 c
|
||||
h
|
||||
1.170000 -5.980957 m
|
||||
1.170000 -9.174265 0.683340 -11.463540 0.209833 -12.936331 c
|
||||
-0.027114 -13.673330 -0.261366 -14.207729 -0.431147 -14.550232 c
|
||||
-0.516061 -14.721529 -0.584915 -14.844961 -0.630008 -14.921844 c
|
||||
-0.652556 -14.960291 -0.669169 -14.987106 -0.678884 -15.002457 c
|
||||
-0.683741 -15.010132 -0.686874 -15.014940 -0.688163 -15.016905 c
|
||||
-0.688808 -15.017887 -0.688991 -15.018158 -0.688699 -15.017719 c
|
||||
-0.688552 -15.017500 -0.688287 -15.017103 -0.687901 -15.016531 c
|
||||
-0.687707 -15.016245 -0.687484 -15.015913 -0.687230 -15.015539 c
|
||||
-0.687103 -15.015350 -0.686890 -15.015036 -0.686827 -15.014942 c
|
||||
-0.686606 -15.014618 -0.686377 -15.014280 0.000000 -15.480957 c
|
||||
0.686377 -15.947634 0.686621 -15.947273 0.686872 -15.946903 c
|
||||
0.686966 -15.946766 0.687225 -15.946383 0.687413 -15.946106 c
|
||||
0.687788 -15.945551 0.688194 -15.944952 0.688629 -15.944305 c
|
||||
0.689500 -15.943014 0.690490 -15.941538 0.691598 -15.939880 c
|
||||
0.693813 -15.936565 0.696498 -15.932516 0.699638 -15.927732 c
|
||||
0.705917 -15.918165 0.714015 -15.905657 0.723805 -15.890184 c
|
||||
0.743388 -15.859243 0.769744 -15.816458 0.801883 -15.761660 c
|
||||
0.866165 -15.652058 0.953561 -15.494444 1.056147 -15.287495 c
|
||||
1.261366 -14.873503 1.527114 -14.262609 1.790166 -13.444414 c
|
||||
2.316660 -11.806815 2.830000 -9.346090 2.830000 -5.980957 c
|
||||
1.170000 -5.980957 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
7785
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000007875 00000 n
|
||||
0000007898 00000 n
|
||||
0000008071 00000 n
|
||||
0000008145 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
8204
|
||||
%%EOF
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -89,7 +89,7 @@ final class ChatBotStartInputPanelNode: ChatInputPanelNode {
|
||||
self.interfaceInteraction?.sendBotStart(presentationInterfaceState.botStartPayload)
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
if self.presentationInterfaceState != interfaceState {
|
||||
self.presentationInterfaceState = interfaceState
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ final class ChatButtonKeyboardInputNode: 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, isExpanded: Bool) -> (CGFloat, CGFloat) {
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: UIScreenPixel)))
|
||||
|
||||
let updatedTheme = self.theme !== interfaceState.theme
|
||||
|
||||
@ -265,7 +265,7 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false)
|
||||
}
|
||||
|
||||
|
||||
@ -532,8 +532,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
if transition.isAnimated {
|
||||
strongSelf.scheduleLayoutTransitionRequest(transition)
|
||||
} else {
|
||||
strongSelf.requestLayout(transition)
|
||||
}
|
||||
}
|
||||
|
||||
self.addSubnode(self.inputPanelContainerNode)
|
||||
|
||||
@ -592,6 +596,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.textInputPanelNode?.updateActivity = { [weak self] in
|
||||
self?.updateTypingActivity(true)
|
||||
}
|
||||
self.textInputPanelNode?.toggleExpandMediaInput = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.inputPanelContainerNode.toggleIfEnabled()
|
||||
}
|
||||
|
||||
self.inputMediaNodeDataDisposable = (self.inputMediaNodeDataPromise.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
@ -1056,7 +1066,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if inputTextPanelNode.isFocused {
|
||||
self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, 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, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
|
||||
}
|
||||
if let prevInputPanelNode = self.inputPanelNode, inputPanelNode.canHandleTransition(from: prevInputPanelNode) {
|
||||
inputPanelNodeHandlesTransition = true
|
||||
@ -1066,7 +1076,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
} else {
|
||||
dismissedInputPanelNode = self.inputPanelNode
|
||||
}
|
||||
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, 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, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
|
||||
inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
|
||||
self.inputPanelNode = inputPanelNode
|
||||
if inputPanelNode.supernode !== self {
|
||||
@ -1074,7 +1084,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.inputPanelClippingNode.insertSubnode(inputPanelNode, aboveSubnode: self.inputPanelBackgroundNode)
|
||||
}
|
||||
} else {
|
||||
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset - 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, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
|
||||
inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
|
||||
}
|
||||
} else {
|
||||
@ -1085,7 +1095,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if let secondaryInputPanelNode = inputPanelNodes.secondary, !previewing {
|
||||
if secondaryInputPanelNode !== self.secondaryInputPanelNode {
|
||||
dismissedSecondaryInputPanelNode = self.secondaryInputPanelNode
|
||||
let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, 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, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
|
||||
secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
|
||||
self.secondaryInputPanelNode = secondaryInputPanelNode
|
||||
if secondaryInputPanelNode.supernode == nil {
|
||||
@ -1093,7 +1103,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.inputPanelClippingNode.insertSubnode(secondaryInputPanelNode, aboveSubnode: self.inputPanelBackgroundNode)
|
||||
}
|
||||
} else {
|
||||
let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, 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, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
|
||||
secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
|
||||
}
|
||||
} else {
|
||||
@ -1142,8 +1152,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
var maximumInputNodeHeight = layout.size.height - max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top) - 10.0
|
||||
if let inputPanelSize = inputPanelSize {
|
||||
if let inputNode = self.inputNode, inputNode.hideInput, !inputNode.adjustLayoutForHiddenInput {
|
||||
maximumInputNodeHeight -= inputPanelNodeBaseHeight
|
||||
} else {
|
||||
maximumInputNodeHeight -= inputPanelSize.height
|
||||
}
|
||||
}
|
||||
if let secondaryInputPanelSize = secondaryInputPanelSize {
|
||||
maximumInputNodeHeight -= secondaryInputPanelSize.height
|
||||
}
|
||||
@ -1210,13 +1224,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if inputNode.hideInput, let inputPanelSize = inputPanelSize {
|
||||
if inputNode.hideInput, inputNode.adjustLayoutForHiddenInput, let inputPanelSize = inputPanelSize {
|
||||
maximumInputNodeHeight += inputPanelSize.height
|
||||
}
|
||||
|
||||
let inputHeight = layout.standardInputHeight + self.inputPanelContainerNode.expansionFraction * (maximumInputNodeHeight - layout.standardInputHeight)
|
||||
|
||||
let heightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: inputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: self.isInFocus)
|
||||
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, isExpanded: self.inputPanelContainerNode.stableIsExpanded)
|
||||
|
||||
let boundedHeight = min(heightAndOverflow.0, layout.standardInputHeight)
|
||||
|
||||
@ -1267,7 +1281,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.historyNode.isSelectionGestureEnabled = isSelectionEnabled
|
||||
|
||||
if let inputMediaNode = self.inputMediaNode, inputMediaNode != self.inputNode {
|
||||
let _ = inputMediaNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelSize?.height ?? 0.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: false)
|
||||
let _ = inputMediaNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelSize?.height ?? 0.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: false, isExpanded: self.inputPanelContainerNode.stableIsExpanded)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 66.0)))
|
||||
@ -1358,8 +1372,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if self.dismissedAsOverlay {
|
||||
inputPanelFrame!.origin.y = layout.size.height
|
||||
}
|
||||
if let inputNode = self.inputNode, inputNode.hideInput, !inputNode.adjustLayoutForHiddenInput {
|
||||
inputPanelsHeight += inputPanelNodeBaseHeight
|
||||
} else {
|
||||
inputPanelsHeight += inputPanelSize!.height
|
||||
}
|
||||
}
|
||||
|
||||
if self.secondaryInputPanelNode != nil {
|
||||
secondaryInputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight - secondaryInputPanelSize!.height), size: CGSize(width: layout.size.width, height: secondaryInputPanelSize!.height))
|
||||
@ -1384,7 +1402,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
if let inputNode = self.inputNode {
|
||||
if inputNode.hideInput {
|
||||
if inputNode.hideInput && inputNode.adjustLayoutForHiddenInput {
|
||||
inputPanelsHeight = 0.0
|
||||
}
|
||||
}
|
||||
@ -1963,7 +1981,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
private func updateInputPanelBackgroundExpansion(transition: ContainedViewLayoutTransition) {
|
||||
if let inputNode = self.inputNode {
|
||||
if inputNode.hideInput {
|
||||
if inputNode.hideInput && inputNode.adjustLayoutForHiddenInput {
|
||||
self.storedHideInputExpanded = self.inputPanelContainerNode.expansionFraction == 1.0
|
||||
self.inputPanelContainerNode.expand()
|
||||
} else {
|
||||
@ -2270,6 +2288,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.inputPanelContainerNode.collapse()
|
||||
if let inputNode = self.inputNode {
|
||||
inputNode.hideInput = false
|
||||
inputNode.adjustLayoutForHiddenInput = false
|
||||
if let inputNode = inputNode as? ChatEntityKeyboardInputNode {
|
||||
inputNode.markInputCollapsed()
|
||||
}
|
||||
@ -2344,7 +2363,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
self.inputMediaNode = inputNode
|
||||
if let (validLayout, _) = self.validLayout {
|
||||
let _ = inputNode.updateLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, bottomInset: validLayout.intrinsicInsets.bottom, standardInputHeight: validLayout.standardInputHeight, inputHeight: validLayout.inputHeight ?? 0.0, maximumHeight: validLayout.standardInputHeight, inputPanelHeight: 44.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: validLayout.deviceMetrics, isVisible: false)
|
||||
let _ = inputNode.updateLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, bottomInset: validLayout.intrinsicInsets.bottom, standardInputHeight: validLayout.standardInputHeight, inputHeight: validLayout.inputHeight ?? 0.0, maximumHeight: validLayout.standardInputHeight, inputPanelHeight: 44.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: validLayout.deviceMetrics, isVisible: false, isExpanded: self.inputPanelContainerNode.stableIsExpanded)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ import TelegramCore
|
||||
import ComponentDisplayAdapters
|
||||
import SettingsUI
|
||||
import TextFormat
|
||||
import PagerComponent
|
||||
|
||||
final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
struct InputData: Equatable {
|
||||
@ -215,11 +216,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
var title: String?
|
||||
if group.id == AnyHashable("recent") {
|
||||
//TODO:localize
|
||||
title = "Recently Used".uppercased()
|
||||
title = "Recently Used"
|
||||
} else {
|
||||
for (id, info, _) in view.collectionInfos {
|
||||
if AnyHashable(id) == group.id, let info = info as? StickerPackCollectionInfo {
|
||||
title = info.title.uppercased()
|
||||
title = info.title
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -396,17 +397,17 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
var title: String?
|
||||
if group.id == AnyHashable("saved") {
|
||||
//TODO:localize
|
||||
title = "Saved".uppercased()
|
||||
title = "Saved"
|
||||
} else if group.id == AnyHashable("recent") {
|
||||
//TODO:localize
|
||||
title = "Recently Used".uppercased()
|
||||
title = "Recently Used"
|
||||
} else if group.id == AnyHashable("premium") {
|
||||
//TODO:localize
|
||||
title = "Premium".uppercased()
|
||||
title = "Premium"
|
||||
} else {
|
||||
for (id, info, _) in view.collectionInfos {
|
||||
if AnyHashable(id) == group.id, let info = info as? StickerPackCollectionInfo {
|
||||
title = info.title.uppercased()
|
||||
title = info.title
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -462,7 +463,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
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)?
|
||||
var externalTopPanelContainerImpl: PagerExternalTopPanelContainer?
|
||||
override var externalTopPanelContainer: UIView? {
|
||||
return self.externalTopPanelContainerImpl
|
||||
}
|
||||
|
||||
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, isExpanded: Bool)?
|
||||
|
||||
init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal<InputData, NoError>, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction) {
|
||||
self.context = context
|
||||
@ -477,7 +483,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
self.view.addSubview(self.entityKeyboardView)
|
||||
|
||||
self.externalTopPanelContainer = SparseContainerView()
|
||||
self.externalTopPanelContainerImpl = PagerExternalTopPanelContainer()
|
||||
|
||||
self.inputDataDisposable = (updatedInputData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] inputData in
|
||||
@ -530,20 +536,25 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
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, isExpanded) = self.currentState else {
|
||||
return
|
||||
}
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded)
|
||||
}
|
||||
|
||||
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)
|
||||
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, isExpanded: Bool) -> (CGFloat, CGFloat) {
|
||||
self.currentState = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded)
|
||||
|
||||
let wasMarkedInputCollapsed = self.isMarkInputCollapsed
|
||||
self.isMarkInputCollapsed = false
|
||||
|
||||
let expandedHeight = standardInputHeight + self.expansionFraction * (maximumHeight - standardInputHeight)
|
||||
|
||||
var hiddenInputHeight: CGFloat = 0.0
|
||||
if self.hideInput && !self.adjustLayoutForHiddenInput {
|
||||
hiddenInputHeight = inputPanelHeight
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let controllerInteraction = self.controllerInteraction
|
||||
let inputNodeInteraction = self.inputNodeInteraction!
|
||||
@ -564,7 +575,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
stickerContent: self.currentInputData.stickers,
|
||||
gifContent: self.currentInputData.gifs,
|
||||
defaultToEmojiTab: self.defaultToEmojiTab,
|
||||
externalTopPanelContainer: self.externalTopPanelContainer,
|
||||
externalTopPanelContainer: self.externalTopPanelContainerImpl,
|
||||
topPanelExtensionUpdated: { [weak self] topPanelExtension, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -574,15 +585,24 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
strongSelf.topBackgroundExtensionUpdated?(transition.containedViewLayoutTransition)
|
||||
}
|
||||
},
|
||||
hideInputUpdated: { [weak self] hideInput, transition in
|
||||
hideInputUpdated: { [weak self] hideInput, adjustLayout, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.hideInput != hideInput {
|
||||
if strongSelf.hideInput != hideInput || strongSelf.adjustLayoutForHiddenInput != adjustLayout {
|
||||
strongSelf.hideInput = hideInput
|
||||
strongSelf.adjustLayoutForHiddenInput = adjustLayout
|
||||
strongSelf.hideInputUpdated?(transition.containedViewLayoutTransition)
|
||||
}
|
||||
},
|
||||
switchToTextInput: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.controllerInteraction.updateInputMode { _ in
|
||||
return .text
|
||||
}
|
||||
},
|
||||
makeSearchContainerNode: { content in
|
||||
let mappedMode: ChatMediaInputSearchMode
|
||||
switch content {
|
||||
@ -605,7 +625,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
)
|
||||
},
|
||||
deviceMetrics: deviceMetrics
|
||||
deviceMetrics: deviceMetrics,
|
||||
hiddenInputHeight: hiddenInputHeight,
|
||||
isExpanded: isExpanded
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: width, height: expandedHeight)
|
||||
|
||||
@ -54,7 +54,7 @@ final class ChatFeedNavigationInputPanelNode: ChatInputPanelNode {
|
||||
self.interfaceInteraction?.navigateFeed()
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
if self.presentationInterfaceState != interfaceState {
|
||||
self.presentationInterfaceState = interfaceState
|
||||
}
|
||||
|
||||
@ -11,18 +11,21 @@ class ChatInputNode: ASDisplayNode {
|
||||
return .single(Void())
|
||||
}
|
||||
|
||||
var externalTopPanelContainer: UIView?
|
||||
var externalTopPanelContainer: UIView? {
|
||||
return nil
|
||||
}
|
||||
|
||||
var topBackgroundExtension: CGFloat = 41.0
|
||||
var topBackgroundExtensionUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
var hideInput: Bool = false
|
||||
var adjustLayoutForHiddenInput: Bool = false
|
||||
var hideInputUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
var expansionFraction: CGFloat = 0.0
|
||||
var expansionFractionUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
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) {
|
||||
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, isExpanded: Bool) -> (CGFloat, CGFloat) {
|
||||
return (0.0, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ class ChatInputPanelNode: ASDisplayNode {
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
|
||||
@ -302,6 +302,9 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
||||
}
|
||||
|
||||
var stickersEnabled = true
|
||||
|
||||
let stickersAreEmoji = !isTextEmpty
|
||||
|
||||
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||
if isTextEmpty, case .broadcast = peer.info, canSendMessagesToPeer(peer) {
|
||||
accessoryItems.append(.silentPost(chatPresentationInterfaceState.interfaceState.silentPosting))
|
||||
@ -318,7 +321,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
||||
accessoryItems.append(.commands)
|
||||
}
|
||||
|
||||
accessoryItems.append(.stickers(stickersEnabled))
|
||||
accessoryItems.append(.stickers(isEnabled: stickersEnabled, isEmoji: stickersAreEmoji))
|
||||
|
||||
if isTextEmpty, let message = chatPresentationInterfaceState.keyboardButtonsMessage, let _ = message.visibleButtonKeyboardMarkup, chatPresentationInterfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId != message.id {
|
||||
accessoryItems.append(.inputButtons)
|
||||
|
||||
@ -577,7 +577,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
|
||||
var requestDisableStickerAnimations: ((Bool) -> Void)?
|
||||
|
||||
private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState, DeviceMetrics, Bool)?
|
||||
private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState, DeviceMetrics, Bool, Bool)?
|
||||
private var paneArrangement: ChatMediaInputPaneArrangement
|
||||
private var initializedArrangement = false
|
||||
|
||||
@ -1473,7 +1473,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
})))
|
||||
|
||||
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
|
||||
if let (_, _, _, _, _, _, _, _, interfaceState, _, _, _) = strongSelf.validLayout {
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = interfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
@ -1595,7 +1595,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
|> map { isStarred -> (UIView, CGRect, PeekControllerContent)? in
|
||||
if let strongSelf = self {
|
||||
var menuItems: [ContextMenuItem] = []
|
||||
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
|
||||
if let (_, _, _, _, _, _, _, _, interfaceState, _, _, _) = strongSelf.validLayout {
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = interfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
@ -1747,7 +1747,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
|> map { isStarred, hasPremium -> (UIView, CGRect, PeekControllerContent)? in
|
||||
if let strongSelf = self {
|
||||
var menuItems: [ContextMenuItem] = []
|
||||
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
|
||||
if let (_, _, _, _, _, _, _, _, interfaceState, _, _, _) = strongSelf.validLayout {
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = interfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
@ -1922,8 +1922,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index)
|
||||
let updatedGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs
|
||||
|
||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: transition, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
|
||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: transition, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded)
|
||||
self.updateAppearanceTransition(transition: transition)
|
||||
}
|
||||
if updatedGifPanelWasActive != previousGifPanelWasActive {
|
||||
@ -1940,8 +1940,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
|
||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2179,20 +2179,20 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
func simulateUpdateLayout(isVisible: Bool) {
|
||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, _) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
|
||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, _, isExpanded) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded)
|
||||
}
|
||||
}
|
||||
|
||||
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, isExpanded: Bool) -> (CGFloat, CGFloat) {
|
||||
var searchMode: ChatMediaInputSearchMode?
|
||||
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = self.validLayout, case let .media(_, maybeExpanded, _) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded {
|
||||
if let (_, _, _, _, _, _, _, _, interfaceState, _, _, _) = self.validLayout, case let .media(_, maybeExpanded, _) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded {
|
||||
searchMode = mode
|
||||
}
|
||||
|
||||
let wasVisible = self.validLayout?.10 ?? false
|
||||
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible)
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded)
|
||||
|
||||
if self.theme !== interfaceState.theme || self.strings !== interfaceState.strings {
|
||||
self.updateThemeAndStrings(chatWallpaper: interfaceState.chatWallpaper, theme: interfaceState.theme, strings: interfaceState.strings)
|
||||
@ -2545,7 +2545,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
self.stickerPane.removeFromSupernode()
|
||||
}
|
||||
case .changed:
|
||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
|
||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded) = self.validLayout {
|
||||
let translationX = -recognizer.translation(in: self.view).x
|
||||
var indexTransition = translationX / width
|
||||
if self.paneArrangement.currentIndex == 0 {
|
||||
@ -2554,10 +2554,10 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
indexTransition = min(0.0, indexTransition)
|
||||
}
|
||||
self.paneArrangement = self.paneArrangement.withIndexTransition(indexTransition)
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded)
|
||||
}
|
||||
case .ended:
|
||||
if let (width, _, _, _, _, _, _, _, _, _, _) = self.validLayout {
|
||||
if let (width, _, _, _, _, _, _, _, _, _, _, _) = self.validLayout {
|
||||
var updatedIndex = self.paneArrangement.currentIndex
|
||||
if abs(self.paneArrangement.indexTransition * width) > 30.0 {
|
||||
if self.paneArrangement.indexTransition < 0.0 {
|
||||
@ -2570,9 +2570,9 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
self.setCurrentPane(self.paneArrangement.panes[updatedIndex], transition: .animated(duration: 0.25, curve: .spring))
|
||||
}
|
||||
case .cancelled:
|
||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
|
||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded) = self.validLayout {
|
||||
self.paneArrangement = self.paneArrangement.withIndexTransition(0.0)
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded)
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
||||
@ -71,7 +71,7 @@ final class ChatMessageReportInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
if self.presentationInterfaceState != interfaceState {
|
||||
self.presentationInterfaceState = interfaceState
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
||||
private let shareButton: HighlightableButtonNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
|
||||
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool)?
|
||||
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)?
|
||||
private var presentationInterfaceState: ChatPresentationInterfaceState?
|
||||
private var actions: ChatAvailableMessageActions?
|
||||
|
||||
@ -33,8 +33,8 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
||||
|
||||
if self.selectedMessages.isEmpty {
|
||||
self.actions = nil
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary) = self.validLayout, let interfaceState = self.presentationInterfaceState {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics)
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
|
||||
}
|
||||
self.canDeleteMessagesDisposable.set(nil)
|
||||
} else if let context = self.context {
|
||||
@ -42,8 +42,8 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
||||
|> deliverOnMainQueue).start(next: { [weak self] actions in
|
||||
if let strongSelf = self {
|
||||
strongSelf.actions = actions
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState {
|
||||
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics)
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState {
|
||||
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -154,8 +154,8 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary)
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded)
|
||||
|
||||
let panelHeight = defaultHeight(metrics: metrics)
|
||||
|
||||
|
||||
@ -355,8 +355,9 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var updatedAttributes: [NSAttributedString.Key: Any] = currentDict
|
||||
updatedAttributes[NSAttributedString.Key.foregroundColor] = UIColor.clear.cgColor
|
||||
updatedAttributes[NSAttributedString.Key("Attribute__EmbeddedItem")] = InlineStickerItem(emoji: ChatTextInputTextCustomEmojiAttribute(stickerPack: stickerPack, fileId: fileId))
|
||||
updatedAttributes[ChatTextInputAttributes.customEmoji] = ChatTextInputTextCustomEmojiAttribute(stickerPack: stickerPack, fileId: fileId)
|
||||
|
||||
let insertString = NSAttributedString(string: "[\u{00a0}\u{00a0}\u{00a0}]", attributes: updatedAttributes)
|
||||
let insertString = NSAttributedString(string: updatedString.attributedSubstring(from: range).string, attributes: updatedAttributes)
|
||||
updatedString.replaceCharacters(in: range, with: insertString)
|
||||
}
|
||||
attributedText = updatedString
|
||||
|
||||
@ -135,7 +135,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
if self.presentationInterfaceState != interfaceState {
|
||||
var updateWaveform = false
|
||||
if self.presentationInterfaceState?.recordedMediaPreview != interfaceState.recordedMediaPreview {
|
||||
|
||||
@ -23,7 +23,7 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
if self.presentationInterfaceState != interfaceState {
|
||||
self.presentationInterfaceState = interfaceState
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
|
||||
private var needsSearchResultsTooltip = true
|
||||
|
||||
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool)?
|
||||
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)?
|
||||
|
||||
override var interfaceInteraction: ChatPanelInterfaceInteraction? {
|
||||
didSet {
|
||||
@ -38,8 +38,8 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
if let strongSelf = self, strongSelf.displayActivity != value {
|
||||
strongSelf.displayActivity = value
|
||||
strongSelf.activityIndicator.isHidden = !value
|
||||
if let interfaceState = strongSelf.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary) = strongSelf.validLayout {
|
||||
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics)
|
||||
if let interfaceState = strongSelf.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout {
|
||||
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -127,8 +127,8 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary)
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded)
|
||||
|
||||
if self.presentationInterfaceState != interfaceState {
|
||||
let themeUpdated = self.presentationInterfaceState?.theme !== interfaceState.theme
|
||||
|
||||
@ -132,7 +132,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
|
||||
self.backdropNode.update(rect: rect, within: containerSize, transition: transition)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
|
||||
func updateLayout(size: CGSize, isMediaInputExpanded: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
|
||||
self.validLayout = size
|
||||
transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.micButton.layoutItems()
|
||||
@ -183,10 +183,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size))
|
||||
var expanded = false
|
||||
if case let .media(_, maybeExpanded, _) = interfaceState.inputMode, maybeExpanded != nil {
|
||||
expanded = true
|
||||
}
|
||||
let expanded = isMediaInputExpanded
|
||||
transition.updateSublayerTransformScale(node: self.expandMediaInputButton, scale: CGPoint(x: 1.0, y: expanded ? 1.0 : -1.0))
|
||||
}
|
||||
|
||||
|
||||
@ -25,17 +25,25 @@ import ManagedAnimationNode
|
||||
import AttachmentUI
|
||||
import EditableChatTextNode
|
||||
import EmojiTextAttachmentView
|
||||
import LottieAnimationComponent
|
||||
import ComponentFlow
|
||||
|
||||
private let accessoryButtonFont = Font.medium(14.0)
|
||||
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
|
||||
|
||||
private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode {
|
||||
private let item: ChatTextInputAccessoryItem
|
||||
private var item: ChatTextInputAccessoryItem
|
||||
private var theme: PresentationTheme
|
||||
private var width: CGFloat
|
||||
private let iconImageNode: ASImageNode
|
||||
private var animationView: ComponentView<Empty>?
|
||||
private var imageEdgeInsets = UIEdgeInsets()
|
||||
|
||||
init(item: ChatTextInputAccessoryItem, theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.item = item
|
||||
self.theme = theme
|
||||
|
||||
self.iconImageNode = ASImageNode()
|
||||
|
||||
let (image, text, accessibilityLabel, alpha, insets) = AccessoryItemIconButtonNode.imageAndInsets(item: item, theme: theme, strings: strings)
|
||||
|
||||
@ -46,7 +54,13 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode {
|
||||
self.isAccessibilityElement = true
|
||||
self.accessibilityTraits = [.button]
|
||||
|
||||
self.addSubnode(self.imageNode)
|
||||
self.iconImageNode.isUserInteractionEnabled = false
|
||||
self.addSubnode(self.iconImageNode)
|
||||
|
||||
if case .stickers = item {
|
||||
self.iconImageNode.isHidden = true
|
||||
self.animationView = ComponentView<Empty>()
|
||||
}
|
||||
|
||||
if let text = text {
|
||||
self.setAttributedTitle(NSAttributedString(string: text, font: accessoryButtonFont, textColor: theme.chat.inputPanel.inputControlColor), for: .normal)
|
||||
@ -54,8 +68,8 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode {
|
||||
self.setAttributedTitle(NSAttributedString(), for: .normal)
|
||||
}
|
||||
|
||||
self.imageNode.image = image
|
||||
self.imageNode.alpha = alpha
|
||||
self.iconImageNode.image = image
|
||||
self.iconImageNode.alpha = alpha
|
||||
self.imageEdgeInsets = insets
|
||||
|
||||
self.accessibilityLabel = accessibilityLabel
|
||||
@ -74,6 +88,8 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode {
|
||||
}
|
||||
|
||||
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.theme = theme
|
||||
|
||||
let (image, text, accessibilityLabel, alpha, insets) = AccessoryItemIconButtonNode.imageAndInsets(item: item, theme: theme, strings: strings)
|
||||
|
||||
self.width = AccessoryItemIconButtonNode.calculateWidth(item: item, image: image, text: text, strings: strings)
|
||||
@ -84,9 +100,9 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode {
|
||||
self.setAttributedTitle(NSAttributedString(), for: .normal)
|
||||
}
|
||||
|
||||
self.imageNode.image = image
|
||||
self.iconImageNode.image = image
|
||||
self.imageEdgeInsets = insets
|
||||
self.imageNode.alpha = alpha
|
||||
self.iconImageNode.alpha = alpha
|
||||
|
||||
self.accessibilityLabel = accessibilityLabel
|
||||
}
|
||||
@ -99,7 +115,7 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode {
|
||||
switch item {
|
||||
case .keyboard:
|
||||
return (PresentationResourcesChat.chatInputTextFieldKeyboardImage(theme), nil, strings.VoiceOver_Keyboard, 1.0, UIEdgeInsets())
|
||||
case let .stickers(enabled):
|
||||
case let .stickers(enabled, _):
|
||||
return (PresentationResourcesChat.chatInputTextFieldStickersImage(theme), nil, strings.VoiceOver_Stickers, enabled ? 1.0 : 0.4, UIEdgeInsets())
|
||||
case .inputButtons:
|
||||
return (PresentationResourcesChat.chatInputTextFieldInputButtonsImage(theme), nil, strings.VoiceOver_BotKeyboard, 1.0, UIEdgeInsets())
|
||||
@ -136,11 +152,48 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode {
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize) {
|
||||
if let image = self.imageNode.image {
|
||||
func updateLayout(item: ChatTextInputAccessoryItem, size: CGSize) {
|
||||
self.item = item
|
||||
|
||||
if let image = self.iconImageNode.image {
|
||||
let bottomInset: CGFloat = 0.0
|
||||
let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0) - bottomInset), size: image.size)
|
||||
self.imageNode.frame = imageFrame
|
||||
self.iconImageNode.frame = imageFrame
|
||||
|
||||
if case let .stickers(_, isEmoji) = item, let animationView = self.animationView {
|
||||
let animationFrame = imageFrame.insetBy(dx: -4.0, dy: -4.0)
|
||||
|
||||
var colors: [String: UIColor] = [:]
|
||||
let colorKeys: [String] = [
|
||||
"Ellipse 33.Ellipse 33.Stroke 1",
|
||||
"Ellipse 34.Ellipse 34.Stroke 1",
|
||||
"Oval.Oval.Fill 1",
|
||||
"Oval 2.Oval.Fill 1",
|
||||
"Path 85.Path 85.Stroke 1"
|
||||
]
|
||||
for colorKey in colorKeys {
|
||||
colors[colorKey] = self.theme.chat.inputPanel.inputControlColor
|
||||
}
|
||||
let _ = animationView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
name: !isEmoji ? "anim_stickertosmile" : "anim_smiletosticker",
|
||||
colors: colors,
|
||||
mode: .animateTransitionFromPrevious
|
||||
),
|
||||
size: animationFrame.size
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: animationFrame.size
|
||||
)
|
||||
if let view = animationView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = animationFrame
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,7 +249,7 @@ private func calculateTextFieldRealInsets(presentationInterfaceState: ChatPresen
|
||||
}
|
||||
|
||||
var right: CGFloat = 0.0
|
||||
right += accessoryButtonsWidth
|
||||
right += max(0.0, accessoryButtonsWidth - 14.0)
|
||||
|
||||
return UIEdgeInsets(top: 4.5 + top, left: 0.0, bottom: 5.5 + bottom, right: right)
|
||||
}
|
||||
@ -347,13 +400,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
|
||||
private var accessoryItemButtons: [(ChatTextInputAccessoryItem, AccessoryItemIconButtonNode)] = []
|
||||
|
||||
private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)?
|
||||
private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool, Bool)?
|
||||
private var leftMenuInset: CGFloat = 0.0
|
||||
|
||||
var displayAttachmentMenu: () -> Void = { }
|
||||
var sendMessage: () -> Void = { }
|
||||
var paste: (ChatTextInputPanelPasteData) -> Void = { _ in }
|
||||
var updateHeight: (Bool) -> Void = { _ in }
|
||||
var toggleExpandMediaInput: (() -> Void)?
|
||||
|
||||
var updateActivity: () -> Void = { }
|
||||
|
||||
@ -450,8 +504,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
for item in accessoryItems {
|
||||
var itemAndButton: (ChatTextInputAccessoryItem, AccessoryItemIconButtonNode)?
|
||||
for i in 0 ..< self.accessoryItemButtons.count {
|
||||
if self.accessoryItemButtons[i].0 == item {
|
||||
if self.accessoryItemButtons[i].0.key == item.key {
|
||||
itemAndButton = self.accessoryItemButtons[i]
|
||||
itemAndButton?.0 = item
|
||||
self.accessoryItemButtons.remove(at: i)
|
||||
break
|
||||
}
|
||||
@ -661,15 +716,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
self.actionButtons.micButton.offsetRecordingControls = { [weak self] in
|
||||
if let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState {
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary) = strongSelf.validLayout {
|
||||
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics)
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout {
|
||||
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.actionButtons.micButton.updateCancelTranslation = { [weak self] in
|
||||
if let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState {
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary) = strongSelf.validLayout {
|
||||
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics)
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout {
|
||||
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -911,10 +966,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.actionButtons.updateAbsoluteRect(CGRect(origin: rect.origin.offsetBy(dx: self.actionButtons.frame.minX, dy: self.actionButtons.frame.minY), size: self.actionButtons.frame.size), within: containerSize, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
let previousAdditionalSideInsets = self.validLayout?.4
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary)
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded)
|
||||
|
||||
let textFieldWaitsForTouchUp: Bool
|
||||
if case .regular = metrics.widthClass, bottomInset.isZero {
|
||||
@ -1228,8 +1282,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
for item in interfaceState.inputTextPanelState.accessoryItems {
|
||||
var itemAndButton: (ChatTextInputAccessoryItem, AccessoryItemIconButtonNode)?
|
||||
for i in 0 ..< self.accessoryItemButtons.count {
|
||||
if self.accessoryItemButtons[i].0 == item {
|
||||
if self.accessoryItemButtons[i].0.key == item.key {
|
||||
itemAndButton = self.accessoryItemButtons[i]
|
||||
itemAndButton?.0 = item
|
||||
self.accessoryItemButtons.remove(at: i)
|
||||
break
|
||||
}
|
||||
@ -1661,7 +1716,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
|
||||
if let presentationInterfaceState = self.presentationInterfaceState {
|
||||
self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), transition: transition, interfaceState: presentationInterfaceState)
|
||||
self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), isMediaInputExpanded: isMediaInputExpanded, transition: transition, interfaceState: presentationInterfaceState)
|
||||
}
|
||||
|
||||
if let _ = interfaceState.inputTextPanelState.mediaRecordingState {
|
||||
@ -1784,9 +1839,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
|
||||
var nextButtonTopRight = CGPoint(x: width - rightInset - textFieldInsets.right - accessoryButtonInset, y: panelHeight - textFieldInsets.bottom - minimalInputHeight)
|
||||
for (_, button) in self.accessoryItemButtons.reversed() {
|
||||
for (item, button) in self.accessoryItemButtons.reversed() {
|
||||
let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight)
|
||||
button.updateLayout(size: buttonSize)
|
||||
button.updateLayout(item: item, size: buttonSize)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: nextButtonTopRight.x - buttonSize.width, y: nextButtonTopRight.y + floor((minimalInputHeight - buttonSize.height) / 2.0)), size: buttonSize)
|
||||
if button.supernode == nil {
|
||||
self.clippingNode.addSubnode(button)
|
||||
@ -2159,7 +2214,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.counterTextNode.attributedText = NSAttributedString(string: "", font: counterFont, textColor: .black)
|
||||
}
|
||||
|
||||
if let (width, leftInset, rightInset, _, _, maxHeight, metrics, _) = self.validLayout {
|
||||
if let (width, leftInset, rightInset, _, _, maxHeight, metrics, _, _) = self.validLayout {
|
||||
var composeButtonsOffset: CGFloat = 0.0
|
||||
if self.extendedSearchLayout {
|
||||
composeButtonsOffset = 44.0
|
||||
@ -2299,8 +2354,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
let hideExpandMediaInput = hideMicButton
|
||||
|
||||
if mediaInputIsActive {
|
||||
//hideMicButton = true
|
||||
hideMicButton = true
|
||||
}
|
||||
|
||||
if hideMicButton {
|
||||
@ -2325,7 +2382,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if mediaInputIsActive && !"".isEmpty {
|
||||
if mediaInputIsActive && !hideExpandMediaInput {
|
||||
if self.actionButtons.expandMediaInputButton.alpha.isZero {
|
||||
self.actionButtons.expandMediaInputButton.alpha = 1.0
|
||||
if animated {
|
||||
@ -2350,7 +2407,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
|
||||
private func updateTextHeight(animated: Bool) {
|
||||
if let (width, leftInset, rightInset, _, additionalSideInsets, maxHeight, metrics, _) = self.validLayout {
|
||||
if let (width, leftInset, rightInset, _, additionalSideInsets, maxHeight, metrics, _, _) = self.validLayout {
|
||||
let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - additionalSideInsets.right - self.leftMenuInset, maxHeight: maxHeight, metrics: metrics)
|
||||
let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics)
|
||||
if !self.bounds.size.height.isEqual(to: panelHeight) {
|
||||
@ -2836,7 +2893,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
|
||||
@objc func expandButtonPressed() {
|
||||
self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in
|
||||
self.toggleExpandMediaInput?()
|
||||
/*self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in
|
||||
if case let .media(mode, expanded, focused) = state.inputMode {
|
||||
if let _ = expanded {
|
||||
return (.media(mode: mode, expanded: nil, focused: focused), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId)
|
||||
@ -2846,14 +2904,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
} else {
|
||||
return (state.inputMode, state.interfaceState.messageActionsState.closedButtonKeyboardMessageId)
|
||||
}
|
||||
})
|
||||
})*/
|
||||
}
|
||||
|
||||
@objc func accessoryItemButtonPressed(_ button: UIView) {
|
||||
for (item, currentButton) in self.accessoryItemButtons {
|
||||
if currentButton === button {
|
||||
switch item {
|
||||
case let .stickers(enabled):
|
||||
case let .stickers(enabled, _):
|
||||
if enabled {
|
||||
self.interfaceInteraction?.openStickers()
|
||||
} else {
|
||||
|
||||
@ -82,7 +82,7 @@ final class ChatUnblockInputPanelNode: ChatInputPanelNode {
|
||||
self.interfaceInteraction?.unblockPeer()
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
if self.presentationInterfaceState != interfaceState {
|
||||
self.presentationInterfaceState = interfaceState
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ final class DeleteChatInputPanelNode: ChatInputPanelNode {
|
||||
self.interfaceInteraction?.deleteChat()
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
if self.presentationInterfaceState != interfaceState {
|
||||
self.presentationInterfaceState = interfaceState
|
||||
|
||||
|
||||
@ -379,7 +379,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
||||
self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
|
||||
|
||||
let interfaceState = ChatPresentationInterfaceState(chatWallpaper: .color(0), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: .regular, bubbleCorners: PresentationChatBubbleCorners(mainRadius: 16.0, auxiliaryRadius: 8.0, mergeBubbleCorners: true), accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(id: self.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil)
|
||||
let panelHeight = self.selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: 0.0, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics)
|
||||
let panelHeight = self.selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: 0.0, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics, isMediaInputExpanded: false)
|
||||
|
||||
transition.updateFrame(node: self.selectionPanel, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeight)))
|
||||
|
||||
|
||||
@ -491,7 +491,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
if textInputPanelNode.frame.width.isZero {
|
||||
panelTransition = .immediate
|
||||
}
|
||||
var panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics)
|
||||
var panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false)
|
||||
if self.searchDisplayController == nil {
|
||||
panelHeight += insets.bottom
|
||||
} else {
|
||||
|
||||
@ -44,7 +44,7 @@ final class SecretChatHandshakeStatusInputPanelNode: ChatInputPanelNode {
|
||||
self.interfaceInteraction?.unblockPeer()
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
if self.presentationInterfaceState != interfaceState {
|
||||
self.presentationInterfaceState = interfaceState
|
||||
|
||||
|
||||
@ -249,6 +249,8 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Timecode), value: TelegramTimecode(time: time, text: text), range: range)
|
||||
}
|
||||
}
|
||||
case let .CustomEmoji(stickerPack, fileId):
|
||||
string.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(stickerPack: stickerPack, fileId: fileId), range: range)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user