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

This commit is contained in:
Ilya Laktyushin 2023-01-24 22:29:09 +04:00
commit cc0595de4b
46 changed files with 951 additions and 184 deletions

View File

@ -460,6 +460,7 @@ private struct NotificationContent: CustomStringConvertible {
string += " userInfo: \(String(describing: self.userInfo)),\n"
string += " senderImage: \(self.senderImage != nil ? "non-empty" : "empty"),\n"
string += " isLockedMessage: \(String(describing: self.isLockedMessage)),\n"
string += " attachments: \(self.attachments),\n"
string += "}"
return string
}
@ -1164,7 +1165,13 @@ private final class NotificationServiceHandler {
} else {
let intervals: Signal<[(Range<Int64>, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int64.max, MediaBoxFetchPriority.maximum)])
fetchMediaSignal = Signal { subscriber in
let collectedData = Atomic<Data>(value: Data())
final class DataValue {
var data = Data()
var totalSize: Int64?
}
let collectedData = Atomic<DataValue>(value: DataValue())
return standaloneMultipartFetch(
postbox: stateManager.postbox,
network: stateManager.network,
@ -1191,11 +1198,33 @@ private final class NotificationServiceHandler {
).start(next: { result in
switch result {
case let .dataPart(_, data, _, _):
var isCompleted = false
let _ = collectedData.modify { current in
var current = current
current.append(data)
let current = current
current.data.append(data)
if let totalSize = current.totalSize, Int64(current.data.count) >= totalSize {
isCompleted = true
}
return current
}
if isCompleted {
subscriber.putNext(collectedData.with({ $0.data }))
subscriber.putCompletion()
}
case let .resourceSizeUpdated(size):
var isCompleted = false
let _ = collectedData.modify { current in
let current = current
current.totalSize = size
if Int64(current.data.count) >= size {
isCompleted = true
}
return current
}
if isCompleted {
subscriber.putNext(collectedData.with({ $0.data }))
subscriber.putCompletion()
}
default:
break
}
@ -1203,7 +1232,7 @@ private final class NotificationServiceHandler {
subscriber.putNext(nil)
subscriber.putCompletion()
}, completed: {
subscriber.putNext(collectedData.with({ $0 }))
subscriber.putNext(collectedData.with({ $0.data }))
subscriber.putCompletion()
})
}
@ -1304,9 +1333,14 @@ private final class NotificationServiceHandler {
Logger.shared.log("NotificationService \(episode)", "Unread count: \(value.0), isCurrentAccount: \(isCurrentAccount)")
Logger.shared.log("NotificationService \(episode)", "mediaAttachment: \(String(describing: mediaAttachment)), mediaData: \(String(describing: mediaData?.count))")
if let image = mediaAttachment as? TelegramMediaImage, let resource = largestImageRepresentation(image.representations)?.resource {
if let mediaData = mediaData {
stateManager.postbox.mediaBox.storeResourceData(resource.id, data: mediaData, synchronous: true)
if let messageId {
let _ = addSynchronizeAutosaveItemOperation(postbox: stateManager.postbox, messageId: messageId, mediaId: image.imageId).start()
}
}
if let storedPath = stateManager.postbox.mediaBox.completedResourcePath(resource, pathExtension: "jpg") {
if let attachment = try? UNNotificationAttachment(identifier: "image", url: URL(fileURLWithPath: storedPath), options: nil) {

View File

@ -252,6 +252,15 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
private var overlayColor: (UIColor?, Bool)? = nil
private var size: CGSize?
public var dynamicColor: UIColor? {
didSet {
if let renderer = self.renderer?.renderer as? SoftwareAnimationRenderer {
renderer.renderAsTemplateImage = self.dynamicColor != nil
}
self.renderer?.renderer.view.tintColor = self.dynamicColor
}
}
public init(useMetalCache: Bool = false) {
self.queue = sharedQueue
self.eventsNode = AnimatedStickerNodeDisplayEvents()
@ -279,12 +288,12 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
if #available(iOS 10.0, *) {
return CompressedAnimationRenderer()
} else {
return SoftwareAnimationRenderer()
return SoftwareAnimationRenderer(templateImageSupport: true)
}
})
private static let softwareRendererPool = AnimationRendererPool(generate: {
return SoftwareAnimationRenderer()
return SoftwareAnimationRenderer(templateImageSupport: true)
})
private weak var nodeToCopyFrameFrom: DefaultAnimatedStickerNodeImpl?
@ -295,6 +304,12 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
self.renderer = DefaultAnimatedStickerNodeImpl.hardwareRendererPool.take()
} else {
self.renderer = DefaultAnimatedStickerNodeImpl.softwareRendererPool.take()
if let renderer = self.renderer?.renderer as? SoftwareAnimationRenderer {
renderer.renderAsTemplateImage = self.dynamicColor != nil
}
self.renderer?.renderer.view.tintColor = self.dynamicColor
if let contents = self.nodeToCopyFrameFrom?.renderer?.renderer.contents {
self.renderer?.renderer.contents = contents
}

View File

@ -7,20 +7,30 @@ import YuvConversion
import Accelerate
final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
private let templateImageSupport: Bool
private var highlightedContentNode: ASDisplayNode?
private var highlightedColor: UIColor?
private var highlightReplacesContent = false
public var renderAsTemplateImage: Bool = false
public var currentFrameImage: UIImage? {
if let contents = self.contents {
return UIImage(cgImage: contents as! CGImage)
} else {
return nil
public private(set) var currentFrameImage: UIImage?
init(templateImageSupport: Bool) {
self.templateImageSupport = templateImageSupport
super.init()
if templateImageSupport {
self.setViewBlock({
return UIImageView()
})
}
}
func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void) {
assert(bytesPerRow > 0)
let renderAsTemplateImage = self.renderAsTemplateImage
queue.async { [weak self] in
switch type {
case .argb:
@ -70,13 +80,21 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
break
}
})
if renderAsTemplateImage {
image = image?.withRenderingMode(.alwaysTemplate)
}
}
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
strongSelf.contents = image?.cgImage
strongSelf.currentFrameImage = image
if strongSelf.templateImageSupport {
(strongSelf.view as? UIImageView)?.image = image
} else {
strongSelf.contents = image?.cgImage
}
strongSelf.updateHighlightedContentNode()
if strongSelf.highlightedContentNode?.frame != strongSelf.bounds {
strongSelf.highlightedContentNode?.frame = strongSelf.bounds
@ -90,12 +108,14 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
guard let highlightedContentNode = self.highlightedContentNode, let highlightedColor = self.highlightedColor else {
return
}
if let contents = self.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID {
(highlightedContentNode.view as! UIImageView).image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate)
}
(highlightedContentNode.view as! UIImageView).image = self.currentFrameImage?.withRenderingMode(.alwaysTemplate)
highlightedContentNode.tintColor = highlightedColor
if self.highlightReplacesContent {
self.contents = nil
if self.templateImageSupport {
(self.view as? UIImageView)?.image = nil
} else {
self.contents = nil
}
}
}

View File

@ -136,21 +136,30 @@ public struct Transition {
//view.layer.position = CGPoint(x: frame.midX, y: frame.midY)
view.layer.removeAnimation(forKey: "position")
view.layer.removeAnimation(forKey: "bounds")
view.layer.removeAnimation(forKey: "bounds.size")
completion?(true)
case .curve:
let previousFrame: CGRect
if (view.layer.animation(forKey: "position") != nil || view.layer.animation(forKey: "bounds") != nil), let presentation = view.layer.presentation() {
previousFrame = presentation.frame
let previousPosition: CGPoint
let previousBounds: CGRect
if (view.layer.animation(forKey: "position") != nil || view.layer.animation(forKey: "bounds") != nil || view.layer.animation(forKey: "bounds.size") != nil), let presentation = view.layer.presentation() {
previousPosition = presentation.position
previousBounds = presentation.bounds
} else {
previousFrame = view.frame
previousPosition = view.layer.position
previousBounds = view.layer.bounds
}
view.frame = frame
//view.bounds = CGRect(origin: previousBounds.origin, size: frame.size)
//view.center = CGPoint(x: frame.midX, y: frame.midY)
self.animatePosition(view: view, from: CGPoint(x: previousFrame.midX, y: previousFrame.midY), to: CGPoint(x: frame.midX, y: frame.midY), completion: completion)
self.animateBoundsSize(view: view, from: previousFrame.size, to: frame.size)
let anchorPoint = view.layer.anchorPoint
let updatedPosition = CGPoint(x: frame.minX + frame.width * anchorPoint.x, y: frame.minY + frame.height * anchorPoint.y)
self.animatePosition(view: view, from: previousPosition, to: updatedPosition, completion: completion)
if previousBounds.size != frame.size {
self.animateBoundsSize(view: view, from: previousBounds.size, to: frame.size)
}
}
}
@ -193,10 +202,12 @@ public struct Transition {
case .none:
view.bounds = bounds
view.layer.removeAnimation(forKey: "bounds")
view.layer.removeAnimation(forKey: "bounds.origin")
view.layer.removeAnimation(forKey: "bounds.size")
completion?(true)
case .curve:
let previousBounds: CGRect
if view.layer.animation(forKey: "bounds") != nil, let presentation = view.layer.presentation() {
if (view.layer.animation(forKey: "bounds") != nil || view.layer.animation(forKey: "bounds.origin") != nil || view.layer.animation(forKey: "bounds.size") != nil), let presentation = view.layer.presentation() {
previousBounds = presentation.bounds
} else {
previousBounds = view.layer.bounds
@ -207,6 +218,30 @@ public struct Transition {
}
}
public func setBoundsOrigin(view: UIView, origin: CGPoint, completion: ((Bool) -> Void)? = nil) {
if view.bounds.origin == origin {
completion?(true)
return
}
switch self.animation {
case .none:
view.bounds = CGRect(origin: origin, size: view.bounds.size)
view.layer.removeAnimation(forKey: "bounds")
view.layer.removeAnimation(forKey: "bounds.origin")
completion?(true)
case .curve:
let previousOrigin: CGPoint
if (view.layer.animation(forKey: "bounds") != nil || view.layer.animation(forKey: "bounds.origin") != nil), let presentation = view.layer.presentation() {
previousOrigin = presentation.bounds.origin
} else {
previousOrigin = view.layer.bounds.origin
}
view.bounds = CGRect(origin: origin, size: view.bounds.size)
self.animateBoundsOrigin(view: view, from: previousOrigin, to: origin, completion: completion)
}
}
public func setBoundsSize(view: UIView, size: CGSize, completion: ((Bool) -> Void)? = nil) {
if view.bounds.size == size {
completion?(true)

View File

@ -395,6 +395,12 @@ public final class NavigationButtonNode: ContextControllerSourceNode {
}
}
public func updateManualAlpha(alpha: CGFloat, transition: ContainedViewLayoutTransition) {
for node in self.nodes {
transition.updateAlpha(node: node, alpha: alpha)
}
}
func updateManualText(_ text: String, isBack: Bool = true) {
let node: NavigationButtonItemNode
if self.nodes.count > 0 {

View File

@ -552,7 +552,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
}
}))
]
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in
guard let strongSelf = self else {
return
}
@ -640,7 +640,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
}
}))
]
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in
guard let strongSelf = self else {
return
}

View File

@ -259,7 +259,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
}
})))
}
return .single((itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems)))
return .single((itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, item: item, menu: menuItems)))
}
}
return nil

View File

@ -9,14 +9,15 @@ import StickerResources
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ContextUI
import AccountContext
final class StickerPreviewPeekContent: PeekControllerContent {
let account: Account
let context: AccountContext
let item: ImportStickerPack.Sticker
let menu: [ContextMenuItem]
init(account: Account, item: ImportStickerPack.Sticker, menu: [ContextMenuItem]) {
self.account = account
init(context: AccountContext, item: ImportStickerPack.Sticker, menu: [ContextMenuItem]) {
self.context = context
self.item = item
self.menu = menu
}
@ -34,7 +35,7 @@ final class StickerPreviewPeekContent: PeekControllerContent {
}
func node() -> PeekControllerContentNode & ASDisplayNode {
return StickerPreviewPeekContentNode(account: self.account, item: self.item)
return StickerPreviewPeekContentNode(account: self.context.account, item: self.item)
}
func topAccessoryNode() -> ASDisplayNode? {

View File

@ -169,6 +169,7 @@ private final class SubItemNode: HighlightTrackingButtonNode {
}
} else {
checkNode = CheckNode(theme: CheckNodeTheme(theme: presentationData.theme, style: .plain))
checkNode.isUserInteractionEnabled = false
self.checkNode = checkNode
self.addSubnode(checkNode)
}

View File

@ -325,11 +325,6 @@ private final class TimeBasedCleanupImpl {
}
private func resetScan(general: Int32, shortLived: Int32, gigabytesLimit: Int32) {
if "".isEmpty {
//TODO:remove debugging
return
}
let generalPaths = self.generalPaths
let totalSizeBasedPath = self.totalSizeBasedPath
let shortLivedPaths = self.shortLivedPaths
@ -362,27 +357,30 @@ private final class TimeBasedCleanupImpl {
//#endif
var totalApproximateSize: Int64 = 0
for path in shortLivedPaths {
totalApproximateSize += statForDirectory(path: path)
}
for path in generalPaths {
totalApproximateSize += statForDirectory(path: path)
}
if gigabytesLimit < Int32.max {
for path in shortLivedPaths {
totalApproximateSize += statForDirectory(path: path)
}
for path in generalPaths {
totalApproximateSize += statForDirectory(path: path)
}
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: totalSizeBasedPath), includingPropertiesForKeys: [.fileSizeKey, .fileResourceIdentifierKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
var fileIds = Set<Data>()
loop: for url in enumerator {
guard let url = url as? URL else {
continue
}
if let fileId = (try? url.resourceValues(forKeys: Set([.fileResourceIdentifierKey])))?.fileResourceIdentifier as? Data {
if fileIds.contains(fileId) {
continue loop
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: totalSizeBasedPath), includingPropertiesForKeys: [.fileSizeKey, .fileResourceIdentifierKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
var fileIds = Set<Data>()
loop: for url in enumerator {
guard let url = url as? URL else {
continue
}
if let fileId = (try? url.resourceValues(forKeys: Set([.fileResourceIdentifierKey])))?.fileResourceIdentifier as? Data {
if fileIds.contains(fileId) {
continue loop
}
if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 {
fileIds.insert(fileId)
totalApproximateSize += Int64(value)
if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 {
fileIds.insert(fileId)
totalApproximateSize += Int64(value)
}
}
}
}
@ -405,15 +403,18 @@ private final class TimeBasedCleanupImpl {
var totalLimitSize: UInt64 = 0
for path in generalPaths {
let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase)
if !paths.contains(path) {
paths.append(path)
if general < Int32.max {
for path in generalPaths {
let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase)
if !paths.contains(path) {
paths.append(path)
}
removedGeneralCount += scanResult.unlinkedCount
totalLimitSize += scanResult.totalSize
}
removedGeneralCount += scanResult.unlinkedCount
totalLimitSize += scanResult.totalSize
}
do {
if gigabytesLimit < Int32.max {
let scanResult = scanFiles(at: totalSizeBasedPath, olderThan: 0, includeSubdirectories: false, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase)
if !paths.contains(totalSizeBasedPath) {
paths.append(totalSizeBasedPath)

View File

@ -460,7 +460,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
} else {
strongSelf.stableEmptyResultEmoji = nil
}
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults, searchState: .active)
} else {
strongSelf.stableEmptyResultEmoji = nil
}

View File

@ -12,14 +12,22 @@ import AccountContext
public func autodownloadDataSizeString(_ size: Int64, decimalSeparator: String = ".") -> String {
if size >= 1024 * 1024 * 1024 {
let remainder = (size % (1024 * 1024 * 1024)) / (1024 * 1024 * 102)
var remainder = (size % (1024 * 1024 * 1024)) / (1024 * 1024 * 102)
while remainder != 0 && remainder % 10 == 0 {
remainder /= 10
}
if remainder != 0 {
return "\(size / (1024 * 1024 * 1024))\(decimalSeparator)\(remainder) GB"
} else {
return "\(size / (1024 * 1024 * 1024)) GB"
}
} else if size >= 1024 * 1024 {
let remainder = (size % (1024 * 1024)) / (1024 * 102)
var remainder = (size % (1024 * 1024)) / (1024 * 102)
while remainder != 0 && remainder % 10 == 0 {
remainder /= 10
}
if size < 10 * 1024 * 1024 {
return "\(size / (1024 * 1024))\(decimalSeparator)\(remainder) MB"
} else {

View File

@ -239,7 +239,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
}
})))
}
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: {
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: {
}))
} else {

View File

@ -481,7 +481,7 @@ private final class StickerPackContainer: ASDisplayNode {
}
})))
}
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in
guard let strongSelf = self else {
return
}

View File

@ -29,7 +29,7 @@ public enum StickerPreviewPeekItem: Equatable {
}
public final class StickerPreviewPeekContent: PeekControllerContent {
let account: Account
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
public let item: StickerPreviewPeekItem
@ -37,8 +37,8 @@ public final class StickerPreviewPeekContent: PeekControllerContent {
let menu: [ContextMenuItem]
let openPremiumIntro: () -> Void
public init(account: Account, theme: PresentationTheme, strings: PresentationStrings, item: StickerPreviewPeekItem, isLocked: Bool = false, menu: [ContextMenuItem], openPremiumIntro: @escaping () -> Void) {
self.account = account
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, item: StickerPreviewPeekItem, isLocked: Bool = false, menu: [ContextMenuItem], openPremiumIntro: @escaping () -> Void) {
self.context = context
self.theme = theme
self.strings = strings
self.item = item
@ -64,7 +64,7 @@ public final class StickerPreviewPeekContent: PeekControllerContent {
}
public func node() -> PeekControllerContentNode & ASDisplayNode {
return StickerPreviewPeekContentNode(account: self.account, item: self.item)
return StickerPreviewPeekContentNode(context: self.context, item: self.item, theme: self.theme)
}
public func topAccessoryNode() -> ASDisplayNode? {
@ -91,7 +91,7 @@ public final class StickerPreviewPeekContent: PeekControllerContent {
}
public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerContentNode {
private let account: Account
private let context: AccountContext
private let item: StickerPreviewPeekItem
private var textNode: ASTextNode
@ -105,8 +105,8 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
private let _ready = Promise<Bool>()
init(account: Account, item: StickerPreviewPeekItem) {
self.account = account
init(context: AccountContext, item: StickerPreviewPeekItem, theme: PresentationTheme) {
self.context = context
self.item = item
self.textNode = ASTextNode()
@ -133,14 +133,18 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
}
let fittedDimensions = dimensions.cgSize.aspectFitted(fitSize)
animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource, isVideo: item.file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: isPremiumSticker ? .once : .loop, mode: .direct(cachePathPrefix: nil))
if item.file.isCustomTemplateEmoji {
animationNode.dynamicColor = theme.list.itemPrimaryTextColor
}
animationNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: item.file.resource, isVideo: item.file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: isPremiumSticker ? .once : .loop, mode: .direct(cachePathPrefix: nil))
animationNode.visibility = true
animationNode.addSubnode(self.textNode)
if isPremiumSticker, let effect = item.file.videoThumbnails.first {
self.effectDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, userLocation: .other, fileReference: .standalone(media: item.file), resource: effect.resource).start())
self.effectDisposable.set(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: item.file), resource: effect.resource).start())
let source = AnimatedStickerResourceSource(account: account, resource: effect.resource, fitzModifier: nil)
let source = AnimatedStickerResourceSource(account: context.account, resource: effect.resource, fitzModifier: nil)
let additionalAnimationNode = DefaultAnimatedStickerNodeImpl()
additionalAnimationNode.setup(source: source, width: Int(fittedDimensions.width * 2.0), height: Int(fittedDimensions.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
additionalAnimationNode.visibility = true
@ -151,7 +155,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
self.animationNode = nil
}
self.imageNode.setSignal(chatMessageSticker(account: account, userLocation: .other, file: item.file, small: false, fetched: true))
self.imageNode.setSignal(chatMessageSticker(account: context.account, userLocation: .other, file: item.file, small: false, fetched: true))
super.init()

View File

@ -193,6 +193,7 @@ private var declaredEncodables: Void = {
declareEncodable(TelegramPeerUsername.self, f: { TelegramPeerUsername(decoder: $0) })
declareEncodable(MediaSpoilerMessageAttribute.self, f: { MediaSpoilerMessageAttribute(decoder: $0) })
declareEncodable(TranslationMessageAttribute.self, f: { TranslationMessageAttribute(decoder: $0) })
declareEncodable(SynchronizeAutosaveItemOperation.self, f: { SynchronizeAutosaveItemOperation(decoder: $0) })
return
}()

View File

@ -146,7 +146,25 @@ func managedSynchronizeEmojiSearchCategories(postbox: Postbox, network: Network,
break
}
return .complete()
var fileIds: [Int64] = []
if let cached = _internal_cachedEmojiSearchCategories(transaction: transaction, kind: kind) {
for group in cached.groups {
fileIds.append(group.id)
}
}
return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: fileIds)
|> mapToSignal { files -> Signal<Never, NoError> in
var fetchSignals: Signal<Never, NoError> = .complete()
for (_, file) in files {
let signal = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .other, reference: .standalone(resource: file.resource))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
fetchSignals = fetchSignals |> then(signal)
}
return fetchSignals
}
}
|> switchToLatest
}

View File

@ -0,0 +1,78 @@
import Foundation
import Postbox
import SwiftSignalKit
public final class SynchronizeAutosaveItemOperation: PostboxCoding {
public struct Content: Codable {
public var messageId: MessageId
public var mediaId: MediaId
public init(messageId: MessageId, mediaId: MediaId) {
self.messageId = messageId
self.mediaId = mediaId
}
}
public let messageId: MessageId
public let mediaId: MediaId
public init(messageId: MessageId, mediaId: MediaId) {
self.messageId = messageId
self.mediaId = mediaId
}
public init(decoder: PostboxDecoder) {
if let content = decoder.decode(Content.self, forKey: "c") {
self.messageId = content.messageId
self.mediaId = content.mediaId
} else {
self.messageId = MessageId(peerId: PeerId(0), namespace: 0, id: 0)
self.mediaId = MediaId(namespace: 0, id: 0)
}
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encode(Content(messageId: self.messageId, mediaId: self.mediaId), forKey: "c")
}
}
public func addSynchronizeAutosaveItemOperation(transaction: Transaction, messageId: MessageId, mediaId: MediaId) {
let tag: PeerOperationLogTag = OperationLogTags.SynchronizeAutosaveItems
let peerId = PeerId(0)
transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeAutosaveItemOperation(messageId: messageId, mediaId: mediaId))
}
public func addSynchronizeAutosaveItemOperation(postbox: Postbox, messageId: MessageId, mediaId: MediaId) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Void in
addSynchronizeAutosaveItemOperation(transaction: transaction, messageId: messageId, mediaId: mediaId)
}
|> ignoreValues
}
public func _internal_getSynchronizeAutosaveItemOperations(transaction: Transaction) -> [(index: Int32, message: Message, mediaId: MediaId)] {
let peerId = PeerId(0)
var result: [(index: Int32, message: Message, mediaId: MediaId)] = []
var removeIndices: [Int32] = []
transaction.operationLogEnumerateEntries(peerId: peerId, tag: OperationLogTags.SynchronizeAutosaveItems, { entry in
if let operation = entry.contents as? SynchronizeAutosaveItemOperation {
if let message = transaction.getMessage(operation.messageId) {
result.append((index: entry.tagLocalIndex, message: message, mediaId: operation.mediaId))
} else {
removeIndices.append(entry.tagLocalIndex)
}
}
return true
})
for index in removeIndices {
let _ = transaction.operationLogRemoveEntry(peerId: PeerId(0), tag: OperationLogTags.SynchronizeAutosaveItems, tagLocalIndex: index)
}
return result
}
public func _internal_removeSyncrhonizeAutosaveItemOperations(transaction: Transaction, indices: [Int32]) {
for index in indices {
let _ = transaction.operationLogRemoveEntry(peerId: PeerId(0), tag: OperationLogTags.SynchronizeAutosaveItems, tagLocalIndex: index)
}
}

View File

@ -180,6 +180,7 @@ public struct OperationLogTags {
public static let SynchronizeChatListFilters = PeerOperationLogTag(value: 20)
public static let SynchronizeMarkAllUnseenReactions = PeerOperationLogTag(value: 21)
public static let SynchronizeInstalledEmoji = PeerOperationLogTag(value: 22)
public static let SynchronizeAutosaveItems = PeerOperationLogTag(value: 23)
}
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {

View File

@ -633,6 +633,9 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
if case .Sticker = attribute {
hasSticker = true
break
} else if case .CustomEmoji = attribute {
hasSticker = true
break
}
}
return hasSticker

View File

@ -507,5 +507,17 @@ public extension TelegramEngine {
return managedSynchronizeMessageHistoryTagSummaries(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, threadId: threadId)
|> ignoreValues
}
public func getSynchronizeAutosaveItemOperations() -> Signal<[(index: Int32, message: Message, mediaId: MediaId)], NoError> {
return self.account.postbox.transaction { transaction -> [(index: Int32, message: Message, mediaId: MediaId)] in
return _internal_getSynchronizeAutosaveItemOperations(transaction: transaction)
}
}
func removeSyncrhonizeAutosaveItemOperations(indices: [Int32]) {
let _ = (self.account.postbox.transaction { transaction -> Void in
_internal_removeSyncrhonizeAutosaveItemOperations(transaction: transaction, indices: indices)
}).start()
}
}
}

View File

@ -850,9 +850,9 @@ final class AvatarEditorScreenComponent: Component {
}
if state?.keyboardContentId == AnyHashable("emoji") {
data.emoji = data.emoji.withUpdatedItemGroups(panelItemGroups: data.emoji.panelItemGroups, contentItemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults)
data.emoji = data.emoji.withUpdatedItemGroups(panelItemGroups: data.emoji.panelItemGroups, contentItemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults, searchState: .active)
} else {
data.stickers = data.stickers?.withUpdatedItemGroups(panelItemGroups: data.stickers?.panelItemGroups ?? searchResult.groups, contentItemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults)
data.stickers = data.stickers?.withUpdatedItemGroups(panelItemGroups: data.stickers?.panelItemGroups ?? searchResult.groups, contentItemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults, searchState: .active)
}
}

View File

@ -608,7 +608,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
strongSelf.currentUndoOverlayController = controller
controllerInteraction.presentController(controller, nil)
},
copyEmoji: { file in
copyEmoji: { [weak self] file in
guard let strongSelf = self else {
return
}
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
loop: for attribute in file.attributes {
@ -625,6 +629,20 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
if let _ = emojiAttribute {
storeMessageTextInPasteboard(text, entities: [MessageTextEntity(range: 0 ..< (text as NSString).length, type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id))])
var animateInAsReplacement = false
if let currentUndoOverlayController = strongSelf.currentUndoOverlayController {
currentUndoOverlayController.dismissWithCommitActionAndReplacementAnimation()
strongSelf.currentUndoOverlayController = nil
animateInAsReplacement = true
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
let controller = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: "Emoji copied to clipboard.", undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return false })
strongSelf.currentUndoOverlayController = controller
controllerInteraction.presentController(controller, nil)
}
},
presentController: controllerInteraction.presentController,
@ -1425,7 +1443,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
)
}
if let emoji = inputData.emoji {
inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults, searchState: .active)
}
}
@ -1439,7 +1457,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
)
}
if let stickers = inputData.stickers {
inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: stickerSearchResult.id, emptySearchResults: stickerSearchResults)
inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: stickerSearchResult.id, emptySearchResults: stickerSearchResults, searchState: .active)
}
}
@ -1861,10 +1879,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
private func processInputData(inputData: InputData) -> InputData {
return InputData(
emoji: inputData.emoji.flatMap { emoji in
return emoji.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: emoji.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: emoji.contentItemGroups), itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults)
return emoji.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: emoji.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: emoji.contentItemGroups), itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults, searchState: emoji.searchState)
},
stickers: inputData.stickers.flatMap { stickers in
return stickers.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.contentItemGroups), itemContentUniqueId: stickers.itemContentUniqueId, emptySearchResults: nil)
return stickers.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.contentItemGroups), itemContentUniqueId: stickers.itemContentUniqueId, emptySearchResults: nil, searchState: stickers.searchState)
},
gifs: inputData.gifs,
availableGifSearchEmojies: inputData.availableGifSearchEmojies
@ -2503,7 +2521,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior {
return nil
}
return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: {
return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: {
guard let strongSelf = self, let interaction = strongSelf.interaction else {
return
}
@ -2632,7 +2650,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior {
return nil
}
return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked && !isStarred, menu: menuItems, openPremiumIntro: {
return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked && !isStarred, menu: menuItems, openPremiumIntro: {
guard let strongSelf = self, let interaction = strongSelf.interaction else {
return
}

View File

@ -371,7 +371,7 @@ public final class EmojiStatusSelectionController: ViewController {
} else {
strongSelf.stableEmptyResultEmoji = nil
}
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults, searchState: .active)
} else {
strongSelf.stableEmptyResultEmoji = nil
}

View File

@ -233,6 +233,31 @@ public final class EmojiSuggestionsComponent: Component {
fatalError("init(coder:) has not been implemented")
}
public func item(at point: CGPoint) -> (CALayer, TelegramMediaFile)? {
let location = self.convert(point, to: self.scrollView)
if self.scrollView.bounds.contains(location) {
var closestFile: (file: TelegramMediaFile, layer: CALayer, distance: CGFloat)?
for (_, itemLayer) in self.visibleLayers {
guard let file = itemLayer.file else {
continue
}
let distance = abs(location.x - itemLayer.position.x)
if let (_, _, currentDistance) = closestFile {
if distance < currentDistance {
closestFile = (file, itemLayer, distance)
}
} else {
closestFile = (file, itemLayer, distance)
}
}
if let (file, itemLayer, _) = closestFile {
return (itemLayer, file)
}
}
return nil
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
let location = recognizer.location(in: self.scrollView)

View File

@ -1330,6 +1330,15 @@ private final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, Pager
self.layer.addSublayer(itemLayer)
}
switch item.tintMode {
case .accent:
itemLayer.layerTintColor = theme.list.itemAccentColor.cgColor
case .primary:
itemLayer.layerTintColor = theme.list.itemPrimaryTextColor.cgColor
case .none:
itemLayer.layerTintColor = nil
}
let itemFrame = itemLayout.frame(at: index)
itemLayer.frame = itemFrame
@ -1708,17 +1717,24 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
if case .ended = recognizer.state {
let location = recognizer.location(in: self)
if self.backIconView.frame.contains(location) {
if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
placeholderContentView.clearSelection(dispatchEvent : true)
}
self.clearCategorySearch()
} else {
self.activateTextInput()
}
}
}
func clearCategorySearch() {
if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
placeholderContentView.clearSelection(dispatchEvent : true)
}
}
private func activateTextInput() {
if self.textField == nil, let textFrame = self.textFrame, self.params?.canFocus == true {
guard let params = self.params else {
return
}
if self.textField == nil, let textFrame = self.textFrame, params.canFocus == true {
let backgroundFrame = self.backgroundLayer.frame
let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height))
@ -1730,9 +1746,11 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
}
self.currentPresetSearchTerm = nil
if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
placeholderContentView.clearSelection(dispatchEvent: false)
if params.canFocus {
self.currentPresetSearchTerm = nil
if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
placeholderContentView.clearSelection(dispatchEvent: false)
}
}
self.activated(true)
@ -1968,9 +1986,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
self.currentPresetSearchTerm = term
if shouldChangeActivation {
self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
if let term {
self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
self.updateQuery(.category(value: term))
self.activated(false)
} else {
@ -2049,6 +2067,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
cancelButtonTitleComponentView.isUserInteractionEnabled = false
}
transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize))
transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0)
}
if let cancelButtonTintTitleComponentView = self.cancelButtonTintTitle.view {
if cancelButtonTintTitleComponentView.superview == nil {
@ -2056,6 +2075,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
cancelButtonTintTitleComponentView.isUserInteractionEnabled = false
}
transition.setFrame(view: cancelButtonTintTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize))
transition.setAlpha(view: cancelButtonTintTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0)
}
var hasText = false
@ -2103,9 +2123,9 @@ private final class EmptySearchResultsView: UIView {
func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, searchInitiallyHidden: Bool, transition: Transition) {
let titleColor: UIColor
if useOpaqueTheme {
titleColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor
titleColor = theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor
} else {
titleColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor
titleColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
}
let iconSize: CGSize
@ -2513,6 +2533,12 @@ public final class EmojiPagerContentComponent: Component {
case detailed
}
public enum SearchState {
case empty
case searching
case active
}
public final class EmptySearchResults: Equatable {
public let text: String
public let iconFile: TelegramMediaFile?
@ -2543,6 +2569,7 @@ public final class EmojiPagerContentComponent: Component {
public let contentItemGroups: [ItemGroup]
public let itemLayoutType: ItemLayoutType
public let itemContentUniqueId: AnyHashable?
public let searchState: SearchState
public let warpContentsOnEdges: Bool
public let displaySearchWithPlaceholder: String?
public let searchCategories: EmojiSearchCategories?
@ -2564,6 +2591,7 @@ public final class EmojiPagerContentComponent: Component {
contentItemGroups: [ItemGroup],
itemLayoutType: ItemLayoutType,
itemContentUniqueId: AnyHashable?,
searchState: SearchState,
warpContentsOnEdges: Bool,
displaySearchWithPlaceholder: String?,
searchCategories: EmojiSearchCategories?,
@ -2584,6 +2612,7 @@ public final class EmojiPagerContentComponent: Component {
self.contentItemGroups = contentItemGroups
self.itemLayoutType = itemLayoutType
self.itemContentUniqueId = itemContentUniqueId
self.searchState = searchState
self.warpContentsOnEdges = warpContentsOnEdges
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
self.searchCategories = searchCategories
@ -2595,7 +2624,7 @@ public final class EmojiPagerContentComponent: Component {
self.selectedItems = selectedItems
}
public func withUpdatedItemGroups(panelItemGroups: [ItemGroup], contentItemGroups: [ItemGroup], itemContentUniqueId: AnyHashable?, emptySearchResults: EmptySearchResults?) -> EmojiPagerContentComponent {
public func withUpdatedItemGroups(panelItemGroups: [ItemGroup], contentItemGroups: [ItemGroup], itemContentUniqueId: AnyHashable?, emptySearchResults: EmptySearchResults?, searchState: SearchState) -> EmojiPagerContentComponent {
return EmojiPagerContentComponent(
id: self.id,
context: self.context,
@ -2607,6 +2636,7 @@ public final class EmojiPagerContentComponent: Component {
contentItemGroups: contentItemGroups,
itemLayoutType: self.itemLayoutType,
itemContentUniqueId: itemContentUniqueId,
searchState: searchState,
warpContentsOnEdges: self.warpContentsOnEdges,
displaySearchWithPlaceholder: self.displaySearchWithPlaceholder,
searchCategories: self.searchCategories,
@ -2650,6 +2680,12 @@ public final class EmojiPagerContentComponent: Component {
if lhs.itemLayoutType != rhs.itemLayoutType {
return false
}
if lhs.itemContentUniqueId != rhs.itemContentUniqueId {
return false
}
if lhs.searchState != rhs.searchState {
return false
}
if lhs.warpContentsOnEdges != rhs.warpContentsOnEdges {
return false
}
@ -2662,6 +2698,9 @@ public final class EmojiPagerContentComponent: Component {
if lhs.searchInitiallyHidden != rhs.searchInitiallyHidden {
return false
}
if lhs.searchAlwaysActive != rhs.searchAlwaysActive {
return false
}
if lhs.searchIsPlaceholderOnly != rhs.searchIsPlaceholderOnly {
return false
}
@ -4313,6 +4352,12 @@ public final class EmojiPagerContentComponent: Component {
guard let component = self.component, let pagerEnvironment = self.pagerEnvironment, let itemLayout = self.itemLayout else {
return
}
if self.isSearchActivated {
self.visibleSearchHeader?.clearCategorySearch()
return
}
for groupIndex in 0 ..< itemLayout.itemGroupLayouts.count {
let group = itemLayout.itemGroupLayouts[groupIndex]
@ -5044,7 +5089,7 @@ public final class EmojiPagerContentComponent: Component {
scrollView.layer.removeAllAnimations()
}
if self.isSearchActivated, let component = self.component, !component.searchAlwaysActive, let visibleSearchHeader = self.visibleSearchHeader, visibleSearchHeader.currentPresetSearchTerm == nil {
if self.isSearchActivated, let component = self.component, component.searchState == .empty, !component.searchAlwaysActive, let visibleSearchHeader = self.visibleSearchHeader, visibleSearchHeader.currentPresetSearchTerm == nil {
scrollView.isScrollEnabled = false
DispatchQueue.main.async {
scrollView.isScrollEnabled = true
@ -6024,6 +6069,9 @@ public final class EmojiPagerContentComponent: Component {
guard let strongSelf = self else {
return nil
}
if !strongSelf.scrollViewClippingView.bounds.contains(strongSelf.convert(point, to: strongSelf.scrollViewClippingView)) {
return nil
}
guard let item = strongSelf.item(atPoint: point), let itemLayer = strongSelf.visibleItemLayers[item.1], let file = item.0.itemFile else {
return nil
}
@ -6459,12 +6507,15 @@ public final class EmojiPagerContentComponent: Component {
strongSelf.isSearchActivated = false
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(false)
if strongSelf.component?.searchInitiallyHidden == false {
if !isFirstResponder {
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.easeInOut(duration: 0.2))
}
if !isFirstResponder {
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(
Transition(animation: .curve(duration: 0.4, curve: .spring)))
} else {
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
DispatchQueue.main.async {
self?.component?.inputInteractionHolder.inputInteraction?.requestUpdate(
Transition(animation: .curve(duration: 0.4, curve: .spring)))
}
}
}
}, updateQuery: { [weak self] query in
@ -6485,17 +6536,19 @@ public final class EmojiPagerContentComponent: Component {
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(context: component.context, theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, searchCategories: component.searchCategories, transition: transition)
/*transition.attachAnimation(view: visibleSearchHeader, id: "search_transition", completion: { [weak self] completed in
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
return
}
if !useOpaqueTheme {
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame)
transition.attachAnimation(view: visibleSearchHeader, id: "search_transition", completion: { [weak self] completed in
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
return
}
if !strongSelf.isSearchActivated && visibleSearchHeader.superview != strongSelf.scrollView {
strongSelf.scrollView.addSubview(visibleSearchHeader)
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
}
})*/
if visibleSearchHeader.frame != searchHeaderFrame {
if !strongSelf.isSearchActivated && visibleSearchHeader.superview != strongSelf.scrollView {
strongSelf.scrollView.addSubview(visibleSearchHeader)
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
}
})
} else {
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
if !useOpaqueTheme {
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
@ -6548,7 +6601,11 @@ public final class EmojiPagerContentComponent: Component {
visibleEmptySearchResultsView = EmptySearchResultsView(frame: CGRect())
self.visibleEmptySearchResultsView = visibleEmptySearchResultsView
self.addSubview(visibleEmptySearchResultsView)
self.mirrorContentClippingView?.addSubview(visibleEmptySearchResultsView.tintContainerView)
if let mirrorContentClippingView = self.mirrorContentClippingView {
mirrorContentClippingView.addSubview(visibleEmptySearchResultsView.tintContainerView)
} else if let vibrancyEffectView = self.vibrancyEffectView {
vibrancyEffectView.contentView.addSubview(visibleEmptySearchResultsView.tintContainerView)
}
}
let emptySearchResultsSize = CGSize(width: availableSize.width, height: availableSize.height - itemLayout.searchInsets.top - itemLayout.searchHeight)
visibleEmptySearchResultsView.update(
@ -6575,23 +6632,97 @@ public final class EmojiPagerContentComponent: Component {
animateContentCrossfade = true
}
let crossfadeMinScale: CGFloat = 0.4
if animateContentCrossfade {
/*for (_, itemLayer) in self.visibleItemLayers {
for (_, itemLayer) in self.visibleItemLayers {
if let snapshotLayer = itemLayer.snapshotContentTree() {
itemLayer.superlayer?.insertSublayer(snapshotLayer, above: itemLayer)
snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
snapshotLayer?.removeFromSuperlayer()
})
snapshotLayer.animateScale(from: 1.0, to: crossfadeMinScale, duration: 0.2, removeOnCompletion: false)
}
}*/
}
for (_, placeholderView) in self.visibleItemPlaceholderViews {
if let snapshotLayer = placeholderView.layer.snapshotContentTree() {
placeholderView.layer.superlayer?.insertSublayer(snapshotLayer, above: placeholderView.layer)
snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
snapshotLayer?.removeFromSuperlayer()
})
snapshotLayer.animateScale(from: 1.0, to: crossfadeMinScale, duration: 0.2, removeOnCompletion: false)
}
}
for (_, selectionLayer) in self.visibleItemSelectionLayers {
if let snapshotLayer = selectionLayer.snapshotContentTree() {
selectionLayer.superlayer?.insertSublayer(snapshotLayer, above: selectionLayer)
snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
snapshotLayer?.removeFromSuperlayer()
})
}
}
for (_, groupHeader) in self.visibleGroupHeaders {
if let snapshotLayer = groupHeader.layer.snapshotContentTree() {
groupHeader.layer.superlayer?.insertSublayer(snapshotLayer, above: groupHeader.layer)
snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
snapshotLayer?.removeFromSuperlayer()
})
}
}
for (_, borderLayer) in self.visibleGroupBorders {
if let snapshotLayer = borderLayer.snapshotContentTree() {
borderLayer.superlayer?.insertSublayer(snapshotLayer, above: borderLayer)
snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
snapshotLayer?.removeFromSuperlayer()
})
}
}
for (_, button) in self.visibleGroupPremiumButtons {
if let buttonView = button.view, let snapshotLayer = buttonView.layer.snapshotContentTree() {
buttonView.layer.superlayer?.insertSublayer(snapshotLayer, above: buttonView.layer)
snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
snapshotLayer?.removeFromSuperlayer()
})
}
}
for (_, button) in self.visibleGroupExpandActionButtons {
if let snapshotLayer = button.layer.snapshotContentTree() {
button.layer.superlayer?.insertSublayer(snapshotLayer, above: button.layer)
snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
snapshotLayer?.removeFromSuperlayer()
})
}
}
}
self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: attemptSynchronousLoads, previousItemPositions: previousItemPositions, previousAbsoluteItemPositions: previousAbsoluteItemPositions, updatedItemPositions: updatedItemPositions, hintDisappearingGroupFrame: hintDisappearingGroupFrame)
if animateContentCrossfade {
/*for (_, itemLayer) in self.visibleItemLayers {
for (_, itemLayer) in self.visibleItemLayers {
itemLayer.animateAlpha(from: 0.0, to: CGFloat(itemLayer.opacity), duration: 0.2)
}*/
itemLayer.animateScale(from: crossfadeMinScale, to: 1.0, duration: 0.2)
}
for (_, placeholderView) in self.visibleItemPlaceholderViews {
placeholderView.layer.animateAlpha(from: 0.0, to: CGFloat(placeholderView.layer.opacity), duration: 0.2)
placeholderView.layer.animateScale(from: crossfadeMinScale, to: 1.0, duration: 0.2)
}
for (_, selectionLayer) in self.visibleItemSelectionLayers {
selectionLayer.animateAlpha(from: 0.0, to: CGFloat(selectionLayer.opacity), duration: 0.2)
}
for (_, groupHeader) in self.visibleGroupHeaders {
groupHeader.layer.animateAlpha(from: 0.0, to: CGFloat(groupHeader.layer.opacity), duration: 0.2)
}
for (_, borderLayer) in self.visibleGroupBorders {
borderLayer.animateAlpha(from: 0.0, to: CGFloat(borderLayer.opacity), duration: 0.2)
}
for (_, button) in self.visibleGroupPremiumButtons {
if let buttonView = button.view {
buttonView.layer.animateAlpha(from: 0.0, to: CGFloat(buttonView.layer.opacity), duration: 0.2)
}
}
for (_, button) in self.visibleGroupExpandActionButtons {
button.layer.animateAlpha(from: 0.0, to: CGFloat(button.layer.opacity), duration: 0.2)
}
}
return availableSize
@ -7548,6 +7679,7 @@ public final class EmojiPagerContentComponent: Component {
contentItemGroups: allItemGroups,
itemLayoutType: .compact,
itemContentUniqueId: nil,
searchState: .empty,
warpContentsOnEdges: isReactionSelection || isStatusSelection || isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection,
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
searchCategories: searchCategories,
@ -8069,6 +8201,7 @@ public final class EmojiPagerContentComponent: Component {
contentItemGroups: allItemGroups,
itemLayoutType: .detailed,
itemContentUniqueId: nil,
searchState: .empty,
warpContentsOnEdges: isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection,
displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil,
searchCategories: searchCategories,

View File

@ -391,6 +391,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
contentItemGroups: self.itemGroups,
itemLayoutType: .compact,
itemContentUniqueId: "main",
searchState: .empty,
warpContentsOnEdges: false,
displaySearchWithPlaceholder: "Search Emoji",
searchCategories: nil,
@ -410,7 +411,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
iconFile: nil
)
}
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults, searchState: .active)
}
let _ = self.keyboardView.update(

View File

@ -15,6 +15,7 @@ import LottieAnimationComponent
import EmojiStatusComponent
import LottieComponent
import LottieComponentEmojiContent
import AudioToolbox
private final class RoundMaskView: UIImageView {
private var currentDiameter: CGFloat?
@ -130,9 +131,16 @@ final class EmojiSearchSearchBarComponent: Component {
self.textFrame = CGRect(origin: CGPoint(x: self.leftInset, y: floor((containerSize.height - textSize.height) * 0.5)), size: textSize)
self.itemStartX = self.textFrame.maxX + self.textSpacing
let itemsWidth: CGFloat = self.itemSize.width * CGFloat(self.itemCount) + self.itemSpacing * CGFloat(max(0, self.itemCount - 1))
self.contentSize = CGSize(width: self.itemStartX + self.itemSize.width * CGFloat(self.itemCount) + self.itemSpacing * CGFloat(max(0, self.itemCount - 1)) + self.rightInset, height: containerSize.height)
var itemStartX = self.textFrame.maxX + self.textSpacing
if itemStartX + itemsWidth + self.rightInset < containerSize.width {
itemStartX = containerSize.width - self.rightInset - itemsWidth
}
self.itemStartX = itemStartX
self.contentSize = CGSize(width: self.itemStartX + itemsWidth + self.rightInset, height: containerSize.height)
}
func visibleItems(for rect: CGRect) -> Range<Int>? {
@ -214,6 +222,10 @@ final class EmojiSearchSearchBarComponent: Component {
private var selectedItem: AnyHashable?
private lazy var hapticFeedback: HapticFeedback = {
return HapticFeedback()
}()
override init(frame: CGRect) {
self.tintContainerView = UIView()
@ -281,6 +293,8 @@ final class EmojiSearchSearchBarComponent: Component {
self.selectedItem = nil
} else {
self.selectedItem = AnyHashable(id)
AudioServicesPlaySystemSound(0x450)
self.hapticFeedback.tap()
}
self.state?.updated(transition: .immediate)
@ -303,7 +317,7 @@ final class EmojiSearchSearchBarComponent: Component {
}
} else {
let transition = Transition(animation: .curve(duration: 0.4, curve: .spring))
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(), size: self.scrollView.bounds.size))
transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint())
self.updateScrolling(transition: transition, fromScrolling: false)
//self.scrollView.setContentOffset(CGPoint(), animated: true)
@ -322,7 +336,7 @@ final class EmojiSearchSearchBarComponent: Component {
self.selectedItem = nil
let transition = Transition(animation: .curve(duration: 0.4, curve: .spring))
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(), size: self.scrollView.bounds.size))
transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint())
self.updateScrolling(transition: transition, fromScrolling: false)
self.state?.updated(transition: transition)
@ -537,7 +551,7 @@ final class EmojiSearchSearchBarComponent: Component {
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize))
}
if case .active = component.textInputState {
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(), size: availableSize))
transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint())
}
if self.scrollView.contentSize != itemLayout.contentSize {
self.scrollView.contentSize = itemLayout.contentSize

View File

@ -98,7 +98,15 @@ private class GifVideoLayer: AVSampleBufferDisplayLayer {
}
override init(layer: Any) {
preconditionFailure()
guard let layer = layer as? GifVideoLayer else {
preconditionFailure()
}
self.context = layer.context
self.userLocation = layer.userLocation
self.file = layer.file
super.init(layer: layer)
}
required init?(coder: NSCoder) {
@ -375,6 +383,13 @@ public final class GifPagerContentComponent: Component {
}
}
override init(layer: Any) {
self.item = nil
self.onUpdateDisplayPlaceholder = { _, _ in }
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

View File

@ -106,7 +106,7 @@ public final class LottieComponent: Component {
if delay != 0.0 {
self.isHidden = true
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.08, execute: { [weak self] in
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay, execute: { [weak self] in
guard let self else {
return
}

View File

@ -43,13 +43,13 @@ final class DataCategoriesComponent: Component {
let theme: PresentationTheme
let strings: PresentationStrings
let categories: [CategoryData]
let toggleCategoryExpanded: (DataUsageScreenComponent.Category) -> Void
let toggleCategoryExpanded: ((DataUsageScreenComponent.Category) -> Void)?
init(
theme: PresentationTheme,
strings: PresentationStrings,
categories: [CategoryData],
toggleCategoryExpanded: @escaping (DataUsageScreenComponent.Category) -> Void
toggleCategoryExpanded: ((DataUsageScreenComponent.Category) -> Void)?
) {
self.theme = theme
self.strings = strings
@ -138,11 +138,11 @@ final class DataCategoriesComponent: Component {
category: category,
isExpanded: category.isExpanded,
hasNext: i != component.categories.count - 1,
action: { [weak self] key in
action: component.toggleCategoryExpanded == nil ? nil : { [weak self] key in
guard let self, let component = self.component else {
return
}
component.toggleCategoryExpanded(key)
component.toggleCategoryExpanded?(key)
}
)),
environment: {},

View File

@ -52,7 +52,7 @@ private final class SubItemComponent: Component {
return true
}
class View: HighlightTrackingButton {
class View: UIView {
private let iconView: UIImageView
private let title = ComponentView<Empty>()
private let titleValue = ComponentView<Empty>()
@ -74,7 +74,7 @@ private final class SubItemComponent: Component {
self.addSubview(self.iconView)
self.highligthedChanged = { [weak self] isHighlighted in
/*self.highligthedChanged = { [weak self] isHighlighted in
guard let self, let component = self.component, let highlightBackgroundFrame = self.highlightBackgroundFrame else {
return
}
@ -103,7 +103,7 @@ private final class SubItemComponent: Component {
}
}
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.isUserInteractionEnabled = false
self.isEnabled = false*/
}
required init?(coder: NSCoder) {
@ -243,7 +243,7 @@ final class DataCategoryItemComponent: Component {
let category: DataCategoriesComponent.CategoryData
let isExpanded: Bool
let hasNext: Bool
let action: (DataUsageScreenComponent.Category) -> Void
let action: ((DataUsageScreenComponent.Category) -> Void)?
init(
theme: PresentationTheme,
@ -251,7 +251,7 @@ final class DataCategoryItemComponent: Component {
category: DataCategoriesComponent.CategoryData,
isExpanded: Bool,
hasNext: Bool,
action: @escaping (DataUsageScreenComponent.Category) -> Void
action: ((DataUsageScreenComponent.Category) -> Void)?
) {
self.theme = theme
self.strings = strings
@ -350,14 +350,14 @@ final class DataCategoryItemComponent: Component {
guard let component = self.component else {
return
}
component.action(component.category.key)
component.action?(component.category.key)
}
@objc private func checkPressed() {
guard let component = self.component else {
return
}
component.action(component.category.key)
component.action?(component.category.key)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -597,6 +597,8 @@ final class DataCategoryItemComponent: Component {
transition.setFrame(view: self.subcategoryClippingContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height)))
self.isEnabled = component.action != nil
return CGSize(width: availableSize.width, height: height)
}
}

View File

@ -468,7 +468,9 @@ final class DataUsageScreenComponent: Component {
transition.setBounds(view: self.headerOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: headerOffset), size: self.headerOffsetContainer.bounds.size))
if let controller = self.controller?(), let backButtonNode = controller.navigationBar?.backButtonNode {
if backButtonNode.alpha != navigationButtonAlpha {
backButtonNode.updateManualAlpha(alpha: navigationButtonAlpha, transition: animatedTransition.containedViewLayoutTransition)
/*if backButtonNode.alpha != navigationButtonAlpha {
if backButtonNode.isHidden {
backButtonNode.alpha = 0.0
backButtonNode.isHidden = false
@ -480,7 +482,7 @@ final class DataUsageScreenComponent: Component {
}
}
})
}
}*/
}
}
}
@ -1035,8 +1037,7 @@ final class DataUsageScreenComponent: Component {
theme: environment.theme,
strings: environment.strings,
categories: totalCategories,
toggleCategoryExpanded: { _ in
}
toggleCategoryExpanded: nil
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude)

View File

@ -522,7 +522,7 @@ final class PieChartComponent: Component {
let fractionValue: Double = floor(displayValue * 100.0 * 10.0) / 10.0
let fractionString: String
if fractionValue == 0.0 {
if displayValue == 0.0 {
fractionString = ""
} else if fractionValue < 0.1 {
fractionString = "<0.1%"

View File

@ -1310,6 +1310,8 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
self.isInForegroundPromise.set(true)
self.isActiveValue = true
self.isActivePromise.set(true)
self.runForegroundTasks()
}
if UIApplication.shared.isStatusBarHidden {
@ -1519,6 +1521,22 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
}
}
self.runForegroundTasks()
}
func runForegroundTasks() {
let _ = (self.sharedContextPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
let _ = (sharedApplicationContext.sharedContext.activeAccountContexts
|> take(1)
|> deliverOnMainQueue).start(next: { activeAccounts in
for (_, context, _) in activeAccounts.accounts {
(context.downloadedMediaStoreManager as? DownloadedMediaStoreManagerImpl)?.runTasks()
}
})
})
}
func applicationDidBecomeActive(_ application: UIApplication) {

View File

@ -196,7 +196,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
}
}
if isMember && !channel.hasPermission(.sendSomething) && !channel.flags.contains(.isGigagroup) {
if case .group = channel.info, isMember && !channel.hasPermission(.sendSomething) && !channel.flags.contains(.isGigagroup) {
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
return (currentPanel, nil)
} else {

View File

@ -1662,7 +1662,7 @@ final class ChatMediaInputNode: ChatInputNode {
}
}
})))
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in
guard let strongSelf = self else {
return
}
@ -1819,7 +1819,7 @@ final class ChatMediaInputNode: ChatInputNode {
}
}))
)
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in
guard let strongSelf = self else {
return
}

View File

@ -30,17 +30,17 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
self.presentationInterfaceState = interfaceState
}
let bannedPermission: (Int32, Bool)?
var bannedPermission: (Int32, Bool)?
if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel {
bannedPermission = channel.hasBannedPermission(.banSendText)
} else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup {
if group.hasBannedPermission(.banSendText) {
if let value = channel.hasBannedPermission(.banSendText) {
bannedPermission = value
} else if !channel.hasPermission(.sendSomething) {
bannedPermission = (Int32.max, false)
}
} else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup {
if !group.hasPermission(.sendSomething) {
bannedPermission = (Int32.max, false)
} else {
bannedPermission = nil
}
} else {
bannedPermission = nil
}
var iconImage: UIImage?

View File

@ -30,6 +30,9 @@ import ComponentFlow
import EmojiSuggestionsComponent
import AudioToolbox
import ChatControllerInteraction
import UndoUI
import PremiumUI
import StickerPeekUI
private let accessoryButtonFont = Font.medium(14.0)
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
@ -2571,6 +2574,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
viewForOverlayContent.addSubview(currentEmojiSuggestionView)
currentEmojiSuggestionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.installEmojiSuggestionPreviewGesture(hostView: currentEmojiSuggestionView)
}
let globalPosition = textInputNode.textView.convert(currentEmojiSuggestion.localPosition, to: self.view)
@ -2693,6 +2698,277 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
}
}
private func installEmojiSuggestionPreviewGesture(hostView: UIView) {
let peekRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in
guard let self else {
return nil
}
return self.emojiSuggestionPeekContentAtPoint(point: point)
}, present: { [weak self] content, sourceView, sourceRect in
guard let strongSelf = self, let context = strongSelf.context else {
return nil
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = PeekController(presentationData: presentationData, content: content, sourceView: {
return (sourceView, sourceRect)
})
//strongSelf.peekController = controller
strongSelf.interfaceInteraction?.presentController(controller, nil)
return controller
}, updateContent: { [weak self] content in
guard let strongSelf = self else {
return
}
let _ = strongSelf
})
hostView.addGestureRecognizer(peekRecognizer)
}
private func emojiSuggestionPeekContentAtPoint(point: CGPoint) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? {
guard let presentationInterfaceState = self.presentationInterfaceState else {
return nil
}
guard let chatPeerId = presentationInterfaceState.renderedPeer?.peer?.id else {
return nil
}
guard let context = self.context else {
return nil
}
var maybeFile: TelegramMediaFile?
var maybeItemLayer: CALayer?
if let currentEmojiSuggestionView = self.currentEmojiSuggestionView?.componentView as? EmojiSuggestionsComponent.View {
if let (itemLayer, file) = currentEmojiSuggestionView.item(at: point) {
maybeFile = file
maybeItemLayer = itemLayer
}
}
guard let file = maybeFile else {
return nil
}
guard let itemLayer = maybeItemLayer else {
return nil
}
let _ = chatPeerId
let _ = file
let _ = itemLayer
var collectionId: ItemCollectionId?
for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute {
switch packReference {
case let .id(id, _):
collectionId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id)
default:
break
}
}
}
var bubbleUpEmojiOrStickersets: [ItemCollectionId] = []
if let collectionId {
bubbleUpEmojiOrStickersets.append(collectionId)
}
let accountPeerId = context.account.peerId
let _ = bubbleUpEmojiOrStickersets
let _ = context
let _ = accountPeerId
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId))
|> map { peer -> Bool in
var hasPremium = false
if case let .user(user) = peer, user.isPremium {
hasPremium = true
}
return hasPremium
}
|> deliverOnMainQueue
|> map { [weak self, weak itemLayer] hasPremium -> (UIView, CGRect, PeekControllerContent)? in
guard let strongSelf = self, let itemLayer = itemLayer else {
return nil
}
let _ = strongSelf
let _ = itemLayer
var menuItems: [ContextMenuItem] = []
menuItems.removeAll()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = presentationData
var isLocked = false
if !hasPremium {
isLocked = file.isPremiumEmoji
if isLocked && chatPeerId == context.account.peerId {
isLocked = false
}
}
if let interaction = strongSelf.interfaceInteraction {
let _ = interaction
let sendEmoji: (TelegramMediaFile) -> Void = { file in
guard let self else {
return
}
guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) else {
return
}
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
loop: for attribute in file.attributes {
switch attribute {
case let .CustomEmoji(_, _, displayText, stickerPackReference):
text = displayText
var packId: ItemCollectionId?
if case let .id(id, _) = stickerPackReference {
packId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id)
}
emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: packId, fileId: file.fileId.id, file: file)
break loop
default:
break
}
}
if let emojiAttribute {
controller.controllerInteraction?.sendEmoji(text, emojiAttribute, true)
}
}
let setStatus: (TelegramMediaFile) -> Void = { file in
guard let self, let context = self.context else {
return
}
guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) else {
return
}
let _ = context.engine.accountData.setEmojiStatus(file: file, expirationDate: nil).start()
var animateInAsReplacement = false
animateInAsReplacement = false
/*if let currentUndoOverlayController = strongSelf.currentUndoOverlayController {
currentUndoOverlayController.dismissWithCommitActionAndReplacementAnimation()
strongSelf.currentUndoOverlayController = nil
animateInAsReplacement = true
}*/
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: "Your emoji status has been updated.", undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return false })
//strongSelf.currentUndoOverlayController = controller
controller.controllerInteraction?.presentController(undoController, nil)
}
let copyEmoji: (TelegramMediaFile) -> Void = { file in
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
loop: for attribute in file.attributes {
switch attribute {
case let .CustomEmoji(_, _, displayText, _):
text = displayText
emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file)
break loop
default:
break
}
}
if let _ = emojiAttribute {
storeMessageTextInPasteboard(text, entities: [MessageTextEntity(range: 0 ..< (text as NSString).length, type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id))])
}
}
//TODO:localize
menuItems.append(.action(ContextMenuActionItem(text: "Send Emoji", icon: { theme in
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) {
return generateImage(image.size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
if let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size))
}
})
} else {
return nil
}
}, action: { _, f in
sendEmoji(file)
f(.default)
})))
//TODO:localize
menuItems.append(.action(ContextMenuActionItem(text: "Set as Status", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Smile"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
guard let strongSelf = self else {
return
}
if hasPremium {
setStatus(file)
} else {
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .animatedEmoji, action: {
let controller = PremiumIntroScreen(context: context, source: .animatedEmoji)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
strongSelf.interfaceInteraction?.getNavigationController()?.pushViewController(controller)
}
})))
//TODO:localize
menuItems.append(.action(ContextMenuActionItem(text: "Copy Emoji", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
copyEmoji(file)
f(.default)
})))
}
if menuItems.isEmpty {
return nil
}
let content = StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { [weak self] in
guard let self else {
return
}
guard let interfaceInteraction = self.interfaceInteraction else {
return
}
let _ = self
let _ = interfaceInteraction
let controller = PremiumIntroScreen(context: context, source: .stickers)
//let _ = controller
interfaceInteraction.getNavigationController()?.pushViewController(controller)
})
let _ = content
//return nil
return (strongSelf.view, itemLayer.convert(itemLayer.bounds, to: strongSelf.view.layer), content)
}
}
private func updateTextNodeText(animated: Bool) {
var inputHasText = false
var hideMicButton = false

View File

@ -423,7 +423,7 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
return nil
}
let content = StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { [weak self] in
let content = StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { [weak self] in
guard let self else {
return
}

View File

@ -46,8 +46,8 @@ private struct HorizontalListContextResultsChatInputContextPanelEntry: Comparabl
return lhs.index < rhs.index
}
func item(account: Account, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> ListViewItem {
return HorizontalListContextResultsChatInputPanelItem(account: account, theme: self.theme, result: self.result, resultSelected: resultSelected)
func item(context: AccountContext, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> ListViewItem {
return HorizontalListContextResultsChatInputPanelItem(context: context, theme: self.theme, result: self.result, resultSelected: resultSelected)
}
}
@ -69,12 +69,12 @@ private final class HorizontalListContextResultsOpaqueState {
}
}
private func preparedTransition(from fromEntries: [HorizontalListContextResultsChatInputContextPanelEntry], to toEntries: [HorizontalListContextResultsChatInputContextPanelEntry], hasMore: Bool, account: Account, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> HorizontalListContextResultsChatInputContextPanelTransition {
private func preparedTransition(from fromEntries: [HorizontalListContextResultsChatInputContextPanelEntry], to toEntries: [HorizontalListContextResultsChatInputContextPanelEntry], hasMore: Bool, context: AccountContext, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> HorizontalListContextResultsChatInputContextPanelTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, resultSelected: resultSelected), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, resultSelected: resultSelected), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, resultSelected: resultSelected), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, resultSelected: resultSelected), directionHint: nil) }
return HorizontalListContextResultsChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates, entryCount: toEntries.count, hasMore: hasMore)
}
@ -147,6 +147,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
}
var selectedItemNodeAndContent: (UIView, CGRect, PeekControllerContent)?
selectedItemNodeAndContent = nil
strongSelf.listView.forEachItemNode { itemNode in
if itemNode.frame.contains(convertedPoint), let itemNode = itemNode as? HorizontalListContextResultsChatInputPanelItemNode, let item = itemNode.item {
if case let .internalReference(internalReference) = item.result, let file = internalReference.file, file.isSticker {
@ -177,7 +178,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
}
})))
}
selectedItemNodeAndContent = (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: item.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: menuItems, openPremiumIntro: { [weak self] in
selectedItemNodeAndContent = (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: item.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: menuItems, openPremiumIntro: { [weak self] in
guard let strongSelf = self else {
return
}
@ -230,7 +231,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
f(.default)
let _ = item.resultSelected(item.result, itemNode, itemNode.bounds)
})))
selectedItemNodeAndContent = (itemNode.view, itemNode.bounds, ChatContextResultPeekContent(account: item.account, contextResult: item.result, menu: menuItems))
selectedItemNodeAndContent = (itemNode.view, itemNode.bounds, ChatContextResultPeekContent(account: item.context.account, contextResult: item.result, menu: menuItems))
}
}
}
@ -313,7 +314,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
}
let firstTime = self.currentEntries == nil
let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, hasMore: results.nextOffset != nil, account: self.context.account, resultSelected: { [weak self] result, node, rect in
let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, hasMore: results.nextOffset != nil, context: self.context, resultSelected: { [weak self] result, node, rect in
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
return interfaceInteraction.sendContextResult(results, result, node, rect)
} else {

View File

@ -18,15 +18,15 @@ import SoftwareVideo
import MultiplexedVideoNode
final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
let account: Account
let context: AccountContext
let theme: PresentationTheme
let result: ChatContextResult
let resultSelected: (ChatContextResult, ASDisplayNode, CGRect) -> Bool
let selectable: Bool = true
public init(account: Account, theme: PresentationTheme, result: ChatContextResult, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) {
self.account = account
public init(context: AccountContext, theme: PresentationTheme, result: ChatContextResult, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) {
self.context = context
self.theme = theme
self.result = result
self.resultSelected = resultSelected
@ -265,9 +265,9 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
}
if let file = videoFile {
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource)
updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(file.resource)
} else if let imageResource = imageResource {
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource)
}
case let .internalReference(internalReference):
if let image = internalReference.image {
@ -296,12 +296,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
if file.isVideo && file.isAnimated {
videoFile = file
imageResource = nil
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource)
updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(file.resource)
} else if let imageResource = imageResource {
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource)
}
} else if let imageResource = imageResource {
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource)
}
}
@ -351,11 +351,11 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
if updatedImageResource {
if let imageResource = imageResource {
if let stickerFile = stickerFile {
updateImageSignal = chatMessageSticker(account: item.account, userLocation: .other, file: stickerFile, small: false, fetched: true)
updateImageSignal = chatMessageSticker(account: item.context.account, userLocation: .other, file: stickerFile, small: false, fetched: true)
} else {
let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(CGSize(width: fittedImageDimensions.width * 2.0, height: fittedImageDimensions.height * 2.0)), resource: imageResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
updateImageSignal = chatMessagePhoto(postbox: item.account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage), synchronousLoad: true)
updateImageSignal = chatMessagePhoto(postbox: item.context.account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage), synchronousLoad: true)
}
} else {
updateImageSignal = .complete()
@ -391,7 +391,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
}
if let videoFile = videoFile {
let thumbnailLayer = SoftwareVideoThumbnailNode(account: item.account, fileReference: .standalone(media: videoFile), synchronousLoad: synchronousLoads)
let thumbnailLayer = SoftwareVideoThumbnailNode(account: item.context.account, fileReference: .standalone(media: videoFile), synchronousLoad: synchronousLoads)
thumbnailLayer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
strongSelf.addSubnode(thumbnailLayer)
let layerHolder = takeSampleBufferLayer()
@ -399,7 +399,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
layerHolder.layer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
strongSelf.layer.addSublayer(layerHolder.layer)
let manager = SoftwareVideoLayerFrameManager(account: item.account, userLocation: .other, userContentType: .other, fileReference: .standalone(media: videoFile), layerHolder: layerHolder)
let manager = SoftwareVideoLayerFrameManager(account: item.context.account, userLocation: .other, userContentType: .other, fileReference: .standalone(media: videoFile), layerHolder: layerHolder)
strongSelf.videoLayer = (thumbnailLayer, manager, layerHolder)
thumbnailLayer.ready = { [weak thumbnailLayer, weak manager] in
if let strongSelf = self, let thumbnailLayer = thumbnailLayer, let manager = manager {
@ -437,8 +437,8 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
}
let dimensions = animatedStickerFile.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))
strongSelf.fetchDisposable.set(freeMediaFileResourceInteractiveFetched(account: item.account, userLocation: .other, fileReference: stickerPackFileReference(animatedStickerFile), resource: animatedStickerFile.resource).start())
animationNode.setup(source: AnimatedStickerResourceSource(account: item.account, resource: animatedStickerFile.resource, isVideo: animatedStickerFile.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .cached)
strongSelf.fetchDisposable.set(freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: stickerPackFileReference(animatedStickerFile), resource: animatedStickerFile.resource).start())
animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: animatedStickerFile.resource, isVideo: animatedStickerFile.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .cached)
}
}

View File

@ -243,7 +243,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
}
}))
]
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in
guard let strongSelf = self else {
return
}

View File

@ -198,7 +198,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
}
}))
)
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in
guard let strongSelf = self, let controllerInteraction = strongSelf.getControllerInteraction?() else {
return
}

View File

@ -199,7 +199,7 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
}
}))
]
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in
guard let strongSelf = self else {
return
}

View File

@ -266,10 +266,12 @@ private final class DownloadedMediaStoreManagerPrivateImpl {
final class DownloadedMediaStoreManagerImpl: DownloadedMediaStoreManager {
private let queue = Queue()
private let postbox: Postbox
private let impl: QueueLocalObject<DownloadedMediaStoreManagerPrivateImpl>
init(postbox: Postbox, accountManager: AccountManager<TelegramAccountManagerTypes>) {
let queue = self.queue
self.postbox = postbox
self.impl = QueueLocalObject(queue: queue, generate: {
return DownloadedMediaStoreManagerPrivateImpl(queue: queue, postbox: postbox, accountManager: accountManager)
})
@ -280,4 +282,27 @@ final class DownloadedMediaStoreManagerImpl: DownloadedMediaStoreManager {
impl.store(media, timestamp: timestamp, peerId: peerId)
}
}
func runTasks() {
let _ = (self.postbox.transaction({ transaction -> [(index: Int32, message: Message, mediaId: MediaId)] in
return _internal_getSynchronizeAutosaveItemOperations(transaction: transaction)
})
|> deliverOnMainQueue).start(next: { [weak self] items in
guard let self else {
return
}
for item in items {
for media in item.message.media {
if let id = media.id, id == item.mediaId {
self.store(.standalone(media: media), timestamp: item.message.timestamp, peerId: item.message.id.peerId)
break
}
}
}
let _ = self.postbox.transaction({ transaction -> Void in
return _internal_removeSyncrhonizeAutosaveItemOperations(transaction: transaction, indices: items.map(\.index))
}).start()
})
}
}