mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-02 20:55:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
ae60febadf
@ -7886,6 +7886,10 @@ Sorry for the inconvenience.";
|
||||
"ChatContextMenu.EmojiSet_1" = "This message contains emoji from [%@ pack]().";
|
||||
"ChatContextMenu.EmojiSet_any" = "This message contains emoji from [%@ packs]().";
|
||||
|
||||
"ChatContextMenu.ReactionEmojiSetSingle" = "This message contains\n#[%@]() reactions.";
|
||||
"ChatContextMenu.ReactionEmojiSet_1" = "This message contains reactions from [%@ pack]().";
|
||||
"ChatContextMenu.ReactionEmojiSet_any" = "This message contains reactions from [%@ packs]().";
|
||||
|
||||
"EmojiPack.Title" = "Emoji";
|
||||
"EmojiPack.Emoji_1" = "%@ emoji";
|
||||
"EmojiPack.Emoji_any" = "%@ emoji";
|
||||
|
||||
@ -673,7 +673,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
public var activateAfterCompletion: Bool = false {
|
||||
didSet {
|
||||
if self.activateAfterCompletion {
|
||||
self.contextGesture?.activatedAfterCompletion = { [weak self] point in
|
||||
self.contextGesture?.activatedAfterCompletion = { [weak self] point, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -717,7 +717,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
}
|
||||
|
||||
if self.activateAfterCompletion {
|
||||
self.contextGesture?.activatedAfterCompletion = { [weak self] point in
|
||||
self.contextGesture?.activatedAfterCompletion = { [weak self] point, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -473,6 +473,33 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
|
||||
func animateTransitionInside(other: InnerTextSelectionTipContainerNode) {
|
||||
let nodes: [ASDisplayNode] = [
|
||||
self.textNode.textNode,
|
||||
self.iconNode,
|
||||
self.placeholderNode
|
||||
]
|
||||
|
||||
for node in nodes {
|
||||
other.addSubnode(node)
|
||||
node.layer.animateAlpha(from: node.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak node] _ in
|
||||
node?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateContentIn() {
|
||||
let nodes: [ASDisplayNode] = [
|
||||
self.textNode.textNode,
|
||||
self.iconNode,
|
||||
self.placeholderNode
|
||||
]
|
||||
|
||||
for node in nodes {
|
||||
node.layer.animateAlpha(from: 0.0, to: node.alpha, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(widthClass: ContainerViewLayoutSizeClass, width: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
switch widthClass {
|
||||
case .compact:
|
||||
|
||||
@ -250,6 +250,14 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
private let blurBackground: Bool
|
||||
|
||||
var overlayWantsToBeBelowKeyboard: Bool {
|
||||
if let presentationNode = self.presentationNode {
|
||||
return presentationNode.wantsDisplayBelowKeyboard()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
init(
|
||||
account: Account,
|
||||
controller: ContextController,
|
||||
@ -624,6 +632,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let validLayout = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(
|
||||
layout: validLayout,
|
||||
@ -632,6 +641,14 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
)
|
||||
}
|
||||
},
|
||||
requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let controller = strongSelf.getController() {
|
||||
controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition)
|
||||
}
|
||||
},
|
||||
requestDismiss: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -681,6 +698,14 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
)
|
||||
}
|
||||
},
|
||||
requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let controller = strongSelf.getController() {
|
||||
controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition)
|
||||
}
|
||||
},
|
||||
requestDismiss: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -716,6 +741,14 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
)
|
||||
}
|
||||
},
|
||||
requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let controller = strongSelf.getController() {
|
||||
controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition)
|
||||
}
|
||||
},
|
||||
requestDismiss: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -1525,25 +1558,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
reactionContextNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
if !items.reactionItems.isEmpty, let context = items.context, let animationCache = items.animationCache {
|
||||
let reactionContextNode = ReactionContextNode(context: context, animationCache: animationCache, presentationData: self.presentationData, items: items.reactionItems, selectedItems: items.selectedReactionItems, getEmojiContent: items.getEmojiContent, isExpandedUpdated: { _ in }, requestLayout: { _ in })
|
||||
self.reactionContextNode = reactionContextNode
|
||||
self.addSubnode(reactionContextNode)
|
||||
|
||||
reactionContextNode.reactionSelected = { [weak self] reaction, isLarge in
|
||||
guard let strongSelf = self, let controller = strongSelf.getController() as? ContextController else {
|
||||
return
|
||||
}
|
||||
controller.reactionSelected?(reaction, isLarge)
|
||||
}
|
||||
reactionContextNode.premiumReactionsSelected = { [weak self] _ in
|
||||
guard let strongSelf = self, let controller = strongSelf.getController() as? ContextController else {
|
||||
return
|
||||
}
|
||||
controller.premiumReactionsSelected?()
|
||||
}
|
||||
}
|
||||
|
||||
let previousActionsContainerNode = self.actionsContainerNode
|
||||
let previousActionsContainerFrame = previousActionsContainerNode.view.convert(previousActionsContainerNode.bounds, to: self.view)
|
||||
@ -2508,6 +2522,14 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
private var animatedDidAppear = false
|
||||
private var wasDismissed = false
|
||||
|
||||
override public var overlayWantsToBeBelowKeyboard: Bool {
|
||||
if self.isNodeLoaded {
|
||||
return self.controllerNode.overlayWantsToBeBelowKeyboard
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private var controllerNode: ContextControllerNode {
|
||||
return self.displayNode as! ContextControllerNode
|
||||
}
|
||||
|
||||
@ -968,16 +968,20 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
var updatedTransition = transition
|
||||
if let tipNode = self.tipNode, tipNode.tip == tip {
|
||||
} else {
|
||||
if let tipNode = self.tipNode {
|
||||
self.tipNode = nil
|
||||
tipNode.removeFromSupernode()
|
||||
}
|
||||
let previousTipNode = self.tipNode
|
||||
updatedTransition = .immediate
|
||||
let tipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData, tip: tip)
|
||||
tipNode.requestDismiss = { [weak self] completion in
|
||||
self?.getController()?.dismiss(completion: completion)
|
||||
}
|
||||
self.tipNode = tipNode
|
||||
|
||||
if let previousTipNode = previousTipNode {
|
||||
previousTipNode.animateTransitionInside(other: tipNode)
|
||||
previousTipNode.removeFromSupernode()
|
||||
|
||||
tipNode.animateContentIn()
|
||||
}
|
||||
}
|
||||
|
||||
if let tipNode = self.tipNode {
|
||||
|
||||
@ -172,6 +172,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
private let requestUpdate: (ContainedViewLayoutTransition) -> Void
|
||||
private let requestUpdateOverlayWantsToBeBelowKeyboard: (ContainedViewLayoutTransition) -> Void
|
||||
private let requestDismiss: (ContextMenuActionResult) -> Void
|
||||
private let requestAnimateOut: (ContextMenuActionResult, @escaping () -> Void) -> Void
|
||||
private let source: ContentSource
|
||||
@ -208,12 +209,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
init(
|
||||
getController: @escaping () -> ContextControllerProtocol?,
|
||||
requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void,
|
||||
requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void,
|
||||
requestDismiss: @escaping (ContextMenuActionResult) -> Void,
|
||||
requestAnimateOut: @escaping (ContextMenuActionResult, @escaping () -> Void) -> Void,
|
||||
source: ContentSource
|
||||
) {
|
||||
self.getController = getController
|
||||
self.requestUpdate = requestUpdate
|
||||
self.requestUpdateOverlayWantsToBeBelowKeyboard = requestUpdateOverlayWantsToBeBelowKeyboard
|
||||
self.requestDismiss = requestDismiss
|
||||
self.requestAnimateOut = requestAnimateOut
|
||||
self.source = source
|
||||
@ -394,6 +397,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
self.actionsStackNode.increaseHighlightedIndex()
|
||||
}
|
||||
|
||||
func wantsDisplayBelowKeyboard() -> Bool {
|
||||
if let reactionContextNode = self.reactionContextNode {
|
||||
return reactionContextNode.wantsDisplayBelowKeyboard()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func replaceItems(items: ContextController.Items, animated: Bool) {
|
||||
self.actionsStackNode.replace(item: makeContextControllerActionsStackItem(items: items), animated: animated)
|
||||
}
|
||||
@ -532,6 +543,12 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
return
|
||||
}
|
||||
strongSelf.requestUpdate(transition)
|
||||
},
|
||||
requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.requestUpdateOverlayWantsToBeBelowKeyboard(transition)
|
||||
}
|
||||
)
|
||||
self.reactionContextNode = reactionContextNode
|
||||
|
||||
@ -17,6 +17,7 @@ protocol ContextControllerPresentationNode: ASDisplayNode {
|
||||
func replaceItems(items: ContextController.Items, animated: Bool)
|
||||
func pushItems(items: ContextController.Items)
|
||||
func popItems()
|
||||
func wantsDisplayBelowKeyboard() -> Bool
|
||||
|
||||
func update(
|
||||
presentationData: PresentationData,
|
||||
|
||||
@ -57,6 +57,7 @@ private func cancelOtherGestures(gesture: ContextGesture, view: UIView) {
|
||||
|
||||
public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDelegate {
|
||||
public var beginDelay: Double = 0.12
|
||||
public var activateOnTap: Bool = false
|
||||
private var currentProgress: CGFloat = 0.0
|
||||
private var delayTimer: Timer?
|
||||
private var animator: DisplayLinkAnimator?
|
||||
@ -68,7 +69,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
|
||||
public var activated: ((ContextGesture, CGPoint) -> Void)?
|
||||
public var externalUpdated: ((UIView?, CGPoint) -> Void)?
|
||||
public var externalEnded: (((UIView?, CGPoint)?) -> Void)?
|
||||
public var activatedAfterCompletion: ((CGPoint) -> Void)?
|
||||
public var activatedAfterCompletion: ((CGPoint, Bool) -> Void)?
|
||||
public var cancelGesturesOnActivation: (() -> Void)?
|
||||
|
||||
override public init(target: Any?, action: Selector?) {
|
||||
@ -208,7 +209,12 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
|
||||
self.currentProgress = 0.0
|
||||
self.activationProgress?(0.0, .ended(self.currentProgress))
|
||||
if self.wasActivated {
|
||||
self.activatedAfterCompletion?(touch.location(in: self.view))
|
||||
self.activatedAfterCompletion?(touch.location(in: self.view), false)
|
||||
}
|
||||
} else {
|
||||
self.currentProgress = 0.0
|
||||
if !self.wasActivated && self.activateOnTap {
|
||||
self.activatedAfterCompletion?(touch.location(in: self.view), true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -505,22 +505,24 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.pauseAnimations()
|
||||
self.displayLink.invalidate()
|
||||
|
||||
for i in (0 ..< self.itemNodes.count).reversed() {
|
||||
var itemNode: AnyObject? = self.itemNodes[i]
|
||||
self.itemNodes.remove(at: i)
|
||||
ASPerformMainThreadDeallocation(&itemNode)
|
||||
}
|
||||
for key in self.itemHeaderNodes.keys {
|
||||
var itemHeaderNode: AnyObject? = self.itemHeaderNodes[key]
|
||||
self.itemHeaderNodes.removeValue(forKey: key)
|
||||
ASPerformMainThreadDeallocation(&itemHeaderNode)
|
||||
}
|
||||
|
||||
self.waitingForNodesDisposable.dispose()
|
||||
self.reorderFeedbackDisposable?.dispose()
|
||||
let _ = { () -> Void in
|
||||
self.pauseAnimations()
|
||||
self.displayLink.invalidate()
|
||||
|
||||
for i in (0 ..< self.itemNodes.count).reversed() {
|
||||
var itemNode: AnyObject? = self.itemNodes[i]
|
||||
self.itemNodes.remove(at: i)
|
||||
ASPerformMainThreadDeallocation(&itemNode)
|
||||
}
|
||||
for key in self.itemHeaderNodes.keys {
|
||||
var itemHeaderNode: AnyObject? = self.itemHeaderNodes[key]
|
||||
self.itemHeaderNodes.removeValue(forKey: key)
|
||||
ASPerformMainThreadDeallocation(&itemHeaderNode)
|
||||
}
|
||||
|
||||
self.waitingForNodesDisposable.dispose()
|
||||
self.reorderFeedbackDisposable?.dispose()
|
||||
}()
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||
|
||||
@ -154,6 +154,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
private var overlayContainers: [NavigationOverlayContainer] = []
|
||||
|
||||
private var globalOverlayContainers: [NavigationOverlayContainer] = []
|
||||
private var globalOverlayBelowKeyboardContainerParent: GlobalOverlayContainerParent?
|
||||
private var globalOverlayContainerParent: GlobalOverlayContainerParent?
|
||||
public var globalOverlayControllersUpdated: (() -> Void)?
|
||||
|
||||
@ -351,7 +352,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
|
||||
private var isUpdatingContainers: Bool = false
|
||||
|
||||
private func updateContainersNonReentrant(transition: ContainedViewLayoutTransition) {
|
||||
func updateContainersNonReentrant(transition: ContainedViewLayoutTransition) {
|
||||
if self.isUpdatingContainers {
|
||||
return
|
||||
}
|
||||
@ -375,7 +376,18 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
|
||||
let initialPrefersOnScreenNavigationHidden = self.collectPrefersOnScreenNavigationHidden()
|
||||
|
||||
var overlayLayout = layout
|
||||
let belowKeyboardOverlayLayout = layout
|
||||
var globalOverlayLayout = layout
|
||||
|
||||
if let globalOverlayBelowKeyboardContainerParent = self.globalOverlayBelowKeyboardContainerParent {
|
||||
if globalOverlayBelowKeyboardContainerParent.view.superview != self.displayNode.view {
|
||||
self.displayNode.addSubnode(globalOverlayBelowKeyboardContainerParent)
|
||||
}
|
||||
|
||||
/*overlayLayout.size.height = overlayLayout.size.height - (layout.inputHeight ?? 0.0)
|
||||
overlayLayout.inputHeight = nil
|
||||
overlayLayout.inputHeightIsInteractivellyChanging = false*/
|
||||
}
|
||||
|
||||
if let globalOverlayContainerParent = self.globalOverlayContainerParent {
|
||||
let portraitSize = CGSize(width: min(layout.size.width, layout.size.height), height: max(layout.size.width, layout.size.height))
|
||||
@ -386,9 +398,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
self.displayNode.addSubnode(globalOverlayContainerParent)
|
||||
}
|
||||
|
||||
overlayLayout.size.height = overlayLayout.size.height - (layout.inputHeight ?? 0.0)
|
||||
overlayLayout.inputHeight = nil
|
||||
overlayLayout.inputHeightIsInteractivellyChanging = false
|
||||
globalOverlayLayout.size.height = globalOverlayLayout.size.height - (layout.inputHeight ?? 0.0)
|
||||
globalOverlayLayout.inputHeight = nil
|
||||
globalOverlayLayout.inputHeightIsInteractivellyChanging = false
|
||||
} else if layout.inputHeight == nil {
|
||||
if globalOverlayContainerParent.view.superview != self.displayNode.view {
|
||||
self.displayNode.addSubnode(globalOverlayContainerParent)
|
||||
@ -438,6 +450,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
}
|
||||
|
||||
if let globalOverlayBelowKeyboardContainerParent = self.globalOverlayBelowKeyboardContainerParent {
|
||||
transition.updateFrame(node: globalOverlayBelowKeyboardContainerParent, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
}
|
||||
if let globalOverlayContainerParent = self.globalOverlayContainerParent {
|
||||
transition.updateFrame(node: globalOverlayContainerParent, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
}
|
||||
@ -525,6 +540,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
var additionalSideInsets = UIEdgeInsets()
|
||||
|
||||
var modalStyleOverlayTransitionFactor: CGFloat = 0.0
|
||||
var previousGlobalOverlayBelowKeyboardContainer: NavigationOverlayContainer?
|
||||
var previousGlobalOverlayContainer: NavigationOverlayContainer?
|
||||
for i in (0 ..< self.globalOverlayContainers.count).reversed() {
|
||||
let overlayContainer = self.globalOverlayContainers[i]
|
||||
@ -536,26 +552,61 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
containerTransition = transition
|
||||
}
|
||||
|
||||
let overlayWantsToBeBelowKeyboard = overlayContainer.controller.overlayWantsToBeBelowKeyboard
|
||||
let overlayLayout: ContainerViewLayout
|
||||
if overlayWantsToBeBelowKeyboard {
|
||||
overlayLayout = belowKeyboardOverlayLayout
|
||||
} else {
|
||||
overlayLayout = globalOverlayLayout
|
||||
}
|
||||
|
||||
containerTransition.updateFrame(node: overlayContainer, frame: CGRect(origin: CGPoint(), size: overlayLayout.size))
|
||||
overlayContainer.update(layout: overlayLayout, transition: containerTransition)
|
||||
|
||||
modalStyleOverlayTransitionFactor = max(modalStyleOverlayTransitionFactor, overlayContainer.controller.modalStyleOverlayTransitionFactor)
|
||||
|
||||
if overlayContainer.supernode == nil && overlayContainer.isReady {
|
||||
if let previousGlobalOverlayContainer = previousGlobalOverlayContainer {
|
||||
self.globalOverlayContainerParent?.insertSubnode(overlayContainer, belowSubnode: previousGlobalOverlayContainer)
|
||||
if overlayContainer.isReady {
|
||||
let wasNotAdded = overlayContainer.supernode == nil
|
||||
|
||||
if overlayWantsToBeBelowKeyboard {
|
||||
if overlayContainer.supernode !== self.globalOverlayBelowKeyboardContainerParent {
|
||||
if let previousGlobalOverlayBelowKeyboardContainer = previousGlobalOverlayBelowKeyboardContainer {
|
||||
self.globalOverlayBelowKeyboardContainerParent?.insertSubnode(overlayContainer, belowSubnode: previousGlobalOverlayBelowKeyboardContainer)
|
||||
} else {
|
||||
self.globalOverlayBelowKeyboardContainerParent?.addSubnode(overlayContainer)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.globalOverlayContainerParent?.addSubnode(overlayContainer)
|
||||
if overlayContainer.supernode !== self.globalOverlayContainerParent {
|
||||
if let previousGlobalOverlayContainer = previousGlobalOverlayContainer {
|
||||
self.globalOverlayContainerParent?.insertSubnode(overlayContainer, belowSubnode: previousGlobalOverlayContainer)
|
||||
} else {
|
||||
self.globalOverlayContainerParent?.addSubnode(overlayContainer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if wasNotAdded {
|
||||
overlayContainer.transitionIn()
|
||||
notifyGlobalOverlayControllersUpdated = true
|
||||
overlayContainer.controller.internalOverlayWantsToBeBelowKeyboardUpdated = { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateContainersNonReentrant(transition: transition)
|
||||
}
|
||||
}
|
||||
overlayContainer.transitionIn()
|
||||
notifyGlobalOverlayControllersUpdated = true
|
||||
}
|
||||
|
||||
let controllerAdditionalSideInsets = overlayContainer.controller.additionalSideInsets
|
||||
additionalSideInsets = UIEdgeInsets(top: 0.0, left: max(additionalSideInsets.left, controllerAdditionalSideInsets.left), bottom: 0.0, right: max(additionalSideInsets.right, controllerAdditionalSideInsets.right))
|
||||
|
||||
if overlayContainer.supernode != nil {
|
||||
previousGlobalOverlayContainer = overlayContainer
|
||||
if overlayContainer.controller.overlayWantsToBeBelowKeyboard {
|
||||
previousGlobalOverlayBelowKeyboardContainer = overlayContainer
|
||||
} else {
|
||||
previousGlobalOverlayContainer = overlayContainer
|
||||
}
|
||||
let controllerStatusBarStyle = overlayContainer.controller.statusBar.statusBarStyle
|
||||
switch controllerStatusBarStyle {
|
||||
case .Black, .White, .Hide:
|
||||
@ -594,6 +645,8 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
self.displayNode.insertSubnode(overlayContainer, belowSubnode: previousOverlayContainer)
|
||||
} else if let globalScrollToTopNode = self.globalScrollToTopNode {
|
||||
self.displayNode.insertSubnode(overlayContainer, belowSubnode: globalScrollToTopNode)
|
||||
} else if let globalOverlayBelowKeyboardContainerParent = self.globalOverlayBelowKeyboardContainerParent {
|
||||
self.displayNode.insertSubnode(overlayContainer, belowSubnode: globalOverlayBelowKeyboardContainerParent)
|
||||
} else if let globalOverlayContainerParent = self.globalOverlayContainerParent {
|
||||
self.displayNode.insertSubnode(overlayContainer, belowSubnode: globalOverlayContainerParent)
|
||||
} else {
|
||||
@ -674,7 +727,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
|
||||
containerTransition.updateFrame(node: modalContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
modalContainer.update(layout: modalContainer.isFlat ? overlayLayout : layout, controllers: navigationLayout.modal[i].controllers, coveredByModalTransition: effectiveModalTransition, transition: containerTransition)
|
||||
modalContainer.update(layout: modalContainer.isFlat ? globalOverlayLayout : layout, controllers: navigationLayout.modal[i].controllers, coveredByModalTransition: effectiveModalTransition, transition: containerTransition)
|
||||
|
||||
if modalContainer.supernode == nil && modalContainer.isReady {
|
||||
if let previousModalContainer = previousModalContainer {
|
||||
@ -1228,6 +1281,10 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
self.displayNode.addSubnode(globalScrollToTopNode)
|
||||
self.globalScrollToTopNode = globalScrollToTopNode
|
||||
|
||||
let globalOverlayBelowKeyboardContainerParent = GlobalOverlayContainerParent()
|
||||
self.displayNode.addSubnode(globalOverlayBelowKeyboardContainerParent)
|
||||
self.globalOverlayBelowKeyboardContainerParent = globalOverlayBelowKeyboardContainerParent
|
||||
|
||||
let globalOverlayContainerParent = GlobalOverlayContainerParent()
|
||||
self.displayNode.addSubnode(globalOverlayContainerParent)
|
||||
self.globalOverlayContainerParent = globalOverlayContainerParent
|
||||
|
||||
@ -210,6 +210,15 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
|
||||
|
||||
open var hasActiveInput: Bool = false
|
||||
|
||||
open var overlayWantsToBeBelowKeyboard: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var internalOverlayWantsToBeBelowKeyboardUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||
public func overlayWantsToBeBelowKeyboardUpdated(transition: ContainedViewLayoutTransition) {
|
||||
self.internalOverlayWantsToBeBelowKeyboardUpdated?(transition)
|
||||
}
|
||||
|
||||
private var navigationBarOrigin: CGFloat = 0.0
|
||||
|
||||
open func navigationLayout(layout: ContainerViewLayout) -> NavigationLayout {
|
||||
|
||||
@ -5,7 +5,7 @@ import TelegramCore
|
||||
func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> CGFloat {
|
||||
if let upper = upper, let lower = lower {
|
||||
switch (upper, lower) {
|
||||
case (_, .cover), (_, .channelBanner), (.details, .details), (.relatedArticles, nil), (_, .anchor):
|
||||
case (_, .cover), (_, .channelBanner), (.details, .details), (.relatedArticles, _), (_, .anchor):
|
||||
return 0.0
|
||||
case (.divider, _), (_, .divider):
|
||||
return 25.0
|
||||
|
||||
@ -140,6 +140,7 @@ public final class MediaBox {
|
||||
private let statusQueue = Queue()
|
||||
private let concurrentQueue = Queue.concurrentDefaultQueue()
|
||||
private let dataQueue = Queue()
|
||||
private let dataFileManager: MediaBoxFileManager
|
||||
private let cacheQueue = Queue()
|
||||
private let timeBasedCleanup: TimeBasedCleanup
|
||||
|
||||
@ -194,6 +195,8 @@ public final class MediaBox {
|
||||
self.basePath + "/short-cache"
|
||||
])
|
||||
|
||||
self.dataFileManager = MediaBoxFileManager(queue: self.dataQueue)
|
||||
|
||||
let _ = self.ensureDirectoryCreated
|
||||
}
|
||||
|
||||
@ -540,7 +543,7 @@ public final class MediaBox {
|
||||
paths.partial,
|
||||
paths.partial + ".meta"
|
||||
])
|
||||
if let fileContext = MediaBoxFileContext(queue: self.dataQueue, path: paths.complete, partialPath: paths.partial, metaPath: paths.partial + ".meta") {
|
||||
if let fileContext = MediaBoxFileContext(queue: self.dataQueue, manager: self.dataFileManager, path: paths.complete, partialPath: paths.partial, metaPath: paths.partial + ".meta") {
|
||||
context = fileContext
|
||||
self.fileContexts[resourceId] = fileContext
|
||||
} else {
|
||||
@ -633,7 +636,11 @@ public final class MediaBox {
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
} else {
|
||||
if let data = MediaBoxPartialFile.extractPartialData(path: paths.partial, metaPath: paths.partial + ".meta", range: range) {
|
||||
let tempManager = MediaBoxFileManager(queue: nil)
|
||||
let data = withExtendedLifetime(tempManager, {
|
||||
return MediaBoxPartialFile.extractPartialData(manager: tempManager, path: paths.partial, metaPath: paths.partial + ".meta", range: range)
|
||||
})
|
||||
if let data = data {
|
||||
subscriber.putNext((data, true))
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
@ -674,6 +681,15 @@ public final class MediaBox {
|
||||
subscriber.putNext((Data(), false))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch mode {
|
||||
case .complete, .incremental:
|
||||
if notifyAboutIncomplete {
|
||||
subscriber.putNext((Data(), false))
|
||||
}
|
||||
case .partial:
|
||||
subscriber.putNext((Data(), false))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -4,7 +4,180 @@ import Crc32
|
||||
import ManagedFile
|
||||
import RangeSet
|
||||
|
||||
final class MediaBoxFileManager {
|
||||
enum Mode {
|
||||
case read
|
||||
case readwrite
|
||||
}
|
||||
|
||||
enum AccessError: Error {
|
||||
case generic
|
||||
}
|
||||
|
||||
final class Item {
|
||||
final class Accessor {
|
||||
private let file: ManagedFile
|
||||
|
||||
init(file: ManagedFile) {
|
||||
self.file = file
|
||||
}
|
||||
|
||||
func write(_ data: UnsafeRawPointer, count: Int) -> Int {
|
||||
return self.file.write(data, count: count)
|
||||
}
|
||||
|
||||
func read(_ data: UnsafeMutableRawPointer, _ count: Int) -> Int {
|
||||
return self.file.read(data, count)
|
||||
}
|
||||
|
||||
func readData(count: Int) -> Data {
|
||||
return self.file.readData(count: count)
|
||||
}
|
||||
|
||||
func seek(position: Int64) {
|
||||
self.file.seek(position: position)
|
||||
}
|
||||
}
|
||||
|
||||
weak var manager: MediaBoxFileManager?
|
||||
let path: String
|
||||
let mode: Mode
|
||||
|
||||
weak var context: ItemContext?
|
||||
|
||||
init(manager: MediaBoxFileManager, path: String, mode: Mode) {
|
||||
self.manager = manager
|
||||
self.path = path
|
||||
self.mode = mode
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let manager = self.manager, let context = self.context {
|
||||
manager.discardItemContext(context: context)
|
||||
}
|
||||
}
|
||||
|
||||
func access(_ f: (Accessor) throws -> Void) throws {
|
||||
if let context = self.context {
|
||||
try f(Accessor(file: context.file))
|
||||
} else {
|
||||
if let manager = self.manager {
|
||||
if let context = manager.takeContext(path: self.path, mode: self.mode) {
|
||||
self.context = context
|
||||
try f(Accessor(file: context.file))
|
||||
} else {
|
||||
throw AccessError.generic
|
||||
}
|
||||
} else {
|
||||
throw AccessError.generic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sync() {
|
||||
if let context = self.context {
|
||||
context.sync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ItemContext {
|
||||
let id: Int
|
||||
let path: String
|
||||
let mode: Mode
|
||||
let file: ManagedFile
|
||||
|
||||
private var isDisposed: Bool = false
|
||||
|
||||
init?(id: Int, path: String, mode: Mode) {
|
||||
let mappedMode: ManagedFile.Mode
|
||||
switch mode {
|
||||
case .read:
|
||||
mappedMode = .read
|
||||
case .readwrite:
|
||||
mappedMode = .readwrite
|
||||
}
|
||||
|
||||
guard let file = ManagedFile(queue: nil, path: path, mode: mappedMode) else {
|
||||
return nil
|
||||
}
|
||||
self.file = file
|
||||
|
||||
self.id = id
|
||||
self.path = path
|
||||
self.mode = mode
|
||||
}
|
||||
|
||||
deinit {
|
||||
assert(self.isDisposed)
|
||||
}
|
||||
|
||||
func dispose() {
|
||||
if !self.isDisposed {
|
||||
self.isDisposed = true
|
||||
self.file._unsafeClose()
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
func sync() {
|
||||
self.file.sync()
|
||||
}
|
||||
}
|
||||
|
||||
private let queue: Queue?
|
||||
private var contexts: [Int: ItemContext] = [:]
|
||||
private var nextItemId: Int = 0
|
||||
private let maxOpenFiles: Int
|
||||
|
||||
init(queue: Queue?) {
|
||||
self.queue = queue
|
||||
self.maxOpenFiles = 16
|
||||
}
|
||||
|
||||
func open(path: String, mode: Mode) -> Item? {
|
||||
if let queue = self.queue {
|
||||
assert(queue.isCurrent())
|
||||
}
|
||||
|
||||
return Item(manager: self, path: path, mode: mode)
|
||||
}
|
||||
|
||||
private func takeContext(path: String, mode: Mode) -> ItemContext? {
|
||||
if let queue = self.queue {
|
||||
assert(queue.isCurrent())
|
||||
}
|
||||
|
||||
if self.contexts.count > self.maxOpenFiles {
|
||||
if let minKey = self.contexts.keys.min(), let context = self.contexts[minKey] {
|
||||
self.discardItemContext(context: context)
|
||||
}
|
||||
}
|
||||
|
||||
let id = self.nextItemId
|
||||
self.nextItemId += 1
|
||||
let context = ItemContext(id: id, path: path, mode: mode)
|
||||
self.contexts[id] = context
|
||||
return context
|
||||
}
|
||||
|
||||
private func discardItemContext(context: ItemContext) {
|
||||
if let queue = self.queue {
|
||||
assert(queue.isCurrent())
|
||||
}
|
||||
|
||||
if let context = self.contexts.removeValue(forKey: context.id) {
|
||||
context.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class MediaBoxFileMap {
|
||||
enum FileMapError: Error {
|
||||
case generic
|
||||
}
|
||||
|
||||
fileprivate(set) var sum: Int64
|
||||
private(set) var ranges: RangeSet<Int64>
|
||||
private(set) var truncationSize: Int64?
|
||||
@ -17,173 +190,216 @@ private final class MediaBoxFileMap {
|
||||
self.progress = nil
|
||||
}
|
||||
|
||||
init?(fd: ManagedFile) {
|
||||
guard let length = fd.getSize() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var firstUInt32: UInt32 = 0
|
||||
guard fd.read(&firstUInt32, 4) == 4 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if firstUInt32 == 0x7bac1487 {
|
||||
var crc: UInt32 = 0
|
||||
guard fd.read(&crc, 4) == 4 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var count: Int32 = 0
|
||||
var sum: Int64 = 0
|
||||
var ranges = RangeSet<Int64>()
|
||||
|
||||
guard fd.read(&count, 4) == 4 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if count < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if count < 0 || length < 4 + 4 + 4 + 8 + count * 2 * 8 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var truncationSizeValue: Int64 = 0
|
||||
|
||||
var data = Data(count: Int(8 + count * 2 * 8))
|
||||
let dataCount = data.count
|
||||
if !(data.withUnsafeMutableBytes { rawBytes -> Bool in
|
||||
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
|
||||
guard fd.read(bytes, dataCount) == dataCount else {
|
||||
return false
|
||||
}
|
||||
|
||||
memcpy(&truncationSizeValue, bytes, 8)
|
||||
|
||||
let calculatedCrc = Crc32(bytes, Int32(dataCount))
|
||||
if calculatedCrc != crc {
|
||||
return false
|
||||
}
|
||||
|
||||
var offset = 8
|
||||
for _ in 0 ..< count {
|
||||
var intervalOffset: Int64 = 0
|
||||
var intervalLength: Int64 = 0
|
||||
memcpy(&intervalOffset, bytes.advanced(by: offset), 8)
|
||||
memcpy(&intervalLength, bytes.advanced(by: offset + 8), 8)
|
||||
offset += 8 * 2
|
||||
|
||||
ranges.insert(contentsOf: intervalOffset ..< (intervalOffset + intervalLength))
|
||||
|
||||
sum += intervalLength
|
||||
}
|
||||
|
||||
return true
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.sum = sum
|
||||
self.ranges = ranges
|
||||
if truncationSizeValue == -1 {
|
||||
self.truncationSize = nil
|
||||
} else if truncationSizeValue < 0 {
|
||||
self.truncationSize = nil
|
||||
} else {
|
||||
self.truncationSize = truncationSizeValue
|
||||
}
|
||||
} else {
|
||||
let crc: UInt32 = firstUInt32
|
||||
var count: Int32 = 0
|
||||
var sum: Int32 = 0
|
||||
var ranges = RangeSet<Int64>()
|
||||
|
||||
guard fd.read(&count, 4) == 4 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if count < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if count < 0 || UInt64(length) < 4 + 4 + UInt64(count) * 2 * 4 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var truncationSizeValue: Int32 = 0
|
||||
|
||||
var data = Data(count: Int(4 + count * 2 * 4))
|
||||
let dataCount = data.count
|
||||
if !(data.withUnsafeMutableBytes { rawBytes -> Bool in
|
||||
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
|
||||
guard fd.read(bytes, dataCount) == dataCount else {
|
||||
return false
|
||||
}
|
||||
|
||||
memcpy(&truncationSizeValue, bytes, 4)
|
||||
|
||||
let calculatedCrc = Crc32(bytes, Int32(dataCount))
|
||||
if calculatedCrc != crc {
|
||||
return false
|
||||
}
|
||||
|
||||
var offset = 4
|
||||
for _ in 0 ..< count {
|
||||
var intervalOffset: Int32 = 0
|
||||
var intervalLength: Int32 = 0
|
||||
memcpy(&intervalOffset, bytes.advanced(by: offset), 4)
|
||||
memcpy(&intervalLength, bytes.advanced(by: offset + 4), 4)
|
||||
offset += 8
|
||||
|
||||
ranges.insert(contentsOf: Int64(intervalOffset) ..< Int64(intervalOffset + intervalLength))
|
||||
|
||||
sum += intervalLength
|
||||
}
|
||||
|
||||
return true
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.sum = Int64(sum)
|
||||
self.ranges = ranges
|
||||
if truncationSizeValue == -1 {
|
||||
self.truncationSize = nil
|
||||
} else {
|
||||
self.truncationSize = Int64(truncationSizeValue)
|
||||
}
|
||||
}
|
||||
private init(
|
||||
sum: Int64,
|
||||
ranges: RangeSet<Int64>,
|
||||
truncationSize: Int64?,
|
||||
progress: Float?
|
||||
) {
|
||||
self.sum = sum
|
||||
self.ranges = ranges
|
||||
self.truncationSize = truncationSize
|
||||
self.progress = progress
|
||||
}
|
||||
|
||||
func serialize(to file: ManagedFile) {
|
||||
file.seek(position: 0)
|
||||
let buffer = WriteBuffer()
|
||||
var magic: UInt32 = 0x7bac1487
|
||||
buffer.write(&magic, offset: 0, length: 4)
|
||||
|
||||
var zero: Int32 = 0
|
||||
buffer.write(&zero, offset: 0, length: 4)
|
||||
|
||||
let rangeView = self.ranges.ranges
|
||||
var count: Int32 = Int32(rangeView.count)
|
||||
buffer.write(&count, offset: 0, length: 4)
|
||||
|
||||
var truncationSizeValue: Int64 = self.truncationSize ?? -1
|
||||
buffer.write(&truncationSizeValue, offset: 0, length: 8)
|
||||
|
||||
for range in rangeView {
|
||||
var intervalOffset = range.lowerBound
|
||||
var intervalLength = range.upperBound - range.lowerBound
|
||||
buffer.write(&intervalOffset, offset: 0, length: 8)
|
||||
buffer.write(&intervalLength, offset: 0, length: 8)
|
||||
static func read(manager: MediaBoxFileManager, path: String) throws -> MediaBoxFileMap {
|
||||
guard let length = fileSize(path) else {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
guard let fileItem = manager.open(path: path, mode: .readwrite) else {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
|
||||
var result: MediaBoxFileMap?
|
||||
|
||||
try fileItem.access { fd in
|
||||
var firstUInt32: UInt32 = 0
|
||||
guard fd.read(&firstUInt32, 4) == 4 else {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
|
||||
if firstUInt32 == 0x7bac1487 {
|
||||
var crc: UInt32 = 0
|
||||
guard fd.read(&crc, 4) == 4 else {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
|
||||
var count: Int32 = 0
|
||||
var sum: Int64 = 0
|
||||
var ranges = RangeSet<Int64>()
|
||||
|
||||
guard fd.read(&count, 4) == 4 else {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
|
||||
if count < 0 {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
|
||||
if count < 0 || length < 4 + 4 + 4 + 8 + count * 2 * 8 {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
|
||||
var truncationSizeValue: Int64 = 0
|
||||
|
||||
var data = Data(count: Int(8 + count * 2 * 8))
|
||||
let dataCount = data.count
|
||||
if !(data.withUnsafeMutableBytes { rawBytes -> Bool in
|
||||
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
|
||||
guard fd.read(bytes, dataCount) == dataCount else {
|
||||
return false
|
||||
}
|
||||
|
||||
memcpy(&truncationSizeValue, bytes, 8)
|
||||
|
||||
let calculatedCrc = Crc32(bytes, Int32(dataCount))
|
||||
if calculatedCrc != crc {
|
||||
return false
|
||||
}
|
||||
|
||||
var offset = 8
|
||||
for _ in 0 ..< count {
|
||||
var intervalOffset: Int64 = 0
|
||||
var intervalLength: Int64 = 0
|
||||
memcpy(&intervalOffset, bytes.advanced(by: offset), 8)
|
||||
memcpy(&intervalLength, bytes.advanced(by: offset + 8), 8)
|
||||
offset += 8 * 2
|
||||
|
||||
ranges.insert(contentsOf: intervalOffset ..< (intervalOffset + intervalLength))
|
||||
|
||||
sum += intervalLength
|
||||
}
|
||||
|
||||
return true
|
||||
}) {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
|
||||
let mappedTruncationSize: Int64?
|
||||
if truncationSizeValue == -1 {
|
||||
mappedTruncationSize = nil
|
||||
} else if truncationSizeValue < 0 {
|
||||
mappedTruncationSize = nil
|
||||
} else {
|
||||
mappedTruncationSize = truncationSizeValue
|
||||
}
|
||||
|
||||
result = MediaBoxFileMap(
|
||||
sum: sum,
|
||||
ranges: ranges,
|
||||
truncationSize: mappedTruncationSize,
|
||||
progress: nil
|
||||
)
|
||||
} else {
|
||||
let crc: UInt32 = firstUInt32
|
||||
var count: Int32 = 0
|
||||
var sum: Int32 = 0
|
||||
var ranges = RangeSet<Int64>()
|
||||
|
||||
guard fd.read(&count, 4) == 4 else {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
|
||||
if count < 0 {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
|
||||
if count < 0 || UInt64(length) < 4 + 4 + UInt64(count) * 2 * 4 {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
|
||||
var truncationSizeValue: Int32 = 0
|
||||
|
||||
var data = Data(count: Int(4 + count * 2 * 4))
|
||||
let dataCount = data.count
|
||||
if !(data.withUnsafeMutableBytes { rawBytes -> Bool in
|
||||
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
|
||||
guard fd.read(bytes, dataCount) == dataCount else {
|
||||
return false
|
||||
}
|
||||
|
||||
memcpy(&truncationSizeValue, bytes, 4)
|
||||
|
||||
let calculatedCrc = Crc32(bytes, Int32(dataCount))
|
||||
if calculatedCrc != crc {
|
||||
return false
|
||||
}
|
||||
|
||||
var offset = 4
|
||||
for _ in 0 ..< count {
|
||||
var intervalOffset: Int32 = 0
|
||||
var intervalLength: Int32 = 0
|
||||
memcpy(&intervalOffset, bytes.advanced(by: offset), 4)
|
||||
memcpy(&intervalLength, bytes.advanced(by: offset + 4), 4)
|
||||
offset += 8
|
||||
|
||||
ranges.insert(contentsOf: Int64(intervalOffset) ..< Int64(intervalOffset + intervalLength))
|
||||
|
||||
sum += intervalLength
|
||||
}
|
||||
|
||||
return true
|
||||
}) {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
|
||||
let mappedTruncationSize: Int64?
|
||||
if truncationSizeValue == -1 {
|
||||
mappedTruncationSize = nil
|
||||
} else {
|
||||
mappedTruncationSize = Int64(truncationSizeValue)
|
||||
}
|
||||
|
||||
result = MediaBoxFileMap(
|
||||
sum: Int64(sum),
|
||||
ranges: ranges,
|
||||
truncationSize: mappedTruncationSize,
|
||||
progress: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
guard let result = result else {
|
||||
throw FileMapError.generic
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func serialize(manager: MediaBoxFileManager, to path: String) {
|
||||
guard let fileItem = manager.open(path: path, mode: .readwrite) else {
|
||||
postboxLog("MediaBoxFile: serialize: cannot open file")
|
||||
return
|
||||
}
|
||||
|
||||
let _ = try? fileItem.access { file in
|
||||
file.seek(position: 0)
|
||||
let buffer = WriteBuffer()
|
||||
var magic: UInt32 = 0x7bac1487
|
||||
buffer.write(&magic, offset: 0, length: 4)
|
||||
|
||||
var zero: Int32 = 0
|
||||
buffer.write(&zero, offset: 0, length: 4)
|
||||
|
||||
let rangeView = self.ranges.ranges
|
||||
var count: Int32 = Int32(rangeView.count)
|
||||
buffer.write(&count, offset: 0, length: 4)
|
||||
|
||||
var truncationSizeValue: Int64 = self.truncationSize ?? -1
|
||||
buffer.write(&truncationSizeValue, offset: 0, length: 8)
|
||||
|
||||
for range in rangeView {
|
||||
var intervalOffset = range.lowerBound
|
||||
var intervalLength = range.upperBound - range.lowerBound
|
||||
buffer.write(&intervalOffset, offset: 0, length: 8)
|
||||
buffer.write(&intervalLength, offset: 0, length: 8)
|
||||
}
|
||||
var crc: UInt32 = Crc32(buffer.memory.advanced(by: 4 + 4 + 4), Int32(buffer.length - (4 + 4 + 4)))
|
||||
memcpy(buffer.memory.advanced(by: 4), &crc, 4)
|
||||
let written = file.write(buffer.memory, count: buffer.length)
|
||||
assert(written == buffer.length)
|
||||
}
|
||||
var crc: UInt32 = Crc32(buffer.memory.advanced(by: 4 + 4 + 4), Int32(buffer.length - (4 + 4 + 4)))
|
||||
memcpy(buffer.memory.advanced(by: 4), &crc, 4)
|
||||
let written = file.write(buffer.memory, count: buffer.length)
|
||||
assert(written == buffer.length)
|
||||
}
|
||||
|
||||
fileprivate func fill(_ range: Range<Int64>) {
|
||||
@ -243,12 +459,12 @@ private class MediaBoxPartialFileDataRequest {
|
||||
|
||||
final class MediaBoxPartialFile {
|
||||
private let queue: Queue
|
||||
private let manager: MediaBoxFileManager
|
||||
private let path: String
|
||||
private let metaPath: String
|
||||
private let completePath: String
|
||||
private let completed: (Int64) -> Void
|
||||
private let metadataFd: ManagedFile
|
||||
private let fd: ManagedFile
|
||||
private let fd: MediaBoxFileManager.Item
|
||||
fileprivate let fileMap: MediaBoxFileMap
|
||||
private var dataRequests = Bag<MediaBoxPartialFileDataRequest>()
|
||||
private let missingRanges: MediaBoxFileMissingRanges
|
||||
@ -260,17 +476,17 @@ final class MediaBoxPartialFile {
|
||||
private var currentFetch: (Promise<[(Range<Int64>, MediaBoxFetchPriority)]>, Disposable)?
|
||||
private var processedAtLeastOneFetch: Bool = false
|
||||
|
||||
init?(queue: Queue, path: String, metaPath: String, completePath: String, completed: @escaping (Int64) -> Void) {
|
||||
init?(queue: Queue, manager: MediaBoxFileManager, path: String, metaPath: String, completePath: String, completed: @escaping (Int64) -> Void) {
|
||||
assert(queue.isCurrent())
|
||||
if let metadataFd = ManagedFile(queue: queue, path: metaPath, mode: .readwrite), let fd = ManagedFile(queue: queue, path: path, mode: .readwrite) {
|
||||
self.manager = manager
|
||||
if let fd = manager.open(path: path, mode: .readwrite) {
|
||||
self.queue = queue
|
||||
self.path = path
|
||||
self.metaPath = metaPath
|
||||
self.completePath = completePath
|
||||
self.completed = completed
|
||||
self.metadataFd = metadataFd
|
||||
self.fd = fd
|
||||
if let fileMap = MediaBoxFileMap(fd: self.metadataFd) {
|
||||
if let fileMap = try? MediaBoxFileMap.read(manager: manager, path: self.metaPath) {
|
||||
if !fileMap.ranges.isEmpty {
|
||||
let upperBound = fileMap.ranges.ranges.last!.upperBound
|
||||
if let actualSize = fileSize(path, useTotalFileAllocatedSize: false) {
|
||||
@ -298,14 +514,11 @@ final class MediaBoxPartialFile {
|
||||
self.currentFetch?.1.dispose()
|
||||
}
|
||||
|
||||
static func extractPartialData(path: String, metaPath: String, range: Range<Int64>) -> Data? {
|
||||
guard let metadataFd = ManagedFile(queue: nil, path: metaPath, mode: .read) else {
|
||||
return nil
|
||||
}
|
||||
static func extractPartialData(manager: MediaBoxFileManager, path: String, metaPath: String, range: Range<Int64>) -> Data? {
|
||||
guard let fd = ManagedFile(queue: nil, path: path, mode: .read) else {
|
||||
return nil
|
||||
}
|
||||
guard let fileMap = MediaBoxFileMap(fd: metadataFd) else {
|
||||
guard let fileMap = try? MediaBoxFileMap.read(manager: manager, path: metaPath) else {
|
||||
return nil
|
||||
}
|
||||
guard let clippedRange = fileMap.contains(range) else {
|
||||
@ -324,7 +537,7 @@ final class MediaBoxPartialFile {
|
||||
assert(self.queue.isCurrent())
|
||||
|
||||
self.fileMap.reset()
|
||||
self.fileMap.serialize(to: self.metadataFd)
|
||||
self.fileMap.serialize(manager: self.manager, to: self.metaPath)
|
||||
|
||||
for request in self.dataRequests.copyItems() {
|
||||
request.completion(MediaResourceData(path: self.path, offset: request.range.lowerBound, size: 0, complete: false))
|
||||
@ -428,7 +641,7 @@ final class MediaBoxPartialFile {
|
||||
let range: Range<Int64> = size ..< Int64.max
|
||||
|
||||
self.fileMap.truncate(size)
|
||||
self.fileMap.serialize(to: self.metadataFd)
|
||||
self.fileMap.serialize(manager: self.manager, to: self.metaPath)
|
||||
|
||||
self.checkDataRequestsAfterFill(range: range)
|
||||
}
|
||||
@ -443,16 +656,23 @@ final class MediaBoxPartialFile {
|
||||
func write(offset: Int64, data: Data, dataRange: Range<Int64>) {
|
||||
assert(self.queue.isCurrent())
|
||||
|
||||
self.fd.seek(position: offset)
|
||||
let written = data.withUnsafeBytes { rawBytes -> Int in
|
||||
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
do {
|
||||
try self.fd.access { fd in
|
||||
fd.seek(position: offset)
|
||||
let written = data.withUnsafeBytes { rawBytes -> Int in
|
||||
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
|
||||
return self.fd.write(bytes.advanced(by: Int(dataRange.lowerBound)), count: dataRange.count)
|
||||
return fd.write(bytes.advanced(by: Int(dataRange.lowerBound)), count: dataRange.count)
|
||||
}
|
||||
assert(written == dataRange.count)
|
||||
}
|
||||
} catch let e {
|
||||
postboxLog("MediaBoxPartialFile.write error: \(e)")
|
||||
}
|
||||
assert(written == dataRange.count)
|
||||
|
||||
let range: Range<Int64> = offset ..< (offset + Int64(dataRange.count))
|
||||
self.fileMap.fill(range)
|
||||
self.fileMap.serialize(to: self.metadataFd)
|
||||
self.fileMap.serialize(manager: self.manager, to: self.metaPath)
|
||||
|
||||
self.checkDataRequestsAfterFill(range: range)
|
||||
}
|
||||
@ -536,16 +756,25 @@ final class MediaBoxPartialFile {
|
||||
assert(self.queue.isCurrent())
|
||||
|
||||
if let actualRange = self.fileMap.contains(range) {
|
||||
self.fd.seek(position: Int64(actualRange.lowerBound))
|
||||
var data = Data(count: actualRange.count)
|
||||
let dataCount = data.count
|
||||
let readBytes = data.withUnsafeMutableBytes { rawBytes -> Int in
|
||||
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: Int8.self)
|
||||
return self.fd.read(bytes, dataCount)
|
||||
}
|
||||
if readBytes == data.count {
|
||||
return data
|
||||
} else {
|
||||
do {
|
||||
var result: Data?
|
||||
try self.fd.access { fd in
|
||||
fd.seek(position: Int64(actualRange.lowerBound))
|
||||
var data = Data(count: actualRange.count)
|
||||
let dataCount = data.count
|
||||
let readBytes = data.withUnsafeMutableBytes { rawBytes -> Int in
|
||||
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: Int8.self)
|
||||
return fd.read(bytes, dataCount)
|
||||
}
|
||||
if readBytes == data.count {
|
||||
result = data
|
||||
} else {
|
||||
result = nil
|
||||
}
|
||||
}
|
||||
return result
|
||||
} catch let e {
|
||||
postboxLog("MediaBoxPartialFile.read error: \(e)")
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
@ -954,7 +1183,7 @@ final class MediaBoxFileContext {
|
||||
return self.references.isEmpty
|
||||
}
|
||||
|
||||
init?(queue: Queue, path: String, partialPath: String, metaPath: String) {
|
||||
init?(queue: Queue, manager: MediaBoxFileManager, path: String, partialPath: String, metaPath: String) {
|
||||
assert(queue.isCurrent())
|
||||
|
||||
self.queue = queue
|
||||
@ -965,7 +1194,7 @@ final class MediaBoxFileContext {
|
||||
var completeImpl: ((Int64) -> Void)?
|
||||
if let size = fileSize(path) {
|
||||
self.content = .complete(path, size)
|
||||
} else if let file = MediaBoxPartialFile(queue: queue, path: partialPath, metaPath: metaPath, completePath: path, completed: { size in
|
||||
} else if let file = MediaBoxPartialFile(queue: queue, manager: manager, path: partialPath, metaPath: metaPath, completePath: path, completed: { size in
|
||||
completeImpl?(size)
|
||||
}) {
|
||||
self.content = .partial(file)
|
||||
|
||||
@ -134,6 +134,36 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContentScrollView: UIScrollView {
|
||||
override static var layerClass: AnyClass {
|
||||
return EmojiPagerContentComponent.View.ContentScrollLayer.self
|
||||
}
|
||||
|
||||
init(mirrorView: UIView) {
|
||||
super.init(frame: CGRect())
|
||||
|
||||
(self.layer as? EmojiPagerContentComponent.View.ContentScrollLayer)?.mirrorLayer = mirrorView.layer
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContentScrollNode: ASDisplayNode {
|
||||
override var view: ContentScrollView {
|
||||
return super.view as! ContentScrollView
|
||||
}
|
||||
|
||||
init(mirrorView: UIView) {
|
||||
super.init()
|
||||
|
||||
self.setViewBlock({
|
||||
return ContentScrollView(mirrorView: mirrorView)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let presentationData: PresentationData
|
||||
private let animationCache: AnimationCache
|
||||
@ -143,6 +173,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private let getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?
|
||||
private let isExpandedUpdated: (ContainedViewLayoutTransition) -> Void
|
||||
private let requestLayout: (ContainedViewLayoutTransition) -> Void
|
||||
private let requestUpdateOverlayWantsToBeBelowKeyboard: (ContainedViewLayoutTransition) -> Void
|
||||
|
||||
private let backgroundNode: ReactionContextBackgroundNode
|
||||
|
||||
@ -152,7 +183,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private let leftBackgroundMaskNode: ASDisplayNode
|
||||
private let rightBackgroundMaskNode: ASDisplayNode
|
||||
private let backgroundMaskNode: ASDisplayNode
|
||||
private let scrollNode: ASScrollNode
|
||||
private let mirrorContentScrollView: UIView
|
||||
private let scrollNode: ContentScrollNode
|
||||
private let previewingItemContainer: ASDisplayNode
|
||||
private var visibleItemNodes: [Int: ReactionItemNode] = [:]
|
||||
private var disappearingVisibleItemNodes: [Int: ReactionItemNode] = [:]
|
||||
@ -253,7 +285,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, items: [ReactionContextItem], selectedItems: Set<MessageReaction.Reaction>, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void) {
|
||||
public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, items: [ReactionContextItem], selectedItems: Set<MessageReaction.Reaction>, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.items = items
|
||||
@ -261,6 +293,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.getEmojiContent = getEmojiContent
|
||||
self.isExpandedUpdated = isExpandedUpdated
|
||||
self.requestLayout = requestLayout
|
||||
self.requestUpdateOverlayWantsToBeBelowKeyboard = requestUpdateOverlayWantsToBeBelowKeyboard
|
||||
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
@ -274,7 +307,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.backgroundMaskNode.addSubnode(self.leftBackgroundMaskNode)
|
||||
self.backgroundMaskNode.addSubnode(self.rightBackgroundMaskNode)
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.mirrorContentScrollView = UIView()
|
||||
self.mirrorContentScrollView.isUserInteractionEnabled = false
|
||||
|
||||
self.scrollNode = ContentScrollNode(mirrorView: self.mirrorContentScrollView)
|
||||
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
self.scrollNode.view.showsVerticalScrollIndicator = false
|
||||
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||
@ -297,6 +333,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.contentTintContainer.clipsToBounds = true
|
||||
self.contentTintContainer.isUserInteractionEnabled = false
|
||||
|
||||
self.contentTintContainer.view.addSubview(self.mirrorContentScrollView)
|
||||
|
||||
self.contentContainerMask = UIImageView()
|
||||
self.contentContainerMask.image = generateImage(CGSize(width: 46.0, height: 46.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
@ -491,6 +529,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
public func wantsDisplayBelowKeyboard() -> Bool {
|
||||
if let emojiView = self.reactionSelectionComponentHost?.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View {
|
||||
return emojiView.wantsDisplayBelowKeyboard()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateBackgroundFrame(containerSize: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, contentSize: CGSize) -> (backgroundFrame: CGRect, visualBackgroundFrame: CGRect, isLeftAligned: Bool, cloudSourcePoint: CGFloat) {
|
||||
var contentSize = contentSize
|
||||
contentSize.width = max(46.0, contentSize.width)
|
||||
@ -690,7 +736,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.scrollNode.addSubnode(itemNode)
|
||||
if let itemNode = itemNode as? ReactionNode {
|
||||
if let reaction = self.items[i].reaction, self.selectedItems.contains(reaction.rawValue) {
|
||||
self.contentTintContainer.view.addSubview(itemNode.selectionTintView)
|
||||
self.mirrorContentScrollView.addSubview(itemNode.selectionTintView)
|
||||
self.scrollNode.view.addSubview(itemNode.selectionView)
|
||||
}
|
||||
}
|
||||
@ -784,7 +830,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
expandItemSize = 30.0
|
||||
expandTintOffset = 0.0
|
||||
}
|
||||
let baseNextFrame = CGRect(origin: CGPoint(x: self.scrollNode.view.bounds.width - expandItemSize - 9.0, y: containerHeight - contentHeight + floor((contentHeight - expandItemSize) / 2.0) + (self.isExpanded ? 46.0 : 0.0)), size: CGSize(width: expandItemSize, height: expandItemSize + self.extensionDistance))
|
||||
let baseNextFrame = CGRect(origin: CGPoint(x: self.scrollNode.view.bounds.width - expandItemSize - 9.0, y: containerHeight - contentHeight + floor((contentHeight - expandItemSize) / 2.0) + (self.isExpanded ? (46.0 + 54.0 - 4.0) : 0.0)), size: CGSize(width: expandItemSize, height: expandItemSize + self.extensionDistance))
|
||||
|
||||
transition.updateFrame(view: expandItemView, frame: baseNextFrame)
|
||||
transition.updateFrame(view: expandItemView.tintView, frame: baseNextFrame.offsetBy(dx: 0.0, dy: expandTintOffset))
|
||||
@ -885,7 +931,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
visibleItemCount: itemCount
|
||||
)
|
||||
|
||||
var scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.isExpanded ? 46.0 : 0.0), size: actualBackgroundFrame.size)
|
||||
var scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.isExpanded ? (46.0 + 54.0 - 4.0) : 0.0), size: actualBackgroundFrame.size)
|
||||
scrollFrame.origin.y += floorToScreenPixels(self.extensionDistance / 2.0)
|
||||
|
||||
transition.updateFrame(node: self.contentContainer, frame: visualBackgroundFrame, beginWithCurrentState: true)
|
||||
@ -977,7 +1023,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
if let mirrorContentClippingView = emojiView.mirrorContentClippingView {
|
||||
mirrorContentClippingView.clipsToBounds = false
|
||||
Transition(transition).animateBoundsOrigin(view: mirrorContentClippingView, from: CGPoint(x: 0.0, y: 46.0), to: CGPoint(), additive: true, completion: { [weak mirrorContentClippingView] _ in
|
||||
Transition(transition).animateBoundsOrigin(view: mirrorContentClippingView, from: CGPoint(x: 0.0, y: 46.0 + 54.0 - 4.0), to: CGPoint(), additive: true, completion: { [weak mirrorContentClippingView] _ in
|
||||
mirrorContentClippingView?.clipsToBounds = true
|
||||
})
|
||||
}
|
||||
@ -1007,7 +1053,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
componentTransition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
|
||||
|
||||
if animateIn {
|
||||
transition.animatePositionAdditive(layer: componentView.layer, offset: CGPoint(x: 0.0, y: -46.0 + floorToScreenPixels(self.animateFromExtensionDistance / 2.0)))
|
||||
transition.animatePositionAdditive(layer: componentView.layer, offset: CGPoint(x: 0.0, y: -(46.0 + 54.0 - 4.0) + floorToScreenPixels(self.animateFromExtensionDistance / 2.0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1195,6 +1241,12 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
navigationController: {
|
||||
return nil
|
||||
},
|
||||
requestUpdate: { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.requestUpdateOverlayWantsToBeBelowKeyboard(transition.containedViewLayoutTransition)
|
||||
},
|
||||
sendSticker: nil,
|
||||
chatPeerId: nil,
|
||||
peekBehavior: nil,
|
||||
|
||||
@ -39,8 +39,13 @@ func cacheStickerPack(transaction: Transaction, info: StickerPackCollectionInfo,
|
||||
case .premiumGifts:
|
||||
namespace = Namespaces.ItemCollection.CloudPremiumGifts
|
||||
id = 0
|
||||
case .id:
|
||||
break
|
||||
case let .id(_id, _):
|
||||
if info.flags.contains(.isEmoji) {
|
||||
namespace = Namespaces.ItemCollection.CloudEmojiPacks
|
||||
} else {
|
||||
namespace = Namespaces.ItemCollection.CloudStickerPacks
|
||||
}
|
||||
id = _id
|
||||
default:
|
||||
assertionFailure()
|
||||
break
|
||||
|
||||
@ -898,7 +898,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
|
||||
panelContentVibrantOverlayColor: UIColor(white: 0.7, alpha: 0.65),
|
||||
panelContentControlVibrantOverlayColor: UIColor(white: 0.85, alpha: 0.65),
|
||||
panelContentControlVibrantSelectionColor: UIColor(white: 0.85, alpha: 0.1),
|
||||
panelContentControlOpaqueOverlayColor: UIColor(white: 0.0, alpha: 0.3),
|
||||
panelContentControlOpaqueOverlayColor: UIColor(white: 0.0, alpha: 0.2),
|
||||
panelContentControlOpaqueSelectionColor: UIColor(white: 0.0, alpha: 0.1),
|
||||
stickersBackgroundColor: UIColor(rgb: 0xe8ebf0),
|
||||
stickersSectionTextColor: UIColor(rgb: 0x9099a2),
|
||||
|
||||
@ -341,8 +341,14 @@ public final class EmojiStatusComponent: Component {
|
||||
}
|
||||
}
|
||||
iconView.image = iconImage
|
||||
size = iconImage.size.aspectFilled(availableSize)
|
||||
iconView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
if case .text = component.content {
|
||||
size = CGSize(width: iconImage.size.width, height: availableSize.height)
|
||||
iconView.frame = CGRect(origin: CGPoint(x: floor((size.width - iconImage.size.width) / 2.0), y: floor((size.height - iconImage.size.height) / 2.0)), size: iconImage.size)
|
||||
} else {
|
||||
size = iconImage.size.aspectFilled(availableSize)
|
||||
iconView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
} else {
|
||||
if let iconView = self.iconView {
|
||||
self.iconView = nil
|
||||
|
||||
@ -488,7 +488,7 @@ private final class TimeSelectionControlComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
let timestamp = Int32(strongSelf.pickerView.date.timeIntervalSince1970)
|
||||
let timestamp = Int32(strongSelf.pickerView.date.timeIntervalSince1970 - Double(TimeZone.current.secondsFromGMT()))
|
||||
component.apply(timestamp)
|
||||
}
|
||||
)),
|
||||
|
||||
@ -146,6 +146,7 @@ public final class EmojiStatusSelectionComponent: Component {
|
||||
component: AnyComponent(EntityKeyboardComponent(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
isContentInFocus: true,
|
||||
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: 0.0, bottom: 0.0, right: 0.0),
|
||||
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
|
||||
emojiContent: component.emojiContent,
|
||||
@ -340,6 +341,8 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
navigationController: {
|
||||
return nil
|
||||
},
|
||||
requestUpdate: { _ in
|
||||
},
|
||||
sendSticker: nil,
|
||||
chatPeerId: nil,
|
||||
peekBehavior: nil,
|
||||
|
||||
@ -1049,6 +1049,10 @@ private final class GroupHeaderLayer: UIView {
|
||||
self.subtitleLayer = nil
|
||||
subtitleLayer.removeFromSuperlayer()
|
||||
}
|
||||
if let tintSubtitleLayer = self.tintSubtitleLayer {
|
||||
self.tintSubtitleLayer = nil
|
||||
tintSubtitleLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
|
||||
var clearWidth: CGFloat = 0.0
|
||||
@ -1178,9 +1182,9 @@ private final class GroupHeaderLayer: UIView {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
func tapGesture(_ recognizer: UITapGestureRecognizer) -> Bool {
|
||||
func tapGesture(point: CGPoint) -> Bool {
|
||||
if let groupEmbeddedView = self.groupEmbeddedView {
|
||||
return groupEmbeddedView.tapGesture(recognizer)
|
||||
return groupEmbeddedView.tapGesture(point: self.convert(point, to: groupEmbeddedView))
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -1261,18 +1265,15 @@ private final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, Pager
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func tapGesture(_ recognizer: UITapGestureRecognizer) -> Bool {
|
||||
func tapGesture(point: CGPoint) -> Bool {
|
||||
guard let itemLayout = self.itemLayout else {
|
||||
return false
|
||||
}
|
||||
|
||||
if case .ended = recognizer.state {
|
||||
let point = recognizer.location(in: self)
|
||||
for (_, itemLayer) in self.visibleItemLayers {
|
||||
if itemLayer.frame.inset(by: UIEdgeInsets(top: 6.0, left: itemLayout.itemSpacing, bottom: 6.0, right: itemLayout.itemSpacing)).contains(point) {
|
||||
self.performItemAction(itemLayer.item, self, itemLayer.frame, itemLayer)
|
||||
return true
|
||||
}
|
||||
for (_, itemLayer) in self.visibleItemLayers {
|
||||
if itemLayer.frame.inset(by: UIEdgeInsets(top: 6.0, left: itemLayout.itemSpacing, bottom: 6.0, right: itemLayout.itemSpacing)).contains(point) {
|
||||
self.performItemAction(itemLayer.item, self, itemLayer.frame, itemLayer)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -1456,9 +1457,9 @@ private final class GroupExpandActionButton: UIButton {
|
||||
let color = theme.list.itemCheckColors.foregroundColor
|
||||
|
||||
if useOpaqueTheme {
|
||||
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor.cgColor
|
||||
} else {
|
||||
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor.cgColor
|
||||
} else {
|
||||
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor.cgColor
|
||||
}
|
||||
self.tintContainerLayer.backgroundColor = UIColor.white.cgColor
|
||||
|
||||
@ -1499,6 +1500,96 @@ private final class GroupExpandActionButton: UIButton {
|
||||
}
|
||||
}
|
||||
|
||||
private final class SearchHeaderView: UIView, UITextFieldDelegate {
|
||||
override static var layerClass: AnyClass {
|
||||
return PassthroughLayer.self
|
||||
}
|
||||
|
||||
private let requestUpdate: () -> Void
|
||||
|
||||
let tintContainerLayer: SimpleLayer
|
||||
|
||||
private let backgroundLayer: SimpleLayer
|
||||
private let tintBackgroundLayer: SimpleLayer
|
||||
|
||||
private var tapRecognizer: UITapGestureRecognizer?
|
||||
private var textField: UITextField?
|
||||
|
||||
var wantsDisplayBelowKeyboard: Bool {
|
||||
return self.textField != nil
|
||||
}
|
||||
|
||||
init(requestUpdate: @escaping () -> Void) {
|
||||
self.requestUpdate = requestUpdate
|
||||
|
||||
self.tintContainerLayer = SimpleLayer()
|
||||
|
||||
self.backgroundLayer = SimpleLayer()
|
||||
self.tintBackgroundLayer = SimpleLayer()
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.layer.addSublayer(self.backgroundLayer)
|
||||
self.tintContainerLayer.addSublayer(self.tintBackgroundLayer)
|
||||
|
||||
(self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||
self.tapRecognizer = tapRecognizer
|
||||
self.addGestureRecognizer(tapRecognizer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
/*if self.textField == nil {
|
||||
let textField = UITextField(frame: self.backgroundLayer.frame.insetBy(dx: 10.0, dy: 0.0))
|
||||
self.textField = textField
|
||||
self.addSubview(textField)
|
||||
textField.delegate = self
|
||||
|
||||
self.requestUpdate()
|
||||
textField.becomeFirstResponder()
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
}
|
||||
|
||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
}
|
||||
|
||||
func update(theme: PresentationTheme, useOpaqueTheme: Bool, title: String, size: CGSize, transition: Transition) {
|
||||
let sideInset: CGFloat = 8.0
|
||||
let topInset: CGFloat = 8.0
|
||||
let inputHeight: CGFloat = 36.0
|
||||
|
||||
if useOpaqueTheme {
|
||||
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor
|
||||
self.tintBackgroundLayer.backgroundColor = UIColor.white.cgColor
|
||||
} else {
|
||||
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.cgColor
|
||||
self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor
|
||||
}
|
||||
|
||||
self.backgroundLayer.cornerRadius = inputHeight * 0.5
|
||||
self.tintBackgroundLayer.cornerRadius = inputHeight * 0.5
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight))
|
||||
transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame)
|
||||
transition.setFrame(layer: self.tintBackgroundLayer, frame: backgroundFrame)
|
||||
|
||||
if let textField = self.textField {
|
||||
textField.textColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor
|
||||
textField.frame = backgroundFrame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol EmojiContentPeekBehavior: AnyObject {
|
||||
func setGestureRecognizerEnabled(view: UIView, isEnabled: Bool, itemAtPoint: @escaping (CGPoint) -> (AnyHashable, EmojiPagerContentComponent.View.ItemLayer, TelegramMediaFile)?)
|
||||
}
|
||||
@ -1576,6 +1667,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
public let presentController: (ViewController) -> Void
|
||||
public let presentGlobalOverlayController: (ViewController) -> Void
|
||||
public let navigationController: () -> NavigationController?
|
||||
public let requestUpdate: (Transition) -> Void
|
||||
public let sendSticker: ((FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Void)?
|
||||
public let chatPeerId: PeerId?
|
||||
public let peekBehavior: EmojiContentPeekBehavior?
|
||||
@ -1595,6 +1687,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
presentController: @escaping (ViewController) -> Void,
|
||||
presentGlobalOverlayController: @escaping (ViewController) -> Void,
|
||||
navigationController: @escaping () -> NavigationController?,
|
||||
requestUpdate: @escaping (Transition) -> Void,
|
||||
sendSticker: ((FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Void)?,
|
||||
chatPeerId: PeerId?,
|
||||
peekBehavior: EmojiContentPeekBehavior?,
|
||||
@ -1613,6 +1706,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.presentController = presentController
|
||||
self.presentGlobalOverlayController = presentGlobalOverlayController
|
||||
self.navigationController = navigationController
|
||||
self.requestUpdate = requestUpdate
|
||||
self.sendSticker = sendSticker
|
||||
self.chatPeerId = chatPeerId
|
||||
self.peekBehavior = peekBehavior
|
||||
@ -1824,6 +1918,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
public let itemGroups: [ItemGroup]
|
||||
public let itemLayoutType: ItemLayoutType
|
||||
public let warpContentsOnEdges: Bool
|
||||
public let displaySearch: Bool
|
||||
public let enableLongPress: Bool
|
||||
public let selectedItems: Set<MediaId>
|
||||
|
||||
@ -1837,6 +1932,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemGroups: [ItemGroup],
|
||||
itemLayoutType: ItemLayoutType,
|
||||
warpContentsOnEdges: Bool,
|
||||
displaySearch: Bool,
|
||||
enableLongPress: Bool,
|
||||
selectedItems: Set<MediaId>
|
||||
) {
|
||||
@ -1849,6 +1945,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.itemGroups = itemGroups
|
||||
self.itemLayoutType = itemLayoutType
|
||||
self.warpContentsOnEdges = warpContentsOnEdges
|
||||
self.displaySearch = displaySearch
|
||||
self.enableLongPress = enableLongPress
|
||||
self.selectedItems = selectedItems
|
||||
}
|
||||
@ -1864,6 +1961,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemGroups: itemGroups,
|
||||
itemLayoutType: self.itemLayoutType,
|
||||
warpContentsOnEdges: self.warpContentsOnEdges,
|
||||
displaySearch: self.displaySearch,
|
||||
enableLongPress: self.enableLongPress,
|
||||
selectedItems: self.selectedItems
|
||||
)
|
||||
@ -1900,6 +1998,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if lhs.warpContentsOnEdges != rhs.warpContentsOnEdges {
|
||||
return false
|
||||
}
|
||||
if lhs.displaySearch != rhs.displaySearch {
|
||||
return false
|
||||
}
|
||||
if lhs.enableLongPress != rhs.enableLongPress {
|
||||
return false
|
||||
}
|
||||
@ -1960,16 +2061,22 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var itemsPerRow: Int
|
||||
var contentSize: CGSize
|
||||
|
||||
var searchInsets: UIEdgeInsets
|
||||
var searchHeight: CGFloat
|
||||
|
||||
var premiumButtonInset: CGFloat
|
||||
var premiumButtonHeight: CGFloat
|
||||
|
||||
init(layoutType: ItemLayoutType, width: CGFloat, containerInsets: UIEdgeInsets, itemGroups: [ItemGroupDescription], expandedGroupIds: Set<AnyHashable>, curveNearBounds: Bool, customLayout: CustomLayout?) {
|
||||
init(layoutType: ItemLayoutType, width: CGFloat, containerInsets: UIEdgeInsets, itemGroups: [ItemGroupDescription], expandedGroupIds: Set<AnyHashable>, curveNearBounds: Bool, displaySearch: Bool, customLayout: CustomLayout?) {
|
||||
self.layoutType = layoutType
|
||||
self.width = width
|
||||
|
||||
self.premiumButtonInset = 6.0
|
||||
self.premiumButtonHeight = 50.0
|
||||
|
||||
self.searchHeight = 54.0
|
||||
self.searchInsets = UIEdgeInsets(top: max(0.0, containerInsets.top - 8.0), left: containerInsets.left, bottom: 0.0, right: containerInsets.right)
|
||||
|
||||
self.curveNearBounds = curveNearBounds
|
||||
|
||||
let minItemsPerRow: Int
|
||||
@ -2029,6 +2136,10 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.itemInsets.left = floorToScreenPixels((width - actualContentWidth) / 2.0)
|
||||
self.itemInsets.right = self.itemInsets.left
|
||||
|
||||
if displaySearch {
|
||||
self.itemInsets.top += self.searchHeight - 4.0
|
||||
}
|
||||
|
||||
var verticalGroupOrigin: CGFloat = self.itemInsets.top
|
||||
self.itemGroupLayouts = []
|
||||
for itemGroup in itemGroups {
|
||||
@ -2550,22 +2661,22 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContentScrollLayer: CALayer {
|
||||
var mirrorLayer: CALayer?
|
||||
public final class ContentScrollLayer: CALayer {
|
||||
public var mirrorLayer: CALayer?
|
||||
|
||||
override init() {
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
override init(layer: Any) {
|
||||
override public init(layer: Any) {
|
||||
super.init(layer: layer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override var position: CGPoint {
|
||||
override public var position: CGPoint {
|
||||
get {
|
||||
return super.position
|
||||
} set(value) {
|
||||
@ -2576,7 +2687,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
override var bounds: CGRect {
|
||||
override public var bounds: CGRect {
|
||||
get {
|
||||
return super.bounds
|
||||
} set(value) {
|
||||
@ -2587,7 +2698,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
override func add(_ animation: CAAnimation, forKey key: String?) {
|
||||
override public func add(_ animation: CAAnimation, forKey key: String?) {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
mirrorLayer.add(animation, forKey: key)
|
||||
}
|
||||
@ -2595,7 +2706,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
super.add(animation, forKey: key)
|
||||
}
|
||||
|
||||
override func removeAllAnimations() {
|
||||
override public func removeAllAnimations() {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
mirrorLayer.removeAllAnimations()
|
||||
}
|
||||
@ -2603,7 +2714,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
super.removeAllAnimations()
|
||||
}
|
||||
|
||||
override func removeAnimation(forKey: String) {
|
||||
override public func removeAnimation(forKey: String) {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
mirrorLayer.removeAnimation(forKey: forKey)
|
||||
}
|
||||
@ -2625,11 +2736,16 @@ public final class EmojiPagerContentComponent: Component {
|
||||
super.init(frame: CGRect())
|
||||
|
||||
(self.layer as? ContentScrollLayer)?.mirrorLayer = mirrorView.layer
|
||||
self.canCancelContentTouches = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private enum VisualItemKey: Hashable {
|
||||
@ -2654,6 +2770,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
private var effectiveVisibleSize: CGSize = CGSize()
|
||||
|
||||
private let placeholdersContainerView: UIView
|
||||
private var visibleSearchHeader: SearchHeaderView?
|
||||
private var visibleItemPlaceholderViews: [ItemLayer.Key: ItemPlaceholderView] = [:]
|
||||
private var visibleItemSelectionLayers: [ItemLayer.Key: ItemSelectionLayer] = [:]
|
||||
private var visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:]
|
||||
@ -2672,6 +2789,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
private var activeItemUpdated: ActionSlot<(AnyHashable, AnyHashable?, Transition)>?
|
||||
private var itemLayout: ItemLayout?
|
||||
|
||||
private var contextFocusItemKey: EmojiPagerContentComponent.View.ItemLayer.Key?
|
||||
|
||||
private var longTapRecognizer: UILongPressGestureRecognizer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
@ -2724,7 +2843,128 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
self.scrollView.addSubview(self.placeholdersContainerView)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
let contextGesture = ContextGesture(target: self, action: #selector(self.tapGesture(_:)))
|
||||
contextGesture.activateOnTap = true
|
||||
contextGesture.shouldBegin = { [weak self] point in
|
||||
guard let `self` = self, let _ = self.component else {
|
||||
return false
|
||||
}
|
||||
|
||||
let locationInScrollView = self.convert(point, to: self.scrollView)
|
||||
outer: for (_, groupHeader) in self.visibleGroupHeaders {
|
||||
if groupHeader.frame.insetBy(dx: -10.0, dy: -6.0).contains(locationInScrollView) {
|
||||
let groupHeaderPoint = self.scrollView.convert(locationInScrollView, to: groupHeader)
|
||||
if let clearIconLayer = groupHeader.clearIconLayer, clearIconLayer.frame.insetBy(dx: -4.0, dy: -4.0).contains(groupHeaderPoint) {
|
||||
return true
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var foundItem = false
|
||||
var foundExactItem = false
|
||||
if let (_, itemKey) = self.item(atPoint: point), let itemLayer = self.visibleItemLayers[itemKey] {
|
||||
foundExactItem = true
|
||||
foundItem = true
|
||||
if !itemLayer.displayPlaceholder {
|
||||
self.contextFocusItemKey = itemKey
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if !foundExactItem {
|
||||
if let (_, itemKey) = self.item(atPoint: point, extendedHitRange: true), let itemLayer = self.visibleItemLayers[itemKey] {
|
||||
foundItem = true
|
||||
if !itemLayer.displayPlaceholder {
|
||||
self.contextFocusItemKey = itemKey
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = foundItem
|
||||
|
||||
return false
|
||||
}
|
||||
contextGesture.activationProgress = { [weak self] progress, transition in
|
||||
guard let self = self, let contextFocusItemKey = self.contextFocusItemKey else {
|
||||
return
|
||||
}
|
||||
if let itemLayer = self.visibleItemLayers[contextFocusItemKey] {
|
||||
switch transition {
|
||||
case .begin:
|
||||
break
|
||||
case .update:
|
||||
ContainedViewLayoutTransition.immediate.updateTransformScale(layer: itemLayer, scale: 1.0 * (1.0 - progress) + 0.7 * progress)
|
||||
case let .ended(previousValue):
|
||||
let _ = previousValue
|
||||
}
|
||||
}
|
||||
}
|
||||
contextGesture.activatedAfterCompletion = { [weak self] point, wasTap in
|
||||
guard let `self` = self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
if let contextFocusItemKey = self.contextFocusItemKey {
|
||||
self.contextFocusItemKey = nil
|
||||
if let itemLayer = self.visibleItemLayers[contextFocusItemKey] {
|
||||
if wasTap {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.08, curve: .linear)
|
||||
transition.updateTransformScale(layer: itemLayer, scale: 0.7, completion: { [weak itemLayer] _ in
|
||||
guard let itemLayer = itemLayer else {
|
||||
return
|
||||
}
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .spring)
|
||||
transition.updateTransformScale(layer: itemLayer, scale: 1.0)
|
||||
})
|
||||
} else {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .spring)
|
||||
transition.updateTransformScale(layer: itemLayer, scale: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let locationInScrollView = self.convert(point, to: self.scrollView)
|
||||
outer: for (id, groupHeader) in self.visibleGroupHeaders {
|
||||
if groupHeader.frame.insetBy(dx: -10.0, dy: -6.0).contains(locationInScrollView) {
|
||||
let groupHeaderPoint = self.scrollView.convert(locationInScrollView, to: groupHeader)
|
||||
if let clearIconLayer = groupHeader.clearIconLayer, clearIconLayer.frame.insetBy(dx: -4.0, dy: -4.0).contains(groupHeaderPoint) {
|
||||
component.inputInteractionHolder.inputInteraction?.clearGroup(id)
|
||||
return
|
||||
} else {
|
||||
if groupHeader.tapGesture(point: self.convert(point, to: groupHeader)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var foundItem = false
|
||||
var foundExactItem = false
|
||||
if let (item, itemKey) = self.item(atPoint: point), let itemLayer = self.visibleItemLayers[itemKey] {
|
||||
foundExactItem = true
|
||||
foundItem = true
|
||||
if !itemLayer.displayPlaceholder {
|
||||
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer, false)
|
||||
}
|
||||
}
|
||||
|
||||
if !foundExactItem {
|
||||
if let (item, itemKey) = self.item(atPoint: point, extendedHitRange: true), let itemLayer = self.visibleItemLayers[itemKey] {
|
||||
foundItem = true
|
||||
if !itemLayer.displayPlaceholder {
|
||||
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = foundItem
|
||||
}
|
||||
self.addGestureRecognizer(contextGesture)
|
||||
|
||||
//self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
|
||||
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:)))
|
||||
longTapRecognizer.minimumPressDuration = 0.2
|
||||
@ -2782,6 +3022,14 @@ public final class EmojiPagerContentComponent: Component {
|
||||
return false
|
||||
}
|
||||
|
||||
public func wantsDisplayBelowKeyboard() -> Bool {
|
||||
if let visibleSearchHeader = self.visibleSearchHeader {
|
||||
return visibleSearchHeader.wantsDisplayBelowKeyboard
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func animateIn(fromLocation: CGPoint) {
|
||||
let scrollLocation = self.convert(fromLocation, to: self.scrollView)
|
||||
for (key, itemLayer) in self.visibleItemLayers {
|
||||
@ -3352,11 +3600,11 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
guard let component = self.component else {
|
||||
guard let _ = self.component else {
|
||||
return
|
||||
}
|
||||
if case .ended = recognizer.state {
|
||||
let locationInScrollView = recognizer.location(in: self.scrollView)
|
||||
/*let locationInScrollView = recognizer.location(in: self.scrollView)
|
||||
outer: for (id, groupHeader) in self.visibleGroupHeaders {
|
||||
if groupHeader.frame.insetBy(dx: -10.0, dy: -6.0).contains(locationInScrollView) {
|
||||
let groupHeaderPoint = self.scrollView.convert(locationInScrollView, to: groupHeader)
|
||||
@ -3390,7 +3638,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
let _ = foundItem
|
||||
let _ = foundItem*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -3611,7 +3859,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
private func updateScrollingOffset(isReset: Bool, transition: Transition) {
|
||||
guard let component = self.component else {
|
||||
guard let _ = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -3622,7 +3870,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY)
|
||||
|
||||
let relativeOffset = scrollView.contentOffset.y - previousScrollingOffsetValue.value
|
||||
if case .detailed = component.itemLayoutType {
|
||||
//if case .detailed = component.itemLayoutType {
|
||||
self.pagerEnvironment?.onChildScrollingUpdate(PagerComponentChildEnvironment.ContentScrollingUpdate(
|
||||
relativeOffset: relativeOffset,
|
||||
absoluteOffsetToTopEdge: offsetToTopEdge,
|
||||
@ -3631,7 +3879,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
isInteracting: isInteracting,
|
||||
transition: transition
|
||||
))
|
||||
}
|
||||
//}
|
||||
}
|
||||
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: isInteracting)
|
||||
}
|
||||
@ -3698,6 +3946,33 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if component.displaySearch {
|
||||
let visibleSearchHeader: SearchHeaderView
|
||||
if let current = self.visibleSearchHeader {
|
||||
visibleSearchHeader = current
|
||||
} else {
|
||||
visibleSearchHeader = SearchHeaderView(requestUpdate: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
|
||||
})
|
||||
self.visibleSearchHeader = visibleSearchHeader
|
||||
self.scrollView.addSubview(visibleSearchHeader)
|
||||
self.mirrorContentScrollView.layer.addSublayer(visibleSearchHeader.tintContainerLayer)
|
||||
}
|
||||
|
||||
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
||||
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, useOpaqueTheme: useOpaqueTheme, title: keyboardChildEnvironment.strings.Common_Search, size: searchHeaderFrame.size, transition: transition)
|
||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame)
|
||||
} else {
|
||||
if let visibleSearchHeader = self.visibleSearchHeader {
|
||||
self.visibleSearchHeader = nil
|
||||
visibleSearchHeader.removeFromSuperview()
|
||||
visibleSearchHeader.tintContainerLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
|
||||
for groupItems in itemLayout.visibleItems(for: effectiveVisibleBounds) {
|
||||
let itemGroup = component.itemGroups[groupItems.groupIndex]
|
||||
let itemGroupLayout = itemLayout.itemGroupLayouts[groupItems.groupIndex]
|
||||
@ -4171,7 +4446,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
placeholderView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
}
|
||||
|
||||
itemLayer.isVisibleForAnimations = true
|
||||
itemLayer.isVisibleForAnimations = keyboardChildEnvironment.isContentInFocus
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4701,6 +4976,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemGroups: itemGroups,
|
||||
expandedGroupIds: self.expandedGroupIds,
|
||||
curveNearBounds: component.warpContentsOnEdges,
|
||||
displaySearch: component.displaySearch,
|
||||
customLayout: component.inputInteractionHolder.inputInteraction?.customLayout
|
||||
)
|
||||
if let previousItemLayout = self.itemLayout {
|
||||
@ -5362,29 +5638,6 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if areUnicodeEmojiEnabled {
|
||||
for (subgroupId, list) in staticEmojiMapping {
|
||||
let groupId: AnyHashable = "static"
|
||||
for emojiString in list {
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: nil,
|
||||
content: .staticEmoji(emojiString),
|
||||
itemFile: nil,
|
||||
subgroupId: subgroupId.rawValue,
|
||||
icon: .none,
|
||||
accentTint: false
|
||||
)
|
||||
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleEmoji, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var installedCollectionIds = Set<ItemCollectionId>()
|
||||
for (id, _, _) in view.collectionInfos {
|
||||
installedCollectionIds.insert(id)
|
||||
@ -5514,6 +5767,29 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if areUnicodeEmojiEnabled {
|
||||
for (subgroupId, list) in staticEmojiMapping {
|
||||
let groupId: AnyHashable = "static"
|
||||
for emojiString in list {
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: nil,
|
||||
content: .staticEmoji(emojiString),
|
||||
itemFile: nil,
|
||||
subgroupId: subgroupId.rawValue,
|
||||
icon: .none,
|
||||
accentTint: false
|
||||
)
|
||||
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleEmoji, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EmojiPagerContentComponent(
|
||||
id: "emoji",
|
||||
context: context,
|
||||
@ -5557,6 +5833,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
},
|
||||
itemLayoutType: .compact,
|
||||
warpContentsOnEdges: isReactionSelection || isStatusSelection,
|
||||
displaySearch: false,
|
||||
enableLongPress: (isReactionSelection && !isQuickReactionSelection) || isStatusSelection,
|
||||
selectedItems: selectedItems
|
||||
)
|
||||
|
||||
@ -14,15 +14,18 @@ import LocalizedPeerData
|
||||
public final class EntityKeyboardChildEnvironment: Equatable {
|
||||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let isContentInFocus: Bool
|
||||
public let getContentActiveItemUpdated: (AnyHashable) -> ActionSlot<(AnyHashable, AnyHashable?, Transition)>?
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
isContentInFocus: Bool,
|
||||
getContentActiveItemUpdated: @escaping (AnyHashable) -> ActionSlot<(AnyHashable, AnyHashable?, Transition)>?
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.isContentInFocus = isContentInFocus
|
||||
self.getContentActiveItemUpdated = getContentActiveItemUpdated
|
||||
}
|
||||
|
||||
@ -33,6 +36,9 @@ public final class EntityKeyboardChildEnvironment: Equatable {
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.isContentInFocus != rhs.isContentInFocus {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -81,6 +87,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
|
||||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let isContentInFocus: Bool
|
||||
public let containerInsets: UIEdgeInsets
|
||||
public let topPanelInsets: UIEdgeInsets
|
||||
public let emojiContent: EmojiPagerContentComponent
|
||||
@ -104,6 +111,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
isContentInFocus: Bool,
|
||||
containerInsets: UIEdgeInsets,
|
||||
topPanelInsets: UIEdgeInsets,
|
||||
emojiContent: EmojiPagerContentComponent,
|
||||
@ -126,6 +134,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.isContentInFocus = isContentInFocus
|
||||
self.containerInsets = containerInsets
|
||||
self.topPanelInsets = topPanelInsets
|
||||
self.emojiContent = emojiContent
|
||||
@ -154,6 +163,9 @@ public final class EntityKeyboardComponent: Component {
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.isContentInFocus != rhs.isContentInFocus {
|
||||
return false
|
||||
}
|
||||
if lhs.containerInsets != rhs.containerInsets {
|
||||
return false
|
||||
}
|
||||
@ -620,6 +632,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
EntityKeyboardChildEnvironment(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
isContentInFocus: component.isContentInFocus,
|
||||
getContentActiveItemUpdated: { id in
|
||||
if id == AnyHashable("gifs") {
|
||||
return gifsContentItemIdUpdated
|
||||
|
||||
@ -1760,7 +1760,16 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
||||
var validIds = Set<AnyHashable>()
|
||||
let visibleItemRange = itemLayout.visibleItemRange(for: visibleBounds)
|
||||
if !self.items.isEmpty && visibleItemRange.maxIndex >= visibleItemRange.minIndex {
|
||||
for index in visibleItemRange.minIndex ... visibleItemRange.maxIndex {
|
||||
var indices = Array(visibleItemRange.minIndex ... visibleItemRange.maxIndex)
|
||||
for i in 0 ..< self.items.count {
|
||||
if self.items[i].id == AnyHashable("static") {
|
||||
if !indices.contains(i) {
|
||||
indices.append(i)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
for index in indices {
|
||||
let item = self.items[index]
|
||||
validIds.insert(item.id)
|
||||
|
||||
@ -1911,6 +1920,9 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
||||
itemIndex = component.items.count - 1
|
||||
useRightAnchor = true
|
||||
}
|
||||
if itemIndex == component.items.count - 1 {
|
||||
useRightAnchor = true
|
||||
}
|
||||
if newBounds.minX < 0.0 {
|
||||
newBounds.origin.x = 0.0
|
||||
itemIndex = 0
|
||||
@ -1918,12 +1930,8 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
||||
}
|
||||
|
||||
if useRightAnchor {
|
||||
var newBounds = CGRect(origin: CGPoint(x: updatedItemFrame.maxX - previousDistanceToItemRight, y: 0.0), size: availableSize)
|
||||
if newBounds.minX > itemLayout.contentSize.width - self.scrollView.bounds.width {
|
||||
newBounds.origin.x = itemLayout.contentSize.width - self.scrollView.bounds.width
|
||||
}
|
||||
if newBounds.minX < 0.0 {
|
||||
}
|
||||
let _ = previousDistanceToItemRight
|
||||
newBounds.origin.x = itemLayout.contentSize.width - self.scrollView.bounds.width
|
||||
}
|
||||
|
||||
previousItemFrame = previousItemLayout.containerFrame(at: itemIndex)
|
||||
|
||||
@ -1495,6 +1495,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
items.tip = .animatedEmoji(text: presentationData.strings.ChatContextMenu_EmojiSet(Int32(packReferences.count)), arguments: nil, file: nil, action: action)
|
||||
} else if let reference = packReferences.first {
|
||||
items.tipSignal = context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|
||||
|> delay(1.0, queue: .mainQueue())
|
||||
|> filter { result in
|
||||
if case .result = result {
|
||||
return true
|
||||
@ -1505,7 +1506,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|> mapToSignal { result -> Signal<ContextController.Tip?, NoError> in
|
||||
if case let .result(info, items, _) = result, let presentationContext = presentationContext {
|
||||
let tip: ContextController.Tip = .animatedEmoji(
|
||||
text: presentationData.strings.ChatContextMenu_EmojiSetSingle(info.title).string,
|
||||
text: presentationData.strings.ChatContextMenu_ReactionEmojiSetSingle(info.title).string,
|
||||
arguments: TextNodeWithEntities.Arguments(
|
||||
context: context,
|
||||
cache: presentationContext.animationCache,
|
||||
@ -5286,78 +5287,82 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let _ = ChatControllerCount.modify { value in
|
||||
return value - 1
|
||||
}
|
||||
self.historyStateDisposable?.dispose()
|
||||
self.messageIndexDisposable.dispose()
|
||||
self.navigationActionDisposable.dispose()
|
||||
self.galleryHiddenMesageAndMediaDisposable.dispose()
|
||||
self.temporaryHiddenGalleryMediaDisposable.dispose()
|
||||
self.peerDisposable.dispose()
|
||||
self.accountPeerDisposable?.dispose()
|
||||
self.titleDisposable.dispose()
|
||||
self.messageContextDisposable.dispose()
|
||||
self.controllerNavigationDisposable.dispose()
|
||||
self.sentMessageEventsDisposable.dispose()
|
||||
self.failedMessageEventsDisposable.dispose()
|
||||
self.messageActionCallbackDisposable.dispose()
|
||||
self.messageActionUrlAuthDisposable.dispose()
|
||||
self.editMessageDisposable.dispose()
|
||||
self.editMessageErrorsDisposable.dispose()
|
||||
self.enqueueMediaMessageDisposable.dispose()
|
||||
self.resolvePeerByNameDisposable?.dispose()
|
||||
self.shareStatusDisposable?.dispose()
|
||||
self.clearCacheDisposable?.dispose()
|
||||
self.bankCardDisposable?.dispose()
|
||||
self.botCallbackAlertMessageDisposable?.dispose()
|
||||
self.selectMessagePollOptionDisposables?.dispose()
|
||||
for (_, info) in self.contextQueryStates {
|
||||
info.1.dispose()
|
||||
|
||||
let deallocate: () -> Void = {
|
||||
self.historyStateDisposable?.dispose()
|
||||
self.messageIndexDisposable.dispose()
|
||||
self.navigationActionDisposable.dispose()
|
||||
self.galleryHiddenMesageAndMediaDisposable.dispose()
|
||||
self.temporaryHiddenGalleryMediaDisposable.dispose()
|
||||
self.peerDisposable.dispose()
|
||||
self.accountPeerDisposable?.dispose()
|
||||
self.titleDisposable.dispose()
|
||||
self.messageContextDisposable.dispose()
|
||||
self.controllerNavigationDisposable.dispose()
|
||||
self.sentMessageEventsDisposable.dispose()
|
||||
self.failedMessageEventsDisposable.dispose()
|
||||
self.messageActionCallbackDisposable.dispose()
|
||||
self.messageActionUrlAuthDisposable.dispose()
|
||||
self.editMessageDisposable.dispose()
|
||||
self.editMessageErrorsDisposable.dispose()
|
||||
self.enqueueMediaMessageDisposable.dispose()
|
||||
self.resolvePeerByNameDisposable?.dispose()
|
||||
self.shareStatusDisposable?.dispose()
|
||||
self.clearCacheDisposable?.dispose()
|
||||
self.bankCardDisposable?.dispose()
|
||||
self.botCallbackAlertMessageDisposable?.dispose()
|
||||
self.selectMessagePollOptionDisposables?.dispose()
|
||||
for (_, info) in self.contextQueryStates {
|
||||
info.1.dispose()
|
||||
}
|
||||
self.urlPreviewQueryState?.1.dispose()
|
||||
self.audioRecorderDisposable?.dispose()
|
||||
self.audioRecorderStatusDisposable?.dispose()
|
||||
self.videoRecorderDisposable?.dispose()
|
||||
self.buttonKeyboardMessageDisposable?.dispose()
|
||||
self.cachedDataDisposable?.dispose()
|
||||
self.resolveUrlDisposable?.dispose()
|
||||
self.chatUnreadCountDisposable?.dispose()
|
||||
self.buttonUnreadCountDisposable?.dispose()
|
||||
self.chatUnreadMentionCountDisposable?.dispose()
|
||||
self.peerInputActivitiesDisposable?.dispose()
|
||||
self.interactiveEmojiSyncDisposable.dispose()
|
||||
self.recentlyUsedInlineBotsDisposable?.dispose()
|
||||
self.unpinMessageDisposable?.dispose()
|
||||
self.inputActivityDisposable?.dispose()
|
||||
self.recordingActivityDisposable?.dispose()
|
||||
self.acquiredRecordingActivityDisposable?.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.searchDisposable?.dispose()
|
||||
self.applicationInForegroundDisposable?.dispose()
|
||||
self.applicationInFocusDisposable?.dispose()
|
||||
self.canReadHistoryDisposable?.dispose()
|
||||
self.networkStateDisposable?.dispose()
|
||||
self.chatAdditionalDataDisposable.dispose()
|
||||
self.shareStatusDisposable?.dispose()
|
||||
self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeTarget(self)
|
||||
self.preloadHistoryPeerIdDisposable.dispose()
|
||||
self.preloadNextChatPeerIdDisposable.dispose()
|
||||
self.reportIrrelvantGeoDisposable?.dispose()
|
||||
self.reminderActivity?.invalidate()
|
||||
self.updateSlowmodeStatusDisposable.dispose()
|
||||
self.keepPeerInfoScreenDataHotDisposable.dispose()
|
||||
self.preloadAvatarDisposable.dispose()
|
||||
self.peekTimerDisposable.dispose()
|
||||
self.hasActiveGroupCallDisposable?.dispose()
|
||||
self.createVoiceChatDisposable.dispose()
|
||||
self.checksTooltipDisposable.dispose()
|
||||
self.peerSuggestionsDisposable.dispose()
|
||||
self.peerSuggestionsDismissDisposable.dispose()
|
||||
self.selectAddMemberDisposable.dispose()
|
||||
self.addMemberDisposable.dispose()
|
||||
self.importStateDisposable?.dispose()
|
||||
self.nextChannelToReadDisposable?.dispose()
|
||||
self.inviteRequestsDisposable.dispose()
|
||||
self.sendAsPeersDisposable?.dispose()
|
||||
self.preloadAttachBotIconsDisposables?.dispose()
|
||||
}
|
||||
self.urlPreviewQueryState?.1.dispose()
|
||||
self.audioRecorderDisposable?.dispose()
|
||||
self.audioRecorderStatusDisposable?.dispose()
|
||||
self.videoRecorderDisposable?.dispose()
|
||||
self.buttonKeyboardMessageDisposable?.dispose()
|
||||
self.cachedDataDisposable?.dispose()
|
||||
self.resolveUrlDisposable?.dispose()
|
||||
self.chatUnreadCountDisposable?.dispose()
|
||||
self.buttonUnreadCountDisposable?.dispose()
|
||||
self.chatUnreadMentionCountDisposable?.dispose()
|
||||
self.peerInputActivitiesDisposable?.dispose()
|
||||
self.interactiveEmojiSyncDisposable.dispose()
|
||||
self.recentlyUsedInlineBotsDisposable?.dispose()
|
||||
self.unpinMessageDisposable?.dispose()
|
||||
self.inputActivityDisposable?.dispose()
|
||||
self.recordingActivityDisposable?.dispose()
|
||||
self.acquiredRecordingActivityDisposable?.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.searchDisposable?.dispose()
|
||||
self.applicationInForegroundDisposable?.dispose()
|
||||
self.applicationInFocusDisposable?.dispose()
|
||||
self.canReadHistoryDisposable?.dispose()
|
||||
self.networkStateDisposable?.dispose()
|
||||
self.chatAdditionalDataDisposable.dispose()
|
||||
self.shareStatusDisposable?.dispose()
|
||||
self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeTarget(self)
|
||||
self.preloadHistoryPeerIdDisposable.dispose()
|
||||
self.preloadNextChatPeerIdDisposable.dispose()
|
||||
self.reportIrrelvantGeoDisposable?.dispose()
|
||||
self.reminderActivity?.invalidate()
|
||||
self.updateSlowmodeStatusDisposable.dispose()
|
||||
self.keepPeerInfoScreenDataHotDisposable.dispose()
|
||||
self.preloadAvatarDisposable.dispose()
|
||||
self.peekTimerDisposable.dispose()
|
||||
self.hasActiveGroupCallDisposable?.dispose()
|
||||
self.createVoiceChatDisposable.dispose()
|
||||
self.checksTooltipDisposable.dispose()
|
||||
self.peerSuggestionsDisposable.dispose()
|
||||
self.peerSuggestionsDismissDisposable.dispose()
|
||||
self.selectAddMemberDisposable.dispose()
|
||||
self.addMemberDisposable.dispose()
|
||||
self.importStateDisposable?.dispose()
|
||||
self.nextChannelToReadDisposable?.dispose()
|
||||
self.inviteRequestsDisposable.dispose()
|
||||
self.sendAsPeersDisposable?.dispose()
|
||||
self.preloadAttachBotIconsDisposables?.dispose()
|
||||
deallocate()
|
||||
}
|
||||
|
||||
public func updatePresentationMode(_ mode: ChatControllerPresentationMode) {
|
||||
|
||||
@ -762,6 +762,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.isInFocus = isInFocus
|
||||
|
||||
self.inputMediaNode?.simulateUpdateLayout(isVisible: isInFocus)
|
||||
if let inputNode = self.inputNode as? ChatEntityKeyboardInputNode {
|
||||
inputNode.simulateUpdateLayout(isVisible: isInFocus)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void, updateExtraNavigationBarBackgroundHeight: (CGFloat, ContainedViewLayoutTransition) -> Void) {
|
||||
|
||||
@ -195,7 +195,30 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
continue
|
||||
}
|
||||
|
||||
let animationData = EntityKeyboardAnimationData(file: item.file)
|
||||
let animationData: EntityKeyboardAnimationData
|
||||
|
||||
if let thumbnail = featuredStickerPack.info.thumbnail {
|
||||
let type: EntityKeyboardAnimationData.ItemType
|
||||
if item.file.isAnimatedSticker {
|
||||
type = .lottie
|
||||
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
|
||||
type = .video
|
||||
} else {
|
||||
type = .still
|
||||
}
|
||||
|
||||
animationData = EntityKeyboardAnimationData(
|
||||
id: .stickerPackThumbnail(featuredStickerPack.info.id),
|
||||
type: type,
|
||||
resource: .stickerPackThumbnail(stickerPack: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), resource: thumbnail.resource),
|
||||
dimensions: thumbnail.dimensions.cgSize,
|
||||
immediateThumbnailData: featuredStickerPack.info.immediateThumbnailData,
|
||||
isReaction: false
|
||||
)
|
||||
} else {
|
||||
animationData = EntityKeyboardAnimationData(file: item.file)
|
||||
}
|
||||
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
@ -513,6 +536,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
},
|
||||
itemLayoutType: .detailed,
|
||||
warpContentsOnEdges: false,
|
||||
displaySearch: false,
|
||||
enableLongPress: false,
|
||||
selectedItems: Set()
|
||||
)
|
||||
@ -1094,6 +1118,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
},
|
||||
navigationController: { [weak controllerInteraction] in
|
||||
return controllerInteraction?.navigationController()
|
||||
},
|
||||
requestUpdate: { _ in
|
||||
|
||||
},
|
||||
sendSticker: { [weak controllerInteraction] fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, bubbleUpEmojiOrStickersets in
|
||||
guard let controllerInteraction = controllerInteraction else {
|
||||
@ -1300,6 +1327,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
},
|
||||
navigationController: { [weak controllerInteraction] in
|
||||
return controllerInteraction?.navigationController()
|
||||
},
|
||||
requestUpdate: { _ in
|
||||
|
||||
},
|
||||
sendSticker: { [weak controllerInteraction] fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, bubbleUpEmojiOrStickersets in
|
||||
guard let controllerInteraction = controllerInteraction else {
|
||||
@ -1465,6 +1495,13 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
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)
|
||||
}
|
||||
|
||||
func simulateUpdateLayout(isVisible: Bool) {
|
||||
guard let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, _, 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, 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, isExpanded: Bool) -> (CGFloat, CGFloat) {
|
||||
self.currentState = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded)
|
||||
|
||||
@ -1526,6 +1563,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
component: AnyComponent(EntityKeyboardComponent(
|
||||
theme: interfaceState.theme,
|
||||
strings: interfaceState.strings,
|
||||
isContentInFocus: isVisible,
|
||||
containerInsets: UIEdgeInsets(top: 0.0, left: leftInset, bottom: bottomInset, right: rightInset),
|
||||
topPanelInsets: UIEdgeInsets(),
|
||||
emojiContent: self.currentInputData.emoji,
|
||||
@ -1636,7 +1674,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
for group in itemGroups {
|
||||
if !(group.groupId.base is ItemCollectionId) {
|
||||
updatedGroups.append(group)
|
||||
if group.groupId != AnyHashable("static") {
|
||||
updatedGroups.append(group)
|
||||
}
|
||||
} else {
|
||||
if group.isEmbedded {
|
||||
continue
|
||||
@ -2016,6 +2056,9 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
|
||||
},
|
||||
navigationController: {
|
||||
return nil
|
||||
},
|
||||
requestUpdate: { _ in
|
||||
|
||||
},
|
||||
sendSticker: nil,
|
||||
chatPeerId: nil,
|
||||
|
||||
@ -1595,7 +1595,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
|
||||
if customReactionEmojiPacks.count == 1, let firstCustomEmojiReaction = firstCustomEmojiReaction {
|
||||
tip = .animatedEmoji(
|
||||
text: presentationData.strings.ChatContextMenu_EmojiSetSingle(customReactionEmojiPacks[0].title).string,
|
||||
text: presentationData.strings.ChatContextMenu_ReactionEmojiSetSingle(customReactionEmojiPacks[0].title).string,
|
||||
arguments: TextNodeWithEntities.Arguments(
|
||||
context: context,
|
||||
cache: controllerInteraction.presentationContext.animationCache,
|
||||
@ -1609,7 +1609,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
}
|
||||
)
|
||||
} else if customReactionEmojiPacks.count > 1 {
|
||||
tip = .animatedEmoji(text: presentationData.strings.ChatContextMenu_EmojiSet(Int32(customReactionEmojiPacks.count)), arguments: nil, file: nil, action: {
|
||||
tip = .animatedEmoji(text: presentationData.strings.ChatContextMenu_ReactionEmojiSet(Int32(customReactionEmojiPacks.count)), arguments: nil, file: nil, action: {
|
||||
(interfaceInteraction.chatController() as? ChatControllerImpl)?.presentEmojiList(references: customReactionEmojiPacks.map { pack -> StickerPackReference in .id(id: pack.id.id, accessHash: pack.accessHash) })
|
||||
})
|
||||
}
|
||||
|
||||
@ -818,7 +818,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
let textLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||
|
||||
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
|
||||
func continueAsyncLayout(_ weakSelf: Weak<ChatMessageAnimatedStickerItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
||||
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||
let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData)
|
||||
@ -1255,8 +1255,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
layoutSize.height += 4.0 + reactionButtonsSizeAndApply.0.height
|
||||
}
|
||||
|
||||
return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _, synchronousLoads in
|
||||
if let strongSelf = self {
|
||||
func finishLayout(_ animation: ListViewItemUpdateAnimation, _ apply: ListViewItemApply, _ synchronousLoads: Bool) {
|
||||
if let strongSelf = weakSelf.value {
|
||||
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
|
||||
strongSelf.updateAccessibilityData(accessibilityData)
|
||||
|
||||
@ -1473,7 +1473,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
} else {
|
||||
isAppearing = true
|
||||
deliveryFailedNode = ChatMessageDeliveryFailedNode(tapped: {
|
||||
if let item = self?.item {
|
||||
if let strongSelf = weakSelf.value, let item = strongSelf.item {
|
||||
item.controllerInteraction.requestRedeliveryOfFailedMessages(item.content.firstMessage.id)
|
||||
}
|
||||
})
|
||||
@ -1504,12 +1504,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
if actionButtonsNode !== strongSelf.actionButtonsNode {
|
||||
strongSelf.actionButtonsNode = actionButtonsNode
|
||||
actionButtonsNode.buttonPressed = { button in
|
||||
if let strongSelf = self {
|
||||
if let strongSelf = weakSelf.value {
|
||||
strongSelf.performMessageButtonAction(button: button)
|
||||
}
|
||||
}
|
||||
actionButtonsNode.buttonLongTapped = { button in
|
||||
if let strongSelf = self {
|
||||
if let strongSelf = weakSelf.value {
|
||||
strongSelf.presentMessageButtonContextMenu(button: button)
|
||||
}
|
||||
}
|
||||
@ -1536,13 +1536,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
if reactionButtonsNode !== strongSelf.reactionButtonsNode {
|
||||
strongSelf.reactionButtonsNode = reactionButtonsNode
|
||||
reactionButtonsNode.reactionSelected = { value in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
guard let strongSelf = weakSelf.value, let item = strongSelf.item else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value))
|
||||
}
|
||||
reactionButtonsNode.openReactionPreview = { gesture, sourceView, value in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
guard let strongSelf = weakSelf.value, let item = strongSelf.item else {
|
||||
gesture?.cancel()
|
||||
return
|
||||
}
|
||||
@ -1590,7 +1590,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||
strongSelf.dateAndStatusNode.pressed = {
|
||||
guard let strongSelf = self else {
|
||||
guard let strongSelf = weakSelf.value else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
|
||||
@ -1605,8 +1605,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
f()
|
||||
}
|
||||
}
|
||||
}
|
||||
return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { (animation: ListViewItemUpdateAnimation, apply: ListViewItemApply, synchronousLoads: Bool) -> Void in
|
||||
finishLayout(animation, apply, synchronousLoads)
|
||||
})
|
||||
}
|
||||
|
||||
let weakSelf = Weak(self)
|
||||
return { (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) in
|
||||
return continueAsyncLayout(weakSelf, item, params, mergedTop, mergedBottom, dateHeaderAtBottom)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
|
||||
@ -20,43 +20,43 @@ private let inlineBotNameFont = nameFont
|
||||
|
||||
class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerDelegate {
|
||||
let contextSourceNode: ContextExtractedContentContainingNode
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
private let interactiveVideoNode: ChatMessageInteractiveInstantVideoNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
let interactiveVideoNode: ChatMessageInteractiveInstantVideoNode
|
||||
|
||||
private var selectionNode: ChatMessageSelectionNode?
|
||||
private var deliveryFailedNode: ChatMessageDeliveryFailedNode?
|
||||
private var shareButtonNode: ChatMessageShareButton?
|
||||
var selectionNode: ChatMessageSelectionNode?
|
||||
var deliveryFailedNode: ChatMessageDeliveryFailedNode?
|
||||
var shareButtonNode: ChatMessageShareButton?
|
||||
|
||||
private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
|
||||
private var swipeToReplyFeedback: HapticFeedback?
|
||||
var swipeToReplyNode: ChatMessageSwipeToReplyNode?
|
||||
var swipeToReplyFeedback: HapticFeedback?
|
||||
|
||||
private var appliedParams: ListViewItemLayoutParams?
|
||||
private var appliedItem: ChatMessageItem?
|
||||
private var appliedForwardInfo: (Peer?, String?)?
|
||||
private var appliedHasAvatar = false
|
||||
private var appliedCurrentlyPlaying: Bool?
|
||||
private var appliedAutomaticDownload = false
|
||||
private var avatarOffset: CGFloat?
|
||||
var appliedParams: ListViewItemLayoutParams?
|
||||
var appliedItem: ChatMessageItem?
|
||||
var appliedForwardInfo: (Peer?, String?)?
|
||||
var appliedHasAvatar = false
|
||||
var appliedCurrentlyPlaying: Bool?
|
||||
var appliedAutomaticDownload = false
|
||||
var avatarOffset: CGFloat?
|
||||
|
||||
private var animatingHeight: Bool {
|
||||
var animatingHeight: Bool {
|
||||
return self.apparentHeightTransition != nil
|
||||
}
|
||||
|
||||
private var viaBotNode: TextNode?
|
||||
private var replyInfoNode: ChatMessageReplyInfoNode?
|
||||
private var replyBackgroundNode: NavigationBackgroundNode?
|
||||
private var forwardInfoNode: ChatMessageForwardInfoNode?
|
||||
var viaBotNode: TextNode?
|
||||
var replyInfoNode: ChatMessageReplyInfoNode?
|
||||
var replyBackgroundNode: NavigationBackgroundNode?
|
||||
var forwardInfoNode: ChatMessageForwardInfoNode?
|
||||
|
||||
private var actionButtonsNode: ChatMessageActionButtonsNode?
|
||||
private var reactionButtonsNode: ChatMessageReactionButtonsNode?
|
||||
var actionButtonsNode: ChatMessageActionButtonsNode?
|
||||
var reactionButtonsNode: ChatMessageReactionButtonsNode?
|
||||
|
||||
private let messageAccessibilityArea: AccessibilityAreaNode
|
||||
let messageAccessibilityArea: AccessibilityAreaNode
|
||||
|
||||
private var currentSwipeToReplyTranslation: CGFloat = 0.0
|
||||
var currentSwipeToReplyTranslation: CGFloat = 0.0
|
||||
|
||||
private var recognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||
var recognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||
|
||||
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
||||
var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
||||
|
||||
override var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
@ -70,7 +70,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
}
|
||||
}
|
||||
|
||||
private var wasPlaying = false
|
||||
fileprivate var wasPlaying = false
|
||||
|
||||
required init() {
|
||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||
@ -266,7 +266,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
let currentForwardInfo = self.appliedForwardInfo
|
||||
let currentPlaying = self.appliedCurrentlyPlaying
|
||||
|
||||
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
|
||||
func continueAsyncLayout(_ weakSelf: Weak<ChatMessageInstantVideoItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
||||
|
||||
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||
@ -582,8 +582,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
layoutSize.height += 6.0 + reactionButtonsSizeAndApply.0.height
|
||||
}
|
||||
|
||||
return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _, synchronousLoads in
|
||||
if let strongSelf = self {
|
||||
func finishAsyncLayout(_ animation: ListViewItemUpdateAnimation, _ synchronousLoads: Bool) {
|
||||
if let strongSelf = weakSelf.value {
|
||||
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||
@ -738,7 +738,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
} else {
|
||||
isAppearing = true
|
||||
deliveryFailedNode = ChatMessageDeliveryFailedNode(tapped: {
|
||||
if let item = self?.item {
|
||||
if let strongSelf = weakSelf.value, let item = strongSelf.item {
|
||||
item.controllerInteraction.requestRedeliveryOfFailedMessages(item.content.firstMessage.id)
|
||||
}
|
||||
})
|
||||
@ -773,13 +773,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
if reactionButtonsNode !== strongSelf.reactionButtonsNode {
|
||||
strongSelf.reactionButtonsNode = reactionButtonsNode
|
||||
reactionButtonsNode.reactionSelected = { value in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
guard let strongSelf = weakSelf.value, let item = strongSelf.item else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value))
|
||||
}
|
||||
reactionButtonsNode.openReactionPreview = { gesture, sourceNode, value in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
guard let strongSelf = weakSelf.value, let item = strongSelf.item else {
|
||||
gesture?.cancel()
|
||||
return
|
||||
}
|
||||
@ -823,12 +823,12 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
if actionButtonsNode !== strongSelf.actionButtonsNode {
|
||||
strongSelf.actionButtonsNode = actionButtonsNode
|
||||
actionButtonsNode.buttonPressed = { button in
|
||||
if let strongSelf = self {
|
||||
if let strongSelf = weakSelf.value {
|
||||
strongSelf.performMessageButtonAction(button: button)
|
||||
}
|
||||
}
|
||||
actionButtonsNode.buttonLongTapped = { button in
|
||||
if let strongSelf = self {
|
||||
if let strongSelf = weakSelf.value {
|
||||
strongSelf.presentMessageButtonContextMenu(button: button)
|
||||
}
|
||||
}
|
||||
@ -861,8 +861,17 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
f()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { animation, _, synchronousLoads in
|
||||
finishAsyncLayout(animation, synchronousLoads)
|
||||
})
|
||||
}
|
||||
|
||||
let weakSelf = Weak(self)
|
||||
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) in
|
||||
continueAsyncLayout(weakSelf, item, params, mergedTop, mergedBottom, dateHeaderAtBottom)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
|
||||
@ -345,7 +345,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
let currentShareButtonNode = self.shareButtonNode
|
||||
let currentForwardInfo = self.appliedForwardInfo
|
||||
|
||||
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
|
||||
func continueAsyncLayout(_ weakSelf: Weak<ChatMessageStickerItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
||||
|
||||
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||
@ -785,8 +785,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _, synchronousLoads in
|
||||
if let strongSelf = self {
|
||||
func finishAsyncLayout(_ animation: ListViewItemUpdateAnimation, _ synchronousLoads: Bool) {
|
||||
if let strongSelf = weakSelf.value {
|
||||
var transition: ContainedViewLayoutTransition = .immediate
|
||||
if case let .System(duration, _) = animation {
|
||||
transition = .animated(duration: duration, curve: .spring)
|
||||
@ -951,7 +951,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
} else {
|
||||
isAppearing = true
|
||||
deliveryFailedNode = ChatMessageDeliveryFailedNode(tapped: {
|
||||
if let item = self?.item {
|
||||
if let strongSelf = weakSelf.value, let item = strongSelf.item {
|
||||
item.controllerInteraction.requestRedeliveryOfFailedMessages(item.content.firstMessage.id)
|
||||
}
|
||||
})
|
||||
@ -982,12 +982,12 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
if actionButtonsNode !== strongSelf.actionButtonsNode {
|
||||
strongSelf.actionButtonsNode = actionButtonsNode
|
||||
actionButtonsNode.buttonPressed = { button in
|
||||
if let strongSelf = self {
|
||||
if let strongSelf = weakSelf.value {
|
||||
strongSelf.performMessageButtonAction(button: button)
|
||||
}
|
||||
}
|
||||
actionButtonsNode.buttonLongTapped = { button in
|
||||
if let strongSelf = self {
|
||||
if let strongSelf = weakSelf.value {
|
||||
strongSelf.presentMessageButtonContextMenu(button: button)
|
||||
}
|
||||
}
|
||||
@ -1014,13 +1014,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
if reactionButtonsNode !== strongSelf.reactionButtonsNode {
|
||||
strongSelf.reactionButtonsNode = reactionButtonsNode
|
||||
reactionButtonsNode.reactionSelected = { value in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
guard let strongSelf = weakSelf.value, let item = strongSelf.item else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value))
|
||||
}
|
||||
reactionButtonsNode.openReactionPreview = { gesture, sourceNode, value in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
guard let strongSelf = weakSelf.value, let item = strongSelf.item else {
|
||||
gesture?.cancel()
|
||||
return
|
||||
}
|
||||
@ -1070,7 +1070,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
|
||||
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||
strongSelf.dateAndStatusNode.pressed = {
|
||||
guard let strongSelf = self else {
|
||||
guard let strongSelf = weakSelf.value else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
|
||||
@ -1085,8 +1085,17 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
f()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { animation, _, synchronousLoads in
|
||||
finishAsyncLayout(animation, synchronousLoads)
|
||||
})
|
||||
}
|
||||
|
||||
let weakSelf = Weak(self)
|
||||
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) in
|
||||
return continueAsyncLayout(weakSelf, item, params, mergedTop, mergedBottom, dateHeaderAtBottom)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
|
||||
@ -2543,7 +2543,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.currentEmojiSuggestionView = nil
|
||||
|
||||
currentEmojiSuggestionView.alpha = 0.0
|
||||
currentEmojiSuggestionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, completion: { [weak currentEmojiSuggestionView] _ in
|
||||
currentEmojiSuggestionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak currentEmojiSuggestionView] _ in
|
||||
currentEmojiSuggestionView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
@ -675,9 +675,9 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
case .verified:
|
||||
titleCredibilityContent = .verified(fillColor: self.theme.list.itemCheckColors.fillColor, foregroundColor: self.theme.list.itemCheckColors.foregroundColor)
|
||||
case .fake:
|
||||
titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_ScamAccount.uppercased())
|
||||
case .scam:
|
||||
titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_FakeAccount.uppercased())
|
||||
case .scam:
|
||||
titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_ScamAccount.uppercased())
|
||||
case let .emojiStatus(emojiStatus):
|
||||
titleCredibilityContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2))
|
||||
}
|
||||
|
||||
@ -2346,10 +2346,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
emojiRegularStatusContent = .verified(fillColor: presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
emojiExpandedStatusContent = .verified(fillColor: UIColor(rgb: 0xffffff, alpha: 0.75), foregroundColor: .clear)
|
||||
case .fake:
|
||||
emojiRegularStatusContent = .text(color: presentationData.theme.chat.message.incoming.scamColor, string: presentationData.strings.Message_ScamAccount.uppercased())
|
||||
emojiRegularStatusContent = .text(color: presentationData.theme.chat.message.incoming.scamColor, string: presentationData.strings.Message_FakeAccount.uppercased())
|
||||
emojiExpandedStatusContent = emojiRegularStatusContent
|
||||
case .scam:
|
||||
emojiRegularStatusContent = .text(color: presentationData.theme.chat.message.incoming.scamColor, string: presentationData.strings.Message_FakeAccount.uppercased())
|
||||
emojiRegularStatusContent = .text(color: presentationData.theme.chat.message.incoming.scamColor, string: presentationData.strings.Message_ScamAccount.uppercased())
|
||||
emojiExpandedStatusContent = emojiRegularStatusContent
|
||||
case let .emojiStatus(emojiStatus):
|
||||
currentEmojiStatus = emojiStatus
|
||||
|
||||
@ -734,7 +734,7 @@ final class PeerChannelMemberCategoriesContext {
|
||||
let context: ChannelMemberCategoryListContext
|
||||
let emptyTimeout: Double
|
||||
switch key {
|
||||
case .admins(nil), .banned(nil), .recentSearch(nil), .restricted(nil), .restrictedAndBanned(nil), .recent, .contacts:
|
||||
case .admins(nil), .banned(nil), .recentSearch(""), .restricted(nil), .restrictedAndBanned(nil), .recent, .contacts:
|
||||
emptyTimeout = defaultEmptyTimeout
|
||||
default:
|
||||
emptyTimeout = 0.0
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user