Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2022-09-13 14:06:11 +03:00
commit ae60febadf
34 changed files with 1298 additions and 463 deletions

View File

@ -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";

View File

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

View File

@ -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:

View File

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

View File

@ -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 {

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

@ -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 {

View File

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

View File

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

View File

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

View 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,

View File

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

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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