mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-17 06:03:38 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
cc0595de4b
@ -460,6 +460,7 @@ private struct NotificationContent: CustomStringConvertible {
|
|||||||
string += " userInfo: \(String(describing: self.userInfo)),\n"
|
string += " userInfo: \(String(describing: self.userInfo)),\n"
|
||||||
string += " senderImage: \(self.senderImage != nil ? "non-empty" : "empty"),\n"
|
string += " senderImage: \(self.senderImage != nil ? "non-empty" : "empty"),\n"
|
||||||
string += " isLockedMessage: \(String(describing: self.isLockedMessage)),\n"
|
string += " isLockedMessage: \(String(describing: self.isLockedMessage)),\n"
|
||||||
|
string += " attachments: \(self.attachments),\n"
|
||||||
string += "}"
|
string += "}"
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
@ -1164,7 +1165,13 @@ private final class NotificationServiceHandler {
|
|||||||
} else {
|
} else {
|
||||||
let intervals: Signal<[(Range<Int64>, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int64.max, MediaBoxFetchPriority.maximum)])
|
let intervals: Signal<[(Range<Int64>, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int64.max, MediaBoxFetchPriority.maximum)])
|
||||||
fetchMediaSignal = Signal { subscriber in
|
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(
|
return standaloneMultipartFetch(
|
||||||
postbox: stateManager.postbox,
|
postbox: stateManager.postbox,
|
||||||
network: stateManager.network,
|
network: stateManager.network,
|
||||||
@ -1191,11 +1198,33 @@ private final class NotificationServiceHandler {
|
|||||||
).start(next: { result in
|
).start(next: { result in
|
||||||
switch result {
|
switch result {
|
||||||
case let .dataPart(_, data, _, _):
|
case let .dataPart(_, data, _, _):
|
||||||
|
var isCompleted = false
|
||||||
let _ = collectedData.modify { current in
|
let _ = collectedData.modify { current in
|
||||||
var current = current
|
let current = current
|
||||||
current.append(data)
|
current.data.append(data)
|
||||||
|
if let totalSize = current.totalSize, Int64(current.data.count) >= totalSize {
|
||||||
|
isCompleted = true
|
||||||
|
}
|
||||||
return current
|
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:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -1203,7 +1232,7 @@ private final class NotificationServiceHandler {
|
|||||||
subscriber.putNext(nil)
|
subscriber.putNext(nil)
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
}, completed: {
|
}, completed: {
|
||||||
subscriber.putNext(collectedData.with({ $0 }))
|
subscriber.putNext(collectedData.with({ $0.data }))
|
||||||
subscriber.putCompletion()
|
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)", "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 image = mediaAttachment as? TelegramMediaImage, let resource = largestImageRepresentation(image.representations)?.resource {
|
||||||
if let mediaData = mediaData {
|
if let mediaData = mediaData {
|
||||||
stateManager.postbox.mediaBox.storeResourceData(resource.id, data: mediaData, synchronous: true)
|
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 storedPath = stateManager.postbox.mediaBox.completedResourcePath(resource, pathExtension: "jpg") {
|
||||||
if let attachment = try? UNNotificationAttachment(identifier: "image", url: URL(fileURLWithPath: storedPath), options: nil) {
|
if let attachment = try? UNNotificationAttachment(identifier: "image", url: URL(fileURLWithPath: storedPath), options: nil) {
|
||||||
|
|||||||
@ -252,6 +252,15 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
|
|||||||
private var overlayColor: (UIColor?, Bool)? = nil
|
private var overlayColor: (UIColor?, Bool)? = nil
|
||||||
private var size: CGSize?
|
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) {
|
public init(useMetalCache: Bool = false) {
|
||||||
self.queue = sharedQueue
|
self.queue = sharedQueue
|
||||||
self.eventsNode = AnimatedStickerNodeDisplayEvents()
|
self.eventsNode = AnimatedStickerNodeDisplayEvents()
|
||||||
@ -279,12 +288,12 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
|
|||||||
if #available(iOS 10.0, *) {
|
if #available(iOS 10.0, *) {
|
||||||
return CompressedAnimationRenderer()
|
return CompressedAnimationRenderer()
|
||||||
} else {
|
} else {
|
||||||
return SoftwareAnimationRenderer()
|
return SoftwareAnimationRenderer(templateImageSupport: true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
private static let softwareRendererPool = AnimationRendererPool(generate: {
|
private static let softwareRendererPool = AnimationRendererPool(generate: {
|
||||||
return SoftwareAnimationRenderer()
|
return SoftwareAnimationRenderer(templateImageSupport: true)
|
||||||
})
|
})
|
||||||
|
|
||||||
private weak var nodeToCopyFrameFrom: DefaultAnimatedStickerNodeImpl?
|
private weak var nodeToCopyFrameFrom: DefaultAnimatedStickerNodeImpl?
|
||||||
@ -295,6 +304,12 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
|
|||||||
self.renderer = DefaultAnimatedStickerNodeImpl.hardwareRendererPool.take()
|
self.renderer = DefaultAnimatedStickerNodeImpl.hardwareRendererPool.take()
|
||||||
} else {
|
} else {
|
||||||
self.renderer = DefaultAnimatedStickerNodeImpl.softwareRendererPool.take()
|
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 {
|
if let contents = self.nodeToCopyFrameFrom?.renderer?.renderer.contents {
|
||||||
self.renderer?.renderer.contents = contents
|
self.renderer?.renderer.contents = contents
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,20 +7,30 @@ import YuvConversion
|
|||||||
import Accelerate
|
import Accelerate
|
||||||
|
|
||||||
final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
||||||
|
private let templateImageSupport: Bool
|
||||||
|
|
||||||
private var highlightedContentNode: ASDisplayNode?
|
private var highlightedContentNode: ASDisplayNode?
|
||||||
private var highlightedColor: UIColor?
|
private var highlightedColor: UIColor?
|
||||||
private var highlightReplacesContent = false
|
private var highlightReplacesContent = false
|
||||||
|
public var renderAsTemplateImage: Bool = false
|
||||||
|
|
||||||
public var currentFrameImage: UIImage? {
|
public private(set) var currentFrameImage: UIImage?
|
||||||
if let contents = self.contents {
|
|
||||||
return UIImage(cgImage: contents as! CGImage)
|
init(templateImageSupport: Bool) {
|
||||||
} else {
|
self.templateImageSupport = templateImageSupport
|
||||||
return nil
|
|
||||||
|
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) {
|
func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void) {
|
||||||
assert(bytesPerRow > 0)
|
assert(bytesPerRow > 0)
|
||||||
|
let renderAsTemplateImage = self.renderAsTemplateImage
|
||||||
queue.async { [weak self] in
|
queue.async { [weak self] in
|
||||||
switch type {
|
switch type {
|
||||||
case .argb:
|
case .argb:
|
||||||
@ -70,13 +80,21 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if renderAsTemplateImage {
|
||||||
|
image = image?.withRenderingMode(.alwaysTemplate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
strongSelf.currentFrameImage = image
|
||||||
|
if strongSelf.templateImageSupport {
|
||||||
|
(strongSelf.view as? UIImageView)?.image = image
|
||||||
|
} else {
|
||||||
strongSelf.contents = image?.cgImage
|
strongSelf.contents = image?.cgImage
|
||||||
|
}
|
||||||
strongSelf.updateHighlightedContentNode()
|
strongSelf.updateHighlightedContentNode()
|
||||||
if strongSelf.highlightedContentNode?.frame != strongSelf.bounds {
|
if strongSelf.highlightedContentNode?.frame != strongSelf.bounds {
|
||||||
strongSelf.highlightedContentNode?.frame = strongSelf.bounds
|
strongSelf.highlightedContentNode?.frame = strongSelf.bounds
|
||||||
@ -90,14 +108,16 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
|||||||
guard let highlightedContentNode = self.highlightedContentNode, let highlightedColor = self.highlightedColor else {
|
guard let highlightedContentNode = self.highlightedContentNode, let highlightedColor = self.highlightedColor else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let contents = self.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID {
|
(highlightedContentNode.view as! UIImageView).image = self.currentFrameImage?.withRenderingMode(.alwaysTemplate)
|
||||||
(highlightedContentNode.view as! UIImageView).image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate)
|
|
||||||
}
|
|
||||||
highlightedContentNode.tintColor = highlightedColor
|
highlightedContentNode.tintColor = highlightedColor
|
||||||
if self.highlightReplacesContent {
|
if self.highlightReplacesContent {
|
||||||
|
if self.templateImageSupport {
|
||||||
|
(self.view as? UIImageView)?.image = nil
|
||||||
|
} else {
|
||||||
self.contents = nil
|
self.contents = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
||||||
self.highlightReplacesContent = replace
|
self.highlightReplacesContent = replace
|
||||||
|
|||||||
@ -136,21 +136,30 @@ public struct Transition {
|
|||||||
//view.layer.position = CGPoint(x: frame.midX, y: frame.midY)
|
//view.layer.position = CGPoint(x: frame.midX, y: frame.midY)
|
||||||
view.layer.removeAnimation(forKey: "position")
|
view.layer.removeAnimation(forKey: "position")
|
||||||
view.layer.removeAnimation(forKey: "bounds")
|
view.layer.removeAnimation(forKey: "bounds")
|
||||||
|
view.layer.removeAnimation(forKey: "bounds.size")
|
||||||
completion?(true)
|
completion?(true)
|
||||||
case .curve:
|
case .curve:
|
||||||
let previousFrame: CGRect
|
let previousPosition: CGPoint
|
||||||
if (view.layer.animation(forKey: "position") != nil || view.layer.animation(forKey: "bounds") != nil), let presentation = view.layer.presentation() {
|
let previousBounds: CGRect
|
||||||
previousFrame = presentation.frame
|
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 {
|
} else {
|
||||||
previousFrame = view.frame
|
previousPosition = view.layer.position
|
||||||
|
previousBounds = view.layer.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
view.frame = frame
|
view.frame = frame
|
||||||
//view.bounds = CGRect(origin: previousBounds.origin, size: frame.size)
|
//view.bounds = CGRect(origin: previousBounds.origin, size: frame.size)
|
||||||
//view.center = CGPoint(x: frame.midX, y: frame.midY)
|
//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)
|
let anchorPoint = view.layer.anchorPoint
|
||||||
self.animateBoundsSize(view: view, from: previousFrame.size, to: frame.size)
|
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:
|
case .none:
|
||||||
view.bounds = bounds
|
view.bounds = bounds
|
||||||
view.layer.removeAnimation(forKey: "bounds")
|
view.layer.removeAnimation(forKey: "bounds")
|
||||||
|
view.layer.removeAnimation(forKey: "bounds.origin")
|
||||||
|
view.layer.removeAnimation(forKey: "bounds.size")
|
||||||
completion?(true)
|
completion?(true)
|
||||||
case .curve:
|
case .curve:
|
||||||
let previousBounds: CGRect
|
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
|
previousBounds = presentation.bounds
|
||||||
} else {
|
} else {
|
||||||
previousBounds = view.layer.bounds
|
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) {
|
public func setBoundsSize(view: UIView, size: CGSize, completion: ((Bool) -> Void)? = nil) {
|
||||||
if view.bounds.size == size {
|
if view.bounds.size == size {
|
||||||
completion?(true)
|
completion?(true)
|
||||||
|
|||||||
@ -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) {
|
func updateManualText(_ text: String, isBack: Bool = true) {
|
||||||
let node: NavigationButtonItemNode
|
let node: NavigationButtonItemNode
|
||||||
if self.nodes.count > 0 {
|
if self.nodes.count > 0 {
|
||||||
|
|||||||
@ -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 {
|
guard let strongSelf = self else {
|
||||||
return
|
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 {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
return nil
|
||||||
|
|||||||
@ -9,14 +9,15 @@ import StickerResources
|
|||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
import ContextUI
|
import ContextUI
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
final class StickerPreviewPeekContent: PeekControllerContent {
|
final class StickerPreviewPeekContent: PeekControllerContent {
|
||||||
let account: Account
|
let context: AccountContext
|
||||||
let item: ImportStickerPack.Sticker
|
let item: ImportStickerPack.Sticker
|
||||||
let menu: [ContextMenuItem]
|
let menu: [ContextMenuItem]
|
||||||
|
|
||||||
init(account: Account, item: ImportStickerPack.Sticker, menu: [ContextMenuItem]) {
|
init(context: AccountContext, item: ImportStickerPack.Sticker, menu: [ContextMenuItem]) {
|
||||||
self.account = account
|
self.context = context
|
||||||
self.item = item
|
self.item = item
|
||||||
self.menu = menu
|
self.menu = menu
|
||||||
}
|
}
|
||||||
@ -34,7 +35,7 @@ final class StickerPreviewPeekContent: PeekControllerContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func node() -> PeekControllerContentNode & ASDisplayNode {
|
func node() -> PeekControllerContentNode & ASDisplayNode {
|
||||||
return StickerPreviewPeekContentNode(account: self.account, item: self.item)
|
return StickerPreviewPeekContentNode(account: self.context.account, item: self.item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func topAccessoryNode() -> ASDisplayNode? {
|
func topAccessoryNode() -> ASDisplayNode? {
|
||||||
|
|||||||
@ -169,6 +169,7 @@ private final class SubItemNode: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
checkNode = CheckNode(theme: CheckNodeTheme(theme: presentationData.theme, style: .plain))
|
checkNode = CheckNode(theme: CheckNodeTheme(theme: presentationData.theme, style: .plain))
|
||||||
|
checkNode.isUserInteractionEnabled = false
|
||||||
self.checkNode = checkNode
|
self.checkNode = checkNode
|
||||||
self.addSubnode(checkNode)
|
self.addSubnode(checkNode)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -325,11 +325,6 @@ private final class TimeBasedCleanupImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func resetScan(general: Int32, shortLived: Int32, gigabytesLimit: Int32) {
|
private func resetScan(general: Int32, shortLived: Int32, gigabytesLimit: Int32) {
|
||||||
if "".isEmpty {
|
|
||||||
//TODO:remove debugging
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let generalPaths = self.generalPaths
|
let generalPaths = self.generalPaths
|
||||||
let totalSizeBasedPath = self.totalSizeBasedPath
|
let totalSizeBasedPath = self.totalSizeBasedPath
|
||||||
let shortLivedPaths = self.shortLivedPaths
|
let shortLivedPaths = self.shortLivedPaths
|
||||||
@ -362,6 +357,7 @@ private final class TimeBasedCleanupImpl {
|
|||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
var totalApproximateSize: Int64 = 0
|
var totalApproximateSize: Int64 = 0
|
||||||
|
if gigabytesLimit < Int32.max {
|
||||||
for path in shortLivedPaths {
|
for path in shortLivedPaths {
|
||||||
totalApproximateSize += statForDirectory(path: path)
|
totalApproximateSize += statForDirectory(path: path)
|
||||||
}
|
}
|
||||||
@ -369,6 +365,7 @@ private final class TimeBasedCleanupImpl {
|
|||||||
totalApproximateSize += statForDirectory(path: path)
|
totalApproximateSize += statForDirectory(path: path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: totalSizeBasedPath), includingPropertiesForKeys: [.fileSizeKey, .fileResourceIdentifierKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
|
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: totalSizeBasedPath), includingPropertiesForKeys: [.fileSizeKey, .fileResourceIdentifierKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
|
||||||
var fileIds = Set<Data>()
|
var fileIds = Set<Data>()
|
||||||
loop: for url in enumerator {
|
loop: for url in enumerator {
|
||||||
@ -387,6 +384,7 @@ private final class TimeBasedCleanupImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var performSizeMapping = true
|
var performSizeMapping = true
|
||||||
if totalApproximateSize <= bytesLimit {
|
if totalApproximateSize <= bytesLimit {
|
||||||
@ -405,6 +403,7 @@ private final class TimeBasedCleanupImpl {
|
|||||||
|
|
||||||
var totalLimitSize: UInt64 = 0
|
var totalLimitSize: UInt64 = 0
|
||||||
|
|
||||||
|
if general < Int32.max {
|
||||||
for path in generalPaths {
|
for path in generalPaths {
|
||||||
let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase)
|
let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase)
|
||||||
if !paths.contains(path) {
|
if !paths.contains(path) {
|
||||||
@ -413,7 +412,9 @@ private final class TimeBasedCleanupImpl {
|
|||||||
removedGeneralCount += scanResult.unlinkedCount
|
removedGeneralCount += scanResult.unlinkedCount
|
||||||
totalLimitSize += scanResult.totalSize
|
totalLimitSize += scanResult.totalSize
|
||||||
}
|
}
|
||||||
do {
|
}
|
||||||
|
|
||||||
|
if gigabytesLimit < Int32.max {
|
||||||
let scanResult = scanFiles(at: totalSizeBasedPath, olderThan: 0, includeSubdirectories: false, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase)
|
let scanResult = scanFiles(at: totalSizeBasedPath, olderThan: 0, includeSubdirectories: false, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase)
|
||||||
if !paths.contains(totalSizeBasedPath) {
|
if !paths.contains(totalSizeBasedPath) {
|
||||||
paths.append(totalSizeBasedPath)
|
paths.append(totalSizeBasedPath)
|
||||||
|
|||||||
@ -460,7 +460,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
} else {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
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 {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
strongSelf.stableEmptyResultEmoji = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,14 +12,22 @@ import AccountContext
|
|||||||
|
|
||||||
public func autodownloadDataSizeString(_ size: Int64, decimalSeparator: String = ".") -> String {
|
public func autodownloadDataSizeString(_ size: Int64, decimalSeparator: String = ".") -> String {
|
||||||
if size >= 1024 * 1024 * 1024 {
|
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 {
|
if remainder != 0 {
|
||||||
return "\(size / (1024 * 1024 * 1024))\(decimalSeparator)\(remainder) GB"
|
return "\(size / (1024 * 1024 * 1024))\(decimalSeparator)\(remainder) GB"
|
||||||
} else {
|
} else {
|
||||||
return "\(size / (1024 * 1024 * 1024)) GB"
|
return "\(size / (1024 * 1024 * 1024)) GB"
|
||||||
}
|
}
|
||||||
} else if size >= 1024 * 1024 {
|
} 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 {
|
if size < 10 * 1024 * 1024 {
|
||||||
return "\(size / (1024 * 1024))\(decimalSeparator)\(remainder) MB"
|
return "\(size / (1024 * 1024))\(decimalSeparator)\(remainder) MB"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -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 {
|
} else {
|
||||||
|
|||||||
@ -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 {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ public enum StickerPreviewPeekItem: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class StickerPreviewPeekContent: PeekControllerContent {
|
public final class StickerPreviewPeekContent: PeekControllerContent {
|
||||||
let account: Account
|
let context: AccountContext
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
public let item: StickerPreviewPeekItem
|
public let item: StickerPreviewPeekItem
|
||||||
@ -37,8 +37,8 @@ public final class StickerPreviewPeekContent: PeekControllerContent {
|
|||||||
let menu: [ContextMenuItem]
|
let menu: [ContextMenuItem]
|
||||||
let openPremiumIntro: () -> Void
|
let openPremiumIntro: () -> Void
|
||||||
|
|
||||||
public init(account: Account, theme: PresentationTheme, strings: PresentationStrings, item: StickerPreviewPeekItem, isLocked: Bool = false, menu: [ContextMenuItem], openPremiumIntro: @escaping () -> Void) {
|
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, item: StickerPreviewPeekItem, isLocked: Bool = false, menu: [ContextMenuItem], openPremiumIntro: @escaping () -> Void) {
|
||||||
self.account = account
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.item = item
|
self.item = item
|
||||||
@ -64,7 +64,7 @@ public final class StickerPreviewPeekContent: PeekControllerContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func node() -> PeekControllerContentNode & ASDisplayNode {
|
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? {
|
public func topAccessoryNode() -> ASDisplayNode? {
|
||||||
@ -91,7 +91,7 @@ public final class StickerPreviewPeekContent: PeekControllerContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerContentNode {
|
public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerContentNode {
|
||||||
private let account: Account
|
private let context: AccountContext
|
||||||
private let item: StickerPreviewPeekItem
|
private let item: StickerPreviewPeekItem
|
||||||
|
|
||||||
private var textNode: ASTextNode
|
private var textNode: ASTextNode
|
||||||
@ -105,8 +105,8 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
|||||||
|
|
||||||
private let _ready = Promise<Bool>()
|
private let _ready = Promise<Bool>()
|
||||||
|
|
||||||
init(account: Account, item: StickerPreviewPeekItem) {
|
init(context: AccountContext, item: StickerPreviewPeekItem, theme: PresentationTheme) {
|
||||||
self.account = account
|
self.context = context
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
self.textNode = ASTextNode()
|
self.textNode = ASTextNode()
|
||||||
@ -133,14 +133,18 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
|||||||
}
|
}
|
||||||
let fittedDimensions = dimensions.cgSize.aspectFitted(fitSize)
|
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.visibility = true
|
||||||
animationNode.addSubnode(self.textNode)
|
animationNode.addSubnode(self.textNode)
|
||||||
|
|
||||||
if isPremiumSticker, let effect = item.file.videoThumbnails.first {
|
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()
|
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.setup(source: source, width: Int(fittedDimensions.width * 2.0), height: Int(fittedDimensions.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||||
additionalAnimationNode.visibility = true
|
additionalAnimationNode.visibility = true
|
||||||
@ -151,7 +155,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
|||||||
self.animationNode = nil
|
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()
|
super.init()
|
||||||
|
|
||||||
|
|||||||
@ -193,6 +193,7 @@ private var declaredEncodables: Void = {
|
|||||||
declareEncodable(TelegramPeerUsername.self, f: { TelegramPeerUsername(decoder: $0) })
|
declareEncodable(TelegramPeerUsername.self, f: { TelegramPeerUsername(decoder: $0) })
|
||||||
declareEncodable(MediaSpoilerMessageAttribute.self, f: { MediaSpoilerMessageAttribute(decoder: $0) })
|
declareEncodable(MediaSpoilerMessageAttribute.self, f: { MediaSpoilerMessageAttribute(decoder: $0) })
|
||||||
declareEncodable(TranslationMessageAttribute.self, f: { TranslationMessageAttribute(decoder: $0) })
|
declareEncodable(TranslationMessageAttribute.self, f: { TranslationMessageAttribute(decoder: $0) })
|
||||||
|
declareEncodable(SynchronizeAutosaveItemOperation.self, f: { SynchronizeAutosaveItemOperation(decoder: $0) })
|
||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@ -146,8 +146,26 @@ func managedSynchronizeEmojiSearchCategories(postbox: Postbox, network: Network,
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
return .complete()
|
||||||
}
|
}
|
||||||
|
fetchSignals = fetchSignals |> then(signal)
|
||||||
|
}
|
||||||
|
return fetchSignals
|
||||||
|
}
|
||||||
|
}
|
||||||
|> switchToLatest
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -180,6 +180,7 @@ public struct OperationLogTags {
|
|||||||
public static let SynchronizeChatListFilters = PeerOperationLogTag(value: 20)
|
public static let SynchronizeChatListFilters = PeerOperationLogTag(value: 20)
|
||||||
public static let SynchronizeMarkAllUnseenReactions = PeerOperationLogTag(value: 21)
|
public static let SynchronizeMarkAllUnseenReactions = PeerOperationLogTag(value: 21)
|
||||||
public static let SynchronizeInstalledEmoji = PeerOperationLogTag(value: 22)
|
public static let SynchronizeInstalledEmoji = PeerOperationLogTag(value: 22)
|
||||||
|
public static let SynchronizeAutosaveItems = PeerOperationLogTag(value: 23)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {
|
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {
|
||||||
|
|||||||
@ -633,6 +633,9 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
|
|||||||
if case .Sticker = attribute {
|
if case .Sticker = attribute {
|
||||||
hasSticker = true
|
hasSticker = true
|
||||||
break
|
break
|
||||||
|
} else if case .CustomEmoji = attribute {
|
||||||
|
hasSticker = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hasSticker
|
return hasSticker
|
||||||
|
|||||||
@ -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)
|
return managedSynchronizeMessageHistoryTagSummaries(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, threadId: threadId)
|
||||||
|> ignoreValues
|
|> 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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -850,9 +850,9 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if state?.keyboardContentId == AnyHashable("emoji") {
|
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 {
|
} 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -608,7 +608,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
strongSelf.currentUndoOverlayController = controller
|
strongSelf.currentUndoOverlayController = controller
|
||||||
controllerInteraction.presentController(controller, nil)
|
controllerInteraction.presentController(controller, nil)
|
||||||
},
|
},
|
||||||
copyEmoji: { file in
|
copyEmoji: { [weak self] file in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var text = "."
|
var text = "."
|
||||||
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
|
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
|
||||||
loop: for attribute in file.attributes {
|
loop: for attribute in file.attributes {
|
||||||
@ -625,6 +629,20 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
|
|
||||||
if let _ = emojiAttribute {
|
if let _ = emojiAttribute {
|
||||||
storeMessageTextInPasteboard(text, entities: [MessageTextEntity(range: 0 ..< (text as NSString).length, type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id))])
|
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,
|
presentController: controllerInteraction.presentController,
|
||||||
@ -1425,7 +1443,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if let emoji = inputData.emoji {
|
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 {
|
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 {
|
private func processInputData(inputData: InputData) -> InputData {
|
||||||
return InputData(
|
return InputData(
|
||||||
emoji: inputData.emoji.flatMap { emoji in
|
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
|
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,
|
gifs: inputData.gifs,
|
||||||
availableGifSearchEmojies: inputData.availableGifSearchEmojies
|
availableGifSearchEmojies: inputData.availableGifSearchEmojies
|
||||||
@ -2503,7 +2521,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior {
|
|||||||
return nil
|
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 {
|
guard let strongSelf = self, let interaction = strongSelf.interaction else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2632,7 +2650,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior {
|
|||||||
return nil
|
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 {
|
guard let strongSelf = self, let interaction = strongSelf.interaction else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -371,7 +371,7 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
} else {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
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 {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
strongSelf.stableEmptyResultEmoji = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -233,6 +233,31 @@ public final class EmojiSuggestionsComponent: Component {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
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) {
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
if case .ended = recognizer.state {
|
if case .ended = recognizer.state {
|
||||||
let location = recognizer.location(in: self.scrollView)
|
let location = recognizer.location(in: self.scrollView)
|
||||||
|
|||||||
@ -1330,6 +1330,15 @@ private final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, Pager
|
|||||||
self.layer.addSublayer(itemLayer)
|
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)
|
let itemFrame = itemLayout.frame(at: index)
|
||||||
itemLayer.frame = itemFrame
|
itemLayer.frame = itemFrame
|
||||||
|
|
||||||
@ -1708,17 +1717,24 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
if case .ended = recognizer.state {
|
if case .ended = recognizer.state {
|
||||||
let location = recognizer.location(in: self)
|
let location = recognizer.location(in: self)
|
||||||
if self.backIconView.frame.contains(location) {
|
if self.backIconView.frame.contains(location) {
|
||||||
if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
|
self.clearCategorySearch()
|
||||||
placeholderContentView.clearSelection(dispatchEvent : true)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.activateTextInput()
|
self.activateTextInput()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearCategorySearch() {
|
||||||
|
if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
|
||||||
|
placeholderContentView.clearSelection(dispatchEvent : true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func activateTextInput() {
|
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 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))
|
let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height))
|
||||||
|
|
||||||
@ -1730,10 +1746,12 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.canFocus {
|
||||||
self.currentPresetSearchTerm = nil
|
self.currentPresetSearchTerm = nil
|
||||||
if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
|
if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
|
||||||
placeholderContentView.clearSelection(dispatchEvent: false)
|
placeholderContentView.clearSelection(dispatchEvent: false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.activated(true)
|
self.activated(true)
|
||||||
|
|
||||||
@ -1968,9 +1986,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
self.currentPresetSearchTerm = term
|
self.currentPresetSearchTerm = term
|
||||||
|
|
||||||
if shouldChangeActivation {
|
if shouldChangeActivation {
|
||||||
|
if let term {
|
||||||
self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
|
|
||||||
if let term {
|
|
||||||
self.updateQuery(.category(value: term))
|
self.updateQuery(.category(value: term))
|
||||||
self.activated(false)
|
self.activated(false)
|
||||||
} else {
|
} else {
|
||||||
@ -2049,6 +2067,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
cancelButtonTitleComponentView.isUserInteractionEnabled = false
|
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.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 let cancelButtonTintTitleComponentView = self.cancelButtonTintTitle.view {
|
||||||
if cancelButtonTintTitleComponentView.superview == nil {
|
if cancelButtonTintTitleComponentView.superview == nil {
|
||||||
@ -2056,6 +2075,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
cancelButtonTintTitleComponentView.isUserInteractionEnabled = false
|
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.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
|
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) {
|
func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, searchInitiallyHidden: Bool, transition: Transition) {
|
||||||
let titleColor: UIColor
|
let titleColor: UIColor
|
||||||
if useOpaqueTheme {
|
if useOpaqueTheme {
|
||||||
titleColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor
|
titleColor = theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor
|
||||||
} else {
|
} else {
|
||||||
titleColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor
|
titleColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let iconSize: CGSize
|
let iconSize: CGSize
|
||||||
@ -2513,6 +2533,12 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
case detailed
|
case detailed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum SearchState {
|
||||||
|
case empty
|
||||||
|
case searching
|
||||||
|
case active
|
||||||
|
}
|
||||||
|
|
||||||
public final class EmptySearchResults: Equatable {
|
public final class EmptySearchResults: Equatable {
|
||||||
public let text: String
|
public let text: String
|
||||||
public let iconFile: TelegramMediaFile?
|
public let iconFile: TelegramMediaFile?
|
||||||
@ -2543,6 +2569,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
public let contentItemGroups: [ItemGroup]
|
public let contentItemGroups: [ItemGroup]
|
||||||
public let itemLayoutType: ItemLayoutType
|
public let itemLayoutType: ItemLayoutType
|
||||||
public let itemContentUniqueId: AnyHashable?
|
public let itemContentUniqueId: AnyHashable?
|
||||||
|
public let searchState: SearchState
|
||||||
public let warpContentsOnEdges: Bool
|
public let warpContentsOnEdges: Bool
|
||||||
public let displaySearchWithPlaceholder: String?
|
public let displaySearchWithPlaceholder: String?
|
||||||
public let searchCategories: EmojiSearchCategories?
|
public let searchCategories: EmojiSearchCategories?
|
||||||
@ -2564,6 +2591,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
contentItemGroups: [ItemGroup],
|
contentItemGroups: [ItemGroup],
|
||||||
itemLayoutType: ItemLayoutType,
|
itemLayoutType: ItemLayoutType,
|
||||||
itemContentUniqueId: AnyHashable?,
|
itemContentUniqueId: AnyHashable?,
|
||||||
|
searchState: SearchState,
|
||||||
warpContentsOnEdges: Bool,
|
warpContentsOnEdges: Bool,
|
||||||
displaySearchWithPlaceholder: String?,
|
displaySearchWithPlaceholder: String?,
|
||||||
searchCategories: EmojiSearchCategories?,
|
searchCategories: EmojiSearchCategories?,
|
||||||
@ -2584,6 +2612,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
self.contentItemGroups = contentItemGroups
|
self.contentItemGroups = contentItemGroups
|
||||||
self.itemLayoutType = itemLayoutType
|
self.itemLayoutType = itemLayoutType
|
||||||
self.itemContentUniqueId = itemContentUniqueId
|
self.itemContentUniqueId = itemContentUniqueId
|
||||||
|
self.searchState = searchState
|
||||||
self.warpContentsOnEdges = warpContentsOnEdges
|
self.warpContentsOnEdges = warpContentsOnEdges
|
||||||
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
|
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
|
||||||
self.searchCategories = searchCategories
|
self.searchCategories = searchCategories
|
||||||
@ -2595,7 +2624,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
self.selectedItems = selectedItems
|
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(
|
return EmojiPagerContentComponent(
|
||||||
id: self.id,
|
id: self.id,
|
||||||
context: self.context,
|
context: self.context,
|
||||||
@ -2607,6 +2636,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
contentItemGroups: contentItemGroups,
|
contentItemGroups: contentItemGroups,
|
||||||
itemLayoutType: self.itemLayoutType,
|
itemLayoutType: self.itemLayoutType,
|
||||||
itemContentUniqueId: itemContentUniqueId,
|
itemContentUniqueId: itemContentUniqueId,
|
||||||
|
searchState: searchState,
|
||||||
warpContentsOnEdges: self.warpContentsOnEdges,
|
warpContentsOnEdges: self.warpContentsOnEdges,
|
||||||
displaySearchWithPlaceholder: self.displaySearchWithPlaceholder,
|
displaySearchWithPlaceholder: self.displaySearchWithPlaceholder,
|
||||||
searchCategories: self.searchCategories,
|
searchCategories: self.searchCategories,
|
||||||
@ -2650,6 +2680,12 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
if lhs.itemLayoutType != rhs.itemLayoutType {
|
if lhs.itemLayoutType != rhs.itemLayoutType {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.itemContentUniqueId != rhs.itemContentUniqueId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.searchState != rhs.searchState {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.warpContentsOnEdges != rhs.warpContentsOnEdges {
|
if lhs.warpContentsOnEdges != rhs.warpContentsOnEdges {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -2662,6 +2698,9 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
if lhs.searchInitiallyHidden != rhs.searchInitiallyHidden {
|
if lhs.searchInitiallyHidden != rhs.searchInitiallyHidden {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.searchAlwaysActive != rhs.searchAlwaysActive {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.searchIsPlaceholderOnly != rhs.searchIsPlaceholderOnly {
|
if lhs.searchIsPlaceholderOnly != rhs.searchIsPlaceholderOnly {
|
||||||
return false
|
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 {
|
guard let component = self.component, let pagerEnvironment = self.pagerEnvironment, let itemLayout = self.itemLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.isSearchActivated {
|
||||||
|
self.visibleSearchHeader?.clearCategorySearch()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for groupIndex in 0 ..< itemLayout.itemGroupLayouts.count {
|
for groupIndex in 0 ..< itemLayout.itemGroupLayouts.count {
|
||||||
let group = itemLayout.itemGroupLayouts[groupIndex]
|
let group = itemLayout.itemGroupLayouts[groupIndex]
|
||||||
|
|
||||||
@ -5044,7 +5089,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
scrollView.layer.removeAllAnimations()
|
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
|
scrollView.isScrollEnabled = false
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
scrollView.isScrollEnabled = true
|
scrollView.isScrollEnabled = true
|
||||||
@ -6024,6 +6069,9 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return nil
|
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 {
|
guard let item = strongSelf.item(atPoint: point), let itemLayer = strongSelf.visibleItemLayers[item.1], let file = item.0.itemFile else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -6459,12 +6507,15 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
|
|
||||||
strongSelf.isSearchActivated = false
|
strongSelf.isSearchActivated = false
|
||||||
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(false)
|
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(false)
|
||||||
if strongSelf.component?.searchInitiallyHidden == false {
|
|
||||||
if !isFirstResponder {
|
if !isFirstResponder {
|
||||||
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.easeInOut(duration: 0.2))
|
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(
|
||||||
}
|
Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
} else {
|
} 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
|
}, updateQuery: { [weak self] query in
|
||||||
@ -6485,7 +6536,9 @@ 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))
|
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)
|
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
|
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 {
|
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -6494,8 +6547,8 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
strongSelf.scrollView.addSubview(visibleSearchHeader)
|
strongSelf.scrollView.addSubview(visibleSearchHeader)
|
||||||
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
||||||
}
|
}
|
||||||
})*/
|
})
|
||||||
if visibleSearchHeader.frame != searchHeaderFrame {
|
} else {
|
||||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
|
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
|
||||||
if !useOpaqueTheme {
|
if !useOpaqueTheme {
|
||||||
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
||||||
@ -6548,7 +6601,11 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
visibleEmptySearchResultsView = EmptySearchResultsView(frame: CGRect())
|
visibleEmptySearchResultsView = EmptySearchResultsView(frame: CGRect())
|
||||||
self.visibleEmptySearchResultsView = visibleEmptySearchResultsView
|
self.visibleEmptySearchResultsView = visibleEmptySearchResultsView
|
||||||
self.addSubview(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)
|
let emptySearchResultsSize = CGSize(width: availableSize.width, height: availableSize.height - itemLayout.searchInsets.top - itemLayout.searchHeight)
|
||||||
visibleEmptySearchResultsView.update(
|
visibleEmptySearchResultsView.update(
|
||||||
@ -6575,23 +6632,97 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
animateContentCrossfade = true
|
animateContentCrossfade = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let crossfadeMinScale: CGFloat = 0.4
|
||||||
|
|
||||||
if animateContentCrossfade {
|
if animateContentCrossfade {
|
||||||
/*for (_, itemLayer) in self.visibleItemLayers {
|
for (_, itemLayer) in self.visibleItemLayers {
|
||||||
if let snapshotLayer = itemLayer.snapshotContentTree() {
|
if let snapshotLayer = itemLayer.snapshotContentTree() {
|
||||||
itemLayer.superlayer?.insertSublayer(snapshotLayer, above: itemLayer)
|
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.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
|
||||||
snapshotLayer?.removeFromSuperlayer()
|
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)
|
self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: attemptSynchronousLoads, previousItemPositions: previousItemPositions, previousAbsoluteItemPositions: previousAbsoluteItemPositions, updatedItemPositions: updatedItemPositions, hintDisappearingGroupFrame: hintDisappearingGroupFrame)
|
||||||
|
|
||||||
if animateContentCrossfade {
|
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.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
|
return availableSize
|
||||||
@ -7548,6 +7679,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
contentItemGroups: allItemGroups,
|
contentItemGroups: allItemGroups,
|
||||||
itemLayoutType: .compact,
|
itemLayoutType: .compact,
|
||||||
itemContentUniqueId: nil,
|
itemContentUniqueId: nil,
|
||||||
|
searchState: .empty,
|
||||||
warpContentsOnEdges: isReactionSelection || isStatusSelection || isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection,
|
warpContentsOnEdges: isReactionSelection || isStatusSelection || isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection,
|
||||||
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
|
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
|
||||||
searchCategories: searchCategories,
|
searchCategories: searchCategories,
|
||||||
@ -8069,6 +8201,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
contentItemGroups: allItemGroups,
|
contentItemGroups: allItemGroups,
|
||||||
itemLayoutType: .detailed,
|
itemLayoutType: .detailed,
|
||||||
itemContentUniqueId: nil,
|
itemContentUniqueId: nil,
|
||||||
|
searchState: .empty,
|
||||||
warpContentsOnEdges: isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection,
|
warpContentsOnEdges: isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection,
|
||||||
displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil,
|
displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil,
|
||||||
searchCategories: searchCategories,
|
searchCategories: searchCategories,
|
||||||
|
|||||||
@ -391,6 +391,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
|||||||
contentItemGroups: self.itemGroups,
|
contentItemGroups: self.itemGroups,
|
||||||
itemLayoutType: .compact,
|
itemLayoutType: .compact,
|
||||||
itemContentUniqueId: "main",
|
itemContentUniqueId: "main",
|
||||||
|
searchState: .empty,
|
||||||
warpContentsOnEdges: false,
|
warpContentsOnEdges: false,
|
||||||
displaySearchWithPlaceholder: "Search Emoji",
|
displaySearchWithPlaceholder: "Search Emoji",
|
||||||
searchCategories: nil,
|
searchCategories: nil,
|
||||||
@ -410,7 +411,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
|||||||
iconFile: nil
|
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(
|
let _ = self.keyboardView.update(
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import LottieAnimationComponent
|
|||||||
import EmojiStatusComponent
|
import EmojiStatusComponent
|
||||||
import LottieComponent
|
import LottieComponent
|
||||||
import LottieComponentEmojiContent
|
import LottieComponentEmojiContent
|
||||||
|
import AudioToolbox
|
||||||
|
|
||||||
private final class RoundMaskView: UIImageView {
|
private final class RoundMaskView: UIImageView {
|
||||||
private var currentDiameter: CGFloat?
|
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.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>? {
|
func visibleItems(for rect: CGRect) -> Range<Int>? {
|
||||||
@ -214,6 +222,10 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
|
|
||||||
private var selectedItem: AnyHashable?
|
private var selectedItem: AnyHashable?
|
||||||
|
|
||||||
|
private lazy var hapticFeedback: HapticFeedback = {
|
||||||
|
return HapticFeedback()
|
||||||
|
}()
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.tintContainerView = UIView()
|
self.tintContainerView = UIView()
|
||||||
|
|
||||||
@ -281,6 +293,8 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
self.selectedItem = nil
|
self.selectedItem = nil
|
||||||
} else {
|
} else {
|
||||||
self.selectedItem = AnyHashable(id)
|
self.selectedItem = AnyHashable(id)
|
||||||
|
AudioServicesPlaySystemSound(0x450)
|
||||||
|
self.hapticFeedback.tap()
|
||||||
}
|
}
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
@ -303,7 +317,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let transition = Transition(animation: .curve(duration: 0.4, curve: .spring))
|
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.updateScrolling(transition: transition, fromScrolling: false)
|
||||||
//self.scrollView.setContentOffset(CGPoint(), animated: true)
|
//self.scrollView.setContentOffset(CGPoint(), animated: true)
|
||||||
|
|
||||||
@ -322,7 +336,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
self.selectedItem = nil
|
self.selectedItem = nil
|
||||||
|
|
||||||
let transition = Transition(animation: .curve(duration: 0.4, curve: .spring))
|
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.updateScrolling(transition: transition, fromScrolling: false)
|
||||||
|
|
||||||
self.state?.updated(transition: transition)
|
self.state?.updated(transition: transition)
|
||||||
@ -537,7 +551,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
}
|
}
|
||||||
if case .active = component.textInputState {
|
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 {
|
if self.scrollView.contentSize != itemLayout.contentSize {
|
||||||
self.scrollView.contentSize = itemLayout.contentSize
|
self.scrollView.contentSize = itemLayout.contentSize
|
||||||
|
|||||||
@ -98,9 +98,17 @@ private class GifVideoLayer: AVSampleBufferDisplayLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override init(layer: Any) {
|
override init(layer: Any) {
|
||||||
|
guard let layer = layer as? GifVideoLayer else {
|
||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.context = layer.context
|
||||||
|
self.userLocation = layer.userLocation
|
||||||
|
self.file = layer.file
|
||||||
|
|
||||||
|
super.init(layer: layer)
|
||||||
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
@ -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) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,7 +106,7 @@ public final class LottieComponent: Component {
|
|||||||
|
|
||||||
if delay != 0.0 {
|
if delay != 0.0 {
|
||||||
self.isHidden = true
|
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 {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,13 +43,13 @@ final class DataCategoriesComponent: Component {
|
|||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let categories: [CategoryData]
|
let categories: [CategoryData]
|
||||||
let toggleCategoryExpanded: (DataUsageScreenComponent.Category) -> Void
|
let toggleCategoryExpanded: ((DataUsageScreenComponent.Category) -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
categories: [CategoryData],
|
categories: [CategoryData],
|
||||||
toggleCategoryExpanded: @escaping (DataUsageScreenComponent.Category) -> Void
|
toggleCategoryExpanded: ((DataUsageScreenComponent.Category) -> Void)?
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
@ -138,11 +138,11 @@ final class DataCategoriesComponent: Component {
|
|||||||
category: category,
|
category: category,
|
||||||
isExpanded: category.isExpanded,
|
isExpanded: category.isExpanded,
|
||||||
hasNext: i != component.categories.count - 1,
|
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 {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.toggleCategoryExpanded(key)
|
component.toggleCategoryExpanded?(key)
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
|
|||||||
@ -52,7 +52,7 @@ private final class SubItemComponent: Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
class View: HighlightTrackingButton {
|
class View: UIView {
|
||||||
private let iconView: UIImageView
|
private let iconView: UIImageView
|
||||||
private let title = ComponentView<Empty>()
|
private let title = ComponentView<Empty>()
|
||||||
private let titleValue = ComponentView<Empty>()
|
private let titleValue = ComponentView<Empty>()
|
||||||
@ -74,7 +74,7 @@ private final class SubItemComponent: Component {
|
|||||||
|
|
||||||
self.addSubview(self.iconView)
|
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 {
|
guard let self, let component = self.component, let highlightBackgroundFrame = self.highlightBackgroundFrame else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -103,7 +103,7 @@ private final class SubItemComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||||
self.isUserInteractionEnabled = false
|
self.isEnabled = false*/
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -243,7 +243,7 @@ final class DataCategoryItemComponent: Component {
|
|||||||
let category: DataCategoriesComponent.CategoryData
|
let category: DataCategoriesComponent.CategoryData
|
||||||
let isExpanded: Bool
|
let isExpanded: Bool
|
||||||
let hasNext: Bool
|
let hasNext: Bool
|
||||||
let action: (DataUsageScreenComponent.Category) -> Void
|
let action: ((DataUsageScreenComponent.Category) -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
@ -251,7 +251,7 @@ final class DataCategoryItemComponent: Component {
|
|||||||
category: DataCategoriesComponent.CategoryData,
|
category: DataCategoriesComponent.CategoryData,
|
||||||
isExpanded: Bool,
|
isExpanded: Bool,
|
||||||
hasNext: Bool,
|
hasNext: Bool,
|
||||||
action: @escaping (DataUsageScreenComponent.Category) -> Void
|
action: ((DataUsageScreenComponent.Category) -> Void)?
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
@ -350,14 +350,14 @@ final class DataCategoryItemComponent: Component {
|
|||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.action(component.category.key)
|
component.action?(component.category.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func checkPressed() {
|
@objc private func checkPressed() {
|
||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.action(component.category.key)
|
component.action?(component.category.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
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)))
|
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)
|
return CGSize(width: availableSize.width, height: height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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))
|
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 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 {
|
if backButtonNode.isHidden {
|
||||||
backButtonNode.alpha = 0.0
|
backButtonNode.alpha = 0.0
|
||||||
backButtonNode.isHidden = false
|
backButtonNode.isHidden = false
|
||||||
@ -480,7 +482,7 @@ final class DataUsageScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1035,8 +1037,7 @@ final class DataUsageScreenComponent: Component {
|
|||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
categories: totalCategories,
|
categories: totalCategories,
|
||||||
toggleCategoryExpanded: { _ in
|
toggleCategoryExpanded: nil
|
||||||
}
|
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude)
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude)
|
||||||
|
|||||||
@ -522,7 +522,7 @@ final class PieChartComponent: Component {
|
|||||||
|
|
||||||
let fractionValue: Double = floor(displayValue * 100.0 * 10.0) / 10.0
|
let fractionValue: Double = floor(displayValue * 100.0 * 10.0) / 10.0
|
||||||
let fractionString: String
|
let fractionString: String
|
||||||
if fractionValue == 0.0 {
|
if displayValue == 0.0 {
|
||||||
fractionString = ""
|
fractionString = ""
|
||||||
} else if fractionValue < 0.1 {
|
} else if fractionValue < 0.1 {
|
||||||
fractionString = "<0.1%"
|
fractionString = "<0.1%"
|
||||||
|
|||||||
@ -1310,6 +1310,8 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
self.isInForegroundPromise.set(true)
|
self.isInForegroundPromise.set(true)
|
||||||
self.isActiveValue = true
|
self.isActiveValue = true
|
||||||
self.isActivePromise.set(true)
|
self.isActivePromise.set(true)
|
||||||
|
|
||||||
|
self.runForegroundTasks()
|
||||||
}
|
}
|
||||||
|
|
||||||
if UIApplication.shared.isStatusBarHidden {
|
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) {
|
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||||
|
|||||||
@ -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) {
|
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -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 {
|
guard let strongSelf = self else {
|
||||||
return
|
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 {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,17 +30,17 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
|
|||||||
self.presentationInterfaceState = interfaceState
|
self.presentationInterfaceState = interfaceState
|
||||||
}
|
}
|
||||||
|
|
||||||
let bannedPermission: (Int32, Bool)?
|
var bannedPermission: (Int32, Bool)?
|
||||||
if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel {
|
if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||||
bannedPermission = channel.hasBannedPermission(.banSendText)
|
if let value = channel.hasBannedPermission(.banSendText) {
|
||||||
} else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup {
|
bannedPermission = value
|
||||||
if group.hasBannedPermission(.banSendText) {
|
} 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)
|
bannedPermission = (Int32.max, false)
|
||||||
} else {
|
|
||||||
bannedPermission = nil
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
bannedPermission = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var iconImage: UIImage?
|
var iconImage: UIImage?
|
||||||
|
|||||||
@ -30,6 +30,9 @@ import ComponentFlow
|
|||||||
import EmojiSuggestionsComponent
|
import EmojiSuggestionsComponent
|
||||||
import AudioToolbox
|
import AudioToolbox
|
||||||
import ChatControllerInteraction
|
import ChatControllerInteraction
|
||||||
|
import UndoUI
|
||||||
|
import PremiumUI
|
||||||
|
import StickerPeekUI
|
||||||
|
|
||||||
private let accessoryButtonFont = Font.medium(14.0)
|
private let accessoryButtonFont = Font.medium(14.0)
|
||||||
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
|
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
|
||||||
@ -2571,6 +2574,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
viewForOverlayContent.addSubview(currentEmojiSuggestionView)
|
viewForOverlayContent.addSubview(currentEmojiSuggestionView)
|
||||||
|
|
||||||
currentEmojiSuggestionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
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)
|
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) {
|
private func updateTextNodeText(animated: Bool) {
|
||||||
var inputHasText = false
|
var inputHasText = false
|
||||||
var hideMicButton = false
|
var hideMicButton = false
|
||||||
|
|||||||
@ -423,7 +423,7 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
return nil
|
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 {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,8 +46,8 @@ private struct HorizontalListContextResultsChatInputContextPanelEntry: Comparabl
|
|||||||
return lhs.index < rhs.index
|
return lhs.index < rhs.index
|
||||||
}
|
}
|
||||||
|
|
||||||
func item(account: Account, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> ListViewItem {
|
func item(context: AccountContext, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> ListViewItem {
|
||||||
return HorizontalListContextResultsChatInputPanelItem(account: account, theme: self.theme, result: self.result, resultSelected: resultSelected)
|
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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
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 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(account: account, 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)
|
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)?
|
var selectedItemNodeAndContent: (UIView, CGRect, PeekControllerContent)?
|
||||||
|
selectedItemNodeAndContent = nil
|
||||||
strongSelf.listView.forEachItemNode { itemNode in
|
strongSelf.listView.forEachItemNode { itemNode in
|
||||||
if itemNode.frame.contains(convertedPoint), let itemNode = itemNode as? HorizontalListContextResultsChatInputPanelItemNode, let item = itemNode.item {
|
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 {
|
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 {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -230,7 +231,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
|
|||||||
f(.default)
|
f(.default)
|
||||||
let _ = item.resultSelected(item.result, itemNode, itemNode.bounds)
|
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 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 {
|
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
||||||
return interfaceInteraction.sendContextResult(results, result, node, rect)
|
return interfaceInteraction.sendContextResult(results, result, node, rect)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -18,15 +18,15 @@ import SoftwareVideo
|
|||||||
import MultiplexedVideoNode
|
import MultiplexedVideoNode
|
||||||
|
|
||||||
final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
|
final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
|
||||||
let account: Account
|
let context: AccountContext
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let result: ChatContextResult
|
let result: ChatContextResult
|
||||||
let resultSelected: (ChatContextResult, ASDisplayNode, CGRect) -> Bool
|
let resultSelected: (ChatContextResult, ASDisplayNode, CGRect) -> Bool
|
||||||
|
|
||||||
let selectable: Bool = true
|
let selectable: Bool = true
|
||||||
|
|
||||||
public init(account: Account, theme: PresentationTheme, result: ChatContextResult, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) {
|
public init(context: AccountContext, theme: PresentationTheme, result: ChatContextResult, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) {
|
||||||
self.account = account
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.result = result
|
self.result = result
|
||||||
self.resultSelected = resultSelected
|
self.resultSelected = resultSelected
|
||||||
@ -265,9 +265,9 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let file = videoFile {
|
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 {
|
} else if let imageResource = imageResource {
|
||||||
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
|
updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource)
|
||||||
}
|
}
|
||||||
case let .internalReference(internalReference):
|
case let .internalReference(internalReference):
|
||||||
if let image = internalReference.image {
|
if let image = internalReference.image {
|
||||||
@ -296,12 +296,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
|
|||||||
if file.isVideo && file.isAnimated {
|
if file.isVideo && file.isAnimated {
|
||||||
videoFile = file
|
videoFile = file
|
||||||
imageResource = nil
|
imageResource = nil
|
||||||
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource)
|
updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(file.resource)
|
||||||
} else if let imageResource = imageResource {
|
} 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 {
|
} 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 updatedImageResource {
|
||||||
if let imageResource = imageResource {
|
if let imageResource = imageResource {
|
||||||
if let stickerFile = stickerFile {
|
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 {
|
} 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 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: [])
|
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 {
|
} else {
|
||||||
updateImageSignal = .complete()
|
updateImageSignal = .complete()
|
||||||
@ -391,7 +391,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let videoFile = videoFile {
|
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)
|
thumbnailLayer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
strongSelf.addSubnode(thumbnailLayer)
|
strongSelf.addSubnode(thumbnailLayer)
|
||||||
let layerHolder = takeSampleBufferLayer()
|
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)
|
layerHolder.layer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
strongSelf.layer.addSublayer(layerHolder.layer)
|
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)
|
strongSelf.videoLayer = (thumbnailLayer, manager, layerHolder)
|
||||||
thumbnailLayer.ready = { [weak thumbnailLayer, weak manager] in
|
thumbnailLayer.ready = { [weak thumbnailLayer, weak manager] in
|
||||||
if let strongSelf = self, let thumbnailLayer = thumbnailLayer, let manager = manager {
|
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 dimensions = animatedStickerFile.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))
|
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())
|
strongSelf.fetchDisposable.set(freeMediaFileResourceInteractiveFetched(account: item.context.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)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
guard let strongSelf = self, let controllerInteraction = strongSelf.getControllerInteraction?() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -266,10 +266,12 @@ private final class DownloadedMediaStoreManagerPrivateImpl {
|
|||||||
|
|
||||||
final class DownloadedMediaStoreManagerImpl: DownloadedMediaStoreManager {
|
final class DownloadedMediaStoreManagerImpl: DownloadedMediaStoreManager {
|
||||||
private let queue = Queue()
|
private let queue = Queue()
|
||||||
|
private let postbox: Postbox
|
||||||
private let impl: QueueLocalObject<DownloadedMediaStoreManagerPrivateImpl>
|
private let impl: QueueLocalObject<DownloadedMediaStoreManagerPrivateImpl>
|
||||||
|
|
||||||
init(postbox: Postbox, accountManager: AccountManager<TelegramAccountManagerTypes>) {
|
init(postbox: Postbox, accountManager: AccountManager<TelegramAccountManagerTypes>) {
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
|
self.postbox = postbox
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
return DownloadedMediaStoreManagerPrivateImpl(queue: queue, postbox: postbox, accountManager: accountManager)
|
return DownloadedMediaStoreManagerPrivateImpl(queue: queue, postbox: postbox, accountManager: accountManager)
|
||||||
})
|
})
|
||||||
@ -280,4 +282,27 @@ final class DownloadedMediaStoreManagerImpl: DownloadedMediaStoreManager {
|
|||||||
impl.store(media, timestamp: timestamp, peerId: peerId)
|
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()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user