diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index b077464c25..93c61932a1 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11594,3 +11594,5 @@ Sorry for the inconvenience."; "Chat.QuickReplyMediaMessageLimitReachedText_1" = "There can be at most %d message in this chat."; "Chat.QuickReplyMediaMessageLimitReachedText_any" = "There can be at most %d messages in this chat."; + +"Stickers.Edit" = "EDIT"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index bdb918f337..6dd57dce8d 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -982,10 +982,13 @@ public protocol SharedAccountContext: AnyObject { func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController - func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController + func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], isEditing: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController +// func makeStickerEditorScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, initialSticker: TelegramMediaFile?, targetStickerPack: StickerPackReference?) -> ViewController + + func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController diff --git a/submodules/ContextUI/Sources/PeekController.swift b/submodules/ContextUI/Sources/PeekController.swift index 3d9c459a33..c76409fbee 100644 --- a/submodules/ContextUI/Sources/PeekController.swift +++ b/submodules/ContextUI/Sources/PeekController.swift @@ -45,9 +45,11 @@ public final class PeekController: ViewController, ContextControllerProtocol { } public func pushItems(items: Signal) { + self.controllerNode.pushItems(items: items) } public func popItems() { + self.controllerNode.popItems() } private var controllerNode: PeekControllerNode { @@ -61,6 +63,7 @@ public final class PeekController: ViewController, ContextControllerProtocol { private let presentationData: PresentationData private let content: PeekControllerContent var sourceView: () -> (UIView, CGRect)? + private let activateImmediately: Bool public var visibilityUpdated: ((Bool) -> Void)? @@ -73,10 +76,11 @@ public final class PeekController: ViewController, ContextControllerProtocol { return self._ready } - public init(presentationData: PresentationData, content: PeekControllerContent, sourceView: @escaping () -> (UIView, CGRect)?) { + public init(presentationData: PresentationData, content: PeekControllerContent, sourceView: @escaping () -> (UIView, CGRect)?, activateImmediately: Bool = false) { self.presentationData = presentationData self.content = content self.sourceView = sourceView + self.activateImmediately = activateImmediately super.init(navigationBarPresentationData: nil) @@ -111,6 +115,10 @@ public final class PeekController: ViewController, ContextControllerProtocol { self.controllerNode.animateIn(from: self.getSourceRect()) self.visibilityUpdated?(true) + + if self.activateImmediately { + self.controllerNode.activateMenu() + } } } diff --git a/submodules/ContextUI/Sources/PeekControllerGestureRecognizer.swift b/submodules/ContextUI/Sources/PeekControllerGestureRecognizer.swift index a766664a2c..26e1025f13 100644 --- a/submodules/ContextUI/Sources/PeekControllerGestureRecognizer.swift +++ b/submodules/ContextUI/Sources/PeekControllerGestureRecognizer.swift @@ -22,6 +22,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { private let present: (PeekControllerContent, UIView, CGRect) -> ViewController? private let updateContent: (PeekControllerContent?) -> Void private let activateBySingleTap: Bool + public var longPressEnabled = true public var checkSingleTapActivationAtPoint: ((CGPoint) -> Bool)? private var tapLocation: CGPoint? @@ -54,6 +55,9 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { } private func startLongTapTimer() { + guard self.longPressEnabled else { + return + } self.longTapTimer?.invalidate() let longTapTimer = SwiftSignalKit.Timer(timeout: 0.4, repeat: false, completion: { [weak self] in self?.longTapTimerFired() diff --git a/submodules/ContextUI/Sources/PeekControllerNode.swift b/submodules/ContextUI/Sources/PeekControllerNode.swift index f2bbdafc5b..eb5454e783 100644 --- a/submodules/ContextUI/Sources/PeekControllerNode.swift +++ b/submodules/ContextUI/Sources/PeekControllerNode.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import AsyncDisplayKit import Display +import SwiftSignalKit import TelegramPresentationData private let animationDurationFactor: Double = 1.0 @@ -28,11 +29,11 @@ final class PeekControllerNode: ViewControllerTracingNode { private var topAccessoryNode: ASDisplayNode? private var fullScreenAccessoryNode: (PeekControllerAccessoryNode & ASDisplayNode)? - - private var actionsContainerNode: ContextActionsContainerNode + + private var actionsStackNode: ContextControllerActionsStackNode private var hapticFeedback = HapticFeedback() - + private var initialContinueGesturePoint: CGPoint? private var didMoveFromInitialGesturePoint = false private var highlightedActionNode: ContextActionNodeProtocol? @@ -55,12 +56,12 @@ final class PeekControllerNode: ViewControllerTracingNode { self.darkDimNode.isUserInteractionEnabled = false switch content.menuActivation() { - case .drag: - self.dimNode.backgroundColor = nil - self.blurView.alpha = 1.0 - case .press: - self.dimNode.backgroundColor = UIColor(white: self.theme.isDark ? 0.0 : 1.0, alpha: 0.5) - self.blurView.alpha = 0.0 + case .drag: + self.dimNode.backgroundColor = nil + self.blurView.alpha = 1.0 + case .press: + self.dimNode.backgroundColor = UIColor(white: self.theme.isDark ? 0.0 : 1.0, alpha: 0.5) + self.blurView.alpha = 0.0 } self.containerBackgroundNode = ASImageNode() @@ -75,28 +76,50 @@ final class PeekControllerNode: ViewControllerTracingNode { self.fullScreenAccessoryNode = content.fullScreenAccessoryNode(blurView: blurView) self.fullScreenAccessoryNode?.alpha = 0.0 - var feedbackTapImpl: (() -> Void)? var activatedActionImpl: (() -> Void)? - var requestLayoutImpl: (() -> Void)? - self.actionsContainerNode = ContextActionsContainerNode(presentationData: presentationData, items: ContextController.Items(content: .list(content.menuItems()), animationCache: nil), getController: { [weak controller] in - return controller - }, actionSelected: { result in - activatedActionImpl?() - }, requestLayout: { - requestLayoutImpl?() - }, feedbackTap: { - feedbackTapImpl?() - }, blurBackground: true) - self.actionsContainerNode.alpha = 0.0 + var requestLayoutImpl: ((ContainedViewLayoutTransition) -> Void)? - super.init() + self.actionsStackNode = ContextControllerActionsStackNode( + getController: { [weak controller] in + return controller + }, + requestDismiss: { result in + activatedActionImpl?() + }, + requestUpdate: { transition in + requestLayoutImpl?(transition) + } + ) + self.actionsStackNode.alpha = 0.0 - feedbackTapImpl = { [weak self] in - self?.hapticFeedback.tap() + let items = ContextController.Items( + id: 0, + content: .list(content.menuItems()), + context: nil, + reactionItems: [], + selectedReactionItems: Set(), + reactionsTitle: nil, + reactionsLocked: false, + animationCache: nil, + alwaysAllowPremiumReactions: false, + allPresetReactionsAreAvailable: false, + getEmojiContent: nil, + disablePositionLock: false, + tip: nil, + tipSignal: nil, + dismissed: nil + ) + if let item = makeContextControllerActionsStackItem(items: items).first { + self.actionsStackNode.replace( + item: item, + animated: false + ) } - - requestLayoutImpl = { [weak self] in - self?.updateLayout() + + super.init() + + requestLayoutImpl = { [weak self] transition in + self?.updateLayout(transition: transition) } if content.presentation() == .freeform { @@ -112,7 +135,7 @@ final class PeekControllerNode: ViewControllerTracingNode { self.containerNode.addSubnode(self.contentNode) self.addSubnode(self.containerNode) - self.addSubnode(self.actionsContainerNode) + self.addSubnode(self.actionsStackNode) if let fullScreenAccessoryNode = self.fullScreenAccessoryNode { self.fullScreenAccessoryNode?.dismiss = { [weak self] in @@ -139,13 +162,41 @@ final class PeekControllerNode: ViewControllerTracingNode { self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTap(_:)))) self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))) } - - func updateLayout() { + + func updateLayout(transition: ContainedViewLayoutTransition = .immediate) { if let layout = self.validLayout { - self.containerLayoutUpdated(layout, transition: .immediate) + self.containerLayoutUpdated(layout, transition: transition) } } + func replaceItem(items: Signal) { + let _ = (items + |> deliverOnMainQueue).start(next: { [weak self] items in + guard let self else { + return + } + if let item = makeContextControllerActionsStackItem(items: items).first { + self.actionsStackNode.replace(item: item, animated: false) + } + }) + } + + func pushItems(items: Signal) { + let _ = (items + |> deliverOnMainQueue).start(next: { [weak self] items in + guard let self else { + return + } + if let item = makeContextControllerActionsStackItem(items: items).first { + self.actionsStackNode.push(item: item, currentScrollingState: nil, positionLock: nil, animated: true) + } + }) + } + + func popItems() { + self.actionsStackNode.pop() + } + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.validLayout = layout @@ -172,12 +223,18 @@ final class PeekControllerNode: ViewControllerTracingNode { } let actionsSideInset: CGFloat = layout.safeInsets.left + 11.0 - let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, presentation: .inline, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: .immediate) + + let actionsSize = self.actionsStackNode.update( + presentationData: self.presentationData, + constrainedSize: CGSize(width: layout.size.width - actionsSideInset * 2.0, height: layout.size.height), + presentation: .inline, + transition: transition + ) let containerFrame: CGRect let actionsFrame: CGRect if layout.size.width > layout.size.height { - if self.actionsContainerNode.alpha.isZero { + if self.actionsStackNode.alpha.isZero { containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize) } else { containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 3.0), y: floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize) @@ -194,12 +251,11 @@ final class PeekControllerNode: ViewControllerTracingNode { } transition.updateFrame(node: self.containerNode, frame: containerFrame) - self.actionsContainerNode.updateSize(containerSize: actionsSize, contentSize: actionsSize) - transition.updateFrame(node: self.actionsContainerNode, frame: actionsFrame) + transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame) if let fullScreenAccessoryNode = self.fullScreenAccessoryNode { - fullScreenAccessoryNode.updateLayout(size: layout.size, transition: transition) transition.updateFrame(node: fullScreenAccessoryNode, frame: CGRect(origin: .zero, size: layout.size)) + fullScreenAccessoryNode.updateLayout(size: layout.size, transition: transition) } self.contentNodeHasValidLayout = true @@ -244,11 +300,11 @@ final class PeekControllerNode: ViewControllerTracingNode { self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) self.containerNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false) - if !self.actionsContainerNode.alpha.isZero { - let actionsOffset = CGPoint(x: rect.midX - self.actionsContainerNode.position.x, y: rect.midY - self.actionsContainerNode.position.y) - self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false) - self.actionsContainerNode.layer.animateSpring(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false) - self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint()), to: NSValue(cgPoint: actionsOffset), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true) + if !self.actionsStackNode.alpha.isZero { + let actionsOffset = CGPoint(x: rect.midX - self.actionsStackNode.position.x, y: rect.midY - self.actionsStackNode.position.y) + self.actionsStackNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false) + self.actionsStackNode.layer.animateSpring(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false) + self.actionsStackNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint()), to: NSValue(cgPoint: actionsOffset), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true) } if let fullScreenAccessoryNode = self.fullScreenAccessoryNode, !fullScreenAccessoryNode.alpha.isZero { @@ -289,7 +345,7 @@ final class PeekControllerNode: ViewControllerTracingNode { initialPoint = localPoint self.initialContinueGesturePoint = localPoint } - if !self.actionsContainerNode.alpha.isZero { + if !self.actionsStackNode.alpha.isZero { if !self.didMoveFromInitialGesturePoint { let distance = abs(localPoint.y - initialPoint.y) if distance > 12.0 { @@ -297,16 +353,19 @@ final class PeekControllerNode: ViewControllerTracingNode { } } if self.didMoveFromInitialGesturePoint { - let actionPoint = self.view.convert(localPoint, to: self.actionsContainerNode.view) - let actionNode = self.actionsContainerNode.actionNode(at: actionPoint) - if self.highlightedActionNode !== actionNode { - self.highlightedActionNode?.setIsHighlighted(false) - self.highlightedActionNode = actionNode - if let actionNode = actionNode { - actionNode.setIsHighlighted(true) - self.hapticFeedback.tap() - } - } + let actionPoint = self.view.convert(localPoint, to: self.actionsStackNode.view) + self.actionsStackNode.highlightGestureMoved(location: actionPoint) + } + } + } + + func endDragging(_ location: CGPoint) { + if self.didMoveFromInitialGesturePoint { + self.actionsStackNode.highlightGestureFinished(performAction: true) + } else if self.actionsStackNode.alpha.isZero { + if let fullScreenAccessoryNode = self.fullScreenAccessoryNode, !fullScreenAccessoryNode.alpha.isZero { + } else { + self.requestDismiss() } } } @@ -322,6 +381,11 @@ final class PeekControllerNode: ViewControllerTracingNode { self.blurView.layer.animateAlpha(from: previousBlurAlpha, to: self.blurView.alpha, duration: 0.3) } return + } else { + if let fullScreenAccessoryNode = self.fullScreenAccessoryNode { + fullScreenAccessoryNode.alpha = 1.0 + fullScreenAccessoryNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } } if case .press = self.content.menuActivation() { self.hapticFeedback.impact() @@ -338,32 +402,20 @@ final class PeekControllerNode: ViewControllerTracingNode { self.darkDimNode.alpha = 1.0 self.darkDimNode.layer.animateAlpha(from: previousDarkDimAlpha, to: 1.0, duration: 0.3) - self.actionsContainerNode.alpha = 1.0 - self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor) - self.actionsContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping) - - let localContentSourceFrame = self.containerNode.frame - self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localContentSourceFrame.center.x - self.actionsContainerNode.position.x, y: localContentSourceFrame.center.y - self.actionsContainerNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true) + Queue.mainQueue().justDispatch { + self.actionsStackNode.alpha = 1.0 + self.actionsStackNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor) + self.actionsStackNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping) + + let localContentSourceFrame = self.containerNode.frame + self.actionsStackNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localContentSourceFrame.center.x - self.actionsStackNode.position.x, y: localContentSourceFrame.center.y - self.actionsStackNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true) + } if let layout = self.validLayout { self.containerLayoutUpdated(layout, transition: .animated(duration: springDuration, curve: .spring)) } } - func endDragging(_ location: CGPoint) { - if self.didMoveFromInitialGesturePoint { - if let highlightedActionNode = self.highlightedActionNode { - self.highlightedActionNode = nil - highlightedActionNode.performAction() - } - } else if self.actionsContainerNode.alpha.isZero { - if let fullScreenAccessoryNode = self.fullScreenAccessoryNode, !fullScreenAccessoryNode.alpha.isZero { - } else { - self.requestDismiss() - } - } - } - func updateContent(content: PeekControllerContent) { let contentNode = self.contentNode contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak contentNode] _ in @@ -376,19 +428,7 @@ final class PeekControllerNode: ViewControllerTracingNode { self.containerNode.addSubnode(self.contentNode) self.contentNodeHasValidLayout = false - let previousActionsContainerNode = self.actionsContainerNode - self.actionsContainerNode = ContextActionsContainerNode(presentationData: self.presentationData, items: ContextController.Items(content: .list(content.menuItems()), animationCache: nil), getController: { [weak self] in - return self?.controller - }, actionSelected: { [weak self] result in - self?.requestDismiss() - }, requestLayout: { [weak self] in - self?.updateLayout() - }, feedbackTap: { [weak self] in - self?.hapticFeedback.tap() - }, blurBackground: true) - self.actionsContainerNode.alpha = 0.0 - self.insertSubnode(self.actionsContainerNode, aboveSubnode: previousActionsContainerNode) - previousActionsContainerNode.removeFromSupernode() + self.replaceItem(items: .single(ContextController.Items(content: .list(content.menuItems())))) self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) self.contentNode.layer.animateSpring(from: 0.35 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index a6d3392043..4e71fb78ab 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -5,7 +5,7 @@ import SwiftSignalKit private var backArrowImageCache: [Int32: UIImage] = [:] open class SparseNode: ASDisplayNode { - override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.alpha.isZero { return nil } diff --git a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift index 34253dde77..7d50682a93 100644 --- a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift +++ b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift @@ -145,7 +145,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { self.angleLayer.opacity = 0.0 self.angleLayer.lineDashPattern = [12, 12] as [NSNumber] - self.stickerOverlayLayer.fillColor = UIColor(rgb: 0x000000, alpha: 0.6).cgColor + self.stickerOverlayLayer.fillColor = UIColor(rgb: 0x000000, alpha: 0.7).cgColor self.stickerFrameLayer.fillColor = UIColor.clear.cgColor self.stickerFrameLayer.strokeColor = UIColor(rgb: 0xffffff, alpha: 0.55).cgColor diff --git a/submodules/DrawingUI/Sources/DrawingReactionView.swift b/submodules/DrawingUI/Sources/DrawingReactionView.swift index 7a3b597a52..a86449a87e 100644 --- a/submodules/DrawingUI/Sources/DrawingReactionView.swift +++ b/submodules/DrawingUI/Sources/DrawingReactionView.swift @@ -177,7 +177,7 @@ public class DrawingReactionEntityView: DrawingStickerEntityView { reactionContextNode.forceTailToRight = true reactionContextNode.forceDark = true self.reactionContextNode = reactionContextNode - + reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in guard let self else { return diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index 8ce1998d79..4688708e5c 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -1012,7 +1012,7 @@ private final class DrawingScreenComponent: CombinedComponent { self.currentMode = .sticker self.updateEntitiesPlayback.invoke(false) - let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get()) + let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get(), hasInteractiveStickers: false) if let presentGallery = self.presentGallery { controller.presentGallery = presentGallery } diff --git a/submodules/DrawingUI/Sources/StickerPickerScreen.swift b/submodules/DrawingUI/Sources/StickerPickerScreen.swift index 270874cdbc..a7c1e190ad 100644 --- a/submodules/DrawingUI/Sources/StickerPickerScreen.swift +++ b/submodules/DrawingUI/Sources/StickerPickerScreen.swift @@ -991,6 +991,7 @@ public class StickerPickerScreen: ViewController { context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet) } }, + editAction: { _ in }, pushController: { c in }, presentController: { c in @@ -1122,6 +1123,7 @@ public class StickerPickerScreen: ViewController { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -1177,6 +1179,7 @@ public class StickerPickerScreen: ViewController { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -1208,6 +1211,7 @@ public class StickerPickerScreen: ViewController { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -1386,6 +1390,7 @@ public class StickerPickerScreen: ViewController { } else if groupId == AnyHashable("peerSpecific") { } }, + editAction: { _ in }, pushController: { c in }, presentController: { c in @@ -1450,6 +1455,7 @@ public class StickerPickerScreen: ViewController { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -1481,6 +1487,7 @@ public class StickerPickerScreen: ViewController { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -1507,7 +1514,7 @@ public class StickerPickerScreen: ViewController { customLayout: nil, externalBackground: nil, externalExpansionView: nil, - customContentView: controller.hasGifs ? self.storyStickersContentView : nil, + customContentView: controller.hasInteractiveStickers ? self.storyStickersContentView : nil, useOpaqueTheme: false, hideBackground: true, stateContext: nil, @@ -1966,6 +1973,7 @@ public class StickerPickerScreen: ViewController { private let inputData: Signal fileprivate let defaultToEmoji: Bool let hasGifs: Bool + let hasInteractiveStickers: Bool private var currentLayout: ContainerViewLayout? @@ -1980,12 +1988,13 @@ public class StickerPickerScreen: ViewController { public var addReaction: () -> Void = { } public var addCamera: () -> Void = { } - public init(context: AccountContext, inputData: Signal, defaultToEmoji: Bool = false, hasGifs: Bool = false) { + public init(context: AccountContext, inputData: Signal, defaultToEmoji: Bool = false, hasGifs: Bool = false, hasInteractiveStickers: Bool = true) { self.context = context self.theme = defaultDarkColorPresentationTheme self.inputData = inputData self.defaultToEmoji = defaultToEmoji self.hasGifs = hasGifs + self.hasInteractiveStickers = hasInteractiveStickers super.init(navigationBarPresentationData: nil) diff --git a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift index 3a25687876..be09ed33a2 100644 --- a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift +++ b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift @@ -496,8 +496,8 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { } if let searchNode = strongSelf.searchNode, searchNode.isActive { if let (itemNode, item) = searchNode.itemAt(point: strongSelf.view.convert(point, to: searchNode.view)) { - if let item = item as? StickerPreviewPeekItem { - return strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId) + if let item = item as? StickerPreviewPeekItem, let file = item.file { + return strongSelf.context.engine.stickers.isStickerSaved(id: file.fileId) |> deliverOnMainQueue |> map { isStarred -> (UIView, CGRect, PeekControllerContent)? in if let strongSelf = self { @@ -506,9 +506,9 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { .action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in if let strongSelf = self, let peekController = strongSelf.peekController { if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { - let _ = strongSelf.sendSticker?(.standalone(media: item.file), animationNode.view, animationNode.bounds) + let _ = strongSelf.sendSticker?(.standalone(media: file), animationNode.view, animationNode.bounds) } else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode { - let _ = strongSelf.sendSticker?(.standalone(media: item.file), imageNode.view, imageNode.bounds) + let _ = strongSelf.sendSticker?(.standalone(media: file), imageNode.view, imageNode.bounds) } } f(.default) @@ -517,11 +517,11 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { f(.default) if let strongSelf = self { - let _ = (strongSelf.context.engine.stickers.toggleStickerSaved(file: item.file, saved: !isStarred) + let _ = (strongSelf.context.engine.stickers.toggleStickerSaved(file: file, saved: !isStarred) |> deliverOnMainQueue).start(next: { result in switch result { case .generic: - strongSelf.controller?.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: item.file, loop: true, title: nil, text: !isStarred ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), with: nil) + strongSelf.controller?.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, loop: true, title: nil, text: !isStarred ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), with: nil) case let .limitExceeded(limit, premiumLimit): let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) let text: String @@ -530,7 +530,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { } else { text = strongSelf.presentationData.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string } - strongSelf.controller?.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: item.file, loop: true, title: strongSelf.presentationData.strings.Premium_MaxFavedStickersTitle("\(limit)").string, text: text, undoText: nil, customAction: nil), elevatedLayout: false, action: { [weak self] action in + strongSelf.controller?.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, loop: true, title: strongSelf.presentationData.strings.Premium_MaxFavedStickersTitle("\(limit)").string, text: text, undoText: nil, customAction: nil), elevatedLayout: false, action: { [weak self] action in if let strongSelf = self { if case .info = action { let controller = PremiumIntroScreen(context: strongSelf.context, source: .savedStickers) @@ -548,7 +548,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { f(.default) if let strongSelf = self { - loop: for attribute in item.file.attributes { + loop: for attribute in file.attributes { switch attribute { case let .Sticker(_, packReference, _): if let packReference = packReference { diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift index 1a64cdec19..6e39581e13 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift @@ -14,6 +14,7 @@ import ContextUI import RadialStatusNode import UndoUI import StickerPackPreviewUI +import StickerPackEditTitleController private struct StickerPackPreviewGridEntry: Comparable, Equatable, Identifiable { let index: Int @@ -718,7 +719,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll @objc private func createActionButtonPressed() { var proceedImpl: ((String, String?) -> Void)? - let titleController = importStickerPackTitleController(context: self.context, title: self.presentationData.strings.ImportStickerPack_ChooseName, text: self.presentationData.strings.ImportStickerPack_ChooseNameDescription, placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, value: nil, maxLength: 128, apply: { [weak self] title in + let titleController = stickerPackEditTitleController(context: self.context, title: self.presentationData.strings.ImportStickerPack_ChooseName, text: self.presentationData.strings.ImportStickerPack_ChooseNameDescription, placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, value: nil, maxLength: 128, apply: { [weak self] title in if let strongSelf = self, let title = title { strongSelf.shortNameSuggestionDisposable.set((strongSelf.context.engine.stickers.getStickerSetShortNameSuggestion(title: title) |> deliverOnMainQueue).start(next: { suggestedShortName in diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 45682f7729..c4859c5482 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -678,7 +678,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if !controller.didSetupGroups { controller.didSetupGroups = true - Queue.concurrentDefaultQueue().after(0.3) { + Queue.concurrentDefaultQueue().after(0.4) { + var isCreateSticker = false + if case .assets(_, .createSticker) = controller.subject { + isCreateSticker = true + } controller.groupsPromise.set( combineLatest( self.mediaAssetsContext.fetchAssetsCollections(.album), @@ -707,6 +711,16 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { supportedAlbums.append(.smartAlbumDepthEffect) supportedAlbums.append(.smartAlbumLivePhotos) } + + if isCreateSticker { + supportedAlbums = supportedAlbums.filter { type in + if type == .smartAlbumSlomoVideos || type == .smartAlbumTimelapses || type == .smartAlbumVideos { + return false + } + return true + } + } + if supportedAlbums.contains(collection.assetCollectionSubtype) { let result = PHAsset.fetchAssets(in: collection, options: nil) if result.count > 0 { @@ -2590,3 +2604,51 @@ public func storyMediaPickerController( controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) return controller } + +public func stickerMediaPickerController( + context: AccountContext, + getSourceRect: @escaping () -> CGRect, + completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, + dismissed: @escaping () -> Void +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + let updatedPresentationData: (PresentationData, Signal) = (presentationData, .single(presentationData)) + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { + return nil + }) + controller.forceSourceRect = true + controller.getSourceRect = getSourceRect + controller.requestController = { _, present in + let mediaPickerController = MediaPickerScreen(context: context, updatedPresentationData: updatedPresentationData, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .createSticker), mainButtonState: nil, mainButtonAction: nil) + mediaPickerController.customSelection = { controller, result in + if let result = result as? PHAsset { + controller.updateHiddenMediaId(result.localIdentifier) + if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { + let transitionOut: (Bool?) -> (UIView, CGRect)? = { isNew in + if let isNew { + if isNew { + controller.updateHiddenMediaId(nil) + if let transitionView = controller.defaultTransitionView() { + return (transitionView, transitionView.bounds) + } + } else if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { + return (transitionView, transitionView.bounds) + } + } + return nil + } + completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), transitionOut, { [weak controller] in + controller?.updateHiddenMediaId(nil) + }) + } + } + } + present(mediaPickerController, mediaPickerController.mediaPickerContext) + } + controller.willDismiss = { + dismissed() + } + controller.navigationPresentation = .flatModal + controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + return controller +} diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index 9d05f7ea39..a1c87d391d 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -1163,7 +1163,6 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer { } } - private var currentItemNode: ASDisplayNode? override public func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) diff --git a/submodules/PremiumUI/Resources/coin.scn b/submodules/PremiumUI/Resources/coin.scn index af559b0d9d..3754412983 100644 Binary files a/submodules/PremiumUI/Resources/coin.scn and b/submodules/PremiumUI/Resources/coin.scn differ diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index ae057ad6c0..7355af6502 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -3243,7 +3243,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { if let emojiFile = state?.emojiFile, let controller = environment?.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController { for attribute in emojiFile.attributes { if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference { - let controller = accountContext.sharedContext.makeStickerPackScreen(context: accountContext, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: loadedEmojiPack.flatMap { [$0] } ?? [], parentNavigationController: navigationController, sendSticker: { _, _, _ in + let controller = accountContext.sharedContext.makeStickerPackScreen(context: accountContext, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: loadedEmojiPack.flatMap { [$0] } ?? [], isEditing: false, parentNavigationController: navigationController, sendSticker: { _, _, _ in return false }) presentController(controller) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index fff04dc3e8..edaffa43d0 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -1595,6 +1595,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet) } }, + editAction: { _ in }, pushController: { _ in }, presentController: { _ in @@ -1728,6 +1729,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -1777,6 +1779,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: 3, displayPremiumBadges: false, headerItem: nil, @@ -1836,6 +1839,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -1868,6 +1872,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -2561,6 +2566,23 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { self.isExpandedUpdated(.animated(duration: 0.4, curve: .spring)) } + public func collapse() { + if self.hapticFeedback == nil { + self.hapticFeedback = HapticFeedback() + } + self.hapticFeedback?.tap() + + self.longPressRecognizer?.isEnabled = false + + self.animateFromExtensionDistance = 0.0 + self.extensionDistance = 0.0 + self.visibleExtensionDistance = 0.0 + self.contentTopInset = self.titleLabelHeight ?? 0.0 + self.currentContentHeight = 46.0 + self.isExpanded = false + self.isExpandedUpdated(.animated(duration: 0.4, curve: .spring)) + } + public func highlightGestureMoved(location: CGPoint, hover: Bool) { if self.allPresetReactionsAreAvailable { return diff --git a/submodules/StickerPackPreviewUI/BUILD b/submodules/StickerPackPreviewUI/BUILD index f09cf1c31d..cd47fab563 100644 --- a/submodules/StickerPackPreviewUI/BUILD +++ b/submodules/StickerPackPreviewUI/BUILD @@ -40,6 +40,7 @@ swift_library( "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", "//submodules/StickerPeekUI:StickerPeekUI", "//submodules/Pasteboard:Pasteboard", + "//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController" ], visibility = [ "//visibility:public", diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift index 317605d956..aa2e68337e 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift @@ -28,7 +28,7 @@ private struct StickerPackPreviewGridEntry: Comparable, Identifiable { } func item(context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme) -> StickerPackPreviewGridItem { - return StickerPackPreviewGridItem(context: context, stickerItem: self.stickerItem, interaction: interaction, theme: theme, isPremium: false, isLocked: false, isEmpty: false) + return StickerPackPreviewGridItem(context: context, stickerItem: self.stickerItem, interaction: interaction, theme: theme, isPremium: false, isLocked: false, isEmpty: false, isEditing: false) } } @@ -144,7 +144,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol super.init() - self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false, addStickerPack: { _, _ in }, removeStickerPack: { _ in }, emojiSelected: { _, _ in }, emojiLongPressed: { _, _, _, _ in }) + self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false, addStickerPack: { _, _ in }, removeStickerPack: { _ in }, emojiSelected: { _, _ in }, emojiLongPressed: { _, _, _, _ in }, addPressed: {}) self.backgroundColor = nil self.isOpaque = false diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift index 55e78497e7..a50a91f93a 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift @@ -16,19 +16,22 @@ import TextFormat final class StickerPackPreviewInteraction { var previewedItem: StickerPreviewPeekItem? + var reorderingFileId: MediaId? var playAnimatedStickers: Bool let addStickerPack: (StickerPackCollectionInfo, [StickerPackItem]) -> Void let removeStickerPack: (StickerPackCollectionInfo) -> Void let emojiSelected: (String, ChatTextInputTextCustomEmojiAttribute) -> Void let emojiLongPressed: (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void + let addPressed: () -> Void - init(playAnimatedStickers: Bool, addStickerPack: @escaping (StickerPackCollectionInfo, [StickerPackItem]) -> Void, removeStickerPack: @escaping (StickerPackCollectionInfo) -> Void, emojiSelected: @escaping (String, ChatTextInputTextCustomEmojiAttribute) -> Void, emojiLongPressed: @escaping (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void) { + init(playAnimatedStickers: Bool, addStickerPack: @escaping (StickerPackCollectionInfo, [StickerPackItem]) -> Void, removeStickerPack: @escaping (StickerPackCollectionInfo) -> Void, emojiSelected: @escaping (String, ChatTextInputTextCustomEmojiAttribute) -> Void, emojiLongPressed: @escaping (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void, addPressed: @escaping () -> Void) { self.playAnimatedStickers = playAnimatedStickers self.addStickerPack = addStickerPack self.removeStickerPack = removeStickerPack self.emojiSelected = emojiSelected self.emojiLongPressed = emojiLongPressed + self.addPressed = addPressed } } @@ -40,10 +43,12 @@ final class StickerPackPreviewGridItem: GridItem { let isPremium: Bool let isLocked: Bool let isEmpty: Bool + let isEditing: Bool + let isAdd: Bool let section: GridSection? = nil - init(context: AccountContext, stickerItem: StickerPackItem?, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isPremium: Bool, isLocked: Bool, isEmpty: Bool) { + init(context: AccountContext, stickerItem: StickerPackItem?, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isPremium: Bool, isLocked: Bool, isEmpty: Bool, isEditing: Bool, isAdd: Bool = false) { self.context = context self.stickerItem = stickerItem self.interaction = interaction @@ -51,11 +56,13 @@ final class StickerPackPreviewGridItem: GridItem { self.isPremium = isPremium self.isLocked = isLocked self.isEmpty = isEmpty + self.isEditing = isEditing + self.isAdd = isAdd } func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { let node = StickerPackPreviewGridItemNode() - node.setup(context: self.context, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isLocked: self.isLocked, isPremium: self.isPremium, isEmpty: self.isEmpty) + node.setup(context: self.context, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isLocked: self.isLocked, isPremium: self.isPremium, isEmpty: self.isEmpty, isEditing: self.isEditing, isAdd: self.isAdd) return node } @@ -64,17 +71,18 @@ final class StickerPackPreviewGridItem: GridItem { assertionFailure() return } - node.setup(context: self.context, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isLocked: self.isLocked, isPremium: self.isPremium, isEmpty: self.isEmpty) + node.setup(context: self.context, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isLocked: self.isLocked, isPremium: self.isPremium, isEmpty: self.isEmpty, isEditing: self.isEditing, isAdd: self.isAdd) } } private let textFont = Font.regular(20.0) final class StickerPackPreviewGridItemNode: GridItemNode { - private var currentState: (AccountContext, StickerPackItem?)? + private var currentState: (AccountContext, StickerPackItem?, Bool, Bool)? private var isLocked: Bool? private var isPremium: Bool? private var isEmpty: Bool? + private let containerNode: ASDisplayNode private let imageNode: TransformImageNode private var animationNode: AnimatedStickerNode? private var placeholderNode: StickerShimmerEffectNode @@ -85,6 +93,8 @@ final class StickerPackPreviewGridItemNode: GridItemNode { private var theme: PresentationTheme? + private var isEditing = false + override var isVisibleInGrid: Bool { didSet { let visibility = self.isVisibleInGrid && (self.interaction?.playAnimatedStickers ?? true) @@ -103,14 +113,18 @@ final class StickerPackPreviewGridItemNode: GridItemNode { private let effectFetchedDisposable = MetaDisposable() var interaction: StickerPackPreviewInteraction? - - var selected: (() -> Void)? - + var stickerPackItem: StickerPackItem? { return self.currentState?.1 } + var isAdd: Bool { + return self.currentState?.2 == true + } + override init() { + self.containerNode = ASDisplayNode() + self.imageNode = TransformImageNode() self.imageNode.isLayerBacked = !smartInvertColorsEnabled() self.placeholderNode = StickerShimmerEffectNode() @@ -118,8 +132,9 @@ final class StickerPackPreviewGridItemNode: GridItemNode { super.init() - self.addSubnode(self.imageNode) - self.addSubnode(self.placeholderNode) + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.imageNode) + self.containerNode.addSubnode(self.placeholderNode) var firstTime = true self.imageNode.imageUpdated = { [weak self] image in @@ -172,15 +187,62 @@ final class StickerPackPreviewGridItemNode: GridItemNode { self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:)))) } + @objc private func handleAddTap() { + self.interaction?.addPressed() + } + private var setupTimestamp: Double? - func setup(context: AccountContext, stickerItem: StickerPackItem?, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isLocked: Bool, isPremium: Bool, isEmpty: Bool) { + func setup(context: AccountContext, stickerItem: StickerPackItem?, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isLocked: Bool, isPremium: Bool, isEmpty: Bool, isEditing: Bool, isAdd: Bool) { self.interaction = interaction self.theme = theme + let isFirstTime = self.currentState == nil + if isAdd { + if !isFirstTime { + return + } + + let color = theme.actionSheet.controlAccentColor + self.imageNode.setSignal(.single({ arguments in + let drawingContext = DrawingContext(size: arguments.imageSize, opaque: false) + let size = arguments.imageSize + drawingContext?.withContext({ context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + UIGraphicsPushContext(context) + + context.setFillColor(color.withMultipliedAlpha(0.1).cgColor) + context.fillEllipse(in: CGRect(origin: .zero, size: size).insetBy(dx: 4.0, dy: 4.0)) + context.setFillColor(color.cgColor) + + let plusSize = CGSize(width: 3.0, height: 21.0) + context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height), cornerRadius: plusSize.width / 2.0).cgPath) + context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width), cornerRadius: plusSize.width / 2.0).cgPath) + context.fillPath() + + UIGraphicsPopContext() + }) + return drawingContext + })) + + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleAddTap))) + + self.currentState = (context, nil, true, false) + self.setNeedsLayout() + + return + } + + if interaction.reorderingFileId != nil { + self.isHidden = stickerItem?.file.fileId == interaction.reorderingFileId + } else { + self.isHidden = false + } + if self.currentState == nil || self.currentState!.0 !== context || self.currentState!.1 != stickerItem || self.isLocked != isLocked || self.isPremium != isPremium || self.isEmpty != isEmpty { self.isLocked = isLocked - - if isLocked { + + if isLocked || isEditing { let lockBackground: UIVisualEffectView let lockIconNode: ASImageNode if let currentBackground = self.lockBackground, let currentIcon = self.lockIconNode { @@ -198,7 +260,24 @@ final class StickerPackPreviewGridItemNode: GridItemNode { lockBackground.isUserInteractionEnabled = false lockIconNode = ASImageNode() lockIconNode.displaysAsynchronously = false - lockIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white) + + if isEditing { + lockIconNode.image = generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + context.setFillColor(UIColor.white.cgColor) + + context.addEllipse(in: CGRect(x: 5.5, y: 11.0, width: 3.0, height: 3.0)) + context.fillPath() + + context.addEllipse(in: CGRect(x: size.width / 2.0 - 1.5, y: 11.0, width: 3.0, height: 3.0)) + context.fillPath() + + context.addEllipse(in: CGRect(x: size.width - 3.0 - 5.5, y: 11.0, width: 3.0, height: 3.0)) + context.fillPath() + }) + } else { + lockIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white) + } let lockTintView = UIView() lockTintView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.15) @@ -209,15 +288,21 @@ final class StickerPackPreviewGridItemNode: GridItemNode { self.lockIconNode = lockIconNode self.view.addSubview(lockBackground) - self.addSubnode(lockIconNode) + lockBackground.contentView.addSubview(lockIconNode.view) + + if !isFirstTime { + lockBackground.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2) + } } - } else if let lockBackground = self.lockBackground, let lockTintView = self.lockTintView, let lockIconNode = self.lockIconNode { + } else if let lockBackground = self.lockBackground { self.lockBackground = nil self.lockTintView = nil self.lockIconNode = nil - lockBackground.removeFromSuperview() - lockTintView.removeFromSuperview() - lockIconNode.removeFromSupernode() + + lockBackground.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + lockBackground.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false, completion: { _ in + lockBackground.removeFromSuperview() + }) } if let stickerItem = stickerItem { @@ -237,7 +322,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode { if self.animationNode == nil { let animationNode = DefaultAnimatedStickerNodeImpl() self.animationNode = animationNode - self.insertSubnode(animationNode, aboveSubnode: self.imageNode) + self.containerNode.insertSubnode(animationNode, aboveSubnode: self.imageNode) animationNode.started = { [weak self] in guard let strongSelf = self else { return @@ -287,21 +372,85 @@ final class StickerPackPreviewGridItemNode: GridItemNode { self.animationNode?.alpha = isLocked ? 0.5 : 1.0 self.imageNode.alpha = isLocked ? 0.5 : 1.0 - self.currentState = (context, stickerItem) + self.currentState = (context, stickerItem, false, isEditing) self.setNeedsLayout() } self.isEmpty = isEmpty + + if self.isEditing != isEditing { + self.isEditing = isEditing + if self.isEditing { + self.startShaking() + } else { + self.containerNode.layer.removeAnimation(forKey: "shaking_position") + self.containerNode.layer.removeAnimation(forKey: "shaking_rotation") + } + } + } + + private func startShaking() { + func degreesToRadians(_ x: CGFloat) -> CGFloat { + return .pi * x / 180.0 + } + + let duration: Double = 0.4 + let displacement: CGFloat = 1.0 + let degreesRotation: CGFloat = 2.0 + + let negativeDisplacement = -1.0 * displacement + let position = CAKeyframeAnimation.init(keyPath: "position") + position.beginTime = 0.8 + position.duration = duration + position.values = [ + NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)), + NSValue(cgPoint: CGPoint(x: 0, y: 0)), + NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: 0)), + NSValue(cgPoint: CGPoint(x: 0, y: negativeDisplacement)), + NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)) + ] + position.calculationMode = .linear + position.isRemovedOnCompletion = false + position.repeatCount = Float.greatestFiniteMagnitude + position.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) + position.isAdditive = true + + let transform = CAKeyframeAnimation.init(keyPath: "transform") + transform.beginTime = 2.6 + transform.duration = 0.3 + transform.valueFunction = CAValueFunction(name: CAValueFunctionName.rotateZ) + transform.values = [ + degreesToRadians(-1.0 * degreesRotation), + degreesToRadians(degreesRotation), + degreesToRadians(-1.0 * degreesRotation) + ] + transform.calculationMode = .linear + transform.isRemovedOnCompletion = false + transform.repeatCount = Float.greatestFiniteMagnitude + transform.isAdditive = true + transform.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) + + self.containerNode.layer.add(position, forKey: "shaking_position") + self.containerNode.layer.add(transform, forKey: "shaking_rotation") } override func layout() { super.layout() let bounds = self.bounds + self.containerNode.frame = bounds + let boundsSide = min(bounds.size.width - 14.0, bounds.size.height - 14.0) var boundingSize = CGSize(width: boundsSide, height: boundsSide) - if let (_, item) = self.currentState { - if let item = item, let dimensions = item.file.dimensions?.cgSize { + if let (_, item, isAdd, _) = self.currentState { + if isAdd { + let imageSize = CGSize(width: 512, height: 512).aspectFitted(boundingSize) + let imageFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize) + self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() + self.imageNode.frame = imageFrame + + return + } else if let item = item, let dimensions = item.file.dimensions?.cgSize { if item.file.isPremiumSticker { boundingSize = CGSize(width: boundingSize.width * 1.1, height: boundingSize.width * 1.1) } @@ -322,13 +471,20 @@ final class StickerPackPreviewGridItemNode: GridItemNode { let placeholderFrame = imageFrame self.placeholderNode.frame = imageFrame - if let theme = self.theme, let (context, stickerItem) = self.currentState, let item = stickerItem { + if let theme = self.theme, let (context, stickerItem, _, _) = self.currentState, let item = stickerItem { self.placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), data: item.file.immediateThumbnailData, size: placeholderFrame.size, enableEffect: context.sharedContext.energyUsageSettings.fullTranslucency) } if let lockBackground = self.lockBackground, let lockTintView = self.lockTintView, let lockIconNode = self.lockIconNode { - let lockSize = CGSize(width: 16.0, height: 16.0) - let lockBackgroundFrame = CGRect(origin: CGPoint(x: bounds.width - lockSize.width, y: bounds.height - lockSize.height), size: lockSize) + let lockSize: CGSize + let lockBackgroundFrame: CGRect + if let (_, _, _, isEditing) = self.currentState, isEditing { + lockSize = CGSize(width: 24.0, height: 24.0) + lockBackgroundFrame = CGRect(origin: CGPoint(x: 3.0, y: 3.0), size: lockSize) + } else { + lockSize = CGSize(width: 16.0, height: 16.0) + lockBackgroundFrame = CGRect(origin: CGPoint(x: bounds.width - lockSize.width, y: bounds.height - lockSize.height), size: lockSize) + } lockBackground.frame = lockBackgroundFrame lockBackground.layer.cornerRadius = lockSize.width / 2.0 if #available(iOS 13.0, *) { @@ -337,7 +493,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode { lockTintView.frame = CGRect(origin: CGPoint(), size: lockBackgroundFrame.size) if let icon = lockIconNode.image { let iconSize = CGSize(width: icon.size.width - 4.0, height: icon.size.height - 4.0) - lockIconNode.frame = CGRect(origin: CGPoint(x: lockBackgroundFrame.minX + floorToScreenPixels((lockBackgroundFrame.width - iconSize.width) / 2.0), y: lockBackgroundFrame.minY + floorToScreenPixels((lockBackgroundFrame.height - iconSize.height) / 2.0)), size: iconSize) + lockIconNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((lockBackgroundFrame.width - iconSize.width) / 2.0), y: floorToScreenPixels((lockBackgroundFrame.height - iconSize.height) / 2.0)), size: iconSize) } } } @@ -355,7 +511,10 @@ final class StickerPackPreviewGridItemNode: GridItemNode { func updatePreviewing(animated: Bool) { var isPreviewing = false - if let (_, maybeItem) = self.currentState, let interaction = self.interaction, let item = maybeItem { + if let (_, maybeItem, isAdd, _) = self.currentState, let interaction = self.interaction, let item = maybeItem { + if isAdd { + return + } isPreviewing = interaction.previewedItem == .pack(item.file) } if self.currentIsPreviewing != isPreviewing { diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 53e973f912..9d23025fd1 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -22,26 +22,32 @@ import StickerPeekUI import AnimationCache import MultiAnimationRenderer import Pasteboard +import StickerPackEditTitleController private enum StickerPackPreviewGridEntry: Comparable, Identifiable { - case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool) + case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool, isEditing: Bool, isAdd: Bool) + case add case emojis(index: Int, stableId: Int, info: StickerPackCollectionInfo, items: [StickerPackItem], title: String?, isInstalled: Bool?) var stableId: Int { switch self { - case let .sticker(_, stableId, _, _, _, _): - return stableId - case let .emojis(_, stableId, _, _, _, _): - return stableId + case let .sticker(_, stableId, _, _, _, _, _, _): + return stableId + case .add: + return -1 + case let .emojis(_, stableId, _, _, _, _): + return stableId } } var index: Int { switch self { - case let .sticker(index, _, _, _, _, _): - return index - case let .emojis(index, _, _, _, _, _): - return index + case let .sticker(index, _, _, _, _, _, _, _): + return index + case .add: + return 100000 + case let .emojis(index, _, _, _, _, _): + return index } } @@ -49,12 +55,14 @@ private enum StickerPackPreviewGridEntry: Comparable, Identifiable { return lhs.index < rhs.index } - func item(context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, strings: PresentationStrings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) -> GridItem { + func item(context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, strings: PresentationStrings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, isEditing: Bool) -> GridItem { switch self { - case let .sticker(_, _, stickerItem, isEmpty, isPremium, isLocked): - return StickerPackPreviewGridItem(context: context, stickerItem: stickerItem, interaction: interaction, theme: theme, isPremium: isPremium, isLocked: isLocked, isEmpty: isEmpty) - case let .emojis(_, _, info, items, title, isInstalled): - return StickerPackEmojisItem(context: context, animationCache: animationCache, animationRenderer: animationRenderer, interaction: interaction, info: info, items: items, theme: theme, strings: strings, title: title, isInstalled: isInstalled, isEmpty: false) + case let .sticker(_, _, stickerItem, isEmpty, isPremium, isLocked, _, isAdd): + return StickerPackPreviewGridItem(context: context, stickerItem: stickerItem, interaction: interaction, theme: theme, isPremium: isPremium, isLocked: isLocked, isEmpty: isEmpty, isEditing: isEditing, isAdd: isAdd) + case .add: + return StickerPackPreviewGridItem(context: context, stickerItem: nil, interaction: interaction, theme: theme, isPremium: false, isLocked: false, isEmpty: false, isEditing: false, isAdd: true) + case let .emojis(_, _, info, items, title, isInstalled): + return StickerPackEmojisItem(context: context, animationCache: animationCache, animationRenderer: animationRenderer, interaction: interaction, info: info, items: items, theme: theme, strings: strings, title: title, isInstalled: isInstalled, isEmpty: false) } } } @@ -65,15 +73,30 @@ private struct StickerPackPreviewGridTransaction { let updates: [GridNodeUpdateItem] let scrollToItem: GridNodeScrollToItem? - init(previousList: [StickerPackPreviewGridEntry], list: [StickerPackPreviewGridEntry], context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, strings: PresentationStrings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, scrollToItem: GridNodeScrollToItem?) { + init(previousList: [StickerPackPreviewGridEntry], list: [StickerPackPreviewGridEntry], context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, strings: PresentationStrings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, scrollToItem: GridNodeScrollToItem?, isEditing: Bool) { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list) self.deletions = deleteIndices - self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, interaction: interaction, theme: theme, strings: strings, animationCache: animationCache, animationRenderer: animationRenderer), previousIndex: $0.2) } - self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, theme: theme, strings: strings, animationCache: animationCache, animationRenderer: animationRenderer)) } + self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, interaction: interaction, theme: theme, strings: strings, animationCache: animationCache, animationRenderer: animationRenderer, isEditing: isEditing), previousIndex: $0.2) } + self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, theme: theme, strings: strings, animationCache: animationCache, animationRenderer: animationRenderer, isEditing: isEditing)) } self.scrollToItem = scrollToItem } + + init(list: [StickerPackPreviewGridEntry], context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, strings: PresentationStrings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, scrollToItem: GridNodeScrollToItem?, isEditing: Bool) { + self.deletions = [] + self.insertions = [] + + var index = 0 + var updates: [GridNodeUpdateItem] = [] + for i in 0 ..< list.count { + updates.append(GridNodeUpdateItem(index: i, previousIndex: i, item: list[i].item(context: context, interaction: interaction, theme: theme, strings: strings, animationCache: animationCache, animationRenderer: animationRenderer, isEditing: isEditing))) + index += 1 + } + self.updates = updates + + self.scrollToItem = nil + } } private enum StickerPackAction { @@ -119,6 +142,8 @@ private final class StickerPackContainer: ASDisplayNode { private var currentEntries: [StickerPackPreviewGridEntry] = [] private var enqueuedTransactions: [StickerPackPreviewGridTransaction] = [] + private var updatedTitle: String? + private var itemsDisposable: Disposable? private var currentContents: [LoadedStickerPack]? private(set) var currentStickerPack: (StickerPackCollectionInfo, [StickerPackItem], Bool)? @@ -174,6 +199,7 @@ private final class StickerPackContainer: ASDisplayNode { self.expandProgressUpdated = expandProgressUpdated self.sendSticker = sendSticker self.sendEmoji = sendEmoji + self.isEditing = controller?.initialIsEditing ?? false self.backgroundNode = ASImageNode() self.backgroundNode.displaysAsynchronously = true @@ -221,6 +247,7 @@ private final class StickerPackContainer: ASDisplayNode { var removeStickerPackImpl: ((StickerPackCollectionInfo) -> Void)? var emojiSelectedImpl: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? var emojiLongPressedImpl: ((String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void)? + var addPressedImpl: (() -> Void)? self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: true, addStickerPack: { info, items in addStickerPackImpl?(info, items) }, removeStickerPack: { info in @@ -229,6 +256,8 @@ private final class StickerPackContainer: ASDisplayNode { emojiSelectedImpl?(text, attribute) }, emojiLongPressed: { text, attribute, node, frame in emojiLongPressedImpl?(text, attribute, node, frame) + }, addPressed: { + addPressedImpl?() }) super.init() @@ -435,18 +464,29 @@ private final class StickerPackContainer: ASDisplayNode { emojiLongPressedImpl = { text, attribute, node, frame in longPressEmoji?(text, attribute, node, frame) } + + addPressedImpl = { [weak self] in + self?.presentAddStickerOptions() + } } deinit { self.itemsDisposable?.dispose() } + private var peekGestureRecognizer: PeekControllerGestureRecognizer? + private var reorderingGestureRecognizer: ReorderingGestureRecognizer? override func didLoad() { super.didLoad() - self.gridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? in + let peekGestureRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? in if let strongSelf = self { if let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem { + var canEdit = false + if let (info, _, _) = strongSelf.currentStickerPack, info.flags.contains(.isCreator) { + canEdit = true + } + let accountPeerId = strongSelf.context.account.peerId return combineLatest( strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId), @@ -493,6 +533,47 @@ private final class StickerPackContainer: ASDisplayNode { }) } }))) + + if canEdit { + menuItems.append(.action(ContextMenuActionItem(text: "Edit Sticker", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Draw"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + if let _ = self { + + } + }))) + if !strongSelf.isEditing { + menuItems.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + if let self { + self.updateIsEditing(true) + } + }))) + } + menuItems.append(.action(ContextMenuActionItem(text: "Delete", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, f in + if let _ = self { + let contextItems: [ContextMenuItem] = [ + .action(ContextMenuActionItem(text: "Back", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { c ,f in + c.popItems() + })), + .separator, + .action(ContextMenuActionItem(text: "Delete for Everyone", textColor: .destructive, icon: { _ in return nil }, action: { [weak self] _ ,f in + f(.default) + + if let self, let (info, items, installed) = self.currentStickerPack { + let updatedItems = items.filter { $0.file.fileId != item.file.fileId } + self.currentStickerPack = (info, updatedItems, installed) + self.updateEntries() + + let _ = self.context.engine.stickers.deleteStickerFromStickerSet(sticker: .stickerPack(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), media: item.file)).startStandalone() + } + })) + ] + c.pushItems(items: .single(ContextController.Items(content: .list(contextItems)))) + } + }))) + } } 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 { @@ -535,7 +616,129 @@ private final class StickerPackContainer: ASDisplayNode { } strongSelf.updatePreviewingItem(item: item, animated: true) } - }, activateBySingleTap: true)) + }, activateBySingleTap: true) + peekGestureRecognizer.longPressEnabled = !self.isEditing + self.peekGestureRecognizer = peekGestureRecognizer + self.gridNode.view.addGestureRecognizer(peekGestureRecognizer) + + let reorderingGestureRecognizer = ReorderingGestureRecognizer(animateOnTouch: false, shouldBegin: { [weak self] point in + if let strongSelf = self, !strongSelf.gridNode.scrollView.isDragging && strongSelf.currentEntries.count > 1 { + if let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, !itemNode.isAdd { + return (true, true, itemNode) + } + return (false, false, nil) + } + return (false, false, nil) + }, willBegin: { _ in + + }, began: { [weak self] itemNode in + self?.beginReordering(itemNode: itemNode) + }, ended: { [weak self] point in + if let strongSelf = self { + if let point = point { + strongSelf.endReordering(point: point) + } else { + strongSelf.endReordering(point: nil) + } + } + }, moved: { [weak self] point, offset in + self?.updateReordering(point: point, offset: offset) + }) + reorderingGestureRecognizer.isEnabled = self.isEditing + self.reorderingGestureRecognizer = reorderingGestureRecognizer + self.gridNode.view.addGestureRecognizer(reorderingGestureRecognizer) + } + + private var reorderFeedback: HapticFeedback? + private var reorderNode: ReorderingItemNode? + + private var isReordering = false + private var reorderPosition: Int? + + private func beginReordering(itemNode: StickerPackPreviewGridItemNode) { + self.isReordering = true + + if let reorderNode = self.reorderNode { + reorderNode.removeFromSupernode() + } + + self.interaction.reorderingFileId = itemNode.stickerPackItem?.file.fileId + + let reorderNode = ReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin) + self.reorderNode = reorderNode + self.gridNode.addSubnode(reorderNode) + + itemNode.isHidden = true + + if self.reorderFeedback == nil { + self.reorderFeedback = HapticFeedback() + } + self.reorderFeedback?.impact() + } + + private func endReordering(point: CGPoint?) { + self.interaction.reorderingFileId = nil + + if let reorderNode = self.reorderNode { + self.reorderNode = nil + + if let itemNode = reorderNode.itemNode, let _ = point { +// var targetNode: StickerPackPreviewGridItemNode? +// if let itemNode = self.gridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode { +// targetNode = itemNode +// } + +// let _ = itemNode +// let _ = targetNode +// if let targetNode = targetNode, let sourceItem = itemNode.asset as? TGMediaSelectableItem, let targetItem = targetNode.asset as? TGMediaSelectableItem, let targetIndex = self.interaction?.selectionState?.index(of: targetItem) { +// self.interaction?.selectionState?.move(sourceItem, to: targetIndex) +// } + reorderNode.animateCompletion(completion: { [weak reorderNode] in + reorderNode?.removeFromSupernode() + }) + self.reorderFeedback?.tap() + + if let reorderPosition = self.reorderPosition, let file = itemNode.stickerPackItem?.file { + let _ = self.context.engine.stickers.reorderSticker(sticker: .standalone(media: file), position: reorderPosition).startStandalone() + } + } else { + reorderNode.removeFromSupernode() + reorderNode.itemNode?.isHidden = false + } + + self.updateEntries(reload: true) + } + + self.isReordering = false + self.reorderPosition = nil + } + + private func updateReordering(point: CGPoint, offset: CGPoint) { + if let reorderNode = self.reorderNode { + reorderNode.updateOffset(offset: offset) + + var targetNode: StickerPackPreviewGridItemNode? + if let itemNode = self.gridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode { + targetNode = itemNode + } + + var reorderPosition: Int? + if targetNode !== reorderNode.itemNode { + var index = 0 + for entry in self.currentEntries { + if case let .sticker(_, _, item, _, _, _, _, _) = entry, item?.file.fileId == targetNode?.stickerPackItem?.file.fileId { + reorderPosition = index + break + } + index += 1 + } + } + + if self.reorderPosition != reorderPosition { + self.reorderPosition = reorderPosition + self.updateEntries() + } + } } private func emojiSuggestionPeekContent(itemLayer: CALayer, file: TelegramMediaFile) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError> { @@ -623,7 +826,7 @@ private final class StickerPackContainer: ASDisplayNode { let context = self.context - let _ = context.engine.accountData.setEmojiStatus(file: file, expirationDate: nil).start() + let _ = context.engine.accountData.setEmojiStatus(file: file, expirationDate: nil).startStandalone() var animateInAsReplacement = false animateInAsReplacement = false @@ -722,7 +925,7 @@ private final class StickerPackContainer: ASDisplayNode { return (strongSelf.view, itemLayer.convert(itemLayer.bounds, to: strongSelf.view.layer), content) } } - + func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData @@ -750,8 +953,12 @@ private final class StickerPackContainer: ASDisplayNode { buttonColor = .clear case .none: buttonColor = self.presentationData.theme.list.itemAccentColor - case let .result(_, _, installed): - buttonColor = installed ? self.presentationData.theme.list.itemDestructiveColor : self.presentationData.theme.list.itemCheckColors.foregroundColor + case let .result(info, _, installed): + if info.flags.contains(.isCreator) { + buttonColor = installed ? self.presentationData.theme.list.itemAccentColor : self.presentationData.theme.list.itemCheckColors.foregroundColor + } else { + buttonColor = installed ? self.presentationData.theme.list.itemDestructiveColor : self.presentationData.theme.list.itemCheckColors.foregroundColor + } if installed { buttonFont = Font.regular(17.0) } @@ -761,13 +968,12 @@ private final class StickerPackContainer: ASDisplayNode { self.buttonNode.setTitle(self.buttonNode.attributedTitle(for: .normal)?.string ?? "", with: buttonFont, with: buttonColor, for: .normal) } - if !self.currentEntries.isEmpty, let controller = self.controller { - let transaction = StickerPackPreviewGridTransaction(previousList: self.currentEntries, list: self.currentEntries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: nil) - self.enqueueTransaction(transaction) + if !self.currentEntries.isEmpty { + self.updateEntries() } let titleFont = Font.semibold(17.0) - let title = self.titleNode.attributedText?.string ?? "" + let title = self.updatedTitle ?? (self.titleNode.attributedText?.string ?? "") let entities = generateTextEntities(title, enabledTypes: [.mention]) self.titleNode.attributedText = stringWithAppliedEntities(title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleFont, italicFont: titleFont, boldItalicFont: titleFont, fixedFont: titleFont, blockQuoteFont: titleFont, message: nil) @@ -777,6 +983,31 @@ private final class StickerPackContainer: ASDisplayNode { } } + private var isEditing = false + func updateEntries(reload: Bool = false) { + guard let controller = self.controller else { + return + } + let transaction: StickerPackPreviewGridTransaction + if reload { + transaction = StickerPackPreviewGridTransaction(list: self.currentEntries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: nil, isEditing: self.isEditing) + } else { + transaction = StickerPackPreviewGridTransaction(previousList: self.currentEntries, list: self.currentEntries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: nil, isEditing: self.isEditing) + } + self.enqueueTransaction(transaction) + } + + private func updateIsEditing(_ isEditing: Bool) { + self.isEditing = isEditing + self.updateEntries(reload: true) + self.updateButton() + self.peekGestureRecognizer?.longPressEnabled = !isEditing + self.reorderingGestureRecognizer?.isEnabled = isEditing + if let (layout, _, _, _) = self.validLayout { + self.updateLayout(layout: layout, transition: .animated(duration: 0.3, curve: .easeInOut)) + } + } + @objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) { guard let controller = self.controller else { return @@ -844,10 +1075,152 @@ private final class StickerPackContainer: ASDisplayNode { } }))) + if let (info, _, _) = self.currentStickerPack, info.flags.contains(.isCreator) { + //TODO:localize + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + self?.updateIsEditing(true) + }))) + + items.append(.action(ContextMenuActionItem(text: "Edit Name", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + self?.presentEditPackTitle() + }))) + + items.append(.action(ContextMenuActionItem(text: "Delete", textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] c, f in + if let self, let (_, _, isInstalled) = self.currentStickerPack { + if isInstalled { + let contextItems: [ContextMenuItem] = [ + .action(ContextMenuActionItem(text: "Delete for Everyone", textColor: .destructive, icon: { _ in return nil }, action: { [weak self] _ ,f in + f(.default) + + self?.presentDeletePack() + })), + .action(ContextMenuActionItem(text: "Remove for Me", icon: { _ in return nil }, action: { [weak self] _ ,f in + f(.default) + + self?.togglePackInstalled() + })) + ] + c.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil, animated: true) + } else { + f(.default) + self.presentDeletePack() + } + } + }))) + + items.append(.separator) + + items.append(.action(ContextMenuActionItem(text: "Check [@stickers]() bot for more options.", textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in + return nil + }, action: { [weak self] _, f in + f(.default) + + guard let self, let controller = self.controller else { + return + } + + controller.controllerNode.openMention("stickers") + }))) + } + let contextController = ContextController(presentationData: self.presentationData, source: .reference(StickerPackContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) self.presentInGlobalOverlay(contextController, nil) } + private func presentAddStickerOptions() { + let actionSheet = ActionSheetController(presentationData: self.presentationData) + var items: [ActionSheetItem] = [] + items.append(ActionSheetButtonItem(title: "Create a New Sticker", color: .accent, action: { [weak actionSheet, weak self] in + actionSheet?.dismissAnimated() + + guard let self, let controller = self.controller else { + return + } + self.presentCreateSticker() + controller.controllerNode.dismiss() + })) + items.append(ActionSheetButtonItem(title: "Add an Existing Sticker", color: .accent, action: { [weak actionSheet, weak self] in + actionSheet?.dismissAnimated() + + guard let self, let controller = self.controller else { + return + } + controller.controllerNode.dismiss() + + self.presentAddExistingSticker() + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + self.presentInGlobalOverlay(actionSheet, nil) + } + + private func presentCreateSticker() { + let controller = self.context.sharedContext.makeStickerMediaPickerScreen( + context: self.context, + getSourceRect: { return .zero }, + completion: { result, transitionView, transitionRect, transitionImage, completion, dismissed in + + }, + dismissed: {} + ) + self.controller?.parentNavigationController?.pushViewController(controller) + } + + private func presentAddExistingSticker() { + + } + + private func presentEditPackTitle() { + guard let (info, _, _) = self.currentStickerPack else { + return + } + let context = self.context + //TODO:localize + var dismissImpl: (() -> Void)? + let controller = stickerPackEditTitleController(context: context, title: "Edit Sticker Set Name", text: "Choose a new name for your set.", placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: self.updatedTitle ?? info.title, maxLength: 128, apply: { [weak self] title in + guard let self, let title else { + return + } + let _ = (context.engine.stickers.renameStickerSet(packReference: .id(id: info.id.id, accessHash: info.accessHash), title: title) + |> deliverOnMainQueue).startStandalone() + + self.updatedTitle = title + self.updatePresentationData(self.presentationData) + + dismissImpl?() + }, cancel: {}) + dismissImpl = { [weak controller] in + controller?.dismiss() + } + self.controller?.present(controller, in: .window(.root)) + } + + private func presentDeletePack() { + guard let controller = self.controller, let (info, _, _) = self.currentStickerPack else { + return + } + let context = self.context + controller.present(textAlertController(context: context, updatedPresentationData: controller.updatedPresentationData, title: "Delete Sticker Set", text: "This will delete the sticker set for all users.", actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: "Delete", action: { [weak self] in + let _ = (context.engine.stickers.deleteStickerSet(packReference: .id(id: info.id.id, accessHash: info.accessHash)) + |> deliverOnMainQueue).startStandalone() + + self?.controller?.controllerNode.dismiss() + })]), in: .window(.root)) + } + @objc func cancelPressed() { self.requestDismiss() } @@ -896,7 +1269,19 @@ private final class StickerPackContainer: ASDisplayNode { self.controller?.actionPerformed?(installedPacks) } self.requestDismiss() - } else if let (info, items, installed) = self.currentStickerPack { + } else if let (info, _, installed) = self.currentStickerPack { + if installed, info.flags.contains(.isCreator) { + self.updateIsEditing(!self.isEditing) + return + } + self.togglePackInstalled() + } else { + self.requestDismiss() + } + } + + private func togglePackInstalled() { + if let (info, items, installed) = self.currentStickerPack { var dismissed = false switch self.decideNextAction(self, installed ? .remove : .add) { case .dismiss: @@ -923,8 +1308,6 @@ private final class StickerPackContainer: ASDisplayNode { actionPerformed?([(info, items, .add)]) } } - } else { - self.requestDismiss() } } @@ -958,6 +1341,74 @@ private final class StickerPackContainer: ASDisplayNode { transition.updateAlpha(node: self.actionAreaSeparatorNode, alpha: backgroundAlpha, delay: delay) } + private func updateButton(count: Int32 = 0) { + if let currentContents = self.currentContents, currentContents.count == 1, let content = currentContents.first, case let .result(info, _, installed) = content { + if installed { + let text: String + if info.flags.contains(.isCreator) { + if self.isEditing { + var updated = false + if let current = self.buttonNode.attributedTitle(for: .normal)?.string, !current.isEmpty && current != self.presentationData.strings.Common_Done { + updated = true + } + + if updated, let snapshotView = self.buttonNode.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.buttonNode.view.frame + self.buttonNode.view.superview?.insertSubview(snapshotView, belowSubview: self.buttonNode.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + self.buttonNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + self.buttonNode.setTitle(self.presentationData.strings.Common_Done, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal) + self.buttonNode.setBackgroundImage(generateStretchableFilledCircleImage(radius: 11, color: self.presentationData.theme.list.itemCheckColors.fillColor), for: []) + } else { + var updated = false + if let current = self.buttonNode.attributedTitle(for: .normal)?.string, !current.isEmpty && current != "Edit Stickers" { + updated = true + } + + if updated, let snapshotView = self.buttonNode.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.buttonNode.view.frame + self.buttonNode.view.superview?.insertSubview(snapshotView, belowSubview: self.buttonNode.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + self.buttonNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + //TODO:localize + text = "Edit Stickers" + self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemAccentColor, for: .normal) + self.buttonNode.setBackgroundImage(nil, for: []) + } + } else { + if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { + text = self.presentationData.strings.StickerPack_RemoveStickerCount(count) + } else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { + text = self.presentationData.strings.StickerPack_RemoveEmojiCount(count) + } else { + text = self.presentationData.strings.StickerPack_RemoveMaskCount(count) + } + self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemDestructiveColor, for: .normal) + self.buttonNode.setBackgroundImage(nil, for: []) + } + } else { + let text: String + if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { + text = self.presentationData.strings.StickerPack_AddStickerCount(count) + } else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { + text = self.presentationData.strings.StickerPack_AddEmojiCount(count) + } else { + text = self.presentationData.strings.StickerPack_AddMaskCount(count) + } + self.buttonNode.setTitle(text, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal) + self.buttonNode.setBackgroundImage(generateStretchableFilledCircleImage(radius: 11, color: self.presentationData.theme.list.itemCheckColors.fillColor), for: []) + } + } + } + private func updateStickerPackContents(_ contents: [LoadedStickerPack], hasPremium: Bool) { self.currentContents = contents self.didReceiveStickerPackResult = true @@ -1045,7 +1496,7 @@ private final class StickerPackContainer: ASDisplayNode { for _ in 0 ..< 16 { var stableId: Int? inner: for entry in self.currentEntries { - if case let .sticker(index, currentStableId, stickerItem, _, _, _) = entry, stickerItem == nil, index == entries.count { + if case let .sticker(index, currentStableId, stickerItem, _, _, _, _, _) = entry, stickerItem == nil, index == entries.count { stableId = currentStableId break inner } @@ -1060,7 +1511,7 @@ private final class StickerPackContainer: ASDisplayNode { } self.nextStableId += 1 - entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: nil, isEmpty: false, isPremium: false, isLocked: false)) + entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: nil, isEmpty: false, isPremium: false, isLocked: false, isEditing: false, isAdd: false)) } if self.titlePlaceholderNode == nil { let titlePlaceholderNode = ShimmerEffectNode() @@ -1112,7 +1563,7 @@ private final class StickerPackContainer: ASDisplayNode { let addItem: (StickerPackItem, Bool, Bool) -> Void = { item, isPremium, isLocked in var stableId: Int? inner: for entry in self.currentEntries { - if case let .sticker(_, currentStableId, stickerItem, _, _, _) = entry, let stickerItem = stickerItem, stickerItem.file.fileId == item.file.fileId { + if case let .sticker(_, currentStableId, stickerItem, _, _, _, _, _) = entry, let stickerItem = stickerItem, stickerItem.file.fileId == item.file.fileId { stableId = currentStableId break inner } @@ -1124,7 +1575,7 @@ private final class StickerPackContainer: ASDisplayNode { resolvedStableId = self.nextStableId self.nextStableId += 1 } - entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: item, isEmpty: false, isPremium: isPremium, isLocked: isLocked)) + entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: item, isEmpty: false, isPremium: isPremium, isLocked: isLocked, isEditing: false, isAdd: false)) } for item in generalItems { @@ -1142,41 +1593,21 @@ private final class StickerPackContainer: ASDisplayNode { if let mainActionTitle = self.controller?.mainActionTitle { self.buttonNode.setTitle(mainActionTitle, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal) - let roundedAccentBackground = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(self.presentationData.theme.list.itemCheckColors.fillColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - })?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11) - self.buttonNode.setBackgroundImage(roundedAccentBackground, for: []) + self.buttonNode.setBackgroundImage(generateStretchableFilledCircleImage(radius: 11, color: self.presentationData.theme.list.itemCheckColors.fillColor), for: []) } else { - if installed { - let text: String - if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { - text = self.presentationData.strings.StickerPack_RemoveStickerCount(Int32(entries.count)) - } else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { - text = self.presentationData.strings.StickerPack_RemoveEmojiCount(Int32(items.count)) - } else { - text = self.presentationData.strings.StickerPack_RemoveMaskCount(Int32(entries.count)) - } - self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemDestructiveColor, for: .normal) - self.buttonNode.setBackgroundImage(nil, for: []) + let count: Int32 + if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { + count = Int32(entries.count) + } else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { + count = Int32(items.count) } else { - let text: String - if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { - text = self.presentationData.strings.StickerPack_AddStickerCount(Int32(entries.count)) - } else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { - text = self.presentationData.strings.StickerPack_AddEmojiCount(Int32(items.count)) - } else { - text = self.presentationData.strings.StickerPack_AddMaskCount(Int32(entries.count)) - } - self.buttonNode.setTitle(text, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal) - let roundedAccentBackground = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(self.presentationData.theme.list.itemCheckColors.fillColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - })?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11) - self.buttonNode.setBackgroundImage(roundedAccentBackground, for: []) + count = Int32(entries.count) } + self.updateButton(count: count) + } + + if info.flags.contains(.isCreator) { + entries.append(.add) } } } @@ -1197,7 +1628,86 @@ private final class StickerPackContainer: ASDisplayNode { } if let controller = self.controller { - let transaction = StickerPackPreviewGridTransaction(previousList: previousEntries, list: entries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: scrollToItem) + let transaction = StickerPackPreviewGridTransaction(previousList: previousEntries, list: entries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: scrollToItem, isEditing: self.isEditing) + self.enqueueTransaction(transaction) + } + } + + func updateEntries() { + guard let (_, items, _) = self.currentStickerPack else { + return + } + let hasPremium = self.context.isPremium + let previousEntries = self.currentEntries + var entries: [StickerPackPreviewGridEntry] = [] + + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) + + var generalItems: [StickerPackItem] = [] + var premiumItems: [StickerPackItem] = [] + + for item in items { + if item.file.isPremiumSticker { + premiumItems.append(item) + } else { + generalItems.append(item) + } + } + + let addItem: (StickerPackItem, Bool, Bool) -> Void = { item, isPremium, isLocked in + var stableId: Int? + inner: for entry in self.currentEntries { + if case let .sticker(_, currentStableId, stickerItem, _, _, _, _, _) = entry, let stickerItem = stickerItem, stickerItem.file.fileId == item.file.fileId { + stableId = currentStableId + break inner + } + } + let resolvedStableId: Int + if let stableId = stableId { + resolvedStableId = stableId + } else { + resolvedStableId = self.nextStableId + self.nextStableId += 1 + } + + entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: item, isEmpty: false, isPremium: isPremium, isLocked: isLocked, isEditing: false, isAdd: false)) + } + + var currentIndex: Int = 0 + for item in generalItems { + if self.isReordering, let reorderNode = self.reorderNode, let reorderItem = reorderNode.itemNode?.stickerPackItem, let reorderPosition { + if currentIndex == reorderPosition { + addItem(reorderItem, false, false) + currentIndex += 1 + } + + if item.file.fileId == reorderItem.file.fileId { + + } else { + addItem(item, false, false) + currentIndex += 1 + } + } else { + addItem(item, false, false) + currentIndex += 1 + } + } + + if !premiumConfiguration.isPremiumDisabled { + if !premiumItems.isEmpty { + for item in premiumItems { + addItem(item, true, !hasPremium) + currentIndex += 1 + } + } + } + + entries.append(.add) + + self.currentEntries = entries + + if let controller = self.controller { + let transaction = StickerPackPreviewGridTransaction(previousList: previousEntries, list: entries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: nil, isEditing: self.isEditing) self.enqueueTransaction(transaction) } } @@ -1257,7 +1767,7 @@ private final class StickerPackContainer: ASDisplayNode { actionAreaBottomInset = 2.0 } } - if let (_, _, isInstalled) = self.currentStickerPack, isInstalled { + if let (info, _, isInstalled) = self.currentStickerPack, isInstalled, !info.flags.contains(.isCreator) { buttonHeight = 42.0 actionAreaTopInset = 1.0 actionAreaBottomInset = 2.0 @@ -1341,6 +1851,10 @@ private final class StickerPackContainer: ASDisplayNode { self.moreButtonNode.frame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - 46.0, y: 5.0), size: CGSize(width: 44.0, height: 44.0)) + + transition.updateAlpha(node: self.cancelButtonNode, alpha: self.isEditing ? 0.0 : 1.0) + transition.updateAlpha(node: self.moreButtonNode, alpha: self.isEditing ? 0.0 : 1.0) + if firstTime { while !self.enqueuedTransactions.isEmpty { self.dequeueTransaction() @@ -1467,7 +1981,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? private let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? private let longPressEmoji: ((String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void)? - private let openMention: (String) -> Void + fileprivate let openMention: (String) -> Void private let dimNode: ASDisplayNode private let shadowNode: ASImageNode @@ -1897,7 +2411,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { public final class StickerPackScreenImpl: ViewController { private let context: AccountContext fileprivate var presentationData: PresentationData - private let updatedPresentationData: (initial: PresentationData, signal: Signal)? + fileprivate let updatedPresentationData: (initial: PresentationData, signal: Signal)? private var presentationDataDisposable: Disposable? private let stickerPacks: [StickerPackReference] @@ -1908,7 +2422,7 @@ public final class StickerPackScreenImpl: ViewController { private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? private let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? - private var controllerNode: StickerPackScreenNode { + fileprivate var controllerNode: StickerPackScreenNode { return self.displayNode as! StickerPackScreenNode } @@ -1926,6 +2440,7 @@ public final class StickerPackScreenImpl: ViewController { private var alreadyDidAppear: Bool = false private var animatedIn: Bool = false + fileprivate var initialIsEditing: Bool = false let animationCache: AnimationCache let animationRenderer: MultiAnimationRenderer @@ -1941,6 +2456,7 @@ public final class StickerPackScreenImpl: ViewController { selectedStickerPackIndex: Int = 0, mainActionTitle: String? = nil, actionTitle: String? = nil, + isEditing: Bool = false, parentNavigationController: NavigationController? = nil, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, @@ -1954,6 +2470,7 @@ public final class StickerPackScreenImpl: ViewController { self.initialSelectedStickerPackIndex = selectedStickerPackIndex self.mainActionTitle = mainActionTitle self.actionTitle = actionTitle + self.initialIsEditing = isEditing self.parentNavigationController = parentNavigationController self.sendSticker = sendSticker self.sendEmoji = sendEmoji @@ -2071,8 +2588,8 @@ public final class StickerPackScreenImpl: ViewController { } if let peer { if let parentNavigationController = strongSelf.parentNavigationController { - strongSelf.dismiss() - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: parentNavigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), animated: true)) + strongSelf.controllerNode.dismiss() + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: parentNavigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .always, animated: true)) } } else { strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) @@ -2186,6 +2703,7 @@ public func StickerPackScreen( loadedStickerPacks: [LoadedStickerPack] = [], mainActionTitle: String? = nil, actionTitle: String? = nil, + isEditing: Bool = false, parentNavigationController: NavigationController? = nil, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? = nil, @@ -2201,6 +2719,7 @@ public func StickerPackScreen( selectedStickerPackIndex: stickerPacks.firstIndex(of: mainStickerPack) ?? 0, mainActionTitle: mainActionTitle, actionTitle: actionTitle, + isEditing: isEditing, parentNavigationController: parentNavigationController, sendSticker: sendSticker, sendEmoji: sendEmoji, @@ -2256,3 +2775,276 @@ private func generateArrowImage(color: UIColor) -> UIImage? { try? drawSvgPath(context, path: "M183.219,208.89 H206.781 C205.648,208.89 204.567,209.371 203.808,210.214 L197.23,217.523 C196.038,218.848 193.962,218.848 192.77,217.523 L186.192,210.214 C185.433,209.371 184.352,208.89 183.219,208.89 Z ") }) } + + +private class ReorderingGestureRecognizer: UIGestureRecognizer { + private let shouldBegin: (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, itemNode: StickerPackPreviewGridItemNode?) + private let willBegin: (CGPoint) -> Void + private let began: (StickerPackPreviewGridItemNode) -> Void + private let ended: (CGPoint?) -> Void + private let moved: (CGPoint, CGPoint) -> Void + + private var initialLocation: CGPoint? + private var longPressTimer: SwiftSignalKit.Timer? + + var animateOnTouch = true + + private var itemNode: StickerPackPreviewGridItemNode? + + public init(animateOnTouch: Bool, shouldBegin: @escaping (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, itemNode: StickerPackPreviewGridItemNode?), willBegin: @escaping (CGPoint) -> Void, began: @escaping (StickerPackPreviewGridItemNode) -> Void, ended: @escaping (CGPoint?) -> Void, moved: @escaping (CGPoint, CGPoint) -> Void) { + self.animateOnTouch = animateOnTouch + self.shouldBegin = shouldBegin + self.willBegin = willBegin + self.began = began + self.ended = ended + self.moved = moved + + super.init(target: nil, action: nil) + } + + deinit { + self.longPressTimer?.invalidate() + } + + private func startLongPressTimer() { + self.longPressTimer?.invalidate() + let longPressTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: false, completion: { [weak self] in + self?.longPressTimerFired() + }, queue: Queue.mainQueue()) + self.longPressTimer = longPressTimer + longPressTimer.start() + } + + private func stopLongPressTimer() { + self.itemNode = nil + self.longPressTimer?.invalidate() + self.longPressTimer = nil + } + + override public func reset() { + super.reset() + + self.itemNode = nil + self.stopLongPressTimer() + self.initialLocation = nil + } + + + private func longPressTimerFired() { + guard let _ = self.initialLocation else { + return + } + + self.state = .began + self.longPressTimer?.invalidate() + self.longPressTimer = nil + if let itemNode = self.itemNode { + self.began(itemNode) + } + } + + override public func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + if self.numberOfTouches > 1 { + self.state = .failed + self.ended(nil) + return + } + + if self.state == .possible { + if let location = touches.first?.location(in: self.view) { + let (allowed, requiresLongPress, itemNode) = self.shouldBegin(location) + if allowed { + if let itemNode = itemNode, self.animateOnTouch { + itemNode.layer.animateScale(from: 1.0, to: 0.98, duration: 0.2, delay: 0.1) + } + self.itemNode = itemNode + self.initialLocation = location + if requiresLongPress { + self.startLongPressTimer() + } else { + self.state = .began + if let itemNode = self.itemNode { + self.began(itemNode) + } + } + } else { + self.state = .failed + } + } else { + self.state = .failed + } + } + } + + override public func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + self.initialLocation = nil + + if self.longPressTimer != nil { + self.stopLongPressTimer() + self.state = .failed + } + if self.state == .began || self.state == .changed { + if let location = touches.first?.location(in: self.view) { + self.ended(location) + } else { + self.ended(nil) + } + self.state = .failed + } + } + + override public func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + self.initialLocation = nil + + if self.longPressTimer != nil { + self.stopLongPressTimer() + self.state = .failed + } + if self.state == .began || self.state == .changed { + self.ended(nil) + self.state = .failed + } + } + + override public func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) { + self.state = .changed + self.moved(location, CGPoint(x: location.x - initialLocation.x, y: location.y - initialLocation.y)) + } else if let touch = touches.first, let initialTapLocation = self.initialLocation, self.longPressTimer != nil { + let touchLocation = touch.location(in: self.view) + let dX = touchLocation.x - initialTapLocation.x + let dY = touchLocation.y - initialTapLocation.y + + if dX * dX + dY * dY > 3.0 * 3.0 { + self.itemNode?.layer.removeAllAnimations() + + self.stopLongPressTimer() + self.initialLocation = nil + self.state = .failed + } + } + } +} + +private func generateShadowImage(corners: CACornerMask, radius: CGFloat) -> UIImage? { + return generateImage(CGSize(width: 120.0, height: 120), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + +// context.saveGState() + context.setShadow(offset: CGSize(), blur: 28.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor) + + var rectCorners: UIRectCorner = [] + if corners.contains(.layerMinXMinYCorner) { + rectCorners.insert(.topLeft) + } + if corners.contains(.layerMaxXMinYCorner) { + rectCorners.insert(.topRight) + } + if corners.contains(.layerMinXMaxYCorner) { + rectCorners.insert(.bottomLeft) + } + if corners.contains(.layerMaxXMaxYCorner) { + rectCorners.insert(.bottomRight) + } + + let path = UIBezierPath(roundedRect: CGRect(x: 30.0, y: 30.0, width: 60.0, height: 60.0), byRoundingCorners: rectCorners, cornerRadii: CGSize(width: radius, height: radius)).cgPath + context.addPath(path) + context.fillPath() +// context.restoreGState() + +// context.setBlendMode(.clear) +// context.addPath(path) +// context.fillPath() + })?.stretchableImage(withLeftCapWidth: 60, topCapHeight: 60) +} + +private final class CopyView: UIView { + let shadow: UIImageView + var snapshotView: UIView? + + init(frame: CGRect, corners: CACornerMask, radius: CGFloat) { + self.shadow = UIImageView() + self.shadow.contentMode = .scaleToFill + + super.init(frame: frame) + + self.addSubview(self.shadow) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class ReorderingItemNode: ASDisplayNode { + weak var itemNode: StickerPackPreviewGridItemNode? + + var currentState: (Int, Int)? + + private let copyView: CopyView + private let initialLocation: CGPoint + + init(itemNode: StickerPackPreviewGridItemNode, initialLocation: CGPoint) { + self.itemNode = itemNode + self.copyView = CopyView(frame: CGRect(), corners: [], radius: 0.0) + let snapshotView = itemNode.view.snapshotView(afterScreenUpdates: false) + self.initialLocation = initialLocation + + super.init() + + if let snapshotView = snapshotView { + snapshotView.frame = CGRect(origin: CGPoint(), size: itemNode.bounds.size) + snapshotView.bounds.origin = itemNode.bounds.origin + snapshotView.layer.shadowRadius = 10.0 + snapshotView.layer.shadowColor = UIColor.black.cgColor + self.copyView.addSubview(snapshotView) + self.copyView.snapshotView = snapshotView + } + self.view.addSubview(self.copyView) + self.copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y), size: itemNode.bounds.size) + self.copyView.shadow.frame = CGRect(origin: CGPoint(x: -30.0, y: -30.0), size: CGSize(width: itemNode.bounds.size.width + 60.0, height: itemNode.bounds.size.height + 60.0)) + self.copyView.shadow.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + + self.copyView.snapshotView?.layer.animateScale(from: 1.0, to: 1.1, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.copyView.shadow.layer.animateScale(from: 1.0, to: 1.1, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + } + + func updateOffset(offset: CGPoint) { + self.copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x + offset.x, y: initialLocation.y + offset.y), size: copyView.bounds.size) + } + + func currentOffset() -> CGFloat? { + return self.copyView.center.y + } + + func animateCompletion(completion: @escaping () -> Void) { + if let itemNode = self.itemNode { + itemNode.view.superview?.bringSubviewToFront(itemNode.view) + + itemNode.layer.animateScale(from: 1.1, to: 1.0, duration: 0.25, removeOnCompletion: false) + +// let sourceFrame = self.view.convert(self.copyView.frame, to: itemNode.supernode?.view) +// let targetFrame = itemNode.frame +// itemNode.updateLayout(size: sourceFrame.size, transition: .immediate) +// itemNode.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in +// completion() +// }) +// itemNode.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.3, curve: .spring)) + + itemNode.isHidden = false + self.copyView.isHidden = true + + completion() + } else { + completion() + } + } +} diff --git a/submodules/StickerPeekUI/BUILD b/submodules/StickerPeekUI/BUILD index ec67754833..33f9618a5b 100644 --- a/submodules/StickerPeekUI/BUILD +++ b/submodules/StickerPeekUI/BUILD @@ -28,6 +28,8 @@ swift_library( "//submodules/ShimmerEffect:ShimmerEffect", "//submodules/ContextUI:ContextUI", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", + "//submodules/ReactionSelectionNode", + "//submodules/TelegramUI/Components/EntityKeyboard", ], visibility = [ "//visibility:public", diff --git a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift index 47ebea3c4a..02b443717c 100644 --- a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift @@ -13,17 +13,22 @@ import SolidRoundedButtonNode import TelegramPresentationData import AccountContext import AppBundle +import ReactionSelectionNode +import EntityKeyboard public enum StickerPreviewPeekItem: Equatable { case pack(TelegramMediaFile) case found(FoundStickerItem) + case image(UIImage) - public var file: TelegramMediaFile { + public var file: TelegramMediaFile? { switch self { case let .pack(file): return file case let .found(item): return item.file + case .image: + return nil } } } @@ -34,20 +39,24 @@ public final class StickerPreviewPeekContent: PeekControllerContent { let strings: PresentationStrings public let item: StickerPreviewPeekItem let isLocked: Bool + let isCreating: Bool + let reactionItems: [ReactionItem] let menu: [ContextMenuItem] let openPremiumIntro: () -> Void - public init(context: AccountContext, 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, isCreating: Bool = false, menu: [ContextMenuItem], reactionItems: [ReactionItem] = [], openPremiumIntro: @escaping () -> Void) { self.context = context self.theme = theme self.strings = strings self.item = item self.isLocked = isLocked + self.isCreating = isCreating if isLocked { self.menu = [] } else { self.menu = menu } + self.reactionItems = reactionItems self.openPremiumIntro = openPremiumIntro } @@ -72,10 +81,11 @@ public final class StickerPreviewPeekContent: PeekControllerContent { } public func fullScreenAccessoryNode(blurView: UIVisualEffectView) -> (PeekControllerAccessoryNode & ASDisplayNode)? { + if self.isCreating { + return EmojiStickerAccessoryNode(context: self.context, theme: self.theme, reactionItems: self.reactionItems) + } if self.isLocked { - let isEmoji = self.item.file.isCustomEmoji - - return PremiumStickerPackAccessoryNode(theme: self.theme, strings: self.strings, isEmoji: isEmoji, proceed: self.openPremiumIntro) + return PremiumStickerPackAccessoryNode(theme: self.theme, strings: self.strings, isEmoji: self.item.file?.isCustomEmoji ?? false, proceed: self.openPremiumIntro) } else { return nil } @@ -112,51 +122,57 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC self.textNode = ASTextNode() self.imageNode = TransformImageNode() - for case let .Sticker(text, _, _) in item.file.attributes { - self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(32.0), textColor: .black) - break - } - - let isPremiumSticker = item.file.isPremiumSticker - - if item.file.isAnimatedSticker || item.file.isVideoSticker { - let animationNode = DefaultAnimatedStickerNodeImpl() - animationNode.overrideVisibility = true - self.animationNode = animationNode - - let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512) - let fitSize: CGSize - if item.file.isCustomEmoji { - fitSize = CGSize(width: 200.0, height: 200.0) - } else { - fitSize = CGSize(width: 400.0, height: 400.0) - } - let fittedDimensions = dimensions.cgSize.aspectFitted(fitSize) - - if item.file.isCustomTemplateEmoji { - animationNode.dynamicColor = theme.list.itemPrimaryTextColor + var isPremiumSticker = false + if let file = item.file { + for case let .Sticker(text, _, _) in file.attributes { + self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(32.0), textColor: .black) + break } - animationNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: item.file.resource, isVideo: item.file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: isPremiumSticker ? .once : .loop, mode: .direct(cachePathPrefix: nil)) - animationNode.visibility = true - animationNode.addSubnode(self.textNode) + isPremiumSticker = file.isPremiumSticker - if isPremiumSticker, let effect = item.file.videoThumbnails.first { - self.effectDisposable.set(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: item.file), resource: effect.resource).start()) + if file.isAnimatedSticker || file.isVideoSticker { + let animationNode = DefaultAnimatedStickerNodeImpl() + animationNode.overrideVisibility = true + self.animationNode = animationNode - let source = AnimatedStickerResourceSource(account: context.account, resource: effect.resource, fitzModifier: nil) - let additionalAnimationNode = DefaultAnimatedStickerNodeImpl() - additionalAnimationNode.setup(source: source, width: Int(fittedDimensions.width * 2.0), height: Int(fittedDimensions.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: nil)) - additionalAnimationNode.visibility = true - self.additionalAnimationNode = additionalAnimationNode + let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) + let fitSize: CGSize + if file.isCustomEmoji { + fitSize = CGSize(width: 200.0, height: 200.0) + } else { + fitSize = CGSize(width: 400.0, height: 400.0) + } + let fittedDimensions = dimensions.cgSize.aspectFitted(fitSize) + + if file.isCustomTemplateEmoji { + animationNode.dynamicColor = theme.list.itemPrimaryTextColor + } + + animationNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: isPremiumSticker ? .once : .loop, mode: .direct(cachePathPrefix: nil)) + animationNode.visibility = true + animationNode.addSubnode(self.textNode) + + if isPremiumSticker, let effect = file.videoThumbnails.first { + self.effectDisposable.set(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: effect.resource).start()) + + let source = AnimatedStickerResourceSource(account: context.account, resource: effect.resource, fitzModifier: nil) + let additionalAnimationNode = DefaultAnimatedStickerNodeImpl() + additionalAnimationNode.setup(source: source, width: Int(fittedDimensions.width * 2.0), height: Int(fittedDimensions.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + additionalAnimationNode.visibility = true + self.additionalAnimationNode = additionalAnimationNode + } + } else { + self.imageNode.addSubnode(self.textNode) + self.animationNode = nil } - } else { - self.imageNode.addSubnode(self.textNode) - self.animationNode = nil + + self.imageNode.setSignal(chatMessageSticker(account: context.account, userLocation: .other, file: file, small: false, fetched: true)) + } else if case let .image(image) = item { + self.imageNode.contents = image.cgImage + self._ready.set(.single(true)) } - self.imageNode.setSignal(chatMessageSticker(account: context.account, userLocation: .other, file: item.file, small: false, fetched: true)) - super.init() self.isUserInteractionEnabled = false @@ -209,57 +225,60 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { let boundingSize: CGSize - if self.item.file.isCustomEmoji { + if self.item.file?.isCustomEmoji == true { boundingSize = CGSize(width: 120.0, height: 120.0) } else if let _ = self.additionalAnimationNode { boundingSize = CGSize(width: 240.0, height: 240.0).fitted(size) } else { boundingSize = CGSize(width: 180.0, height: 180.0).fitted(size) } - - if let dimensitons = self.item.file.dimensions { - var topOffset: CGFloat = 0.0 - var textSpacing: CGFloat = 50.0 - - if size.width == 292.0 { - topOffset = 60.0 - textSpacing -= 10.0 - } else if size.width == 347.0 && size.height == 577.0 { - topOffset = 60.0 - textSpacing -= 10.0 - } - - let textSize = self.textNode.measure(CGSize(width: 100.0, height: 100.0)) - - let imageSize = dimensitons.cgSize.aspectFitted(boundingSize) - self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() - var imageFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0), y: textSize.height + textSpacing - topOffset), size: imageSize) - var centerOffset: CGFloat = 0.0 - if self.item.file.isPremiumSticker { - let originalImageFrame = imageFrame - imageFrame.origin.x = min(imageFrame.minX + imageFrame.width * 0.1, size.width - imageFrame.width - 18.0) - centerOffset = imageFrame.minX - originalImageFrame.minX - } - self.imageNode.frame = imageFrame - if let animationNode = self.animationNode { - animationNode.frame = imageFrame - animationNode.updateLayout(size: imageSize) - - if let additionalAnimationNode = self.additionalAnimationNode { - additionalAnimationNode.frame = imageFrame.offsetBy(dx: -imageFrame.width * 0.245 + 21.0, dy: -1.0).insetBy(dx: -imageFrame.width * 0.245, dy: -imageFrame.height * 0.245) - additionalAnimationNode.updateLayout(size: additionalAnimationNode.frame.size) - } - } - - self.textNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - textSize.width) / 2.0) - centerOffset, y: -textSize.height - textSpacing), size: textSize) - - if self.item.file.isCustomEmoji { - return CGSize(width: boundingSize.width, height: imageFrame.height) - } else { - return CGSize(width: boundingSize.width, height: imageFrame.height + textSize.height + textSpacing) - } + + let dimensions: PixelDimensions + if let dimensionsValue = self.item.file?.dimensions { + dimensions = dimensionsValue } else { - return CGSize(width: size.width, height: 10.0) + dimensions = PixelDimensions(width: 512, height: 512) + } + + var topOffset: CGFloat = 0.0 + var textSpacing: CGFloat = 50.0 + + if size.width == 292.0 { + topOffset = 60.0 + textSpacing -= 10.0 + } else if size.width == 347.0 && size.height == 577.0 { + topOffset = 60.0 + textSpacing -= 10.0 + } + + let textSize = self.textNode.measure(CGSize(width: 100.0, height: 100.0)) + + let imageSize = dimensions.cgSize.aspectFitted(boundingSize) + self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() + var imageFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0), y: textSize.height + textSpacing - topOffset), size: imageSize) + var centerOffset: CGFloat = 0.0 + if self.item.file?.isPremiumSticker == true { + let originalImageFrame = imageFrame + imageFrame.origin.x = min(imageFrame.minX + imageFrame.width * 0.1, size.width - imageFrame.width - 18.0) + centerOffset = imageFrame.minX - originalImageFrame.minX + } + self.imageNode.frame = imageFrame + if let animationNode = self.animationNode { + animationNode.frame = imageFrame + animationNode.updateLayout(size: imageSize) + + if let additionalAnimationNode = self.additionalAnimationNode { + additionalAnimationNode.frame = imageFrame.offsetBy(dx: -imageFrame.width * 0.245 + 21.0, dy: -1.0).insetBy(dx: -imageFrame.width * 0.245, dy: -imageFrame.height * 0.245) + additionalAnimationNode.updateLayout(size: additionalAnimationNode.frame.size) + } + } + + self.textNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - textSize.width) / 2.0) - centerOffset, y: -textSize.height - textSpacing), size: textSize) + + if self.item.file?.isCustomEmoji == true { + return CGSize(width: boundingSize.width, height: imageFrame.height) + } else { + return CGSize(width: boundingSize.width, height: imageFrame.height + textSize.height + textSpacing) } } } @@ -337,3 +356,146 @@ final class PremiumStickerPackAccessoryNode: SparseNode, PeekControllerAccessory self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - cancelSize.height - 48.0 - buttonHeight - 20.0 - textSize.height - 31.0 + bottomOffset), size: textSize) } } + +final class EmojiStickerAccessoryNode: SparseNode, PeekControllerAccessoryNode { + let context: AccountContext + + let reactionContextNode: ReactionContextNode + + var dismiss: () -> Void = {} + + init(context: AccountContext, theme: PresentationTheme, reactionItems: [ReactionItem]) { + self.context = context + + var layoutImpl: ((ContainedViewLayoutTransition) -> Void)? + + var items: [ReactionItem] = [] + if let reaction = reactionItems.first(where: { $0.reaction.rawValue == .builtin("👍") }) { + items.append(reaction) + } + if let reaction = reactionItems.first(where: { $0.reaction.rawValue == .builtin("👎") }) { + items.append(reaction) + } + if let reaction = reactionItems.first(where: { $0.reaction.rawValue == .builtin("❤") }) { + items.append(reaction) + } + if let reaction = reactionItems.first(where: { $0.reaction.rawValue == .builtin("🔥") }) { + items.append(reaction) + } + if let reaction = reactionItems.first(where: { $0.reaction.rawValue == .builtin("🥰") }) { + items.append(reaction) + } + if let reaction = reactionItems.first(where: { $0.reaction.rawValue == .builtin("👏") }) { + items.append(reaction) + } + if let reaction = reactionItems.first(where: { $0.reaction.rawValue == .builtin("😁") }) { + items.append(reaction) + } + + let selectedItems = ValuePromise>() + //TODO:localize + let reactionContextNode = ReactionContextNode( + context: self.context, + animationCache: self.context.animationCache, + presentationData: self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme), + items: items.map { .reaction($0) }, + selectedItems: Set(), + title: "Set emoji that corresponds to your sticker", + reactionsLocked: false, + alwaysAllowPremiumReactions: true, + allPresetReactionsAreAvailable: true, + getEmojiContent: { animationCache, animationRenderer in + let mappedReactionItems: [EmojiComponentReactionItem] = items.map { reaction -> EmojiComponentReactionItem in + return EmojiComponentReactionItem(reaction: reaction.reaction.rawValue, file: reaction.stillAnimation) + } + + return selectedItems.get() + |> mapToSignal { selectedItems in + let selected = Set() + + return EmojiPagerContentComponent.emojiInputData( + context: context, + animationCache: animationCache, + animationRenderer: animationRenderer, + isStandalone: false, + subject: .stickerAlt, + hasTrending: false, + topReactionItems: mappedReactionItems, + areUnicodeEmojiEnabled: true, + areCustomEmojiEnabled: false, + chatPeerId: context.account.peerId, + selectedItems: selected, + hasRecent: false, + premiumIfSavedMessages: false + ) + } + }, + isExpandedUpdated: { transition in + layoutImpl?(transition) + }, + requestLayout: { transition in + layoutImpl?(transition) + }, + requestUpdateOverlayWantsToBeBelowKeyboard: { transition in + layoutImpl?(transition) + } + ) + reactionContextNode.displayTail = true + reactionContextNode.forceTailToRight = true + reactionContextNode.forceDark = true + self.reactionContextNode = reactionContextNode + + super.init() + + layoutImpl = { [weak self] transition in + self?.requestLayout(transition: transition) + } + + reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in + guard let self else { + return + } + self.reactionContextNode.collapse() + + let _ = (selectedItems.get() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { items in + var items = items + items.insert(updateReaction.reaction) + selectedItems.set(items) + }) + } + + self.addSubnode(reactionContextNode) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + if let result, result.isDescendant(of: self.reactionContextNode.view) { + return result + } + return nil + } + + func requestLayout(transition: ContainedViewLayoutTransition) { + guard let size = self.currentLayout else { + return + } + self.updateLayout(size: size, transition: transition) + } + + private var currentLayout: CGSize? + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + let isFirstTime = self.currentLayout == nil + self.currentLayout = size + + let anchorRect = CGRect(x: size.width / 2.0, y: size.height / 3.0 - 50.0, width: 0.0, height: 0.0) + + transition.updateFrame(view: self.reactionContextNode.view, frame: CGRect(origin: CGPoint(), size: size)) + self.reactionContextNode.updateLayout(size: size, insets: UIEdgeInsets(top: 64.0, left: 0.0, bottom: 0.0, right: 0.0), anchorRect: anchorRect, centerAligned: true, isCoveredByInput: false, isAnimatingOut: false, transition: transition) + + if isFirstTime { + self.reactionContextNode.animateIn(from: anchorRect) + } + } +} diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 1a147326a5..062a5adc26 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -6,6 +6,7 @@ public enum Api { public enum channels {} public enum chatlists {} public enum contacts {} + public enum fragment {} public enum help {} public enum messages {} public enum payments {} @@ -28,6 +29,7 @@ public enum Api { public enum chatlists {} public enum contacts {} public enum folders {} + public enum fragment {} public enum help {} public enum langpack {} public enum messages {} @@ -321,6 +323,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1736378792] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordEmpty($0) } dict[-763367294] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordSRP($0) } dict[1968737087] = { return Api.InputClientProxy.parse_inputClientProxy($0) } + dict[-1562241884] = { return Api.InputCollectible.parse_inputCollectiblePhone($0) } + dict[-476815191] = { return Api.InputCollectible.parse_inputCollectibleUsername($0) } dict[-208488460] = { return Api.InputContact.parse_inputPhoneContact($0) } dict[-55902537] = { return Api.InputDialogPeer.parse_inputDialogPeer($0) } dict[1684014375] = { return Api.InputDialogPeer.parse_inputDialogPeerFolder($0) } @@ -491,7 +495,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) } dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } dict[64088654] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) } - dict[-1502839044] = { return Api.Message.parse_message($0) } + dict[556221267] = { return Api.Message.parse_message($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } dict[721967202] = { return Api.Message.parse_messageService($0) } dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) } @@ -1125,6 +1129,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1891070632] = { return Api.contacts.TopPeers.parse_topPeers($0) } dict[-1255369827] = { return Api.contacts.TopPeers.parse_topPeersDisabled($0) } dict[-567906571] = { return Api.contacts.TopPeers.parse_topPeersNotModified($0) } + dict[1857945489] = { return Api.fragment.CollectibleInfo.parse_collectibleInfo($0) } dict[-585598930] = { return Api.help.AppConfig.parse_appConfig($0) } dict[2094949405] = { return Api.help.AppConfig.parse_appConfigNotModified($0) } dict[-860107216] = { return Api.help.AppUpdate.parse_appUpdate($0) } @@ -1203,6 +1208,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1938715001] = { return Api.messages.Messages.parse_messages($0) } dict[1951620897] = { return Api.messages.Messages.parse_messagesNotModified($0) } dict[978610270] = { return Api.messages.Messages.parse_messagesSlice($0) } + dict[-83926371] = { return Api.messages.MyStickers.parse_myStickers($0) } dict[863093588] = { return Api.messages.PeerDialogs.parse_peerDialogs($0) } dict[1753266509] = { return Api.messages.PeerSettings.parse_peerSettings($0) } dict[-963811691] = { return Api.messages.QuickReplies.parse_quickReplies($0) } @@ -1587,6 +1593,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.InputClientProxy: _1.serialize(buffer, boxed) + case let _1 as Api.InputCollectible: + _1.serialize(buffer, boxed) case let _1 as Api.InputContact: _1.serialize(buffer, boxed) case let _1 as Api.InputDialogPeer: @@ -2043,6 +2051,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.contacts.TopPeers: _1.serialize(buffer, boxed) + case let _1 as Api.fragment.CollectibleInfo: + _1.serialize(buffer, boxed) case let _1 as Api.help.AppConfig: _1.serialize(buffer, boxed) case let _1 as Api.help.AppUpdate: @@ -2149,6 +2159,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.Messages: _1.serialize(buffer, boxed) + case let _1 as Api.messages.MyStickers: + _1.serialize(buffer, boxed) case let _1 as Api.messages.PeerDialogs: _1.serialize(buffer, boxed) case let _1 as Api.messages.PeerSettings: diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index 4fe4c26f44..2148237347 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -1,3 +1,183 @@ +public extension Api { + enum InputPrivacyRule: TypeConstructorDescription { + case inputPrivacyValueAllowAll + case inputPrivacyValueAllowChatParticipants(chats: [Int64]) + case inputPrivacyValueAllowCloseFriends + case inputPrivacyValueAllowContacts + case inputPrivacyValueAllowUsers(users: [Api.InputUser]) + case inputPrivacyValueDisallowAll + case inputPrivacyValueDisallowChatParticipants(chats: [Int64]) + case inputPrivacyValueDisallowContacts + case inputPrivacyValueDisallowUsers(users: [Api.InputUser]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputPrivacyValueAllowAll: + if boxed { + buffer.appendInt32(407582158) + } + + break + case .inputPrivacyValueAllowChatParticipants(let chats): + if boxed { + buffer.appendInt32(-2079962673) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .inputPrivacyValueAllowCloseFriends: + if boxed { + buffer.appendInt32(793067081) + } + + break + case .inputPrivacyValueAllowContacts: + if boxed { + buffer.appendInt32(218751099) + } + + break + case .inputPrivacyValueAllowUsers(let users): + if boxed { + buffer.appendInt32(320652927) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .inputPrivacyValueDisallowAll: + if boxed { + buffer.appendInt32(-697604407) + } + + break + case .inputPrivacyValueDisallowChatParticipants(let chats): + if boxed { + buffer.appendInt32(-380694650) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .inputPrivacyValueDisallowContacts: + if boxed { + buffer.appendInt32(195371015) + } + + break + case .inputPrivacyValueDisallowUsers(let users): + if boxed { + buffer.appendInt32(-1877932953) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputPrivacyValueAllowAll: + return ("inputPrivacyValueAllowAll", []) + case .inputPrivacyValueAllowChatParticipants(let chats): + return ("inputPrivacyValueAllowChatParticipants", [("chats", chats as Any)]) + case .inputPrivacyValueAllowCloseFriends: + return ("inputPrivacyValueAllowCloseFriends", []) + case .inputPrivacyValueAllowContacts: + return ("inputPrivacyValueAllowContacts", []) + case .inputPrivacyValueAllowUsers(let users): + return ("inputPrivacyValueAllowUsers", [("users", users as Any)]) + case .inputPrivacyValueDisallowAll: + return ("inputPrivacyValueDisallowAll", []) + case .inputPrivacyValueDisallowChatParticipants(let chats): + return ("inputPrivacyValueDisallowChatParticipants", [("chats", chats as Any)]) + case .inputPrivacyValueDisallowContacts: + return ("inputPrivacyValueDisallowContacts", []) + case .inputPrivacyValueDisallowUsers(let users): + return ("inputPrivacyValueDisallowUsers", [("users", users as Any)]) + } + } + + public static func parse_inputPrivacyValueAllowAll(_ reader: BufferReader) -> InputPrivacyRule? { + return Api.InputPrivacyRule.inputPrivacyValueAllowAll + } + public static func parse_inputPrivacyValueAllowChatParticipants(_ reader: BufferReader) -> InputPrivacyRule? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.InputPrivacyRule.inputPrivacyValueAllowChatParticipants(chats: _1!) + } + else { + return nil + } + } + public static func parse_inputPrivacyValueAllowCloseFriends(_ reader: BufferReader) -> InputPrivacyRule? { + return Api.InputPrivacyRule.inputPrivacyValueAllowCloseFriends + } + public static func parse_inputPrivacyValueAllowContacts(_ reader: BufferReader) -> InputPrivacyRule? { + return Api.InputPrivacyRule.inputPrivacyValueAllowContacts + } + public static func parse_inputPrivacyValueAllowUsers(_ reader: BufferReader) -> InputPrivacyRule? { + var _1: [Api.InputUser]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.InputPrivacyRule.inputPrivacyValueAllowUsers(users: _1!) + } + else { + return nil + } + } + public static func parse_inputPrivacyValueDisallowAll(_ reader: BufferReader) -> InputPrivacyRule? { + return Api.InputPrivacyRule.inputPrivacyValueDisallowAll + } + public static func parse_inputPrivacyValueDisallowChatParticipants(_ reader: BufferReader) -> InputPrivacyRule? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.InputPrivacyRule.inputPrivacyValueDisallowChatParticipants(chats: _1!) + } + else { + return nil + } + } + public static func parse_inputPrivacyValueDisallowContacts(_ reader: BufferReader) -> InputPrivacyRule? { + return Api.InputPrivacyRule.inputPrivacyValueDisallowContacts + } + public static func parse_inputPrivacyValueDisallowUsers(_ reader: BufferReader) -> InputPrivacyRule? { + var _1: [Api.InputUser]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.InputPrivacyRule.inputPrivacyValueDisallowUsers(users: _1!) + } + else { + return nil + } + } + + } +} public extension Api { enum InputQuickReplyShortcut: TypeConstructorDescription { case inputQuickReplyShortcut(shortcut: String) @@ -844,229 +1024,3 @@ public extension Api { } } -public extension Api { - enum InputTheme: TypeConstructorDescription { - case inputTheme(id: Int64, accessHash: Int64) - case inputThemeSlug(slug: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputTheme(let id, let accessHash): - if boxed { - buffer.appendInt32(1012306921) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - case .inputThemeSlug(let slug): - if boxed { - buffer.appendInt32(-175567375) - } - serializeString(slug, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputTheme(let id, let accessHash): - return ("inputTheme", [("id", id as Any), ("accessHash", accessHash as Any)]) - case .inputThemeSlug(let slug): - return ("inputThemeSlug", [("slug", slug as Any)]) - } - } - - public static func parse_inputTheme(_ reader: BufferReader) -> InputTheme? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputTheme.inputTheme(id: _1!, accessHash: _2!) - } - else { - return nil - } - } - public static func parse_inputThemeSlug(_ reader: BufferReader) -> InputTheme? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.InputTheme.inputThemeSlug(slug: _1!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum InputThemeSettings: TypeConstructorDescription { - case inputThemeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, outboxAccentColor: Int32?, messageColors: [Int32]?, wallpaper: Api.InputWallPaper?, wallpaperSettings: Api.WallPaperSettings?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputThemeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper, let wallpaperSettings): - if boxed { - buffer.appendInt32(-1881255857) - } - serializeInt32(flags, buffer: buffer, boxed: false) - baseTheme.serialize(buffer, true) - serializeInt32(accentColor, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(outboxAccentColor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messageColors!.count)) - for item in messageColors! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - if Int(flags) & Int(1 << 1) != 0 {wallpaper!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {wallpaperSettings!.serialize(buffer, true)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputThemeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper, let wallpaperSettings): - return ("inputThemeSettings", [("flags", flags as Any), ("baseTheme", baseTheme as Any), ("accentColor", accentColor as Any), ("outboxAccentColor", outboxAccentColor as Any), ("messageColors", messageColors as Any), ("wallpaper", wallpaper as Any), ("wallpaperSettings", wallpaperSettings as Any)]) - } - } - - public static func parse_inputThemeSettings(_ reader: BufferReader) -> InputThemeSettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.BaseTheme? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BaseTheme - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() } - var _5: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } } - var _6: Api.InputWallPaper? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.InputWallPaper - } } - var _7: Api.WallPaperSettings? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.InputThemeSettings.inputThemeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6, wallpaperSettings: _7) - } - else { - return nil - } - } - - } -} -public extension Api { - indirect enum InputUser: TypeConstructorDescription { - case inputUser(userId: Int64, accessHash: Int64) - case inputUserEmpty - case inputUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int64) - case inputUserSelf - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputUser(let userId, let accessHash): - if boxed { - buffer.appendInt32(-233744186) - } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - case .inputUserEmpty: - if boxed { - buffer.appendInt32(-1182234929) - } - - break - case .inputUserFromMessage(let peer, let msgId, let userId): - if boxed { - buffer.appendInt32(497305826) - } - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - break - case .inputUserSelf: - if boxed { - buffer.appendInt32(-138301121) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputUser(let userId, let accessHash): - return ("inputUser", [("userId", userId as Any), ("accessHash", accessHash as Any)]) - case .inputUserEmpty: - return ("inputUserEmpty", []) - case .inputUserFromMessage(let peer, let msgId, let userId): - return ("inputUserFromMessage", [("peer", peer as Any), ("msgId", msgId as Any), ("userId", userId as Any)]) - case .inputUserSelf: - return ("inputUserSelf", []) - } - } - - public static func parse_inputUser(_ reader: BufferReader) -> InputUser? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputUser.inputUser(userId: _1!, accessHash: _2!) - } - else { - return nil - } - } - public static func parse_inputUserEmpty(_ reader: BufferReader) -> InputUser? { - return Api.InputUser.inputUserEmpty - } - public static func parse_inputUserFromMessage(_ reader: BufferReader) -> InputUser? { - var _1: Api.InputPeer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputUser.inputUserFromMessage(peer: _1!, msgId: _2!, userId: _3!) - } - else { - return nil - } - } - public static func parse_inputUserSelf(_ reader: BufferReader) -> InputUser? { - return Api.InputUser.inputUserSelf - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index 89037ef849..024db526a7 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -1,3 +1,229 @@ +public extension Api { + enum InputTheme: TypeConstructorDescription { + case inputTheme(id: Int64, accessHash: Int64) + case inputThemeSlug(slug: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputTheme(let id, let accessHash): + if boxed { + buffer.appendInt32(1012306921) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputThemeSlug(let slug): + if boxed { + buffer.appendInt32(-175567375) + } + serializeString(slug, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputTheme(let id, let accessHash): + return ("inputTheme", [("id", id as Any), ("accessHash", accessHash as Any)]) + case .inputThemeSlug(let slug): + return ("inputThemeSlug", [("slug", slug as Any)]) + } + } + + public static func parse_inputTheme(_ reader: BufferReader) -> InputTheme? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputTheme.inputTheme(id: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputThemeSlug(_ reader: BufferReader) -> InputTheme? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputTheme.inputThemeSlug(slug: _1!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum InputThemeSettings: TypeConstructorDescription { + case inputThemeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, outboxAccentColor: Int32?, messageColors: [Int32]?, wallpaper: Api.InputWallPaper?, wallpaperSettings: Api.WallPaperSettings?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputThemeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper, let wallpaperSettings): + if boxed { + buffer.appendInt32(-1881255857) + } + serializeInt32(flags, buffer: buffer, boxed: false) + baseTheme.serialize(buffer, true) + serializeInt32(accentColor, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(outboxAccentColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messageColors!.count)) + for item in messageColors! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + if Int(flags) & Int(1 << 1) != 0 {wallpaper!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {wallpaperSettings!.serialize(buffer, true)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputThemeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper, let wallpaperSettings): + return ("inputThemeSettings", [("flags", flags as Any), ("baseTheme", baseTheme as Any), ("accentColor", accentColor as Any), ("outboxAccentColor", outboxAccentColor as Any), ("messageColors", messageColors as Any), ("wallpaper", wallpaper as Any), ("wallpaperSettings", wallpaperSettings as Any)]) + } + } + + public static func parse_inputThemeSettings(_ reader: BufferReader) -> InputThemeSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.BaseTheme? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.BaseTheme + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() } + var _5: [Int32]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + var _6: Api.InputWallPaper? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.InputWallPaper + } } + var _7: Api.WallPaperSettings? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.InputThemeSettings.inputThemeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6, wallpaperSettings: _7) + } + else { + return nil + } + } + + } +} +public extension Api { + indirect enum InputUser: TypeConstructorDescription { + case inputUser(userId: Int64, accessHash: Int64) + case inputUserEmpty + case inputUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int64) + case inputUserSelf + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputUser(let userId, let accessHash): + if boxed { + buffer.appendInt32(-233744186) + } + serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputUserEmpty: + if boxed { + buffer.appendInt32(-1182234929) + } + + break + case .inputUserFromMessage(let peer, let msgId, let userId): + if boxed { + buffer.appendInt32(497305826) + } + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + break + case .inputUserSelf: + if boxed { + buffer.appendInt32(-138301121) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputUser(let userId, let accessHash): + return ("inputUser", [("userId", userId as Any), ("accessHash", accessHash as Any)]) + case .inputUserEmpty: + return ("inputUserEmpty", []) + case .inputUserFromMessage(let peer, let msgId, let userId): + return ("inputUserFromMessage", [("peer", peer as Any), ("msgId", msgId as Any), ("userId", userId as Any)]) + case .inputUserSelf: + return ("inputUserSelf", []) + } + } + + public static func parse_inputUser(_ reader: BufferReader) -> InputUser? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputUser.inputUser(userId: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputUserEmpty(_ reader: BufferReader) -> InputUser? { + return Api.InputUser.inputUserEmpty + } + public static func parse_inputUserFromMessage(_ reader: BufferReader) -> InputUser? { + var _1: Api.InputPeer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputUser.inputUserFromMessage(peer: _1!, msgId: _2!, userId: _3!) + } + else { + return nil + } + } + public static func parse_inputUserSelf(_ reader: BufferReader) -> InputUser? { + return Api.InputUser.inputUserSelf + } + + } +} public extension Api { enum InputWallPaper: TypeConstructorDescription { case inputWallPaper(id: Int64, accessHash: Int64) @@ -942,45 +1168,3 @@ public extension Api { } } -public extension Api { - enum KeyboardButtonRow: TypeConstructorDescription { - case keyboardButtonRow(buttons: [Api.KeyboardButton]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .keyboardButtonRow(let buttons): - if boxed { - buffer.appendInt32(2002815875) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(buttons.count)) - for item in buttons { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .keyboardButtonRow(let buttons): - return ("keyboardButtonRow", [("buttons", buttons as Any)]) - } - } - - public static func parse_keyboardButtonRow(_ reader: BufferReader) -> KeyboardButtonRow? { - var _1: [Api.KeyboardButton]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButton.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButtonRow.keyboardButtonRow(buttons: _1!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index 1edeff5668..ca1f24d375 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -1,3 +1,45 @@ +public extension Api { + enum KeyboardButtonRow: TypeConstructorDescription { + case keyboardButtonRow(buttons: [Api.KeyboardButton]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .keyboardButtonRow(let buttons): + if boxed { + buffer.appendInt32(2002815875) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(buttons.count)) + for item in buttons { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .keyboardButtonRow(let buttons): + return ("keyboardButtonRow", [("buttons", buttons as Any)]) + } + } + + public static func parse_keyboardButtonRow(_ reader: BufferReader) -> KeyboardButtonRow? { + var _1: [Api.KeyboardButton]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButton.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.KeyboardButtonRow.keyboardButtonRow(buttons: _1!) + } + else { + return nil + } + } + + } +} public extension Api { enum LabeledPrice: TypeConstructorDescription { case labeledPrice(label: String, amount: Int64) @@ -586,17 +628,18 @@ public extension Api { } public extension Api { indirect enum Message: TypeConstructorDescription { - case message(flags: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?) + case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?) case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?) case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, ttlPeriod: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .message(let flags, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId): + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId): if boxed { - buffer.appendInt32(-1502839044) + buffer.appendInt32(556221267) } serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(flags2, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} if Int(flags) & Int(1 << 29) != 0 {serializeInt32(fromBoostsApplied!, buffer: buffer, boxed: false)} @@ -655,8 +698,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .message(let flags, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId): - return ("message", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any)]) + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId): + return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any)]) case .messageEmpty(let flags, let id, let peerId): return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)]) case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let ttlPeriod): @@ -669,98 +712,101 @@ public extension Api { _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: Api.Peer? + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Peer? if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer + _4 = Api.parse(reader, signature: signature) as? Api.Peer } } - var _4: Int32? - if Int(_1!) & Int(1 << 29) != 0 {_4 = reader.readInt32() } - var _5: Api.Peer? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Peer - } + var _5: Int32? + if Int(_1!) & Int(1 << 29) != 0 {_5 = reader.readInt32() } var _6: Api.Peer? - if Int(_1!) & Int(1 << 28) != 0 {if let signature = reader.readInt32() { + if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _7: Api.Peer? + if Int(_1!) & Int(1 << 28) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Peer } } - var _7: Api.MessageFwdHeader? + var _8: Api.MessageFwdHeader? if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader } } - var _8: Int64? - if Int(_1!) & Int(1 << 11) != 0 {_8 = reader.readInt64() } - var _9: Api.MessageReplyHeader? + var _9: Int64? + if Int(_1!) & Int(1 << 11) != 0 {_9 = reader.readInt64() } + var _10: Api.MessageReplyHeader? if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + _10 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader } } - var _10: Int32? - _10 = reader.readInt32() - var _11: String? - _11 = parseString(reader) - var _12: Api.MessageMedia? + var _11: Int32? + _11 = reader.readInt32() + var _12: String? + _12 = parseString(reader) + var _13: Api.MessageMedia? if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.MessageMedia + _13 = Api.parse(reader, signature: signature) as? Api.MessageMedia } } - var _13: Api.ReplyMarkup? + var _14: Api.ReplyMarkup? if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { - _13 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup + _14 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } - var _14: [Api.MessageEntity]? + var _15: [Api.MessageEntity]? if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } - var _15: Int32? - if Int(_1!) & Int(1 << 10) != 0 {_15 = reader.readInt32() } var _16: Int32? if Int(_1!) & Int(1 << 10) != 0 {_16 = reader.readInt32() } - var _17: Api.MessageReplies? + var _17: Int32? + if Int(_1!) & Int(1 << 10) != 0 {_17 = reader.readInt32() } + var _18: Api.MessageReplies? if Int(_1!) & Int(1 << 23) != 0 {if let signature = reader.readInt32() { - _17 = Api.parse(reader, signature: signature) as? Api.MessageReplies + _18 = Api.parse(reader, signature: signature) as? Api.MessageReplies } } - var _18: Int32? - if Int(_1!) & Int(1 << 15) != 0 {_18 = reader.readInt32() } - var _19: String? - if Int(_1!) & Int(1 << 16) != 0 {_19 = parseString(reader) } - var _20: Int64? - if Int(_1!) & Int(1 << 17) != 0 {_20 = reader.readInt64() } - var _21: Api.MessageReactions? + var _19: Int32? + if Int(_1!) & Int(1 << 15) != 0 {_19 = reader.readInt32() } + var _20: String? + if Int(_1!) & Int(1 << 16) != 0 {_20 = parseString(reader) } + var _21: Int64? + if Int(_1!) & Int(1 << 17) != 0 {_21 = reader.readInt64() } + var _22: Api.MessageReactions? if Int(_1!) & Int(1 << 20) != 0 {if let signature = reader.readInt32() { - _21 = Api.parse(reader, signature: signature) as? Api.MessageReactions + _22 = Api.parse(reader, signature: signature) as? Api.MessageReactions } } - var _22: [Api.RestrictionReason]? + var _23: [Api.RestrictionReason]? if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() { - _22 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + _23 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) } } - var _23: Int32? - if Int(_1!) & Int(1 << 25) != 0 {_23 = reader.readInt32() } var _24: Int32? - if Int(_1!) & Int(1 << 30) != 0 {_24 = reader.readInt32() } + if Int(_1!) & Int(1 << 25) != 0 {_24 = reader.readInt32() } + var _25: Int32? + if Int(_1!) & Int(1 << 30) != 0 {_25 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 29) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 28) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 11) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil - let _c10 = _10 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 29) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 28) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 11) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil let _c11 = _11 != nil - let _c12 = (Int(_1!) & Int(1 << 9) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 6) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 7) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 10) == 0) || _15 != nil + let _c12 = _12 != nil + let _c13 = (Int(_1!) & Int(1 << 9) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil + let _c15 = (Int(_1!) & Int(1 << 7) == 0) || _15 != nil let _c16 = (Int(_1!) & Int(1 << 10) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 23) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 15) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 16) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 17) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 20) == 0) || _21 != nil - let _c22 = (Int(_1!) & Int(1 << 22) == 0) || _22 != nil - let _c23 = (Int(_1!) & Int(1 << 25) == 0) || _23 != nil - let _c24 = (Int(_1!) & Int(1 << 30) == 0) || _24 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 { - return Api.Message.message(flags: _1!, id: _2!, fromId: _3, fromBoostsApplied: _4, peerId: _5!, savedPeerId: _6, fwdFrom: _7, viaBotId: _8, replyTo: _9, date: _10!, message: _11!, media: _12, replyMarkup: _13, entities: _14, views: _15, forwards: _16, replies: _17, editDate: _18, postAuthor: _19, groupedId: _20, reactions: _21, restrictionReason: _22, ttlPeriod: _23, quickReplyShortcutId: _24) + let _c17 = (Int(_1!) & Int(1 << 10) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 23) == 0) || _18 != nil + let _c19 = (Int(_1!) & Int(1 << 15) == 0) || _19 != nil + let _c20 = (Int(_1!) & Int(1 << 16) == 0) || _20 != nil + let _c21 = (Int(_1!) & Int(1 << 17) == 0) || _21 != nil + let _c22 = (Int(_1!) & Int(1 << 20) == 0) || _22 != nil + let _c23 = (Int(_1!) & Int(1 << 22) == 0) || _23 != nil + let _c24 = (Int(_1!) & Int(1 << 25) == 0) || _24 != nil + let _c25 = (Int(_1!) & Int(1 << 30) == 0) || _25 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 { + return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, replyTo: _10, date: _11!, message: _12!, media: _13, replyMarkup: _14, entities: _15, views: _16, forwards: _17, replies: _18, editDate: _19, postAuthor: _20, groupedId: _21, reactions: _22, restrictionReason: _23, ttlPeriod: _24, quickReplyShortcutId: _25) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index e688ea037b..6967f4eb80 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -634,6 +634,62 @@ public extension Api.contacts { } } +public extension Api.fragment { + enum CollectibleInfo: TypeConstructorDescription { + case collectibleInfo(purchaseDate: Int32, currency: String, amount: Int64, cryptoCurrency: String, cryptoAmount: Int64, url: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .collectibleInfo(let purchaseDate, let currency, let amount, let cryptoCurrency, let cryptoAmount, let url): + if boxed { + buffer.appendInt32(1857945489) + } + serializeInt32(purchaseDate, buffer: buffer, boxed: false) + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) + serializeString(cryptoCurrency, buffer: buffer, boxed: false) + serializeInt64(cryptoAmount, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .collectibleInfo(let purchaseDate, let currency, let amount, let cryptoCurrency, let cryptoAmount, let url): + return ("collectibleInfo", [("purchaseDate", purchaseDate as Any), ("currency", currency as Any), ("amount", amount as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("url", url as Any)]) + } + } + + public static func parse_collectibleInfo(_ reader: BufferReader) -> CollectibleInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + var _6: String? + _6 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.fragment.CollectibleInfo.collectibleInfo(purchaseDate: _1!, currency: _2!, amount: _3!, cryptoCurrency: _4!, cryptoAmount: _5!, url: _6!) + } + else { + return nil + } + } + + } +} public extension Api.help { enum AppConfig: TypeConstructorDescription { case appConfig(hash: Int32, config: Api.JSONValue) @@ -1308,89 +1364,3 @@ public extension Api.help { } } -public extension Api.help { - enum PremiumPromo: TypeConstructorDescription { - case premiumPromo(statusText: String, statusEntities: [Api.MessageEntity], videoSections: [String], videos: [Api.Document], periodOptions: [Api.PremiumSubscriptionOption], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users): - if boxed { - buffer.appendInt32(1395946908) - } - serializeString(statusText, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(statusEntities.count)) - for item in statusEntities { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(videoSections.count)) - for item in videoSections { - serializeString(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(videos.count)) - for item in videos { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(periodOptions.count)) - for item in periodOptions { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users): - return ("premiumPromo", [("statusText", statusText as Any), ("statusEntities", statusEntities as Any), ("videoSections", videoSections as Any), ("videos", videos as Any), ("periodOptions", periodOptions as Any), ("users", users as Any)]) - } - } - - public static func parse_premiumPromo(_ reader: BufferReader) -> PremiumPromo? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.MessageEntity]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - var _3: [String]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - var _4: [Api.Document]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - var _5: [Api.PremiumSubscriptionOption]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumSubscriptionOption.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, periodOptions: _5!, users: _6!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 5069e6504e..91159e86a9 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -1,3 +1,89 @@ +public extension Api.help { + enum PremiumPromo: TypeConstructorDescription { + case premiumPromo(statusText: String, statusEntities: [Api.MessageEntity], videoSections: [String], videos: [Api.Document], periodOptions: [Api.PremiumSubscriptionOption], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users): + if boxed { + buffer.appendInt32(1395946908) + } + serializeString(statusText, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(statusEntities.count)) + for item in statusEntities { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(videoSections.count)) + for item in videoSections { + serializeString(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(videos.count)) + for item in videos { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(periodOptions.count)) + for item in periodOptions { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users): + return ("premiumPromo", [("statusText", statusText as Any), ("statusEntities", statusEntities as Any), ("videoSections", videoSections as Any), ("videos", videos as Any), ("periodOptions", periodOptions as Any), ("users", users as Any)]) + } + } + + public static func parse_premiumPromo(_ reader: BufferReader) -> PremiumPromo? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + var _3: [String]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + var _4: [Api.Document]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + var _5: [Api.PremiumSubscriptionOption]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumSubscriptionOption.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, periodOptions: _5!, users: _6!) + } + else { + return nil + } + } + + } +} public extension Api.help { enum PromoData: TypeConstructorDescription { case promoData(flags: Int32, expires: Int32, peer: Api.Peer, chats: [Api.Chat], users: [Api.User], psaType: String?, psaMessage: String?) @@ -1290,49 +1376,3 @@ public extension Api.messages { } } -public extension Api.messages { - enum DialogFilters: TypeConstructorDescription { - case dialogFilters(flags: Int32, filters: [Api.DialogFilter]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .dialogFilters(let flags, let filters): - if boxed { - buffer.appendInt32(718878489) - } - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(filters.count)) - for item in filters { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .dialogFilters(let flags, let filters): - return ("dialogFilters", [("flags", flags as Any), ("filters", filters as Any)]) - } - } - - public static func parse_dialogFilters(_ reader: BufferReader) -> DialogFilters? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.DialogFilter]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.DialogFilters.dialogFilters(flags: _1!, filters: _2!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index cb218918b1..df72f59ee8 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -1,3 +1,49 @@ +public extension Api.messages { + enum DialogFilters: TypeConstructorDescription { + case dialogFilters(flags: Int32, filters: [Api.DialogFilter]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .dialogFilters(let flags, let filters): + if boxed { + buffer.appendInt32(718878489) + } + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(filters.count)) + for item in filters { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .dialogFilters(let flags, let filters): + return ("dialogFilters", [("flags", flags as Any), ("filters", filters as Any)]) + } + } + + public static func parse_dialogFilters(_ reader: BufferReader) -> DialogFilters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.DialogFilter]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.DialogFilters.dialogFilters(flags: _1!, filters: _2!) + } + else { + return nil + } + } + + } +} public extension Api.messages { enum Dialogs: TypeConstructorDescription { case dialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) @@ -1304,6 +1350,52 @@ public extension Api.messages { } } +public extension Api.messages { + enum MyStickers: TypeConstructorDescription { + case myStickers(count: Int32, sets: [Api.StickerSetCovered]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .myStickers(let count, let sets): + if boxed { + buffer.appendInt32(-83926371) + } + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sets.count)) + for item in sets { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .myStickers(let count, let sets): + return ("myStickers", [("count", count as Any), ("sets", sets as Any)]) + } + } + + public static func parse_myStickers(_ reader: BufferReader) -> MyStickers? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.StickerSetCovered]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.MyStickers.myStickers(count: _1!, sets: _2!) + } + else { + return nil + } + } + + } +} public extension Api.messages { enum PeerDialogs: TypeConstructorDescription { case peerDialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) @@ -1524,61 +1616,3 @@ public extension Api.messages { } } -public extension Api.messages { - enum Reactions: TypeConstructorDescription { - case reactions(hash: Int64, reactions: [Api.Reaction]) - case reactionsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .reactions(let hash, let reactions): - if boxed { - buffer.appendInt32(-352454890) - } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(reactions.count)) - for item in reactions { - item.serialize(buffer, true) - } - break - case .reactionsNotModified: - if boxed { - buffer.appendInt32(-1334846497) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .reactions(let hash, let reactions): - return ("reactions", [("hash", hash as Any), ("reactions", reactions as Any)]) - case .reactionsNotModified: - return ("reactionsNotModified", []) - } - } - - public static func parse_reactions(_ reader: BufferReader) -> Reactions? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Reaction]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.Reactions.reactions(hash: _1!, reactions: _2!) - } - else { - return nil - } - } - public static func parse_reactionsNotModified(_ reader: BufferReader) -> Reactions? { - return Api.messages.Reactions.reactionsNotModified - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index fa6a398716..6e364e808a 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -1,3 +1,61 @@ +public extension Api.messages { + enum Reactions: TypeConstructorDescription { + case reactions(hash: Int64, reactions: [Api.Reaction]) + case reactionsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .reactions(let hash, let reactions): + if boxed { + buffer.appendInt32(-352454890) + } + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(reactions.count)) + for item in reactions { + item.serialize(buffer, true) + } + break + case .reactionsNotModified: + if boxed { + buffer.appendInt32(-1334846497) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .reactions(let hash, let reactions): + return ("reactions", [("hash", hash as Any), ("reactions", reactions as Any)]) + case .reactionsNotModified: + return ("reactionsNotModified", []) + } + } + + public static func parse_reactions(_ reader: BufferReader) -> Reactions? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Reaction]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.Reactions.reactions(hash: _1!, reactions: _2!) + } + else { + return nil + } + } + public static func parse_reactionsNotModified(_ reader: BufferReader) -> Reactions? { + return Api.messages.Reactions.reactionsNotModified + } + + } +} public extension Api.messages { enum RecentStickers: TypeConstructorDescription { case recentStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32]) @@ -1346,121 +1404,3 @@ public extension Api.payments { } } -public extension Api.payments { - enum PaymentForm: TypeConstructorDescription { - case paymentForm(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, additionalMethods: [Api.PaymentFormMethod]?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: [Api.PaymentSavedCredentials]?, users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let additionalMethods, let savedInfo, let savedCredentials, let users): - if boxed { - buffer.appendInt32(-1610250415) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(formId, buffer: buffer, boxed: false) - serializeInt64(botId, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 5) != 0 {photo!.serialize(buffer, true)} - invoice.serialize(buffer, true) - serializeInt64(providerId, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 4) != 0 {serializeString(nativeProvider!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {nativeParams!.serialize(buffer, true)} - if Int(flags) & Int(1 << 6) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(additionalMethods!.count)) - for item in additionalMethods! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 0) != 0 {savedInfo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(savedCredentials!.count)) - for item in savedCredentials! { - item.serialize(buffer, true) - }} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let additionalMethods, let savedInfo, let savedCredentials, let users): - return ("paymentForm", [("flags", flags as Any), ("formId", formId as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("providerId", providerId as Any), ("url", url as Any), ("nativeProvider", nativeProvider as Any), ("nativeParams", nativeParams as Any), ("additionalMethods", additionalMethods as Any), ("savedInfo", savedInfo as Any), ("savedCredentials", savedCredentials as Any), ("users", users as Any)]) - } - } - - public static func parse_paymentForm(_ reader: BufferReader) -> PaymentForm? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Api.WebDocument? - if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WebDocument - } } - var _7: Api.Invoice? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Invoice - } - var _8: Int64? - _8 = reader.readInt64() - var _9: String? - _9 = parseString(reader) - var _10: String? - if Int(_1!) & Int(1 << 4) != 0 {_10 = parseString(reader) } - var _11: Api.DataJSON? - if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.DataJSON - } } - var _12: [Api.PaymentFormMethod]? - if Int(_1!) & Int(1 << 6) != 0 {if let _ = reader.readInt32() { - _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentFormMethod.self) - } } - var _13: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _13 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } } - var _14: [Api.PaymentSavedCredentials]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentSavedCredentials.self) - } } - var _15: [Api.User]? - if let _ = reader.readInt32() { - _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 4) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 0) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 1) == 0) || _14 != nil - let _c15 = _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.payments.PaymentForm.paymentForm(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, providerId: _8!, url: _9!, nativeProvider: _10, nativeParams: _11, additionalMethods: _12, savedInfo: _13, savedCredentials: _14, users: _15!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift index 3e5ca3e260..54afb14e4b 100644 --- a/submodules/TelegramApi/Sources/Api30.swift +++ b/submodules/TelegramApi/Sources/Api30.swift @@ -1,3 +1,121 @@ +public extension Api.payments { + enum PaymentForm: TypeConstructorDescription { + case paymentForm(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, additionalMethods: [Api.PaymentFormMethod]?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: [Api.PaymentSavedCredentials]?, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let additionalMethods, let savedInfo, let savedCredentials, let users): + if boxed { + buffer.appendInt32(-1610250415) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(formId, buffer: buffer, boxed: false) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 5) != 0 {photo!.serialize(buffer, true)} + invoice.serialize(buffer, true) + serializeInt64(providerId, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 4) != 0 {serializeString(nativeProvider!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {nativeParams!.serialize(buffer, true)} + if Int(flags) & Int(1 << 6) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(additionalMethods!.count)) + for item in additionalMethods! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 0) != 0 {savedInfo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(savedCredentials!.count)) + for item in savedCredentials! { + item.serialize(buffer, true) + }} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let additionalMethods, let savedInfo, let savedCredentials, let users): + return ("paymentForm", [("flags", flags as Any), ("formId", formId as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("providerId", providerId as Any), ("url", url as Any), ("nativeProvider", nativeProvider as Any), ("nativeParams", nativeParams as Any), ("additionalMethods", additionalMethods as Any), ("savedInfo", savedInfo as Any), ("savedCredentials", savedCredentials as Any), ("users", users as Any)]) + } + } + + public static func parse_paymentForm(_ reader: BufferReader) -> PaymentForm? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.WebDocument? + if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WebDocument + } } + var _7: Api.Invoice? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _8: Int64? + _8 = reader.readInt64() + var _9: String? + _9 = parseString(reader) + var _10: String? + if Int(_1!) & Int(1 << 4) != 0 {_10 = parseString(reader) } + var _11: Api.DataJSON? + if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.DataJSON + } } + var _12: [Api.PaymentFormMethod]? + if Int(_1!) & Int(1 << 6) != 0 {if let _ = reader.readInt32() { + _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentFormMethod.self) + } } + var _13: Api.PaymentRequestedInfo? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _13 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo + } } + var _14: [Api.PaymentSavedCredentials]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentSavedCredentials.self) + } } + var _15: [Api.User]? + if let _ = reader.readInt32() { + _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 4) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 0) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 1) == 0) || _14 != nil + let _c15 = _15 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { + return Api.payments.PaymentForm.paymentForm(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, providerId: _8!, url: _9!, nativeProvider: _10, nativeParams: _11, additionalMethods: _12, savedInfo: _13, savedCredentials: _14, users: _15!) + } + else { + return nil + } + } + + } +} public extension Api.payments { enum PaymentReceipt: TypeConstructorDescription { case paymentReceipt(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User]) diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index 5b224f22fc..fda6b6fbd3 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -3934,6 +3934,21 @@ public extension Api.functions.folders { }) } } +public extension Api.functions.fragment { + static func getCollectibleInfo(collectible: Api.InputCollectible) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1105295942) + collectible.serialize(buffer, true) + return (FunctionDescription(name: "fragment.getCollectibleInfo", parameters: [("collectible", String(describing: collectible))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.fragment.CollectibleInfo? in + let reader = BufferReader(buffer) + var result: Api.fragment.CollectibleInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.fragment.CollectibleInfo + } + return result + }) + } +} public extension Api.functions.help { static func acceptTermsOfService(id: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -5851,6 +5866,22 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func getMyStickers(offsetId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-793386500) + serializeInt64(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getMyStickers", parameters: [("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MyStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.MyStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.MyStickers + } + return result + }) + } +} public extension Api.functions.messages { static func getOldFeaturedStickers(offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -7385,12 +7416,22 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendQuickReplyMessages(peer: Api.InputPeer, shortcutId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendQuickReplyMessages(peer: Api.InputPeer, shortcutId: Int32, id: [Int32], randomId: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(857029332) + buffer.appendInt32(1819610593) peer.serialize(buffer, true) serializeInt32(shortcutId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendQuickReplyMessages", parameters: [("peer", String(describing: peer)), ("shortcutId", String(describing: shortcutId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomId.count)) + for item in randomId { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.sendQuickReplyMessages", parameters: [("peer", String(describing: peer)), ("shortcutId", String(describing: shortcutId)), ("id", String(describing: id)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -9444,6 +9485,22 @@ public extension Api.functions.stickers { }) } } +public extension Api.functions.stickers { + static func replaceSticker(sticker: Api.InputDocument, newSticker: Api.InputStickerSetItem) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1184253338) + sticker.serialize(buffer, true) + newSticker.serialize(buffer, true) + return (FunctionDescription(name: "stickers.replaceSticker", parameters: [("sticker", String(describing: sticker)), ("newSticker", String(describing: newSticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} public extension Api.functions.stickers { static func setStickerSetThumb(flags: Int32, stickerset: Api.InputStickerSet, thumb: Api.InputDocument?, thumbDocumentId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index cbdf0ebf38..7ad68b056b 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -712,6 +712,62 @@ public extension Api { } } +public extension Api { + enum InputCollectible: TypeConstructorDescription { + case inputCollectiblePhone(phone: String) + case inputCollectibleUsername(username: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputCollectiblePhone(let phone): + if boxed { + buffer.appendInt32(-1562241884) + } + serializeString(phone, buffer: buffer, boxed: false) + break + case .inputCollectibleUsername(let username): + if boxed { + buffer.appendInt32(-476815191) + } + serializeString(username, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputCollectiblePhone(let phone): + return ("inputCollectiblePhone", [("phone", phone as Any)]) + case .inputCollectibleUsername(let username): + return ("inputCollectibleUsername", [("username", username as Any)]) + } + } + + public static func parse_inputCollectiblePhone(_ reader: BufferReader) -> InputCollectible? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputCollectible.inputCollectiblePhone(phone: _1!) + } + else { + return nil + } + } + public static func parse_inputCollectibleUsername(_ reader: BufferReader) -> InputCollectible? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputCollectible.inputCollectibleUsername(username: _1!) + } + else { + return nil + } + } + + } +} public extension Api { enum InputContact: TypeConstructorDescription { case inputPhoneContact(clientId: Int64, phone: String, firstName: String, lastName: String) @@ -1102,313 +1158,3 @@ public extension Api { } } -public extension Api { - indirect enum InputFileLocation: TypeConstructorDescription { - case inputDocumentFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, thumbSize: String) - case inputEncryptedFileLocation(id: Int64, accessHash: Int64) - case inputFileLocation(volumeId: Int64, localId: Int32, secret: Int64, fileReference: Buffer) - case inputGroupCallStream(flags: Int32, call: Api.InputGroupCall, timeMs: Int64, scale: Int32, videoChannel: Int32?, videoQuality: Int32?) - case inputPeerPhotoFileLocation(flags: Int32, peer: Api.InputPeer, photoId: Int64) - case inputPhotoFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, thumbSize: String) - case inputPhotoLegacyFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, volumeId: Int64, localId: Int32, secret: Int64) - case inputSecureFileLocation(id: Int64, accessHash: Int64) - case inputStickerSetThumb(stickerset: Api.InputStickerSet, thumbVersion: Int32) - case inputTakeoutFileLocation - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputDocumentFileLocation(let id, let accessHash, let fileReference, let thumbSize): - if boxed { - buffer.appendInt32(-1160743548) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeBytes(fileReference, buffer: buffer, boxed: false) - serializeString(thumbSize, buffer: buffer, boxed: false) - break - case .inputEncryptedFileLocation(let id, let accessHash): - if boxed { - buffer.appendInt32(-182231723) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - case .inputFileLocation(let volumeId, let localId, let secret, let fileReference): - if boxed { - buffer.appendInt32(-539317279) - } - serializeInt64(volumeId, buffer: buffer, boxed: false) - serializeInt32(localId, buffer: buffer, boxed: false) - serializeInt64(secret, buffer: buffer, boxed: false) - serializeBytes(fileReference, buffer: buffer, boxed: false) - break - case .inputGroupCallStream(let flags, let call, let timeMs, let scale, let videoChannel, let videoQuality): - if boxed { - buffer.appendInt32(93890858) - } - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - serializeInt64(timeMs, buffer: buffer, boxed: false) - serializeInt32(scale, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(videoChannel!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(videoQuality!, buffer: buffer, boxed: false)} - break - case .inputPeerPhotoFileLocation(let flags, let peer, let photoId): - if boxed { - buffer.appendInt32(925204121) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt64(photoId, buffer: buffer, boxed: false) - break - case .inputPhotoFileLocation(let id, let accessHash, let fileReference, let thumbSize): - if boxed { - buffer.appendInt32(1075322878) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeBytes(fileReference, buffer: buffer, boxed: false) - serializeString(thumbSize, buffer: buffer, boxed: false) - break - case .inputPhotoLegacyFileLocation(let id, let accessHash, let fileReference, let volumeId, let localId, let secret): - if boxed { - buffer.appendInt32(-667654413) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeBytes(fileReference, buffer: buffer, boxed: false) - serializeInt64(volumeId, buffer: buffer, boxed: false) - serializeInt32(localId, buffer: buffer, boxed: false) - serializeInt64(secret, buffer: buffer, boxed: false) - break - case .inputSecureFileLocation(let id, let accessHash): - if boxed { - buffer.appendInt32(-876089816) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - case .inputStickerSetThumb(let stickerset, let thumbVersion): - if boxed { - buffer.appendInt32(-1652231205) - } - stickerset.serialize(buffer, true) - serializeInt32(thumbVersion, buffer: buffer, boxed: false) - break - case .inputTakeoutFileLocation: - if boxed { - buffer.appendInt32(700340377) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputDocumentFileLocation(let id, let accessHash, let fileReference, let thumbSize): - return ("inputDocumentFileLocation", [("id", id as Any), ("accessHash", accessHash as Any), ("fileReference", fileReference as Any), ("thumbSize", thumbSize as Any)]) - case .inputEncryptedFileLocation(let id, let accessHash): - return ("inputEncryptedFileLocation", [("id", id as Any), ("accessHash", accessHash as Any)]) - case .inputFileLocation(let volumeId, let localId, let secret, let fileReference): - return ("inputFileLocation", [("volumeId", volumeId as Any), ("localId", localId as Any), ("secret", secret as Any), ("fileReference", fileReference as Any)]) - case .inputGroupCallStream(let flags, let call, let timeMs, let scale, let videoChannel, let videoQuality): - return ("inputGroupCallStream", [("flags", flags as Any), ("call", call as Any), ("timeMs", timeMs as Any), ("scale", scale as Any), ("videoChannel", videoChannel as Any), ("videoQuality", videoQuality as Any)]) - case .inputPeerPhotoFileLocation(let flags, let peer, let photoId): - return ("inputPeerPhotoFileLocation", [("flags", flags as Any), ("peer", peer as Any), ("photoId", photoId as Any)]) - case .inputPhotoFileLocation(let id, let accessHash, let fileReference, let thumbSize): - return ("inputPhotoFileLocation", [("id", id as Any), ("accessHash", accessHash as Any), ("fileReference", fileReference as Any), ("thumbSize", thumbSize as Any)]) - case .inputPhotoLegacyFileLocation(let id, let accessHash, let fileReference, let volumeId, let localId, let secret): - return ("inputPhotoLegacyFileLocation", [("id", id as Any), ("accessHash", accessHash as Any), ("fileReference", fileReference as Any), ("volumeId", volumeId as Any), ("localId", localId as Any), ("secret", secret as Any)]) - case .inputSecureFileLocation(let id, let accessHash): - return ("inputSecureFileLocation", [("id", id as Any), ("accessHash", accessHash as Any)]) - case .inputStickerSetThumb(let stickerset, let thumbVersion): - return ("inputStickerSetThumb", [("stickerset", stickerset as Any), ("thumbVersion", thumbVersion as Any)]) - case .inputTakeoutFileLocation: - return ("inputTakeoutFileLocation", []) - } - } - - public static func parse_inputDocumentFileLocation(_ reader: BufferReader) -> InputFileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Buffer? - _3 = parseBytes(reader) - var _4: String? - _4 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputFileLocation.inputDocumentFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, thumbSize: _4!) - } - else { - return nil - } - } - public static func parse_inputEncryptedFileLocation(_ reader: BufferReader) -> InputFileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputFileLocation.inputEncryptedFileLocation(id: _1!, accessHash: _2!) - } - else { - return nil - } - } - public static func parse_inputFileLocation(_ reader: BufferReader) -> InputFileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: Buffer? - _4 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputFileLocation.inputFileLocation(volumeId: _1!, localId: _2!, secret: _3!, fileReference: _4!) - } - else { - return nil - } - } - public static func parse_inputGroupCallStream(_ reader: BufferReader) -> InputFileLocation? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputGroupCall? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } - var _6: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputFileLocation.inputGroupCallStream(flags: _1!, call: _2!, timeMs: _3!, scale: _4!, videoChannel: _5, videoQuality: _6) - } - else { - return nil - } - } - public static func parse_inputPeerPhotoFileLocation(_ reader: BufferReader) -> InputFileLocation? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputPeer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputFileLocation.inputPeerPhotoFileLocation(flags: _1!, peer: _2!, photoId: _3!) - } - else { - return nil - } - } - public static func parse_inputPhotoFileLocation(_ reader: BufferReader) -> InputFileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Buffer? - _3 = parseBytes(reader) - var _4: String? - _4 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputFileLocation.inputPhotoFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, thumbSize: _4!) - } - else { - return nil - } - } - public static func parse_inputPhotoLegacyFileLocation(_ reader: BufferReader) -> InputFileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Buffer? - _3 = parseBytes(reader) - var _4: Int64? - _4 = reader.readInt64() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int64? - _6 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputFileLocation.inputPhotoLegacyFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, volumeId: _4!, localId: _5!, secret: _6!) - } - else { - return nil - } - } - public static func parse_inputSecureFileLocation(_ reader: BufferReader) -> InputFileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputFileLocation.inputSecureFileLocation(id: _1!, accessHash: _2!) - } - else { - return nil - } - } - public static func parse_inputStickerSetThumb(_ reader: BufferReader) -> InputFileLocation? { - var _1: Api.InputStickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputStickerSet - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputFileLocation.inputStickerSetThumb(stickerset: _1!, thumbVersion: _2!) - } - else { - return nil - } - } - public static func parse_inputTakeoutFileLocation(_ reader: BufferReader) -> InputFileLocation? { - return Api.InputFileLocation.inputTakeoutFileLocation - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api8.swift b/submodules/TelegramApi/Sources/Api8.swift index 8048250b31..3276fce952 100644 --- a/submodules/TelegramApi/Sources/Api8.swift +++ b/submodules/TelegramApi/Sources/Api8.swift @@ -1,3 +1,313 @@ +public extension Api { + indirect enum InputFileLocation: TypeConstructorDescription { + case inputDocumentFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, thumbSize: String) + case inputEncryptedFileLocation(id: Int64, accessHash: Int64) + case inputFileLocation(volumeId: Int64, localId: Int32, secret: Int64, fileReference: Buffer) + case inputGroupCallStream(flags: Int32, call: Api.InputGroupCall, timeMs: Int64, scale: Int32, videoChannel: Int32?, videoQuality: Int32?) + case inputPeerPhotoFileLocation(flags: Int32, peer: Api.InputPeer, photoId: Int64) + case inputPhotoFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, thumbSize: String) + case inputPhotoLegacyFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, volumeId: Int64, localId: Int32, secret: Int64) + case inputSecureFileLocation(id: Int64, accessHash: Int64) + case inputStickerSetThumb(stickerset: Api.InputStickerSet, thumbVersion: Int32) + case inputTakeoutFileLocation + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputDocumentFileLocation(let id, let accessHash, let fileReference, let thumbSize): + if boxed { + buffer.appendInt32(-1160743548) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) + serializeString(thumbSize, buffer: buffer, boxed: false) + break + case .inputEncryptedFileLocation(let id, let accessHash): + if boxed { + buffer.appendInt32(-182231723) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputFileLocation(let volumeId, let localId, let secret, let fileReference): + if boxed { + buffer.appendInt32(-539317279) + } + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) + break + case .inputGroupCallStream(let flags, let call, let timeMs, let scale, let videoChannel, let videoQuality): + if boxed { + buffer.appendInt32(93890858) + } + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + serializeInt64(timeMs, buffer: buffer, boxed: false) + serializeInt32(scale, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(videoChannel!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(videoQuality!, buffer: buffer, boxed: false)} + break + case .inputPeerPhotoFileLocation(let flags, let peer, let photoId): + if boxed { + buffer.appendInt32(925204121) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt64(photoId, buffer: buffer, boxed: false) + break + case .inputPhotoFileLocation(let id, let accessHash, let fileReference, let thumbSize): + if boxed { + buffer.appendInt32(1075322878) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) + serializeString(thumbSize, buffer: buffer, boxed: false) + break + case .inputPhotoLegacyFileLocation(let id, let accessHash, let fileReference, let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(-667654413) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + case .inputSecureFileLocation(let id, let accessHash): + if boxed { + buffer.appendInt32(-876089816) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputStickerSetThumb(let stickerset, let thumbVersion): + if boxed { + buffer.appendInt32(-1652231205) + } + stickerset.serialize(buffer, true) + serializeInt32(thumbVersion, buffer: buffer, boxed: false) + break + case .inputTakeoutFileLocation: + if boxed { + buffer.appendInt32(700340377) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputDocumentFileLocation(let id, let accessHash, let fileReference, let thumbSize): + return ("inputDocumentFileLocation", [("id", id as Any), ("accessHash", accessHash as Any), ("fileReference", fileReference as Any), ("thumbSize", thumbSize as Any)]) + case .inputEncryptedFileLocation(let id, let accessHash): + return ("inputEncryptedFileLocation", [("id", id as Any), ("accessHash", accessHash as Any)]) + case .inputFileLocation(let volumeId, let localId, let secret, let fileReference): + return ("inputFileLocation", [("volumeId", volumeId as Any), ("localId", localId as Any), ("secret", secret as Any), ("fileReference", fileReference as Any)]) + case .inputGroupCallStream(let flags, let call, let timeMs, let scale, let videoChannel, let videoQuality): + return ("inputGroupCallStream", [("flags", flags as Any), ("call", call as Any), ("timeMs", timeMs as Any), ("scale", scale as Any), ("videoChannel", videoChannel as Any), ("videoQuality", videoQuality as Any)]) + case .inputPeerPhotoFileLocation(let flags, let peer, let photoId): + return ("inputPeerPhotoFileLocation", [("flags", flags as Any), ("peer", peer as Any), ("photoId", photoId as Any)]) + case .inputPhotoFileLocation(let id, let accessHash, let fileReference, let thumbSize): + return ("inputPhotoFileLocation", [("id", id as Any), ("accessHash", accessHash as Any), ("fileReference", fileReference as Any), ("thumbSize", thumbSize as Any)]) + case .inputPhotoLegacyFileLocation(let id, let accessHash, let fileReference, let volumeId, let localId, let secret): + return ("inputPhotoLegacyFileLocation", [("id", id as Any), ("accessHash", accessHash as Any), ("fileReference", fileReference as Any), ("volumeId", volumeId as Any), ("localId", localId as Any), ("secret", secret as Any)]) + case .inputSecureFileLocation(let id, let accessHash): + return ("inputSecureFileLocation", [("id", id as Any), ("accessHash", accessHash as Any)]) + case .inputStickerSetThumb(let stickerset, let thumbVersion): + return ("inputStickerSetThumb", [("stickerset", stickerset as Any), ("thumbVersion", thumbVersion as Any)]) + case .inputTakeoutFileLocation: + return ("inputTakeoutFileLocation", []) + } + } + + public static func parse_inputDocumentFileLocation(_ reader: BufferReader) -> InputFileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Buffer? + _3 = parseBytes(reader) + var _4: String? + _4 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputFileLocation.inputDocumentFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, thumbSize: _4!) + } + else { + return nil + } + } + public static func parse_inputEncryptedFileLocation(_ reader: BufferReader) -> InputFileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputFileLocation.inputEncryptedFileLocation(id: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputFileLocation(_ reader: BufferReader) -> InputFileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Buffer? + _4 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputFileLocation.inputFileLocation(volumeId: _1!, localId: _2!, secret: _3!, fileReference: _4!) + } + else { + return nil + } + } + public static func parse_inputGroupCallStream(_ reader: BufferReader) -> InputFileLocation? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputGroupCall? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.InputFileLocation.inputGroupCallStream(flags: _1!, call: _2!, timeMs: _3!, scale: _4!, videoChannel: _5, videoQuality: _6) + } + else { + return nil + } + } + public static func parse_inputPeerPhotoFileLocation(_ reader: BufferReader) -> InputFileLocation? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputPeer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputFileLocation.inputPeerPhotoFileLocation(flags: _1!, peer: _2!, photoId: _3!) + } + else { + return nil + } + } + public static func parse_inputPhotoFileLocation(_ reader: BufferReader) -> InputFileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Buffer? + _3 = parseBytes(reader) + var _4: String? + _4 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputFileLocation.inputPhotoFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, thumbSize: _4!) + } + else { + return nil + } + } + public static func parse_inputPhotoLegacyFileLocation(_ reader: BufferReader) -> InputFileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Buffer? + _3 = parseBytes(reader) + var _4: Int64? + _4 = reader.readInt64() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int64? + _6 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.InputFileLocation.inputPhotoLegacyFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, volumeId: _4!, localId: _5!, secret: _6!) + } + else { + return nil + } + } + public static func parse_inputSecureFileLocation(_ reader: BufferReader) -> InputFileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputFileLocation.inputSecureFileLocation(id: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputStickerSetThumb(_ reader: BufferReader) -> InputFileLocation? { + var _1: Api.InputStickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputStickerSet + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputFileLocation.inputStickerSetThumb(stickerset: _1!, thumbVersion: _2!) + } + else { + return nil + } + } + public static func parse_inputTakeoutFileLocation(_ reader: BufferReader) -> InputFileLocation? { + return Api.InputFileLocation.inputTakeoutFileLocation + } + + } +} public extension Api { indirect enum InputFolderPeer: TypeConstructorDescription { case inputFolderPeer(peer: Api.InputPeer, folderId: Int32) @@ -884,195 +1194,3 @@ public extension Api { } } -public extension Api { - enum InputMessage: TypeConstructorDescription { - case inputMessageCallbackQuery(id: Int32, queryId: Int64) - case inputMessageID(id: Int32) - case inputMessagePinned - case inputMessageReplyTo(id: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputMessageCallbackQuery(let id, let queryId): - if boxed { - buffer.appendInt32(-1392895362) - } - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - break - case .inputMessageID(let id): - if boxed { - buffer.appendInt32(-1502174430) - } - serializeInt32(id, buffer: buffer, boxed: false) - break - case .inputMessagePinned: - if boxed { - buffer.appendInt32(-2037963464) - } - - break - case .inputMessageReplyTo(let id): - if boxed { - buffer.appendInt32(-1160215659) - } - serializeInt32(id, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputMessageCallbackQuery(let id, let queryId): - return ("inputMessageCallbackQuery", [("id", id as Any), ("queryId", queryId as Any)]) - case .inputMessageID(let id): - return ("inputMessageID", [("id", id as Any)]) - case .inputMessagePinned: - return ("inputMessagePinned", []) - case .inputMessageReplyTo(let id): - return ("inputMessageReplyTo", [("id", id as Any)]) - } - } - - public static func parse_inputMessageCallbackQuery(_ reader: BufferReader) -> InputMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputMessage.inputMessageCallbackQuery(id: _1!, queryId: _2!) - } - else { - return nil - } - } - public static func parse_inputMessageID(_ reader: BufferReader) -> InputMessage? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.InputMessage.inputMessageID(id: _1!) - } - else { - return nil - } - } - public static func parse_inputMessagePinned(_ reader: BufferReader) -> InputMessage? { - return Api.InputMessage.inputMessagePinned - } - public static func parse_inputMessageReplyTo(_ reader: BufferReader) -> InputMessage? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.InputMessage.inputMessageReplyTo(id: _1!) - } - else { - return nil - } - } - - } -} -public extension Api { - indirect enum InputNotifyPeer: TypeConstructorDescription { - case inputNotifyBroadcasts - case inputNotifyChats - case inputNotifyForumTopic(peer: Api.InputPeer, topMsgId: Int32) - case inputNotifyPeer(peer: Api.InputPeer) - case inputNotifyUsers - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputNotifyBroadcasts: - if boxed { - buffer.appendInt32(-1311015810) - } - - break - case .inputNotifyChats: - if boxed { - buffer.appendInt32(1251338318) - } - - break - case .inputNotifyForumTopic(let peer, let topMsgId): - if boxed { - buffer.appendInt32(1548122514) - } - peer.serialize(buffer, true) - serializeInt32(topMsgId, buffer: buffer, boxed: false) - break - case .inputNotifyPeer(let peer): - if boxed { - buffer.appendInt32(-1195615476) - } - peer.serialize(buffer, true) - break - case .inputNotifyUsers: - if boxed { - buffer.appendInt32(423314455) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputNotifyBroadcasts: - return ("inputNotifyBroadcasts", []) - case .inputNotifyChats: - return ("inputNotifyChats", []) - case .inputNotifyForumTopic(let peer, let topMsgId): - return ("inputNotifyForumTopic", [("peer", peer as Any), ("topMsgId", topMsgId as Any)]) - case .inputNotifyPeer(let peer): - return ("inputNotifyPeer", [("peer", peer as Any)]) - case .inputNotifyUsers: - return ("inputNotifyUsers", []) - } - } - - public static func parse_inputNotifyBroadcasts(_ reader: BufferReader) -> InputNotifyPeer? { - return Api.InputNotifyPeer.inputNotifyBroadcasts - } - public static func parse_inputNotifyChats(_ reader: BufferReader) -> InputNotifyPeer? { - return Api.InputNotifyPeer.inputNotifyChats - } - public static func parse_inputNotifyForumTopic(_ reader: BufferReader) -> InputNotifyPeer? { - var _1: Api.InputPeer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputNotifyPeer.inputNotifyForumTopic(peer: _1!, topMsgId: _2!) - } - else { - return nil - } - } - public static func parse_inputNotifyPeer(_ reader: BufferReader) -> InputNotifyPeer? { - var _1: Api.InputPeer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - let _c1 = _1 != nil - if _c1 { - return Api.InputNotifyPeer.inputNotifyPeer(peer: _1!) - } - else { - return nil - } - } - public static func parse_inputNotifyUsers(_ reader: BufferReader) -> InputNotifyPeer? { - return Api.InputNotifyPeer.inputNotifyUsers - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api9.swift b/submodules/TelegramApi/Sources/Api9.swift index 8605e3757f..b838506bde 100644 --- a/submodules/TelegramApi/Sources/Api9.swift +++ b/submodules/TelegramApi/Sources/Api9.swift @@ -1,3 +1,195 @@ +public extension Api { + enum InputMessage: TypeConstructorDescription { + case inputMessageCallbackQuery(id: Int32, queryId: Int64) + case inputMessageID(id: Int32) + case inputMessagePinned + case inputMessageReplyTo(id: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputMessageCallbackQuery(let id, let queryId): + if boxed { + buffer.appendInt32(-1392895362) + } + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + break + case .inputMessageID(let id): + if boxed { + buffer.appendInt32(-1502174430) + } + serializeInt32(id, buffer: buffer, boxed: false) + break + case .inputMessagePinned: + if boxed { + buffer.appendInt32(-2037963464) + } + + break + case .inputMessageReplyTo(let id): + if boxed { + buffer.appendInt32(-1160215659) + } + serializeInt32(id, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputMessageCallbackQuery(let id, let queryId): + return ("inputMessageCallbackQuery", [("id", id as Any), ("queryId", queryId as Any)]) + case .inputMessageID(let id): + return ("inputMessageID", [("id", id as Any)]) + case .inputMessagePinned: + return ("inputMessagePinned", []) + case .inputMessageReplyTo(let id): + return ("inputMessageReplyTo", [("id", id as Any)]) + } + } + + public static func parse_inputMessageCallbackQuery(_ reader: BufferReader) -> InputMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputMessage.inputMessageCallbackQuery(id: _1!, queryId: _2!) + } + else { + return nil + } + } + public static func parse_inputMessageID(_ reader: BufferReader) -> InputMessage? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.InputMessage.inputMessageID(id: _1!) + } + else { + return nil + } + } + public static func parse_inputMessagePinned(_ reader: BufferReader) -> InputMessage? { + return Api.InputMessage.inputMessagePinned + } + public static func parse_inputMessageReplyTo(_ reader: BufferReader) -> InputMessage? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.InputMessage.inputMessageReplyTo(id: _1!) + } + else { + return nil + } + } + + } +} +public extension Api { + indirect enum InputNotifyPeer: TypeConstructorDescription { + case inputNotifyBroadcasts + case inputNotifyChats + case inputNotifyForumTopic(peer: Api.InputPeer, topMsgId: Int32) + case inputNotifyPeer(peer: Api.InputPeer) + case inputNotifyUsers + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputNotifyBroadcasts: + if boxed { + buffer.appendInt32(-1311015810) + } + + break + case .inputNotifyChats: + if boxed { + buffer.appendInt32(1251338318) + } + + break + case .inputNotifyForumTopic(let peer, let topMsgId): + if boxed { + buffer.appendInt32(1548122514) + } + peer.serialize(buffer, true) + serializeInt32(topMsgId, buffer: buffer, boxed: false) + break + case .inputNotifyPeer(let peer): + if boxed { + buffer.appendInt32(-1195615476) + } + peer.serialize(buffer, true) + break + case .inputNotifyUsers: + if boxed { + buffer.appendInt32(423314455) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputNotifyBroadcasts: + return ("inputNotifyBroadcasts", []) + case .inputNotifyChats: + return ("inputNotifyChats", []) + case .inputNotifyForumTopic(let peer, let topMsgId): + return ("inputNotifyForumTopic", [("peer", peer as Any), ("topMsgId", topMsgId as Any)]) + case .inputNotifyPeer(let peer): + return ("inputNotifyPeer", [("peer", peer as Any)]) + case .inputNotifyUsers: + return ("inputNotifyUsers", []) + } + } + + public static func parse_inputNotifyBroadcasts(_ reader: BufferReader) -> InputNotifyPeer? { + return Api.InputNotifyPeer.inputNotifyBroadcasts + } + public static func parse_inputNotifyChats(_ reader: BufferReader) -> InputNotifyPeer? { + return Api.InputNotifyPeer.inputNotifyChats + } + public static func parse_inputNotifyForumTopic(_ reader: BufferReader) -> InputNotifyPeer? { + var _1: Api.InputPeer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputNotifyPeer.inputNotifyForumTopic(peer: _1!, topMsgId: _2!) + } + else { + return nil + } + } + public static func parse_inputNotifyPeer(_ reader: BufferReader) -> InputNotifyPeer? { + var _1: Api.InputPeer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + let _c1 = _1 != nil + if _c1 { + return Api.InputNotifyPeer.inputNotifyPeer(peer: _1!) + } + else { + return nil + } + } + public static func parse_inputNotifyUsers(_ reader: BufferReader) -> InputNotifyPeer? { + return Api.InputNotifyPeer.inputNotifyUsers + } + + } +} public extension Api { enum InputPaymentCredentials: TypeConstructorDescription { case inputPaymentCredentials(flags: Int32, data: Api.DataJSON) @@ -584,183 +776,3 @@ public extension Api { } } -public extension Api { - enum InputPrivacyRule: TypeConstructorDescription { - case inputPrivacyValueAllowAll - case inputPrivacyValueAllowChatParticipants(chats: [Int64]) - case inputPrivacyValueAllowCloseFriends - case inputPrivacyValueAllowContacts - case inputPrivacyValueAllowUsers(users: [Api.InputUser]) - case inputPrivacyValueDisallowAll - case inputPrivacyValueDisallowChatParticipants(chats: [Int64]) - case inputPrivacyValueDisallowContacts - case inputPrivacyValueDisallowUsers(users: [Api.InputUser]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputPrivacyValueAllowAll: - if boxed { - buffer.appendInt32(407582158) - } - - break - case .inputPrivacyValueAllowChatParticipants(let chats): - if boxed { - buffer.appendInt32(-2079962673) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .inputPrivacyValueAllowCloseFriends: - if boxed { - buffer.appendInt32(793067081) - } - - break - case .inputPrivacyValueAllowContacts: - if boxed { - buffer.appendInt32(218751099) - } - - break - case .inputPrivacyValueAllowUsers(let users): - if boxed { - buffer.appendInt32(320652927) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .inputPrivacyValueDisallowAll: - if boxed { - buffer.appendInt32(-697604407) - } - - break - case .inputPrivacyValueDisallowChatParticipants(let chats): - if boxed { - buffer.appendInt32(-380694650) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .inputPrivacyValueDisallowContacts: - if boxed { - buffer.appendInt32(195371015) - } - - break - case .inputPrivacyValueDisallowUsers(let users): - if boxed { - buffer.appendInt32(-1877932953) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputPrivacyValueAllowAll: - return ("inputPrivacyValueAllowAll", []) - case .inputPrivacyValueAllowChatParticipants(let chats): - return ("inputPrivacyValueAllowChatParticipants", [("chats", chats as Any)]) - case .inputPrivacyValueAllowCloseFriends: - return ("inputPrivacyValueAllowCloseFriends", []) - case .inputPrivacyValueAllowContacts: - return ("inputPrivacyValueAllowContacts", []) - case .inputPrivacyValueAllowUsers(let users): - return ("inputPrivacyValueAllowUsers", [("users", users as Any)]) - case .inputPrivacyValueDisallowAll: - return ("inputPrivacyValueDisallowAll", []) - case .inputPrivacyValueDisallowChatParticipants(let chats): - return ("inputPrivacyValueDisallowChatParticipants", [("chats", chats as Any)]) - case .inputPrivacyValueDisallowContacts: - return ("inputPrivacyValueDisallowContacts", []) - case .inputPrivacyValueDisallowUsers(let users): - return ("inputPrivacyValueDisallowUsers", [("users", users as Any)]) - } - } - - public static func parse_inputPrivacyValueAllowAll(_ reader: BufferReader) -> InputPrivacyRule? { - return Api.InputPrivacyRule.inputPrivacyValueAllowAll - } - public static func parse_inputPrivacyValueAllowChatParticipants(_ reader: BufferReader) -> InputPrivacyRule? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.InputPrivacyRule.inputPrivacyValueAllowChatParticipants(chats: _1!) - } - else { - return nil - } - } - public static func parse_inputPrivacyValueAllowCloseFriends(_ reader: BufferReader) -> InputPrivacyRule? { - return Api.InputPrivacyRule.inputPrivacyValueAllowCloseFriends - } - public static func parse_inputPrivacyValueAllowContacts(_ reader: BufferReader) -> InputPrivacyRule? { - return Api.InputPrivacyRule.inputPrivacyValueAllowContacts - } - public static func parse_inputPrivacyValueAllowUsers(_ reader: BufferReader) -> InputPrivacyRule? { - var _1: [Api.InputUser]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.InputPrivacyRule.inputPrivacyValueAllowUsers(users: _1!) - } - else { - return nil - } - } - public static func parse_inputPrivacyValueDisallowAll(_ reader: BufferReader) -> InputPrivacyRule? { - return Api.InputPrivacyRule.inputPrivacyValueDisallowAll - } - public static func parse_inputPrivacyValueDisallowChatParticipants(_ reader: BufferReader) -> InputPrivacyRule? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.InputPrivacyRule.inputPrivacyValueDisallowChatParticipants(chats: _1!) - } - else { - return nil - } - } - public static func parse_inputPrivacyValueDisallowContacts(_ reader: BufferReader) -> InputPrivacyRule? { - return Api.InputPrivacyRule.inputPrivacyValueDisallowContacts - } - public static func parse_inputPrivacyValueDisallowUsers(_ reader: BufferReader) -> InputPrivacyRule? { - var _1: [Api.InputUser]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.InputPrivacyRule.inputPrivacyValueDisallowUsers(users: _1!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 5ecd19cfc1..86121ae367 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -126,7 +126,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { - case let .message(_, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let chatPeerId = messagePeerId return chatPeerId.peerId case let .messageEmpty(_, _, peerId): @@ -142,7 +142,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(_, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = chatPeerId.peerId var result = [peerId] @@ -263,7 +263,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { switch message { - case let .message(_, id, _, _, chatPeerId, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, chatPeerId, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyTo = replyTo { let peerId: PeerId = chatPeerId.peerId @@ -597,7 +597,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId): + case let .message(flags, _, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId): let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId var namespace = namespace diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index 0a2a0b1fb4..cfb0b59954 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -96,7 +96,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes var updatedTimestamp: Int32? if let apiMessage = apiMessage { switch apiMessage { - case let .message(_, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatedTimestamp = date case .messageEmpty: break diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 6e1311188e..03166bd199 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 176 + return 177 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift b/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift index b6788804a4..fc2c881026 100644 --- a/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift +++ b/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift @@ -40,56 +40,57 @@ public func getIsStickerSaved(transaction: Transaction, fileId: MediaId) -> Bool public func addSavedSticker(postbox: Postbox, network: Network, file: TelegramMediaFile, limit: Int = 5) -> Signal { return postbox.transaction { transaction -> Signal in for attribute in file.attributes { - if case let .Sticker(_, maybePackReference, _) = attribute, let packReference = maybePackReference { - var fetchReference: StickerPackReference? - switch packReference { - case .name: - fetchReference = packReference - case let .id(id, _): - let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: Namespaces.ItemCollection.CloudStickerPacks, id: id)) - var found = false - inner: for item in items { - if let stickerItem = item as? StickerPackItem { - if stickerItem.file.fileId == file.fileId { - let stringRepresentations = stickerItem.getStringRepresentationsOfIndexKeys() - found = true - addSavedSticker(transaction: transaction, file: stickerItem.file, stringRepresentations: stringRepresentations) - break inner + if case let .Sticker(_, maybePackReference, _) = attribute { + if let packReference = maybePackReference { + var fetchReference: StickerPackReference? + switch packReference { + case .name: + fetchReference = packReference + case let .id(id, _): + let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: Namespaces.ItemCollection.CloudStickerPacks, id: id)) + var found = false + inner: for item in items { + if let stickerItem = item as? StickerPackItem { + if stickerItem.file.fileId == file.fileId { + let stringRepresentations = stickerItem.getStringRepresentationsOfIndexKeys() + found = true + addSavedSticker(transaction: transaction, file: stickerItem.file, stringRepresentations: stringRepresentations) + break inner + } } } - } - if !found { - fetchReference = packReference + if !found { + fetchReference = packReference + } + case .animatedEmoji, .animatedEmojiAnimations, .dice, .premiumGifts, .emojiGenericAnimations, .iconStatusEmoji, .iconChannelStatusEmoji, .iconTopicEmoji: + break } - case .animatedEmoji, .animatedEmojiAnimations, .dice, .premiumGifts, .emojiGenericAnimations, .iconStatusEmoji, .iconChannelStatusEmoji, .iconTopicEmoji: - break - } - if let fetchReference = fetchReference { - return network.request(Api.functions.messages.getStickerSet(stickerset: fetchReference.apiInputStickerSet, hash: 0)) + if let fetchReference = fetchReference { + return network.request(Api.functions.messages.getStickerSet(stickerset: fetchReference.apiInputStickerSet, hash: 0)) |> mapError { _ -> AddSavedStickerError in return .generic } |> mapToSignal { result -> Signal in var stickerStringRepresentations: [String]? switch result { - case .stickerSetNotModified: - break - case let .stickerSet(_, packs, _, _): - var stringRepresentationsByFile: [MediaId: [String]] = [:] - for pack in packs { - switch pack { - case let .stickerPack(text, fileIds): - for fileId in fileIds { - let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) - if stringRepresentationsByFile[mediaId] == nil { - stringRepresentationsByFile[mediaId] = [text] - } else { - stringRepresentationsByFile[mediaId]!.append(text) - } - } + case .stickerSetNotModified: + break + case let .stickerSet(_, packs, _, _): + var stringRepresentationsByFile: [MediaId: [String]] = [:] + for pack in packs { + switch pack { + case let .stickerPack(text, fileIds): + for fileId in fileIds { + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) + if stringRepresentationsByFile[mediaId] == nil { + stringRepresentationsByFile[mediaId] = [text] + } else { + stringRepresentationsByFile[mediaId]!.append(text) + } } } - stickerStringRepresentations = stringRepresentationsByFile[file.fileId] + } + stickerStringRepresentations = stringRepresentationsByFile[file.fileId] } if let stickerStringRepresentations = stickerStringRepresentations { return postbox.transaction { transaction -> Void in @@ -99,8 +100,13 @@ public func addSavedSticker(postbox: Postbox, network: Network, file: TelegramMe return .fail(.notFound) } } + } + return .complete() + } else { + return postbox.transaction { transaction -> Void in + addSavedSticker(transaction: transaction, file: file, stringRepresentations: []) + } |> mapError { _ -> AddSavedStickerError in } } - return .complete() } } return .complete() diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index 279064d68a..2976fef9ca 100644 --- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod): - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService { let generatedPeerId = Api.Peer.peerUser(userId: userId) - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index a7fcc88093..a5f7b9db77 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -104,7 +104,7 @@ extension Api.MessageMedia { extension Api.Message { var rawId: Int32 { switch self { - case let .message(_, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return id case let .messageEmpty(_, id, _): return id @@ -115,7 +115,7 @@ extension Api.Message { func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? { switch self { - case let .message(_, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return MessageId(peerId: peerId, namespace: namespace, id: id) case let .messageEmpty(_, id, peerId): @@ -132,7 +132,7 @@ extension Api.Message { var peerId: PeerId? { switch self { - case let .message(_, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return peerId case let .messageEmpty(_, _, peerId): @@ -145,7 +145,7 @@ extension Api.Message { var timestamp: Int32? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return date case let .messageService(_, _, _, _, _, date, _, _): return date @@ -156,7 +156,7 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedResources default: return nil @@ -165,7 +165,7 @@ extension Api.Message { var preCachedStories: [StoryId: Api.StoryItem]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedStories default: return nil diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StickerPackCollectionInfo.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StickerPackCollectionInfo.swift index 2abeda4fcd..1823edd5ff 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StickerPackCollectionInfo.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StickerPackCollectionInfo.swift @@ -44,6 +44,7 @@ public struct StickerPackCollectionInfoFlags: OptionSet { public static let isEmoji = StickerPackCollectionInfoFlags(rawValue: 1 << 4) public static let isAvailableAsChannelStatus = StickerPackCollectionInfoFlags(rawValue: 1 << 5) public static let isCustomTemplateEmoji = StickerPackCollectionInfoFlags(rawValue: 1 << 6) + public static let isCreator = StickerPackCollectionInfoFlags(rawValue: 1 << 7) } @@ -115,35 +116,27 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable { if lhs.id != rhs.id { return false } - if lhs.title != rhs.title { return false } - if lhs.shortName != rhs.shortName { return false } - if lhs.hash != rhs.hash { return false } - if lhs.immediateThumbnailData != rhs.immediateThumbnailData { return false } - if lhs.thumbnailFileId != rhs.thumbnailFileId { return false } - if lhs.flags != rhs.flags { return false } - if lhs.count != rhs.count { return false } - return true } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift index a7e321c872..fcc57b2281 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift @@ -388,7 +388,7 @@ func _internal_sendMessageShortcut(account: Account, peerId: PeerId, id: Int32) guard let peer, let inputPeer = apiInputPeer(peer) else { return .complete() } - return account.network.request(Api.functions.messages.sendQuickReplyMessages(peer: inputPeer, shortcutId: id)) + return account.network.request(Api.functions.messages.sendQuickReplyMessages(peer: inputPeer, shortcutId: id, id: [], randomId: [])) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift index 2df16ae9a7..7672673002 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift @@ -209,66 +209,8 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri return .generic } |> mapToSignal { result -> Signal in - let info: StickerPackCollectionInfo - var items: [StickerPackItem] = [] - - switch result { - case .stickerSetNotModified: + guard let (info, items) = parseStickerSetInfoAndItems(apiStickerSet: result) else { return .complete() - case let .stickerSet(set, packs, keywords, documents): - let namespace: ItemCollectionId.Namespace - switch set { - case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _): - if (flags & (1 << 3)) != 0 { - namespace = Namespaces.ItemCollection.CloudMaskPacks - } else if (flags & (1 << 7)) != 0 { - namespace = Namespaces.ItemCollection.CloudEmojiPacks - } else { - namespace = Namespaces.ItemCollection.CloudStickerPacks - } - } - info = StickerPackCollectionInfo(apiSet: set, namespace: namespace) - var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:] - for pack in packs { - switch pack { - case let .stickerPack(text, fileIds): - let key = ValueBoxKey(text).toMemoryBuffer() - for fileId in fileIds { - let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) - if indexKeysByFile[mediaId] == nil { - indexKeysByFile[mediaId] = [key] - } else { - indexKeysByFile[mediaId]!.append(key) - } - } - } - } - for keyword in keywords { - switch keyword { - case let .stickerKeyword(documentId, texts): - for text in texts { - let key = ValueBoxKey(text).toMemoryBuffer() - let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId) - if indexKeysByFile[mediaId] == nil { - indexKeysByFile[mediaId] = [key] - } else { - indexKeysByFile[mediaId]!.append(key) - } - } - } - } - - for apiDocument in documents { - if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id { - let fileIndexKeys: [MemoryBuffer] - if let indexKeys = indexKeysByFile[id] { - fileIndexKeys = indexKeys - } else { - fileIndexKeys = [] - } - items.append(StickerPackItem(index: ItemCollectionItemIndex(index: Int32(items.count), id: id.id), file: file, indexKeys: fileIndexKeys)) - } - } } return .single(.complete(info, items)) } @@ -294,6 +236,279 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri } } +public enum RenameStickerSetError { + case generic +} + +func _internal_renameStickerSet(account: Account, packReference: StickerPackReference, title: String) -> Signal { + return account.network.request(Api.functions.stickers.renameStickerSet(stickerset: packReference.apiInputStickerSet, title: title)) + |> mapError { error -> RenameStickerSetError in + return .generic + } + |> mapToSignal { result -> Signal in + guard let (info, items) = parseStickerSetInfoAndItems(apiStickerSet: result) else { + return .complete() + } + return account.postbox.transaction { transaction -> Void in + transaction.replaceItemCollectionInfos(namespace: Namespaces.ItemCollection.CloudStickerPacks, itemCollectionInfos: [(info.id, info)]) + + cacheStickerPack(transaction: transaction, info: info, items: items) + } + |> castError(RenameStickerSetError.self) + |> ignoreValues + } +} + +public enum DeleteStickerSetError { + case generic +} + +func _internal_deleteStickerSet(account: Account, packReference: StickerPackReference) -> Signal { + return account.network.request(Api.functions.stickers.deleteStickerSet(stickerset: packReference.apiInputStickerSet)) + |> mapError { error -> DeleteStickerSetError in + return .generic + } + |> mapToSignal { _ in + return account.postbox.transaction { transaction in + if case let .id(id, _) = packReference { + transaction.removeItemCollection(collectionId: ItemCollectionId(namespace: Namespaces.ItemCollection.CloudStickerPacks, id: id)) + } + } + |> castError(DeleteStickerSetError.self) + } + |> ignoreValues +} + +public enum AddStickerToSetError { + case generic +} + +func _internal_addStickerToStickerSet(account: Account, packReference: StickerPackReference, sticker: ImportSticker) -> Signal { + let uploadSticker: Signal + if let resource = sticker.resource as? CloudDocumentMediaResource { + uploadSticker = .single(.complete(resource, sticker.mimeType)) + } else { + uploadSticker = account.postbox.loadedPeerWithId(account.peerId) + |> castError(AddStickerToSetError.self) + |> mapToSignal { peer in + return _internal_uploadSticker(account: account, peer: peer, resource: sticker.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, mimeType: sticker.mimeType) + |> mapError { _ -> AddStickerToSetError in + return .generic + } + } + } + return uploadSticker + |> mapToSignal { uploadedSticker in + guard case let .complete(resource, _) = uploadedSticker else { + return .complete() + } + + var flags: Int32 = 0 + if sticker.keywords.count > 0 { + flags |= (1 << 1) + } + let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil, keywords: sticker.keywords) + + return account.network.request(Api.functions.stickers.addStickerToSet(stickerset: packReference.apiInputStickerSet, sticker: inputSticker)) + |> mapError { error -> AddStickerToSetError in + return .generic + } + |> mapToSignal { result -> Signal in + guard let (info, items) = parseStickerSetInfoAndItems(apiStickerSet: result) else { + return .complete() + } + return account.postbox.transaction { transaction -> Bool in + if transaction.getItemCollectionInfo(collectionId: info.id) != nil { + transaction.replaceItemCollectionItems(collectionId: info.id, items: items) + } + cacheStickerPack(transaction: transaction, info: info, items: items) + return true + } + |> castError(AddStickerToSetError.self) + } + } +} + +public enum ReorderStickerError { + case generic +} + +func _internal_reorderSticker(account: Account, sticker: FileMediaReference, position: Int) -> Signal { + guard let resource = sticker.media.resource as? CloudDocumentMediaResource else { + return .fail(.generic) + } + return account.network.request(Api.functions.stickers.changeStickerPosition(sticker: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), position: Int32(position))) + |> mapError { error -> ReorderStickerError in + return .generic + } + |> mapToSignal { result -> Signal in + guard let (info, items) = parseStickerSetInfoAndItems(apiStickerSet: result) else { + return .complete() + } + return account.postbox.transaction { transaction -> Void in + if transaction.getItemCollectionInfo(collectionId: info.id) != nil { + transaction.replaceItemCollectionItems(collectionId: info.id, items: items) + } + cacheStickerPack(transaction: transaction, info: info, items: items) + } + |> castError(ReorderStickerError.self) + |> ignoreValues + } +} + + +public enum DeleteStickerError { + case generic +} + +func _internal_deleteStickerFromStickerSet(account: Account, sticker: FileMediaReference) -> Signal { + guard let resource = sticker.media.resource as? CloudDocumentMediaResource else { + return .fail(.generic) + } + return account.network.request(Api.functions.stickers.removeStickerFromSet(sticker: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)))) + |> mapError { error -> DeleteStickerError in + return .generic + } + |> mapToSignal { result -> Signal in + guard let (info, items) = parseStickerSetInfoAndItems(apiStickerSet: result) else { + return .complete() + } + return account.postbox.transaction { transaction -> Void in + if transaction.getItemCollectionInfo(collectionId: info.id) != nil { + transaction.replaceItemCollectionItems(collectionId: info.id, items: items) + } + cacheStickerPack(transaction: transaction, info: info, items: items) + } + |> castError(DeleteStickerError.self) + |> ignoreValues + } +} + +func _internal_getMyStickerSets(account: Account) -> Signal<[(StickerPackCollectionInfo, StickerPackItem?)], NoError> { + return account.network.request(Api.functions.messages.getMyStickers(offsetId: 0, limit: 100)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> map { result -> [(StickerPackCollectionInfo, StickerPackItem?)] in + guard let result else { + return [] + } + var infos: [(StickerPackCollectionInfo, StickerPackItem?)] = [] + switch result { + case let .myStickers(_, sets): + for set in sets { + switch set { + case let .stickerSetCovered(set, cover): + let namespace: ItemCollectionId.Namespace + switch set { + case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _): + if (flags & (1 << 3)) != 0 { + namespace = Namespaces.ItemCollection.CloudMaskPacks + } else if (flags & (1 << 7)) != 0 { + namespace = Namespaces.ItemCollection.CloudEmojiPacks + } else { + namespace = Namespaces.ItemCollection.CloudStickerPacks + } + } + let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace) + var firstItem: StickerPackItem? + if let file = telegramMediaFileFromApiDocument(cover), let id = file.id { + firstItem = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: id.id), file: file, indexKeys: []) + } + infos.append((info, firstItem)) + case let .stickerSetFullCovered(set, _, _, documents): + let namespace: ItemCollectionId.Namespace + switch set { + case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _): + if (flags & (1 << 3)) != 0 { + namespace = Namespaces.ItemCollection.CloudMaskPacks + } else if (flags & (1 << 7)) != 0 { + namespace = Namespaces.ItemCollection.CloudEmojiPacks + } else { + namespace = Namespaces.ItemCollection.CloudStickerPacks + } + } + let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace) + var firstItem: StickerPackItem? + if let apiDocument = documents.first { + if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id { + firstItem = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: id.id), file: file, indexKeys: []) + } + } + infos.append((info, firstItem)) + default: + break + } + } + } + return infos + } +} + +private func parseStickerSetInfoAndItems(apiStickerSet: Api.messages.StickerSet) -> (StickerPackCollectionInfo, [StickerPackItem])? { + switch apiStickerSet { + case .stickerSetNotModified: + return nil + case let .stickerSet(set, packs, keywords, documents): + let namespace: ItemCollectionId.Namespace + switch set { + case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _): + if (flags & (1 << 3)) != 0 { + namespace = Namespaces.ItemCollection.CloudMaskPacks + } else if (flags & (1 << 7)) != 0 { + namespace = Namespaces.ItemCollection.CloudEmojiPacks + } else { + namespace = Namespaces.ItemCollection.CloudStickerPacks + } + } + let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace) + var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:] + for pack in packs { + switch pack { + case let .stickerPack(text, fileIds): + let key = ValueBoxKey(text).toMemoryBuffer() + for fileId in fileIds { + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) + if indexKeysByFile[mediaId] == nil { + indexKeysByFile[mediaId] = [key] + } else { + indexKeysByFile[mediaId]!.append(key) + } + } + } + } + for keyword in keywords { + switch keyword { + case let .stickerKeyword(documentId, texts): + for text in texts { + let key = ValueBoxKey(text).toMemoryBuffer() + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId) + if indexKeysByFile[mediaId] == nil { + indexKeysByFile[mediaId] = [key] + } else { + indexKeysByFile[mediaId]!.append(key) + } + } + } + } + + var items: [StickerPackItem] = [] + for apiDocument in documents { + if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id { + let fileIndexKeys: [MemoryBuffer] + if let indexKeys = indexKeysByFile[id] { + fileIndexKeys = indexKeys + } else { + fileIndexKeys = [] + } + items.append(StickerPackItem(index: ItemCollectionItemIndex(index: Int32(items.count), id: id.id), file: file, indexKeys: fileIndexKeys)) + } + } + return (info, items) + } +} + func _internal_getStickerSetShortNameSuggestion(account: Account, title: String) -> Signal { return account.network.request(Api.functions.stickers.suggestShortName(title: title)) |> map (Optional.init) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift index de8ac8e496..a8e6444d67 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift @@ -55,6 +55,9 @@ extension StickerPackCollectionInfo { if (flags & (1 << 10)) != 0 { setFlags.insert(.isAvailableAsChannelStatus) } + if (flags & (1 << 11)) != 0 { + setFlags.insert(.isCreator) + } var thumbnailRepresentation: TelegramMediaImageRepresentation? var immediateThumbnailData: Data? diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index acc8702807..862d38351e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -86,6 +86,30 @@ public extension TelegramEngine { return _internal_createStickerSet(account: self.account, title: title, shortName: shortName, stickers: stickers, thumbnail: thumbnail, type: type, software: software) } + public func renameStickerSet(packReference: StickerPackReference, title: String) -> Signal { + return _internal_renameStickerSet(account: self.account, packReference: packReference, title: title) + } + + public func deleteStickerSet(packReference: StickerPackReference) -> Signal { + return _internal_deleteStickerSet(account: self.account, packReference: packReference) + } + + public func addStickerToStickerSet(packReference: StickerPackReference, sticker: ImportSticker) -> Signal { + return _internal_addStickerToStickerSet(account: self.account, packReference: packReference, sticker: sticker) + } + + public func reorderSticker(sticker: FileMediaReference, position: Int) -> Signal { + return _internal_reorderSticker(account: self.account, sticker: sticker, position: position) + } + + public func deleteStickerFromStickerSet(sticker: FileMediaReference) -> Signal { + return _internal_deleteStickerFromStickerSet(account: self.account, sticker: sticker) + } + + public func getMyStickerSets() -> Signal<[(StickerPackCollectionInfo, StickerPackItem?)], NoError> { + return _internal_getMyStickerSets(account: self.account) + } + public func getStickerSetShortNameSuggestion(title: String) -> Signal { return _internal_getStickerSetShortNameSuggestion(account: self.account, title: title) } diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index 28692a32ca..cfab79f187 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -434,6 +434,7 @@ final class AvatarEditorScreenComponent: Component { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -455,6 +456,7 @@ final class AvatarEditorScreenComponent: Component { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -513,6 +515,7 @@ final class AvatarEditorScreenComponent: Component { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -547,6 +550,7 @@ final class AvatarEditorScreenComponent: Component { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -654,6 +658,7 @@ final class AvatarEditorScreenComponent: Component { context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet) } }, + editAction: { _ in }, pushController: { c in }, presentController: { c in @@ -784,6 +789,7 @@ final class AvatarEditorScreenComponent: Component { } else if groupId == AnyHashable("peerSpecific") { } }, + editAction: { _ in }, pushController: { c in }, presentController: { c in diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index d746fb87a8..5f8b66660a 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -804,6 +804,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { }) } }, + editAction: { _ in }, pushController: { [weak interaction] controller in guard let interaction else { return @@ -969,6 +970,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -1018,6 +1020,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: 3, displayPremiumBadges: false, headerItem: nil, @@ -1077,6 +1080,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -1108,6 +1112,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -1313,6 +1318,33 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } else if groupId == AnyHashable("peerSpecific") { } }, + editAction: { [weak interaction] groupId in + guard let collectionId = groupId.base as? ItemCollectionId else { + return + } + let viewKey = PostboxViewKey.itemCollectionInfo(id: collectionId) + let _ = (context.account.postbox.combinedView(keys: [viewKey]) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak interaction] views in + guard let interaction, let view = views.views[viewKey] as? ItemCollectionInfoView, let info = view.info as? StickerPackCollectionInfo else { + return + } + let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash) + let controller = context.sharedContext.makeStickerPackScreen( + context: context, + updatedPresentationData: nil, + mainStickerPack: packReference, + stickerPacks: [packReference], + loadedStickerPacks: [], + isEditing: true, + parentNavigationController: nil, + sendSticker: { [weak interaction] fileReference, sourceView, sourceRect in + return interaction?.sendSticker(fileReference, false, false, nil, false, sourceView, sourceRect, nil, []) ?? false + } + ) + interaction.presentController(controller, nil) + }) + }, pushController: { [weak interaction] controller in guard let interaction else { return @@ -1382,6 +1414,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -1413,6 +1446,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -2327,6 +2361,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi strongSelf.presentController?(actionSheet) } }, + editAction: { _ in }, pushController: { _ in }, presentController: { _ in @@ -2774,7 +2809,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { switch attribute { case let .CustomEmoji(_, _, _, packReference), let .Sticker(_, packReference, _): if let packReference = packReference { - let controller = strongSelf.context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], parentNavigationController: interaction.navigationController(), sendSticker: { file, sourceView, sourceRect in + let controller = strongSelf.context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], isEditing: false, parentNavigationController: interaction.navigationController(), sendSticker: { file, sourceView, sourceRect in sendSticker(file, false, false, nil, false, sourceView, sourceRect, nil) return true }) diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift index be655b579d..fe6a48cd38 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift @@ -124,6 +124,8 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer maybeFile = foundItem.file case let .pack(fileValue): maybeFile = fileValue + case .image: + break } } guard let file = maybeFile else { diff --git a/submodules/TelegramUI/Components/DustEffect/Sources/DustEffectLayer.swift b/submodules/TelegramUI/Components/DustEffect/Sources/DustEffectLayer.swift index 1dc7209e00..bec3370cf2 100644 --- a/submodules/TelegramUI/Components/DustEffect/Sources/DustEffectLayer.swift +++ b/submodules/TelegramUI/Components/DustEffect/Sources/DustEffectLayer.swift @@ -145,6 +145,7 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject private var lastTimeStep: Double = 0.0 public var animationSpeed: Float = 1.0 + public var playsBackwards: Bool = false public var becameEmpty: (() -> Void)? diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index bce0b9310a..f8349a2818 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -462,6 +462,7 @@ public final class EmojiStatusSelectionController: ViewController { }, clearGroup: { groupId in }, + editAction: { _ in }, pushController: { c in }, presentController: { c in @@ -584,6 +585,7 @@ public final class EmojiStatusSelectionController: ViewController { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -639,6 +641,7 @@ public final class EmojiStatusSelectionController: ViewController { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -671,6 +674,7 @@ public final class EmojiStatusSelectionController: ViewController { isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 7c141f89ee..90da47d24d 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -660,25 +660,44 @@ private final class PremiumBadgeView: UIView { } private final class GroupHeaderActionButton: UIButton { + override static var layerClass: AnyClass { + return PassthroughLayer.self + } + + let tintContainerLayer: SimpleLayer + private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? private let backgroundLayer: SimpleLayer + private let tintBackgroundLayer: SimpleLayer private let textLayer: SimpleLayer + private let tintTextLayer: SimpleLayer private let pressed: () -> Void init(pressed: @escaping () -> Void) { self.pressed = pressed + self.tintContainerLayer = SimpleLayer() + self.backgroundLayer = SimpleLayer() self.backgroundLayer.masksToBounds = true + self.tintBackgroundLayer = SimpleLayer() + self.tintBackgroundLayer.masksToBounds = true + self.textLayer = SimpleLayer() + self.tintTextLayer = SimpleLayer() super.init(frame: CGRect()) + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer + self.layer.addSublayer(self.backgroundLayer) self.layer.addSublayer(self.textLayer) self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside) + + self.tintContainerLayer.addSublayer(self.tintBackgroundLayer) + self.tintContainerLayer.addSublayer(self.tintTextLayer) } required init(coder: NSCoder) { @@ -719,18 +738,34 @@ private final class GroupHeaderActionButton: UIButton { super.touchesCancelled(touches, with: event) } - func update(theme: PresentationTheme, title: String) -> CGSize { + func update(theme: PresentationTheme, title: String, compact: Bool) -> CGSize { let textConstrainedWidth: CGFloat = 100.0 - let color = theme.list.itemCheckColors.foregroundColor - self.backgroundLayer.backgroundColor = theme.list.itemCheckColors.fillColor.cgColor + let needsVibrancy = !theme.overallDarkAppearance && compact + + let foregroundColor: UIColor + let backgroundColor: UIColor + + if compact { + foregroundColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor + backgroundColor = foregroundColor.withMultipliedAlpha(0.2) + } else { + foregroundColor = theme.list.itemCheckColors.foregroundColor + backgroundColor = theme.list.itemCheckColors.fillColor + } + + self.backgroundLayer.backgroundColor = backgroundColor.cgColor + self.tintBackgroundLayer.backgroundColor = UIColor.white.withAlphaComponent(0.2).cgColor + + self.tintContainerLayer.isHidden = !needsVibrancy let textSize: CGSize - if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth { + if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == foregroundColor, currentTextLayout.constrainedWidth == textConstrainedWidth { textSize = currentTextLayout.size } else { - let font: UIFont = Font.semibold(15.0) - let string = NSAttributedString(string: title.uppercased(), font: font, textColor: color) + let font: UIFont = compact ? Font.medium(11.0) : Font.semibold(15.0) + let string = NSAttributedString(string: title.uppercased(), font: font, textColor: foregroundColor) + let tintString = NSAttributedString(string: title.uppercased(), font: font, textColor: .white) let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in @@ -741,17 +776,29 @@ private final class GroupHeaderActionButton: UIButton { UIGraphicsPopContext() })?.cgImage - self.currentTextLayout = (title, color, textConstrainedWidth, textSize) + self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + tintString.draw(in: stringBounds) + + UIGraphicsPopContext() + })?.cgImage + self.currentTextLayout = (title, foregroundColor, textConstrainedWidth, textSize) } - let size = CGSize(width: textSize.width + 16.0 * 2.0, height: 28.0) + let size = CGSize(width: textSize.width + (compact ? 6.0 : 16.0) * 2.0, height: compact ? 16.0 : 28.0) - let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize) + let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) self.textLayer.frame = textFrame + self.tintTextLayer.frame = textFrame self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size) self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0 + self.tintBackgroundLayer.frame = self.backgroundLayer.frame + self.tintBackgroundLayer.cornerRadius = self.backgroundLayer.cornerRadius + return size } } @@ -816,6 +863,7 @@ private final class GroupHeaderLayer: UIView { layoutType: EmojiPagerContentComponent.ItemLayoutType, hasTopSeparator: Bool, actionButtonTitle: String?, + actionButtonIsCompact: Bool, title: String, subtitle: String?, badge: String?, @@ -877,9 +925,10 @@ private final class GroupHeaderLayer: UIView { actionButton = GroupHeaderActionButton(pressed: self.actionPressed) self.actionButton = actionButton self.addSubview(actionButton) + self.tintContentLayer.addSublayer(actionButton.tintContainerLayer) } - actionButtonSize = actionButton.update(theme: theme, title: actionButtonTitle) + actionButtonSize = actionButton.update(theme: theme, title: actionButtonTitle, compact: actionButtonIsCompact) } else { if let actionButton = self.actionButton { self.actionButton = nil @@ -1210,7 +1259,9 @@ private final class GroupHeaderLayer: UIView { } if let actionButtonSize = actionButtonSize, let actionButton = self.actionButton { - actionButton.frame = CGRect(origin: CGPoint(x: size.width - actionButtonSize.width, y: textFrame.minY + 3.0), size: actionButtonSize) + let actionButtonFrame = CGRect(origin: CGPoint(x: size.width - actionButtonSize.width, y: textFrame.minY + (actionButtonIsCompact ? 0.0 : 3.0)), size: actionButtonSize) + actionButton.bounds = CGRect(origin: CGPoint(), size: actionButtonFrame.size) + actionButton.center = actionButtonFrame.center } if hasTopSeparator { @@ -2414,6 +2465,7 @@ public final class EmojiPagerContentComponent: Component { public let openSearch: () -> Void public let addGroupAction: (AnyHashable, Bool, Bool) -> Void public let clearGroup: (AnyHashable) -> Void + public let editAction: (AnyHashable) -> Void public let pushController: (ViewController) -> Void public let presentController: (ViewController) -> Void public let presentGlobalOverlayController: (ViewController) -> Void @@ -2443,6 +2495,7 @@ public final class EmojiPagerContentComponent: Component { openSearch: @escaping () -> Void, addGroupAction: @escaping (AnyHashable, Bool, Bool) -> Void, clearGroup: @escaping (AnyHashable) -> Void, + editAction: @escaping (AnyHashable) -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController) -> Void, presentGlobalOverlayController: @escaping (ViewController) -> Void, @@ -2470,6 +2523,7 @@ public final class EmojiPagerContentComponent: Component { self.openSearch = openSearch self.addGroupAction = addGroupAction self.clearGroup = clearGroup + self.editAction = editAction self.pushController = pushController self.presentController = presentController self.presentGlobalOverlayController = presentGlobalOverlayController @@ -2613,6 +2667,7 @@ public final class EmojiPagerContentComponent: Component { public let isPremiumLocked: Bool public let isEmbedded: Bool public let hasClear: Bool + public let hasEdit: Bool public let collapsedLineCount: Int? public let displayPremiumBadges: Bool public let headerItem: EntityKeyboardAnimationData? @@ -2631,6 +2686,7 @@ public final class EmojiPagerContentComponent: Component { isPremiumLocked: Bool, isEmbedded: Bool, hasClear: Bool, + hasEdit: Bool, collapsedLineCount: Int?, displayPremiumBadges: Bool, headerItem: EntityKeyboardAnimationData?, @@ -2648,6 +2704,7 @@ public final class EmojiPagerContentComponent: Component { self.isPremiumLocked = isPremiumLocked self.isEmbedded = isEmbedded self.hasClear = hasClear + self.hasEdit = hasEdit self.collapsedLineCount = collapsedLineCount self.displayPremiumBadges = displayPremiumBadges self.headerItem = headerItem @@ -3382,14 +3439,22 @@ public final class EmojiPagerContentComponent: Component { public let item: Item - private let content: ItemContent + private var content: ItemContent + private var theme: PresentationTheme? + private let placeholderColor: UIColor let pixelSize: CGSize + let pointSize: CGSize private let size: CGSize private var disposable: Disposable? private var fetchDisposable: Disposable? private var premiumBadgeView: PremiumBadgeView? + private var iconLayer: SimpleLayer? + private var tintIconLayer: SimpleLayer? + + private(set) var tintContentLayer: SimpleLayer? + private var badge: Badge? private var validSize: CGSize? @@ -3421,6 +3486,52 @@ public final class EmojiPagerContentComponent: Component { } } + override public var position: CGPoint { + get { + return super.position + } set(value) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.position = value + } + super.position = value + } + } + + override public var bounds: CGRect { + get { + return super.bounds + } set(value) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.bounds = value + } + super.bounds = value + } + } + + override public func add(_ animation: CAAnimation, forKey key: String?) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.add(animation, forKey: key) + } + + super.add(animation, forKey: key) + } + + override public func removeAllAnimations() { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.removeAllAnimations() + } + + super.removeAllAnimations() + } + + override public func removeAnimation(forKey: String) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.removeAnimation(forKey: forKey) + } + + super.removeAnimation(forKey: forKey) + } + public var onContentsUpdate: () -> Void = {} public var onLoop: () -> Void = {} @@ -3445,6 +3556,7 @@ public final class EmojiPagerContentComponent: Component { let scale = min(2.0, UIScreenScale) let pixelSize = CGSize(width: pointSize.width * scale, height: pointSize.height * scale) self.pixelSize = pixelSize + self.pointSize = pointSize self.size = CGSize(width: pixelSize.width / scale, height: pixelSize.height / scale) super.init() @@ -3561,20 +3673,26 @@ public final class EmojiPagerContentComponent: Component { image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) } case .add: - context.setFillColor(UIColor.black.withAlphaComponent(0.08).cgColor) - context.fillEllipse(in: CGRect(origin: .zero, size: size).insetBy(dx: 8.0, dy: 8.0)) - context.setFillColor(UIColor.black.withAlphaComponent(0.16).cgColor) - - let plusSize = CGSize(width: 4.5, height: 31.5) - context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height), cornerRadius: plusSize.width / 2.0).cgPath) - context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width), cornerRadius: plusSize.width / 2.0).cgPath) - context.fillPath() + break } UIGraphicsPopContext() })?.withRenderingMode(icon == .stop ? .alwaysTemplate : .alwaysOriginal) self.contents = image?.cgImage } + + if case .icon(.add) = content { + let tintContentLayer = SimpleLayer() + self.tintContentLayer = tintContentLayer + + let iconLayer = SimpleLayer() + self.iconLayer = iconLayer + self.addSublayer(iconLayer) + + let tintIconLayer = SimpleLayer() + self.tintIconLayer = tintIconLayer + tintContentLayer.addSublayer(tintIconLayer) + } } override public init(layer: Any) { @@ -3588,6 +3706,7 @@ public final class EmojiPagerContentComponent: Component { self.placeholderColor = layer.placeholderColor self.size = layer.size self.pixelSize = layer.pixelSize + self.pointSize = layer.pointSize self.onUpdateDisplayPlaceholder = { _, _ in } @@ -3613,8 +3732,22 @@ public final class EmojiPagerContentComponent: Component { return nullAction } - func update(content: ItemContent) { + func update( + content: ItemContent, + theme: PresentationTheme + ) { + var themeUpdated = false + if self.theme !== theme { + self.theme = theme + themeUpdated = true + } + var contentUpdated = false if self.content != content { + self.content = content + contentUpdated = true + } + + if themeUpdated || contentUpdated { if case let .icon(icon) = content, case let .topic(title, color) = icon { let image = generateImage(self.size, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -3630,15 +3763,56 @@ public final class EmojiPagerContentComponent: Component { UIGraphicsPopContext() }) self.contents = image?.cgImage + } else if case .icon(.add) = content { + guard let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer else { + return + } + func generateIcon(color: UIColor) -> UIImage? { + return generateImage(self.pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + UIGraphicsPushContext(context) + + context.setFillColor(color.withMultipliedAlpha(0.2).cgColor) + context.fillEllipse(in: CGRect(origin: .zero, size: size).insetBy(dx: 8.0, dy: 8.0)) + context.setFillColor(color.cgColor) + + let plusSize = CGSize(width: 4.5, height: 31.5) + context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height), cornerRadius: plusSize.width / 2.0).cgPath) + context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width), cornerRadius: plusSize.width / 2.0).cgPath) + context.fillPath() + + UIGraphicsPopContext() + }) + } + + let needsVibrancy = !theme.overallDarkAppearance + let color = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor + + iconLayer.contents = generateIcon(color: color)?.cgImage + tintIconLayer.contents = generateIcon(color: .white)?.cgImage + + tintIconLayer.isHidden = !needsVibrancy } } } - func update(transition: Transition, size: CGSize, badge: Badge?, blurredBadgeColor: UIColor, blurredBadgeBackgroundColor: UIColor) { + func update( + transition: Transition, + size: CGSize, + badge: Badge?, + blurredBadgeColor: UIColor, + blurredBadgeBackgroundColor: UIColor + ) { if self.badge != badge || self.validSize != size { self.badge = badge self.validSize = size + if let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer { + transition.setFrame(layer: iconLayer, frame: CGRect(origin: .zero, size: size)) + transition.setFrame(layer: tintIconLayer, frame: CGRect(origin: .zero, size: size)) + } + if let badge = badge { var badgeTransition = transition let premiumBadgeView: PremiumBadgeView @@ -5583,6 +5757,7 @@ public final class EmojiPagerContentComponent: Component { var headerCentralContentWidth: CGFloat? var headerSizeUpdated = false if let title = itemGroup.title { + let hasEdit = itemGroup.hasEdit validGroupHeaderIds.insert(itemGroup.groupId) let groupHeaderView: GroupHeaderLayer var groupHeaderTransition = transition @@ -5596,7 +5771,11 @@ public final class EmojiPagerContentComponent: Component { guard let strongSelf = self, let component = strongSelf.component else { return } - component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, false, true) + if hasEdit { + component.inputInteractionHolder.inputInteraction?.editAction(groupId) + } else { + component.inputInteractionHolder.inputInteraction?.addGroupAction(groupId, false, true) + } }, performItemAction: { [weak self] item, view, rect, layer in guard let strongSelf = self, let component = strongSelf.component else { @@ -5611,8 +5790,12 @@ public final class EmojiPagerContentComponent: Component { } var actionButtonTitle: String? + var actionButtonIsCompact = false if case .detailed = itemLayout.layoutType, itemGroup.isFeatured { actionButtonTitle = itemGroup.actionButtonTitle + } else if itemGroup.hasEdit { + actionButtonTitle = keyboardChildEnvironment.strings.Stickers_Edit + actionButtonIsCompact = true } let hasTopSeparator = false @@ -5624,6 +5807,7 @@ public final class EmojiPagerContentComponent: Component { layoutType: itemLayout.layoutType, hasTopSeparator: hasTopSeparator, actionButtonTitle: actionButtonTitle, + actionButtonIsCompact: actionButtonIsCompact, title: title, subtitle: itemGroup.subtitle, badge: itemGroup.badge, @@ -5956,8 +6140,11 @@ public final class EmojiPagerContentComponent: Component { } ) - self.scrollView.layer.addSublayer(itemLayer) self.visibleItemLayers[itemId] = itemLayer + self.scrollView.layer.addSublayer(itemLayer) + if let tintContentLayer = itemLayer.tintContentLayer { + self.mirrorContentScrollView.layer.addSublayer(tintContentLayer) + } } var itemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: index) @@ -6002,10 +6189,16 @@ public final class EmojiPagerContentComponent: Component { } if case .icon = item.content { - itemLayer.update(content: item.content) + itemLayer.update(content: item.content, theme: keyboardChildEnvironment.theme) } - itemLayer.update(transition: transition, size: itemFrame.size, badge: badge, blurredBadgeColor: UIColor(white: 0.0, alpha: 0.1), blurredBadgeBackgroundColor: keyboardChildEnvironment.theme.list.plainBackgroundColor) + itemLayer.update( + transition: transition, + size: itemFrame.size, + badge: badge, + blurredBadgeColor: UIColor(white: 0.0, alpha: 0.1), + blurredBadgeBackgroundColor: keyboardChildEnvironment.theme.list.plainBackgroundColor + ) switch item.tintMode { case let .custom(color): @@ -6166,6 +6359,7 @@ public final class EmojiPagerContentComponent: Component { itemLayer.opacity = 0.0 itemLayer.animateScale(from: 1.0, to: 0.01, duration: 0.16) itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak itemLayer] _ in + itemLayer?.tintContentLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer() }) @@ -6185,6 +6379,7 @@ public final class EmojiPagerContentComponent: Component { } } else if let position = updatedItemPositions?[.item(id: id)], transitionHintInstalledGroupId != id.groupId { transition.setPosition(layer: itemLayer, position: position, completion: { [weak itemLayer] _ in + itemLayer?.tintContentLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer() }) if let itemSelectionLayer = itemSelectionLayer { @@ -6198,6 +6393,7 @@ public final class EmojiPagerContentComponent: Component { itemLayer.opacity = 0.0 itemLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2) itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak itemLayer] _ in + itemLayer?.tintContentLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer() }) @@ -6217,7 +6413,9 @@ public final class EmojiPagerContentComponent: Component { } } } else { + itemLayer.tintContentLayer?.removeFromSuperlayer() itemLayer.removeFromSuperlayer() + if let itemSelectionLayer = itemSelectionLayer { itemSelectionLayer.removeFromSuperlayer() itemSelectionLayer.tintContainerLayer.removeFromSuperlayer() diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index 4fbf2390c1..1c05326830 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -39,6 +39,7 @@ public extension EmojiPagerContentComponent { case groupPhoto case backgroundIcon case reactionList + case stickerAlt } static func emojiInputData( @@ -58,6 +59,7 @@ public extension EmojiPagerContentComponent { topicColor: Int32? = nil, backgroundIconColor: UIColor? = nil, hasSearch: Bool = true, + hasRecent: Bool = true, forceHasPremium: Bool = false, premiumIfSavedMessages: Bool = true, hideBackground: Bool = false @@ -1158,6 +1160,50 @@ public extension EmojiPagerContentComponent { } } } + } else if case .stickerAlt = subject { + for reactionItem in topReactionItems { +// if existingIds.contains(reactionItem.reaction) { +// continue +// } +// existingIds.insert(reactionItem.reaction) + + let icon: EmojiPagerContentComponent.Item.Icon + if case .reaction(onlyTop: true) = subject { + icon = .none + } else if !hasPremium, case .custom = reactionItem.reaction { + icon = .locked + } else { + icon = .none + } + + var tintMode: Item.TintMode = .none + if reactionItem.file.isCustomTemplateEmoji { + tintMode = .primary + } + + let animationFile = reactionItem.file + let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true) + let resultItem = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: animationFile, + subgroupId: nil, + icon: icon, + tintMode: tintMode + ) + + let groupId = "recent" + if let groupIndex = itemGroupIndexById[groupId] { + itemGroups[groupIndex].items.append(resultItem) + + if itemGroups[groupIndex].items.count >= 8 * 1000 { + break + } + } else { + itemGroupIndexById[groupId] = itemGroups.count + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) + } + } } let hasRecentEmoji = ![.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction, .status, .profilePhoto, .groupPhoto, .topicIcon, .backgroundIcon, .reactionList, .messageTag].contains(subject) @@ -1515,6 +1561,7 @@ public extension EmojiPagerContentComponent { isPremiumLocked: group.isPremiumLocked, isEmbedded: isEmbedded, hasClear: hasClear, + hasEdit: false, collapsedLineCount: group.collapsedLineCount, displayPremiumBadges: false, headerItem: headerItem, @@ -1643,6 +1690,7 @@ public extension EmojiPagerContentComponent { var isPremiumLocked: Bool var isFeatured: Bool var displayPremiumBadges: Bool + var hasEdit: Bool var headerItem: EntityKeyboardAnimationData? var items: [EmojiPagerContentComponent.Item] } @@ -1741,6 +1789,7 @@ public extension EmojiPagerContentComponent { isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, + hasEdit: false, headerItem: nil, items: [resultItem] ) @@ -1778,7 +1827,7 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleFavoriteStickers, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleFavoriteStickers, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem])) } } } @@ -1812,9 +1861,21 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Stickers_FrequentlyUsed, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Stickers_FrequentlyUsed, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem])) } } + + if !forceHasPremium, let groupIndex = itemGroupIndexById[groupId] { + let resultItem = EmojiPagerContentComponent.Item( + animationData: nil, + content: .icon(.add), + itemFile: nil, + subgroupId: nil, + icon: .none, + tintMode: .none + ) + itemGroups[groupIndex].items.insert(resultItem, at: 0) + } } var avatarPeer: EnginePeer? @@ -1851,7 +1912,7 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: peerSpecificPack.peer.compactDisplayTitle, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: peerSpecificPack.peer.compactDisplayTitle, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem])) } } } @@ -1883,9 +1944,11 @@ public extension EmojiPagerContentComponent { var title = "" var headerItem: EntityKeyboardAnimationData? + var hasEdit = false inner: for (id, info, _) in view.collectionInfos { if id == groupId, let info = info as? StickerPackCollectionInfo { title = info.title + hasEdit = info.flags.contains(.isCreator) if let thumbnail = info.thumbnail { let type: EntityKeyboardAnimationData.ItemType @@ -1911,7 +1974,7 @@ public extension EmojiPagerContentComponent { break inner } } - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: true, headerItem: headerItem, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: true, hasEdit: hasEdit, headerItem: headerItem, items: [resultItem])) } } @@ -1974,7 +2037,7 @@ public extension EmojiPagerContentComponent { ) } - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: featuredStickerPack.info.title, subtitle: subtitle, actionButtonTitle: strings.Stickers_Install, isPremiumLocked: isPremiumLocked, isFeatured: true, displayPremiumBadges: false, headerItem: headerItem, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: featuredStickerPack.info.title, subtitle: subtitle, actionButtonTitle: strings.Stickers_Install, isPremiumLocked: isPremiumLocked, isFeatured: true, displayPremiumBadges: false, hasEdit: false, headerItem: headerItem, items: [resultItem])) } } } @@ -1989,6 +2052,7 @@ public extension EmojiPagerContentComponent { } else if group.id == AnyHashable("featuredTop") { hasClear = true isEmbedded = true + } else if group.id == AnyHashable("saved") { } return EmojiPagerContentComponent.ItemGroup( @@ -2002,6 +2066,7 @@ public extension EmojiPagerContentComponent { isPremiumLocked: group.isPremiumLocked, isEmbedded: isEmbedded, hasClear: hasClear, + hasEdit: group.hasEdit, collapsedLineCount: nil, displayPremiumBadges: group.displayPremiumBadges, headerItem: group.headerItem, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift index 6307c7d3d0..68a150eb48 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift @@ -128,6 +128,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode isPremiumLocked: !self.hasPremiumForInstallation, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: 3, displayPremiumBadges: false, headerItem: nil, @@ -171,6 +172,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode }, clearGroup: { _ in }, + editAction: { _ in }, pushController: { _ in }, presentController: { _ in @@ -293,6 +295,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -348,6 +351,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, @@ -382,6 +386,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode isPremiumLocked: false, isEmbedded: false, hasClear: false, + hasEdit: false, collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 59e48bdfc0..e7d812db0e 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -919,6 +919,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { }, clearGroup: { _ in }, + editAction: { _ in + }, pushController: { c in }, presentController: { c in diff --git a/submodules/TelegramUI/Components/MediaEditor/BUILD b/submodules/TelegramUI/Components/MediaEditor/BUILD index 54f2c6a7b9..ce3155b45d 100644 --- a/submodules/TelegramUI/Components/MediaEditor/BUILD +++ b/submodules/TelegramUI/Components/MediaEditor/BUILD @@ -69,6 +69,8 @@ swift_library( "//submodules/YuvConversion:YuvConversion", "//submodules/FastBlur:FastBlur", "//submodules/WallpaperBackgroundNode", + "//submodules/ImageTransparency", + "//submodules/FFMpegBinding", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorAdjustments.metal b/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorAdjustments.metal index 730de12915..bb880e31b7 100644 --- a/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorAdjustments.metal +++ b/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorAdjustments.metal @@ -200,5 +200,5 @@ fragment half4 adjustmentsFragmentShader(RasterizerData in [[stage_in]], result.rgb = result.rgb + noise * adjustments.grain * 0.04; } - return result; + return half4(result.rgb * result.a, result.a); } diff --git a/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorDual.metal b/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorDual.metal index a4fa037c62..5a9790436c 100644 --- a/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorDual.metal +++ b/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorDual.metal @@ -39,12 +39,12 @@ fragment half4 dualFragmentShader(RasterizerData in [[stage_in]], float aspectRatio = R.x / R.y; constexpr sampler samplr(filter::linear, mag_filter::linear, min_filter::linear); - half3 color = texture.sample(samplr, in.texCoord).rgb; - float colorAlpha = min(1.0, adjustments.isOpaque + mask.sample(samplr, in.texCoord).r); + half4 color = texture.sample(samplr, in.texCoord); + float colorAlpha = min(1.0, adjustments.isOpaque * color.a + mask.sample(samplr, in.texCoord).r); float t = 1.0 / adjustments.dimensions.y; float side = 1.0 * aspectRatio; float distance = smoothstep(t, -t, sdfRoundedRectangle(uv, float2(0.0, 0.0), float2(side, mix(1.0, side, adjustments.roundness)), side * adjustments.roundness)); - return mix(half4(color, 0.0), half4(color, colorAlpha * adjustments.alpha), distance); + return mix(half4(color.rgb, 0.0), half4(color.rgb, colorAlpha * adjustments.alpha), distance); } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/AdjustmentsRenderPass.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/AdjustmentsRenderPass.swift index 9ee76ac9cf..cd92007ef7 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/AdjustmentsRenderPass.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/AdjustmentsRenderPass.swift @@ -97,9 +97,9 @@ final class AdjustmentsRenderPass: DefaultRenderPass { } override func process(input: MTLTexture, device: MTLDevice, commandBuffer: MTLCommandBuffer) -> MTLTexture? { - guard self.adjustments.hasValues else { - return input - } +// guard self.adjustments.hasValues else { +// return input +// } self.setupVerticesBuffer(device: device) let width = input.width diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/ImageObjectSeparation.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/ImageObjectSeparation.swift index fe2f1100f0..09856f307f 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/ImageObjectSeparation.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/ImageObjectSeparation.swift @@ -55,67 +55,126 @@ public func cutoutStickerImage(from image: UIImage, onlyCheck: Bool = false) -> } } -public enum CutoutResult { - case image(UIImage) - case pixelBuffer(CVPixelBuffer) +public struct CutoutResult { + public enum Image { + case image(UIImage, CIImage) + case pixelBuffer(CVPixelBuffer) + } + + public let index: Int + public let extractedImage: Image? + public let maskImage: Image? + public let backgroundImage: Image? } -public func cutoutImage(from image: UIImage, atPoint point: CGPoint?, asImage: Bool) -> Signal { - if #available(iOS 17.0, *) { - guard let cgImage = image.cgImage else { - return .single(nil) - } - return Signal { subscriber in - let ciContext = CIContext(options: nil) - let inputImage = CIImage(cgImage: cgImage) +public enum CutoutTarget { + case point(CGPoint?) + case index(Int) + case all +} + +public func cutoutImage( + from image: UIImage, + editedImage: UIImage? = nil, + values: MediaEditorValues?, + target: CutoutTarget, + includeExtracted: Bool = true, + completion: @escaping ([CutoutResult]) -> Void +) { + if #available(iOS 17.0, *), let cgImage = image.cgImage { + let ciContext = CIContext(options: nil) + let inputImage = CIImage(cgImage: cgImage) + + queue.async { let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) let request = VNGenerateForegroundInstanceMaskRequest { [weak handler] request, error in guard let handler, let result = request.results?.first as? VNInstanceMaskObservation else { - subscriber.putNext(nil) - subscriber.putCompletion() + completion([]) return } - let instances = IndexSet(instances(atPoint: point, inObservation: result).prefix(1)) - if let mask = try? result.generateScaledMaskForImage(forInstances: instances, from: handler) { - if asImage { - let filter = CIFilter.blendWithMask() - filter.inputImage = inputImage - filter.backgroundImage = CIImage(color: .clear) - filter.maskImage = CIImage(cvPixelBuffer: mask) - if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: inputImage.extent) { - let image = UIImage(cgImage: cgImage) - subscriber.putNext(.image(image)) - subscriber.putCompletion() - return + let targetInstances: IndexSet + switch target { + case let .point(point): + targetInstances = instances(atPoint: point, inObservation: result) + case let .index(index): + targetInstances = IndexSet([index]) + case .all: + targetInstances = result.allInstances + } + + var results: [CutoutResult] = [] + for instance in targetInstances { + if let mask = try? result.generateScaledMaskForImage(forInstances: IndexSet(integer: instance), from: handler) { + let extractedImage: CutoutResult.Image? + if includeExtracted { + let filter = CIFilter.blendWithMask() + filter.backgroundImage = CIImage(color: .clear) + + let dimensions: CGSize + var maskImage = CIImage(cvPixelBuffer: mask) + if let editedImage = editedImage?.cgImage.flatMap({ CIImage(cgImage: $0) }) { + filter.inputImage = editedImage + dimensions = editedImage.extent.size + + if let values { + let initialScale: CGFloat + if maskImage.extent.height > maskImage.extent.width { + initialScale = dimensions.width / maskImage.extent.width + } else { + initialScale = dimensions.width / maskImage.extent.height + } + + let dimensions = editedImage.extent.size + maskImage = maskImage.transformed(by: CGAffineTransform(translationX: -maskImage.extent.width / 2.0, y: -maskImage.extent.height / 2.0)) + + var transform = CGAffineTransform.identity + let position = values.cropOffset + let rotation = values.cropRotation + let scale = values.cropScale + transform = transform.translatedBy(x: dimensions.width / 2.0 + position.x, y: dimensions.height / 2.0 + position.y * -1.0) + transform = transform.rotated(by: -rotation) + transform = transform.scaledBy(x: scale * initialScale, y: scale * initialScale) + maskImage = maskImage.transformed(by: transform) + } + } else { + filter.inputImage = inputImage + dimensions = inputImage.extent.size + } + filter.maskImage = maskImage + + if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: CGRect(origin: .zero, size: dimensions)) { + extractedImage = .image(UIImage(cgImage: cgImage), output) + } else { + extractedImage = nil + } + } else { + extractedImage = nil } - } else { - let filter = CIFilter.blendWithMask() - filter.inputImage = CIImage(color: .white) - filter.backgroundImage = CIImage(color: .black) - filter.maskImage = CIImage(cvPixelBuffer: mask) - if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: inputImage.extent) { - let image = UIImage(cgImage: cgImage) - subscriber.putNext(.image(image)) - subscriber.putCompletion() - return + + let maskFilter = CIFilter.blendWithMask() + maskFilter.inputImage = CIImage(color: .white) + maskFilter.backgroundImage = CIImage(color: .black) + maskFilter.maskImage = CIImage(cvPixelBuffer: mask) + let maskImage: CutoutResult.Image? + if let maskOutput = maskFilter.outputImage?.cropped(to: inputImage.extent), let maskCgImage = ciContext.createCGImage(maskOutput, from: inputImage.extent) { + maskImage = .image(UIImage(cgImage: maskCgImage), maskOutput) + } else { + maskImage = nil + } + + if extractedImage != nil || maskImage != nil { + results.append(CutoutResult(index: instance, extractedImage: extractedImage, maskImage: maskImage, backgroundImage: nil)) } -// subscriber.putNext(.pixelBuffer(mask)) -// subscriber.putCompletion() } } - subscriber.putNext(nil) - subscriber.putCompletion() + completion(results) } try? handler.perform([request]) - return ActionDisposable { - request.cancel() - } } - |> runOn(queue) } else { - return .single(nil) + completion([]) } } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 183b2405e3..8d0bee5074 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -11,6 +11,7 @@ import TelegramCore import TelegramPresentationData import FastBlur import AccountContext +import ImageTransparency public struct MediaEditorPlayerState { public struct Track: Equatable { @@ -681,7 +682,7 @@ public final class MediaEditor { } - if case .sticker = self.mode { + if case .sticker = self.mode, let cgImage = image.cgImage, !imageHasTransparency(cgImage) { let _ = (cutoutStickerImage(from: image, onlyCheck: true) |> deliverOnMainQueue).start(next: { [weak self] result in guard let self, result != nil else { @@ -1683,21 +1684,7 @@ public final class MediaEditor { self.renderer.renderFrame() } - public func getSeparatedImage(point: CGPoint?) -> Signal { - guard let textureSource = self.renderer.textureSource as? UniversalTextureSource, let image = textureSource.mainImage else { - return .single(nil) - } - return cutoutImage(from: image, atPoint: point, asImage: true) - |> map { result in - if let result, case let .image(image) = result { - return image - } else { - return nil - } - } - } - - public func removeSeparationMask() { + public func removeSegmentationMask() { self.isCutoutUpdated(false) self.renderer.currentMainInputMask = nil @@ -1706,26 +1693,27 @@ public final class MediaEditor { } } - public func setSeparationMask(point: CGPoint?) { + public func setSegmentationMask(_ image: UIImage) { guard let renderTarget = self.previewView, let device = renderTarget.mtlDevice else { return } + + self.isCutoutUpdated(true) + + //TODO:replace with pixelbuffer + self.renderer.currentMainInputMask = loadTexture(image: image, device: device) + if !self.skipRendering { + self.updateRenderChain() + } + } + + public func processImage(with f: @escaping (UIImage, UIImage?) -> Void) { guard let textureSource = self.renderer.textureSource as? UniversalTextureSource, let image = textureSource.mainImage else { return } - self.isCutoutUpdated(true) - - let _ = (cutoutImage(from: image, atPoint: point, asImage: false) - |> deliverOnMainQueue).start(next: { [weak self] result in - guard let self, let result, case let .image(image) = result else { - return - } - //TODO:replace with pixelbuffer - self.renderer.currentMainInputMask = loadTexture(image: image, device: device) - if !self.skipRendering { - self.updateRenderChain() - } - }) + Queue.concurrentDefaultQueue().async { + f(image, self.resultImage) + } } private func maybeGeneratePersonSegmentation(_ image: UIImage?) { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift index 0270ec0a7e..6277737481 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift @@ -78,10 +78,11 @@ func loadTexture(image: UIImage, device: MTLDevice) -> MTLTexture? { let bytePerPixel = 4 let bytesPerRow = bytePerPixel * Int(width) let bitsPerComponent = 8 - let bitmapInfo = CGBitmapInfo.byteOrder32Little.rawValue + CGImageAlphaInfo.premultipliedFirst.rawValue - let context = CGContext.init(data: rawData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) - context?.draw(imageRef!, in: CGRect(x: 0, y: 0, width: width, height: height)) - + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) + if let context = CGContext(data: rawData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) { + context.clear(CGRect(x: 0, y: 0, width: width, height: height)) + context.draw(imageRef!, in: CGRect(x: 0, y: 0, width: width, height: height)) + } return rawData } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift index d6e3fcbe43..443390652e 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift @@ -149,7 +149,7 @@ struct VideoEncodeParameters { var roundness: simd_float1 var alpha: simd_float1 var isOpaque: simd_float1 - var empty: simd_float1 + var empty: simd_float1 = 0.0 } final class VideoFinishPass: RenderPass { @@ -242,18 +242,6 @@ final class VideoFinishPass: RenderPass { empty: 0 ) encoder.setFragmentBytes(¶meters, length: MemoryLayout.size, index: 0) -// var resolution = simd_uint2(UInt32(size.width), UInt32(size.height)) -// encoder.setFragmentBytes(&resolution, length: MemoryLayout.size * 2, index: 0) -// -// var roundness = roundness -// encoder.setFragmentBytes(&roundness, length: MemoryLayout.size, index: 1) -// -// var alpha = alpha -// encoder.setFragmentBytes(&alpha, length: MemoryLayout.size, index: 2) -// -// var isOpaque = maskTexture == nil ? 1.0 : 0.0 -// encoder.setFragmentBytes(&isOpaque, length: MemoryLayout.size, index: 3) - encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) } @@ -645,9 +633,7 @@ final class VideoFinishPass: RenderPass { length: MemoryLayout.stride * vertices.count, options: []) encoder.setVertexBuffer(buffer, offset: 0, index: 0) - encoder.setFragmentBytes(&self.gradientColors, length: MemoryLayout.size, index: 0) - encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD index 88ab5ba5ef..b210d78e82 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD +++ b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD @@ -52,6 +52,10 @@ swift_library( "//submodules/TelegramUI/Components/MediaScrubberComponent", "//submodules/Components/BlurredBackgroundComponent", "//submodules/TelegramUI/Components/DustEffect", + "//submodules/WebPBinding", + "//submodules/StickerResources", + "//submodules/StickerPeekUI", + "//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController" ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift index fd68d35fbb..7b33ae8f67 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift @@ -47,8 +47,8 @@ private final class MediaCutoutScreenComponent: Component { private let doneButton = ComponentView() private let fadeView = UIView() - private let separatedImageView = UIImageView() - + private var outlineViews: [StickerCutoutOutlineView] = [] + private var component: MediaCutoutScreenComponent? private weak var state: State? private var environment: ViewControllerComponentContainer.Environment? @@ -57,9 +57,7 @@ private final class MediaCutoutScreenComponent: Component { self.buttonsContainerView.clipsToBounds = true self.fadeView.alpha = 0.0 - self.fadeView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.6) - - self.separatedImageView.contentMode = .scaleAspectFit + self.fadeView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.7) super.init(frame: frame) @@ -69,7 +67,6 @@ private final class MediaCutoutScreenComponent: Component { self.buttonsContainerView.addSubview(self.buttonsBackgroundView) self.addSubview(self.fadeView) - self.addSubview(self.separatedImageView) self.addSubview(self.previewContainerView) self.previewContainerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.previewTap(_:)))) @@ -89,9 +86,21 @@ private final class MediaCutoutScreenComponent: Component { x: location.x / self.previewContainerView.frame.width, y: location.y / self.previewContainerView.frame.height ) - component.mediaEditor.setSeparationMask(point: point) - self.playDissolveAnimation() + component.mediaEditor.processImage { [weak self] originalImage, _ in + cutoutImage(from: originalImage, values: nil, target: .point(point), includeExtracted: false, completion: { [weak self] results in + Queue.mainQueue().async { + if let self, let component = self.component, let result = results.first, let maskImage = result.maskImage { + if case let .image(mask, _) = maskImage { + self.playDissolveAnimation() + component.mediaEditor.setSegmentationMask(mask) + } + } + } + }) + } + + HapticFeedback().impact(.medium) } func animateInFromEditor() { @@ -106,6 +115,9 @@ private final class MediaCutoutScreenComponent: Component { self.cancelButton.view?.isHidden = true self.fadeView.layer.animateAlpha(from: self.fadeView.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + for outlineView in self.outlineViews { + outlineView.layer.animateAlpha(from: self.fadeView.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + } self.buttonsBackgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in completion() }) @@ -210,7 +222,7 @@ private final class MediaCutoutScreenComponent: Component { let labelSize = self.label.update( transition: transition, - component: AnyComponent(Text(text: "Tap an object to cut it out", font: Font.regular(17.0), color: .white)), + component: AnyComponent(Text(text: "Tap on an object to cut it out", font: Font.regular(17.0), color: .white)), environment: {}, containerSize: CGSize(width: availableSize.width - 88.0, height: 44.0) ) @@ -229,7 +241,9 @@ private final class MediaCutoutScreenComponent: Component { transition.setFrame(view: self.buttonsBackgroundView, frame: CGRect(origin: .zero, size: buttonsContainerFrame.size)) transition.setFrame(view: self.previewContainerView, frame: previewContainerFrame) - transition.setFrame(view: self.separatedImageView, frame: previewContainerFrame) + for view in self.outlineViews { + transition.setFrame(view: view, frame: previewContainerFrame) + } let frameWidth = floor(previewContainerFrame.width * 0.97) @@ -237,20 +251,28 @@ private final class MediaCutoutScreenComponent: Component { self.fadeView.layer.cornerRadius = frameWidth / 8.0 if isFirstTime { - let _ = (component.mediaEditor.getSeparatedImage(point: nil) - |> deliverOnMainQueue).start(next: { [weak self] image in - guard let self else { - return - } - self.separatedImageView.image = image - self.state?.updated(transition: .easeInOut(duration: 0.2)) - }) - } else { - if let _ = self.separatedImageView.image { - transition.setAlpha(view: self.fadeView, alpha: 1.0) - } else { - transition.setAlpha(view: self.fadeView, alpha: 0.0) + let values = component.mediaEditor.values + component.mediaEditor.processImage { originalImage, editedImage in + cutoutImage(from: originalImage, editedImage: editedImage, values: values, target: .all, completion: { results in + Queue.mainQueue().async { + if !results.isEmpty { + for result in results { + if let extractedImage = result.extractedImage, let maskImage = result.maskImage { + if case let .image(image, _) = extractedImage, case let .image(_, mask) = maskImage { + let outlineView = StickerCutoutOutlineView(frame: self.previewContainerView.frame) + outlineView.update(image: image, maskImage: mask, size: self.previewContainerView.bounds.size, values: values) + self.insertSubview(outlineView, belowSubview: self.previewContainerView) + self.outlineViews.append(outlineView) + } + } + } + self.state?.updated(transition: .easeInOut(duration: 0.4)) + } + } + }) } + } else { + transition.setAlpha(view: self.fadeView, alpha: !self.outlineViews.isEmpty ? 1.0 : 0.0) } return availableSize } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index cfd9e042df..e8adab8d0f 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -41,6 +41,10 @@ import ForwardInfoPanelComponent import ContextReferenceButtonComponent import MediaScrubberComponent import BlurredBackgroundComponent +import WebPBinding +import StickerResources +import StickerPeekUI +import StickerPackEditTitleController private let playbackButtonTag = GenericComponentViewTag() private let muteButtonTag = GenericComponentViewTag() @@ -2148,7 +2152,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate private let stickerPickerInputData = Promise() - private var availableReactions: [ReactionItem] = [] + fileprivate var availableReactions: [ReactionItem] = [] private var availableReactionsDisposable: Disposable? private var panGestureRecognizer: UIPanGestureRecognizer? @@ -4057,7 +4061,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate switch mode { case .sticker: self.mediaEditor?.maybePauseVideo() - let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get(), defaultToEmoji: self.defaultToEmoji, hasGifs: true) + + var hasInteractiveStickers = true + if let controller = self.controller, case .stickerEditor = controller.mode { + hasInteractiveStickers = false + } + let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get(), defaultToEmoji: self.defaultToEmoji, hasGifs: hasInteractiveStickers, hasInteractiveStickers: hasInteractiveStickers) controller.completion = { [weak self] content in if let self { if let content { @@ -4227,7 +4236,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if self.entitiesView.hasSelection { self.entitiesView.selectEntity(nil) } - let controller = MediaToolsScreen(context: self.context, mediaEditor: mediaEditor, hiddenTools: !self.canEnhance ? [.enhance] : []) + var hiddenTools: [EditorToolKey] = [] + if !self.canEnhance { + hiddenTools.append(.enhance) + } + if let controller = self.controller, case .stickerEditor = controller.mode { + hiddenTools.append(.grain) + hiddenTools.append(.vignette) + } + let controller = MediaToolsScreen(context: self.context, mediaEditor: mediaEditor, hiddenTools: hiddenTools) controller.dismissed = { [weak self] in if let self { self.animateInFromTool() @@ -4237,8 +4254,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.animateOutToTool() } }, - openCutout: { [weak self] in - if let self, let mediaEditor = self.mediaEditor { + openCutout: { [weak self, weak controller] in + if let self, let controller, let mediaEditor = self.mediaEditor { if self.entitiesView.hasSelection { self.entitiesView.selectEntity(nil) } @@ -4251,17 +4268,18 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.previewView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, completion: { _ in snapshotView?.removeFromSuperview() }) - mediaEditor.removeSeparationMask() + mediaEditor.removeSegmentationMask() } else { - let controller = MediaCutoutScreen(context: self.context, mediaEditor: mediaEditor, previewView: self.previewView) - controller.dismissed = { [weak self] in + let cutoutController = MediaCutoutScreen(context: self.context, mediaEditor: mediaEditor, previewView: self.previewView) + cutoutController.dismissed = { [weak self] in if let self { self.animateInFromTool(inPlace: true) } } - self.controller?.present(controller, in: .window(.root)) + controller.present(cutoutController, in: .window(.root)) self.animateOutToTool(inPlace: true) } + controller.hapticFeedback.impact(.medium) } } ) @@ -4468,6 +4486,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } case image(image: UIImage, dimensions: PixelDimensions) case video(video: VideoResult, coverImage: UIImage?, values: MediaEditorValues, duration: Double, dimensions: PixelDimensions) + case sticker(file: TelegramMediaFile) } public struct Result { @@ -4477,6 +4496,31 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate public let options: MediaEditorResultPrivacy public let stickers: [TelegramMediaFile] public let randomId: Int64 + + init() { + self.media = nil + self.mediaAreas = [] + self.caption = NSAttributedString() + self.options = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false) + self.stickers = [] + self.randomId = 0 + } + + init( + media: MediaResult?, + mediaAreas: [MediaArea], + caption: NSAttributedString, + options: MediaEditorResultPrivacy, + stickers: [TelegramMediaFile], + randomId: Int64 + ) { + self.media = media + self.mediaAreas = mediaAreas + self.caption = caption + self.options = options + self.stickers = stickers + self.randomId = randomId + } } let context: AccountContext @@ -4508,6 +4552,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate private var audioSessionDisposable: Disposable? private let postingAvailabilityPromise = Promise() private var postingAvailabilityDisposable: Disposable? + + fileprivate var myStickerPacks: [(StickerPackCollectionInfo, StickerPackItem?)] = [] + private var myStickerPacksDisposable: Disposable? public init( context: AccountContext, @@ -4581,6 +4628,16 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate updateStorySources(engine: self.context.engine) updateStoryDrafts(engine: self.context.engine) + + if case .stickerEditor = mode { + self.myStickerPacksDisposable = (self.context.engine.stickers.getMyStickerSets() + |> deliverOnMainQueue).start(next: { [weak self] packs in + guard let self else { + return + } + self.myStickerPacks = packs + }) + } } required public init(coder aDecoder: NSCoder) { @@ -4591,6 +4648,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.exportDisposable.dispose() self.audioSessionDisposable?.dispose() self.postingAvailabilityDisposable?.dispose() + self.myStickerPacksDisposable?.dispose() } fileprivate func setupAudioSessionIfNeeded() { @@ -5567,22 +5625,20 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } func requestStickerCompletion(animated: Bool) { - guard let mediaEditor = self.node.mediaEditor, !self.didComplete else { + guard let mediaEditor = self.node.mediaEditor else { return } - - self.didComplete = true - + self.dismissAllTooltips() - mediaEditor.stop() - mediaEditor.invalidate() - self.node.entitiesView.invalidate() - if let navigationController = self.navigationController as? NavigationController { navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate) } +// mediaEditor.stop() +// mediaEditor.invalidate() +// self.node.entitiesView.invalidate() + let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) } let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView) mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities) @@ -5592,7 +5648,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if let self, let resultImage { Logger.shared.log("MediaEditor", "Completed with image \(resultImage)") - let scaledImage = generateImage(CGSize(width: 512, height: 512), contextGenerator: { size, context in + let dimensions = CGSize(width: 512, height: 512) + let scaledImage = generateImage(dimensions, contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.addPath(CGPath(roundedRect: CGRect(origin: .zero, size: size), cornerWidth: size.width / 8.0, cornerHeight: size.width / 8.0, transform: nil)) @@ -5602,17 +5659,338 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate context.draw(resultImage.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - scaledSize.width) / 2.0), y: floor((size.height - scaledSize.height) / 2.0)), size: scaledSize)) }, opaque: false, scale: 1.0)! - self.completion(MediaEditorScreen.Result(media: .image(image: scaledImage, dimensions: PixelDimensions(scaledImage.size)), mediaAreas: [], caption: NSAttributedString(), options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false), stickers: [], randomId: 0), { [weak self] finished in + self.presentStickerPreview(image: scaledImage) + } + }) + } + } + + func presentStickerPreview(image: UIImage) { + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + Queue.concurrentDefaultQueue().async { + if let data = try? WebP.convert(toWebP: image, quality: 97.0) { + self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + } + } + + let file = stickerFile(resource: resource, size: Int64(0), dimensions: PixelDimensions(image.size)) + + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) + let peekController = PeekController( + presentationData: presentationData, + content: StickerPreviewPeekContent( + context: self.context, + theme: presentationData.theme, + strings: presentationData.strings, + item: .image(image), + isCreating: true, + menu: [ + .action(ContextMenuActionItem(text: presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + guard let self else { + return + } + self.completion(MediaEditorScreen.Result( + media: .sticker(file: file), + mediaAreas: [], + caption: NSAttributedString(), + options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false), + stickers: [], + randomId: 0 + ), { [weak self] finished in + self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in + self?.dismiss() + Queue.mainQueue().justDispatch { + finished() + } + }) + }) + })), + .action(ContextMenuActionItem(text: presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + guard let self else { + return + } + self.uploadSticker(file, action: .addToFavorites) + })), + .action(ContextMenuActionItem(text: "Add to Sticker Set", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddSticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in + guard let self else { + return + } + + var contextItems: [ContextMenuItem] = [] + contextItems.append(.action(ContextMenuActionItem(text: "Back", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { c, _ in + c.popItems() + }))) + + contextItems.append(.separator) + + contextItems.append(.action(ContextMenuActionItem(text: "New Sticker Set", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] _, f in + if let self { + self.presentCreateStickerPack(file: file, completion: { + f(.default) + }) + } + }))) + + let thumbSize = CGSize(width: 24.0, height: 24.0) + for (pack, firstItem) in self.myStickerPacks { + let thumbnailResource = pack.thumbnail?.resource ?? firstItem?.file.resource + let thumbnailIconSource: ContextMenuActionItemIconSource? + if let thumbnailResource { + var resourceId: Int64 = 0 + if let resource = thumbnailResource as? CloudDocumentMediaResource { + resourceId = resource.fileId + } + let thumbnailFile = firstItem?.file ?? TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: resourceId), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: thumbnailResource.size ?? 0, attributes: []) + + let _ = freeMediaFileInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: .stickerPack(stickerPack: .id(id: pack.id.id, accessHash: pack.accessHash), media: thumbnailFile)).start() + thumbnailIconSource = ContextMenuActionItemIconSource( + size: thumbSize, + signal: chatMessageStickerPackThumbnail(postbox: self.context.account.postbox, resource: thumbnailResource) + |> map { generator -> UIImage? in + return generator(TransformImageArguments(corners: ImageCorners(), imageSize: thumbSize, boundingSize: thumbSize, intrinsicInsets: .zero))?.generateImage() + } + ) + } else { + thumbnailIconSource = nil + } + contextItems.append(.action(ContextMenuActionItem(text: pack.title, icon: { _ in return nil }, iconSource: thumbnailIconSource, iconPosition: .left, action: { [weak self] _, f in + guard let self else { + return + } + f(.default) + self.uploadSticker(file, action: .addToStickerPack(pack: .id(id: pack.id.id, accessHash: pack.accessHash), title: pack.title)) + }))) + } + + let items = ContextController.Items( + id: 1, + content: .list(contextItems), + context: nil, + reactionItems: [], + selectedReactionItems: Set(), + reactionsTitle: nil, + reactionsLocked: false, + animationCache: nil, + alwaysAllowPremiumReactions: false, + allPresetReactionsAreAvailable: false, + getEmojiContent: nil, + disablePositionLock: false, + tip: nil, + tipSignal: nil, + dismissed: nil + ) + c.pushItems(items: .single(items)) + })) + ], + reactionItems: self.node.availableReactions, + openPremiumIntro: {} + ), + sourceView: { [weak self] in + if let self { + let size = CGSize(width: self.view.frame.width, height: self.view.frame.width) + return (self.view, CGRect(origin: CGPoint(x: (self.view.frame.width - size.width) / 2.0, y: (self.view.frame.height - size.height) / 2.0), size: size)) + } else { + return nil + } + }, + activateImmediately: true + ) + self.present(peekController, in: .window(.root)) + } + + private enum StickerAction { + case addToFavorites + case createStickerPack(title: String) + case addToStickerPack(pack: StickerPackReference, title: String) + } + + private func presentCreateStickerPack(file: TelegramMediaFile, completion: @escaping () -> Void) { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) + + var dismissImpl: (() -> Void)? + let controller = stickerPackEditTitleController(context: self.context, forceDark: true, title: "New Sticker Set", text: "Choose a name for your sticker set.", placeholder: presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: nil, maxLength: 128, apply: { [weak self] title in + guard let self else { + return + } + dismissImpl?() + completion() + + self.updateEditProgress(0.0, cancel: { [weak self] in + self?.stickerUploadDisposable.set(nil) + }) + self.stickerUploadDisposable.set((self.context.engine.stickers.createStickerSet( + title: title ?? "", + shortName: "", + stickers: [ + ImportSticker( + resource: file.resource, + emojis: ["😀"], + dimensions: PixelDimensions(width: 512, height: 512), + mimeType: "image/webp", + keywords: "" + ) + ], + thumbnail: nil, + type: .stickers(content: .image), + software: nil + ) |> deliverOnMainQueue).startStandalone(next: { [weak self] status in + guard let self else { + return + } + switch status { + case let .progress(progress, _, _): + self.updateEditProgress(progress, cancel: { [weak self] in + self?.stickerUploadDisposable.set(nil) + }) + case let .complete(info, items): + self.completion(MediaEditorScreen.Result(), { [weak self] finished in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in - self?.dismiss() - Queue.mainQueue().justDispatch { - finished() + guard let self else { + return + } + let navigationController = self.navigationController as? NavigationController + self.dismiss() + if let navigationController { + Queue.mainQueue().after(0.2) { + let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash) + let controller = self.context.sharedContext.makeStickerPackScreen(context: self.context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [.result(info: info, items: items, installed: true)], isEditing: false, parentNavigationController: nil, sendSticker: nil) + (navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root)) + } } }) }) } - }) + })) + }, cancel: {}) + dismissImpl = { [weak controller] in + controller?.dismiss() } + self.present(controller, in: .window(.root)) + } + + private let stickerUploadDisposable = MetaDisposable() + private func uploadSticker(_ file: TelegramMediaFile, action: StickerAction) { + let context = self.context + let dimensions = PixelDimensions(width: 512, height: 512) + let mimeType = "image/webp" + + self.updateEditProgress(0.0, cancel: { [weak self] in + self?.stickerUploadDisposable.set(nil) + }) + + let signal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> castError(UploadStickerError.self) + |> mapToSignal { peer -> Signal in + guard let peer else { + return .complete() + } + return context.engine.stickers.uploadSticker(peer: peer._asPeer(), resource: file.resource, alt: "", dimensions: dimensions, mimeType: mimeType) + |> mapToSignal { status -> Signal in + switch status { + case .progress: + return .single(status) + case let .complete(resource, _): + let file = stickerFile(resource: resource, size: file.size ?? 0, dimensions: dimensions) + switch action { + case .addToFavorites: + return context.engine.stickers.toggleStickerSaved(file: file, saved: true) + |> `catch` { _ -> Signal in + return .fail(.generic) + } + |> map { _ in + return status + } + case let .createStickerPack(title): + let sticker = ImportSticker( + resource: resource, + emojis: ["😀"], + dimensions: dimensions, + mimeType: "image/webp", + keywords: "" + ) + return context.engine.stickers.createStickerSet(title: title, shortName: "", stickers: [sticker], thumbnail: nil, type: .stickers(content: .image), software: nil) + |> `catch` { _ -> Signal in + return .fail(.generic) + } + |> mapToSignal { innerStatus in + if case .complete = innerStatus { + return .single(status) + } else { + return .complete() + } + } + case let .addToStickerPack(pack, _): + let sticker = ImportSticker( + resource: resource, + emojis: ["😀"], + dimensions: dimensions, + mimeType: "image/webp", + keywords: "" + ) + return context.engine.stickers.addStickerToStickerSet(packReference: pack, sticker: sticker) + |> `catch` { _ -> Signal in + return .fail(.generic) + } + |> map { _ in + return status + } + } + } + } + } + self.stickerUploadDisposable.set((signal + |> deliverOnMainQueue).startStandalone(next: { [weak self] status in + guard let self else { + return + } + + switch status { + case let .progress(progress): + self.updateEditProgress(progress, cancel: { [weak self] in + self?.stickerUploadDisposable.set(nil) + }) + case .complete: + let navigationController = self.navigationController as? NavigationController + self.completion(MediaEditorScreen.Result(), { [weak self] finished in + self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in + guard let self else { + return + } + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + self.dismiss() + Queue.mainQueue().justDispatch { + finished() + + switch action { + case .addToFavorites: + if let parentController = navigationController?.viewControllers.last as? ViewController { + parentController.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: self.context, file: file, loop: true, title: nil, text: presentationData.strings.Conversation_StickerAddedToFavorites, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current) + } + case let .addToStickerPack(packReference, title): + let navigationController = self.navigationController as? NavigationController + self.dismiss() + if let navigationController { + Queue.mainQueue().after(0.2) { + let controller = self.context.sharedContext.makeStickerPackScreen(context: self.context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], isEditing: false, parentNavigationController: nil, sendSticker: nil) + (navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root)) + + Queue.mainQueue().after(0.1) { + controller.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: self.context, file: file, loop: true, title: nil, text: "Sticker added to **\(title)** sticker set.", undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current) + } + } + } + default: + break + } + } + }) + }) + } + })) } private var videoExport: MediaEditorVideoExport? @@ -6441,3 +6819,12 @@ extension MediaScrubberComponent.Track { ) } } + +private func stickerFile(resource: TelegramMediaResource, size: Int64, dimensions: PixelDimensions) -> TelegramMediaFile { + var fileAttributes: [TelegramMediaFileAttribute] = [] + fileAttributes.append(.FileName(fileName: "sticker.webp")) + fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) + fileAttributes.append(.ImageSize(size: dimensions)) + + return TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: size, attributes: fileAttributes) +} diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift index 11f3086dcb..4401869e98 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift @@ -665,7 +665,7 @@ private final class MediaToolsScreenComponent: Component { tools = tools.filter { !component.hiddenTools.contains($0.key) } - if !component.mediaEditor.sourceIsVideo { + if !component.mediaEditor.sourceIsVideo && !component.hiddenTools.contains(.grain) { tools.insert(AdjustmentTool( key: .grain, title: presentationData.strings.Story_Editor_Tool_Grain, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerCutoutOutlineView.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerCutoutOutlineView.swift new file mode 100644 index 0000000000..b0973cb1f9 --- /dev/null +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerCutoutOutlineView.swift @@ -0,0 +1,507 @@ +import Foundation +import UIKit +import Display +import CoreImage +import MediaEditor + +func createEmitterBehavior(type: String) -> NSObject { + let selector = ["behaviorWith", "Type:"].joined(separator: "") + let behaviorClass = NSClassFromString(["CA", "Emitter", "Behavior"].joined(separator: "")) as! NSObject.Type + let behaviorWithType = behaviorClass.method(for: NSSelectorFromString(selector))! + let castedBehaviorWithType = unsafeBitCast(behaviorWithType, to:(@convention(c)(Any?, Selector, Any?) -> NSObject).self) + return castedBehaviorWithType(behaviorClass, NSSelectorFromString(selector), type) +} + +private var previousBeginTime: Int = 3 + +final class StickerCutoutOutlineView: UIView { + let strokeLayer = SimpleShapeLayer() + let imageLayer = SimpleLayer() + var outlineLayer = CAEmitterLayer() + var glowLayer = CAEmitterLayer() + + override init(frame: CGRect) { + super.init(frame: frame) + + self.strokeLayer.fillColor = UIColor.clear.cgColor + self.strokeLayer.strokeColor = UIColor.clear.cgColor + self.strokeLayer.shadowColor = UIColor.white.cgColor + self.strokeLayer.shadowOpacity = 0.35 + self.strokeLayer.shadowRadius = 4.0 + + self.layer.allowsGroupOpacity = true + +// self.imageLayer.contentsGravity = .resizeAspect + + self.layer.addSublayer(self.strokeLayer) + self.layer.addSublayer(self.imageLayer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var hasContents: Bool { + self.imageLayer.contents != nil + } + + func update(image: UIImage, maskImage: CIImage, size: CGSize, values: MediaEditorValues) { + self.imageLayer.contents = image.cgImage + + if let path = getPathFromMaskImage(maskImage, size: size, values: values) { + self.strokeLayer.shadowPath = path.cgPath.expand(width: 1.5) + + self.setupAnimation(path: path) + } + } + + func setupAnimation(path: UIBezierPath) { + self.outlineLayer = CAEmitterLayer() + self.outlineLayer.opacity = 0.7 + self.glowLayer = CAEmitterLayer() + + self.layer.addSublayer(self.outlineLayer) + self.layer.addSublayer(self.glowLayer) + + let randomBeginTime = (previousBeginTime + 4) % 6 + previousBeginTime = randomBeginTime + + let outlineAnimation = CAKeyframeAnimation(keyPath: "emitterPosition") + outlineAnimation.path = path.cgPath + outlineAnimation.duration = 5.0 + outlineAnimation.repeatCount = .infinity + outlineAnimation.calculationMode = .paced + outlineAnimation.beginTime = Double(randomBeginTime) + self.outlineLayer.add(outlineAnimation, forKey: "emitterPosition") + + let lineEmitterCell = CAEmitterCell() + lineEmitterCell.beginTime = CACurrentMediaTime() + let lineAlphaBehavior = createEmitterBehavior(type: "valueOverLife") + lineAlphaBehavior.setValue("color.alpha", forKey: "keyPath") + lineAlphaBehavior.setValue([0.0, 0.5, 0.8, 0.5, 0.0], forKey: "values") + lineEmitterCell.setValue([lineAlphaBehavior], forKey: "emitterBehaviors") + lineEmitterCell.color = UIColor.white.cgColor + lineEmitterCell.contents = UIImage(named: "Media Editor/ParticleDot")?.cgImage + lineEmitterCell.lifetime = 2.2 + lineEmitterCell.birthRate = 600 + lineEmitterCell.scale = 0.13 + lineEmitterCell.alphaSpeed = -0.4 + + self.outlineLayer.emitterCells = [lineEmitterCell] + self.outlineLayer.emitterMode = .points + self.outlineLayer.emitterSize = CGSize(width: 1.0, height: 1.0) + self.outlineLayer.emitterShape = .point + + let glowAnimation = CAKeyframeAnimation(keyPath: "emitterPosition") + glowAnimation.path = path.cgPath + glowAnimation.duration = 5.0 + glowAnimation.repeatCount = .infinity + glowAnimation.calculationMode = .cubicPaced + glowAnimation.beginTime = Double(randomBeginTime) + self.glowLayer.add(glowAnimation, forKey: "emitterPosition") + + let glowEmitterCell = CAEmitterCell() + glowEmitterCell.beginTime = CACurrentMediaTime() + let glowAlphaBehavior = createEmitterBehavior(type: "valueOverLife") + glowAlphaBehavior.setValue("color.alpha", forKey: "keyPath") + glowAlphaBehavior.setValue([0.0, 0.32, 0.4, 0.2, 0.0], forKey: "values") + glowEmitterCell.setValue([glowAlphaBehavior], forKey: "emitterBehaviors") + glowEmitterCell.color = UIColor.white.cgColor + glowEmitterCell.contents = UIImage(named: "Media Editor/ParticleGlow")?.cgImage + glowEmitterCell.lifetime = 2.0 + glowEmitterCell.birthRate = 30 + glowEmitterCell.scale = 1.9 + glowEmitterCell.alphaSpeed = -0.1 + + self.glowLayer.emitterCells = [glowEmitterCell] + self.glowLayer.emitterMode = .points + self.glowLayer.emitterSize = CGSize(width: 1.0, height: 1.0) + self.glowLayer.emitterShape = .point + + self.strokeLayer.animateAlpha(from: 0.0, to: CGFloat(self.strokeLayer.opacity), duration: 0.4) + + self.outlineLayer.animateAlpha(from: 0.0, to: CGFloat(self.outlineLayer.opacity), duration: 0.4, delay: 0.0) + self.glowLayer.animateAlpha(from: 0.0, to: CGFloat(self.glowLayer.opacity), duration: 0.4, delay: 0.0) + + let values = [1.0, 1.07, 1.0] + let keyTimes = [0.0, 0.67, 1.0] + self.imageLayer.animateKeyframes(values: values as [NSNumber], keyTimes: keyTimes as [NSNumber], duration: 0.4, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) + } + + override func layoutSubviews() { + self.strokeLayer.frame = self.bounds.offsetBy(dx: 0.0, dy: 1.0) + self.outlineLayer.frame = self.bounds + self.imageLayer.frame = self.bounds + self.glowLayer.frame = self.bounds + } +} + +private func getPathFromMaskImage(_ image: CIImage, size: CGSize, values: MediaEditorValues) -> UIBezierPath? { + let edges = image.applyingFilter("CILineOverlay", parameters: ["inputEdgeIntensity": 0.1]) + + guard let pixelBuffer = getEdgesBitmap(edges) else { + return nil + } + let minSide = min(size.width, size.height) + let scaledImageSize = image.extent.size.aspectFilled(CGSize(width: minSide, height: minSide)) + let positionOffset = CGPoint( + x: (size.width - scaledImageSize.width) / 2.0, + y: (size.height - scaledImageSize.height) / 2.0 + ) + + var contour = findContours(pixelBuffer: pixelBuffer) + contour = simplify(contour, tolerance: 1.4) + let path = UIBezierPath(points: contour, close: true) + + let firstScale = min(size.width, size.height) / 256.0 + let secondScale = size.width / 1080.0 + + var transform = CGAffineTransform.identity + let position = values.cropOffset + let rotation = values.cropRotation + let scale = values.cropScale + + transform = transform.translatedBy(x: positionOffset.x + position.x * secondScale, y: positionOffset.y + position.y * secondScale) + transform = transform.rotated(by: rotation) + transform = transform.scaledBy(x: scale * firstScale, y: scale * firstScale) + + if !path.isEmpty { + path.apply(transform) + return path + } + return nil +} + +private func findContours(pixelBuffer: CVPixelBuffer) -> [CGPoint] { + struct Point: Hashable { + let x: Int + let y: Int + + var cgPoint: CGPoint { + return CGPoint(x: x, y: y) + } + } + + var contours = [[Point]]() + + CVPixelBufferLockBaseAddress(pixelBuffer, []) + defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, []) } + + let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer) + let width = CVPixelBufferGetWidth(pixelBuffer) + let height = CVPixelBufferGetHeight(pixelBuffer) + let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer) + + var visited: [Point: Bool] = [:] + func markVisited(_ point: Point) { + visited[point] = true + } + + func getPixelIntensity(_ point: Point) -> UInt8 { + let pixelOffset = point.y * bytesPerRow + point.x + let pixelPtr = baseAddress?.advanced(by: pixelOffset) + return pixelPtr?.load(as: UInt8.self) ?? 0 + } + + func isBlackPixel(_ point: Point) -> Bool { + if point.x >= 0 && point.x < width && point.y >= 0 && point.y < height { + let value = getPixelIntensity(point) + return value < 220 + } else { + return false + } + } + + func traceContour(startPoint: Point) -> [Point] { + var contour = [startPoint] + var currentPoint = startPoint + var previousDirection = 7 + + let dx = [1, 1, 0, -1, -1, -1, 0, 1] + let dy = [0, 1, 1, 1, 0, -1, -1, -1] + + repeat { + var found = false + for i in 0 ..< 8 { + let direction = (previousDirection + i) % 8 + let newX = currentPoint.x + dx[direction] + let newY = currentPoint.y + dy[direction] + let newPoint = Point(x: newX, y: newY) + + if isBlackPixel(newPoint) && !(visited[newPoint] == true) { + contour.append(newPoint) + previousDirection = (direction + 5) % 8 + currentPoint = newPoint + found = true + markVisited(newPoint) + break + } + } + if !found { + break + } + } while currentPoint != startPoint + + return contour + } + + for y in 0 ..< height { + for x in 0 ..< width { + let point = Point(x: x, y: y) + if visited[point] == true { + continue + } + if isBlackPixel(point) { + let contour = traceContour(startPoint: point) + if contour.count > 25 { + contours.append(contour) + } + } + } + } + + return (contours.sorted(by: { lhs, rhs in lhs.count > rhs.count }).first ?? []).map { $0.cgPoint } +} + +private func getEdgesBitmap(_ ciImage: CIImage) -> CVPixelBuffer? { + let context = CIContext(options: nil) + guard let contourCgImage = context.createCGImage(ciImage, from: ciImage.extent) else { + return nil + } + let image = UIImage(cgImage: contourCgImage) + + let size = image.size.aspectFilled(CGSize(width: 256, height: 256)) + let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, + kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary + var pixelBuffer: CVPixelBuffer? + let status = CVPixelBufferCreate(kCFAllocatorDefault, + Int(size.width), + Int(size.height), + kCVPixelFormatType_OneComponent8, + attrs, + &pixelBuffer) + guard status == kCVReturnSuccess, let buffer = pixelBuffer else { + return nil + } + + CVPixelBufferLockBaseAddress(buffer, []) + defer { CVPixelBufferUnlockBaseAddress(buffer, []) } + + let pixelData = CVPixelBufferGetBaseAddress(buffer) + let rgbColorSpace = CGColorSpaceCreateDeviceGray() + guard let context = CGContext(data: pixelData, + width: Int(size.width), + height: Int(size.height), + bitsPerComponent: 8, + bytesPerRow: CVPixelBufferGetBytesPerRow(buffer), + space: rgbColorSpace, + bitmapInfo: 0) else { + return nil + } + + context.translateBy(x: 0, y: size.height) + context.scaleBy(x: 1.0, y: -1.0) + + UIGraphicsPushContext(context) + context.setFillColor(UIColor.white.cgColor) + context.fill(CGRect(origin: .zero, size: size)) + image.draw(in: CGRect(origin: .zero, size: size)) + UIGraphicsPopContext() + + return buffer +} + +private extension CGPath { + func expand(width: CGFloat) -> CGPath { + let expandedPath = self.copy(strokingWithWidth: width * 2.0, lineCap: .round, lineJoin: .round, miterLimit: 0.0) + + class UserInfo { + let outputPath = CGMutablePath() + var passedFirst = false + } + var userInfo = UserInfo() + + withUnsafeMutablePointer(to: &userInfo) { userInfoPointer in + expandedPath.apply(info: userInfoPointer) { (userInfo, nextElementPointer) in + let element = nextElementPointer.pointee + let userInfoPointer = userInfo!.assumingMemoryBound(to: UserInfo.self) + let userInfo = userInfoPointer.pointee + + if !userInfo.passedFirst { + if case .closeSubpath = element.type { + userInfo.passedFirst = true + } + } else { + switch element.type { + case .moveToPoint: + userInfo.outputPath.move(to: element.points[0]) + case .addLineToPoint: + userInfo.outputPath.addLine(to: element.points[0]) + case .addQuadCurveToPoint: + userInfo.outputPath.addQuadCurve(to: element.points[1], control: element.points[0]) + case .addCurveToPoint: + userInfo.outputPath.addCurve(to: element.points[2], control1: element.points[0], control2: element.points[1]) + case .closeSubpath: + userInfo.outputPath.closeSubpath() + @unknown default: + userInfo.outputPath.closeSubpath() + } + } + } + } + return userInfo.outputPath + } +} + +private func simplify(_ points: [CGPoint], tolerance: CGFloat?) -> [CGPoint] { + guard points.count > 1 else { + return points + } + + let sqTolerance = tolerance != nil ? (tolerance! * tolerance!) : 1.0 + var result = simplifyRadialDistance(points, tolerance: sqTolerance) + result = simplifyDouglasPeucker(result, sqTolerance: sqTolerance) + + return result +} + +private func simplifyRadialDistance(_ points: [CGPoint], tolerance: CGFloat) -> [CGPoint] { + guard points.count > 2 else { + return points + } + + var prevPoint = points.first! + var newPoints = [prevPoint] + var currentPoint: CGPoint! + + for i in 1.. tolerance { + newPoints.append(currentPoint) + prevPoint = currentPoint + } + } + + if prevPoint.equalsTo(currentPoint) == false { + newPoints.append(currentPoint) + } + + return newPoints +} + +private func simplifyDPStep(_ points: [CGPoint], first: Int, last: Int, sqTolerance: CGFloat, simplified: inout [CGPoint]) { + guard last > first else { + return + } + var maxSqDistance = sqTolerance + var index = 0 + + for currentIndex in first+1.. maxSqDistance { + maxSqDistance = sqDistance + index = currentIndex + } + } + + if maxSqDistance > sqTolerance { + if (index - first) > 1 { + simplifyDPStep(points, first: first, last: index, sqTolerance: sqTolerance, simplified: &simplified) + } + simplified.append(points[index]) + if (last - index) > 1 { + simplifyDPStep(points, first: index, last: last, sqTolerance: sqTolerance, simplified: &simplified) + } + } +} + +private func simplifyDouglasPeucker(_ points: [CGPoint], sqTolerance: CGFloat) -> [CGPoint] { + guard points.count > 1 else { + return [] + } + + let last = (points.count - 1) + var simplied = [points.first!] + simplifyDPStep(points, first: 0, last: last, sqTolerance: sqTolerance, simplified: &simplied) + simplied.append(points.last!) + + return simplied +} + +private extension CGPoint { + func equalsTo(_ compare: CGPoint) -> Bool { + return self.x == compare.self.x && self.y == compare.y + } + + func distanceFrom(_ otherPoint: CGPoint) -> CGFloat { + let dx = self.x - otherPoint.x + let dy = self.y - otherPoint.y + return (dx * dx) + (dy * dy) + } + + func distanceToSegment(_ p1: CGPoint, _ p2: CGPoint) -> CGFloat { + var x = p1.x + var y = p1.y + var dx = p2.x - x + var dy = p2.y - y + + if dx != 0 || dy != 0 { + let t = ((self.x - x) * dx + (self.y - y) * dy) / (dx * dx + dy * dy) + if t > 1 { + x = p2.x + y = p2.y + } else if t > 0 { + x += dx * t + y += dy * t + } + } + + dx = self.x - x + dy = self.y - y + + return dx * dx + dy * dy + } +} + +fileprivate extension Array { + subscript(circularIndex index: Int) -> Element { + get { + assert(self.count > 0) + let index = (index + self.count) % self.count + return self[index] + } + set { + assert(self.count > 0) + let index = (index + self.count) % self.count + return self[index] = newValue + } + } + func circularIndex(_ index: Int) -> Int { + return (index + self.count) % self.count + } +} +extension UIBezierPath { + convenience init(points: [CGPoint], close: Bool) { + self.init() + let K: CGFloat = 0.2 + var c1 = [Int: CGPoint]() + var c2 = [Int: CGPoint]() + let count = close ? points.count + 1 : points.count - 1 + for index in 1 ..< count { + let p = points[circularIndex: index] + let vP1 = points[circularIndex: index + 1] + let vP2 = points[index - 1] + let vP = CGPoint(x: vP1.x - vP2.x, y: vP1.y - vP2.y) + let v = CGPoint(x: vP.x * K, y: vP.y * K) + c2[(index + points.count - 1) % points.count] = CGPoint(x: p.x - v.x, y: p.y - v.y) //(p - v) + c1[(index + points.count) % points.count] = CGPoint(x: p.x + v.x, y: p.y + v.y) //(p + v) + } + self.move(to: points[0]) + for index in 0 ..< points.count - 1 { + let c1 = c1[index] ?? points[points.circularIndex(index)] + let c2 = c2[index] ?? points[points.circularIndex(index + 1)] + self.addCurve(to: points[circularIndex: index + 1], controlPoint1: c1, controlPoint2: c2) + } + self.close() + } +} diff --git a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift index 031539d733..b5b550cda8 100644 --- a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift +++ b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift @@ -473,6 +473,8 @@ final class PeerAllowedReactionsScreenComponent: Component { }, clearGroup: { _ in }, + editAction: { _ in + }, pushController: { c in }, presentController: { c in diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift index 72cfb99bc2..c233426027 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift @@ -663,6 +663,8 @@ public func PeerNameColorScreen( }, clearGroup: { _ in }, + editAction: { _ in + }, pushController: { c in }, presentController: { c in diff --git a/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/BUILD b/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/BUILD new file mode 100644 index 0000000000..0b594f8af3 --- /dev/null +++ b/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "StickerPackEditTitleController", + module_name = "StickerPackEditTitleController", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/UrlEscaping", + "//submodules/ActivityIndicator", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift b/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/Sources/StickerPackEditTitleController.swift similarity index 96% rename from submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift rename to submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/Sources/StickerPackEditTitleController.swift index c8f34868f9..c94161c26b 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift +++ b/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/Sources/StickerPackEditTitleController.swift @@ -275,6 +275,7 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextF self.textInputNode.returnKeyType = returnKeyType self.textInputNode.autocorrectionType = .default self.textInputNode.tintColor = theme.actionSheet.controlAccentColor + self.textInputNode.textColor = theme.actionSheet.inputTextColor self.clearButton = HighlightableButtonNode() self.clearButton.imageNode.displaysAsynchronously = false @@ -308,7 +309,9 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextF self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0) self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor - self.clearButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.actionSheet.inputClearButtonColor), for: []) + self.textInputNode.textColor = self.theme.actionSheet.inputTextColor + self.textInputNode.typingAttributes = [NSAttributedString.Key.font: Font.regular(14.0), NSAttributedString.Key.foregroundColor: self.theme.actionSheet.inputTextColor] + self.clearButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: self.theme.actionSheet.inputClearButtonColor), for: []) } func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { @@ -725,15 +728,18 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode { } } -func importStickerPackTitleController(context: AccountContext, title: String, text: String, placeholder: String, value: String?, maxLength: Int, apply: @escaping (String?) -> Void, cancel: @escaping () -> Void) -> AlertController { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } +public func stickerPackEditTitleController(context: AccountContext, forceDark: Bool = false, title: String, text: String, placeholder: String, actionTitle: String? = nil, value: String?, maxLength: Int, apply: @escaping (String?) -> Void, cancel: @escaping () -> Void) -> AlertController { + var presentationData = context.sharedContext.currentPresentationData.with { $0 } + if forceDark { + presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) + } var dismissImpl: ((Bool) -> Void)? var applyImpl: (() -> Void)? let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { dismissImpl?(true) cancel() - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Next, action: { + }), TextAlertAction(type: .defaultAction, title: actionTitle ?? presentationData.strings.Common_Next, action: { applyImpl?() })] @@ -759,6 +765,10 @@ func importStickerPackTitleController(context: AccountContext, title: String, te let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in + var presentationData = presentationData + if forceDark { + presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) + } controller?.theme = AlertControllerTheme(presentationData: presentationData) contentNode?.inputFieldNode.updateTheme(presentationData.theme) }) @@ -784,7 +794,7 @@ func importStickerPackTitleController(context: AccountContext, title: String, te } -func importStickerPackShortNameController(context: AccountContext, title: String, text: String, placeholder: String, value: String?, maxLength: Int, existingAlertController: AlertController?, apply: @escaping (String?) -> Void) -> AlertController { +public func importStickerPackShortNameController(context: AccountContext, title: String, text: String, placeholder: String, value: String?, maxLength: Int, existingAlertController: AlertController?, apply: @escaping (String?) -> Void) -> AlertController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } var dismissImpl: ((Bool) -> Void)? var applyImpl: (() -> Void)? diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 412e4c50ff..26853ef250 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -5583,6 +5583,8 @@ public final class StoryItemSetContainerComponent: Component { } })) } + default: + break } } else if updatedText != nil { let _ = (context.engine.messages.editStory(peerId: peerId, id: id, media: nil, mediaAreas: nil, text: updatedText, entities: updatedEntities, privacy: nil) diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddCircle.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddCircle.imageset/Contents.json new file mode 100644 index 0000000000..2f71e3c395 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddCircle.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "pluscircle_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddCircle.imageset/pluscircle_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddCircle.imageset/pluscircle_24.pdf new file mode 100644 index 0000000000..f91e560299 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddCircle.imageset/pluscircle_24.pdf @@ -0,0 +1,95 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.334961 3.334961 cm +0.000000 0.000000 0.000000 scn +1.330000 8.665078 m +1.330000 12.716087 4.613991 16.000078 8.665000 16.000078 c +12.716008 16.000078 16.000000 12.716087 16.000000 8.665078 c +16.000000 4.614070 12.716008 1.330078 8.665000 1.330078 c +4.613991 1.330078 1.330000 4.614070 1.330000 8.665078 c +h +8.665000 17.330078 m +3.879453 17.330078 0.000000 13.450625 0.000000 8.665078 c +0.000000 3.879531 3.879453 0.000076 8.665000 0.000076 c +13.450547 0.000076 17.330002 3.879531 17.330002 8.665078 c +17.330002 13.450625 13.450547 17.330078 8.665000 17.330078 c +h +8.664985 13.330078 m +9.032254 13.330078 9.329985 13.032348 9.329985 12.665078 c +9.329985 9.330078 l +12.664985 9.330078 l +13.032254 9.330078 13.329985 9.032348 13.329985 8.665078 c +13.329985 8.297809 13.032254 8.000078 12.664985 8.000078 c +9.329985 8.000078 l +9.329985 4.665078 l +9.329985 4.297809 9.032254 4.000078 8.664985 4.000078 c +8.297715 4.000078 7.999985 4.297809 7.999985 4.665078 c +7.999985 8.000078 l +4.664985 8.000078 l +4.297715 8.000078 3.999985 8.297809 3.999985 8.665078 c +3.999985 9.032348 4.297715 9.330078 4.664985 9.330078 c +7.999985 9.330078 l +7.999985 12.665078 l +7.999985 13.032348 8.297715 13.330078 8.664985 13.330078 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1284 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001374 00000 n +0000001397 00000 n +0000001570 00000 n +0000001644 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1703 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSticker.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSticker.imageset/Contents.json new file mode 100644 index 0000000000..29df4dfe49 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSticker.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "addsticker_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSticker.imageset/addsticker_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSticker.imageset/addsticker_24.pdf new file mode 100644 index 0000000000..4c92cf0842 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSticker.imageset/addsticker_24.pdf @@ -0,0 +1,229 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.834961 3.834961 cm +0.000000 0.000000 0.000000 scn +5.465001 16.330078 m +5.436179 16.330078 l +5.436174 16.330078 l +4.620535 16.330084 3.967873 16.330088 3.440454 16.286997 c +2.899074 16.242764 2.431364 16.149834 2.001125 15.930616 c +1.311511 15.579241 0.750837 15.018567 0.399462 14.328953 c +0.180244 13.898714 0.087314 13.431005 0.043082 12.889624 c +-0.000010 12.362206 -0.000006 11.709543 0.000000 10.893904 c +0.000000 10.893900 l +0.000000 10.865078 l +0.000000 8.165078 l +0.000000 7.797809 0.297731 7.500078 0.665000 7.500078 c +1.032270 7.500078 1.330001 7.797809 1.330001 8.165078 c +1.330001 10.865078 l +1.330001 11.716129 1.330518 12.314425 1.368665 12.781320 c +1.406177 13.240452 1.476737 13.513649 1.584500 13.725145 c +1.808365 14.164504 2.165574 14.521713 2.604933 14.745578 c +2.816429 14.853341 3.089627 14.923901 3.548759 14.961413 c +4.015654 14.999560 4.613949 15.000077 5.465001 15.000077 c +10.865000 15.000077 l +11.716052 15.000077 12.314346 14.999560 12.781242 14.961413 c +13.240374 14.923901 13.513572 14.853341 13.725068 14.745578 c +14.164426 14.521713 14.521636 14.164504 14.745501 13.725145 c +14.853263 13.513649 14.923823 13.240452 14.961336 12.781320 c +14.999483 12.314425 15.000000 11.716129 15.000000 10.865078 c +15.000001 10.165077 l +15.000000 8.165078 l +15.000000 7.949642 14.999989 7.745213 14.999686 7.550866 c +14.998948 7.380232 14.996512 7.259462 14.988585 7.162432 c +14.983507 7.100278 14.977125 7.062618 14.971725 7.039696 c +14.969157 7.028788 14.966998 7.022051 14.965687 7.018376 c +14.964426 7.014845 14.963658 7.013326 14.963488 7.012991 c +14.931371 6.949957 14.880122 6.898708 14.817087 6.866590 c +14.816753 6.866420 14.815233 6.865652 14.811703 6.864392 c +14.808028 6.863081 14.801291 6.860921 14.790383 6.858353 c +14.767460 6.852954 14.729801 6.846571 14.667646 6.841494 c +14.534256 6.830595 14.356001 6.830078 14.065001 6.830078 c +13.365002 6.830078 l +13.337530 6.830078 l +12.800831 6.830087 12.357981 6.830093 11.997252 6.800621 c +11.622624 6.770012 11.278399 6.704330 10.955116 6.539610 c +10.453665 6.284107 10.045971 5.876414 9.790468 5.374962 c +9.757796 5.310840 9.729023 5.245891 9.703645 5.180046 c +9.294682 4.955009 8.789659 4.830078 8.165001 4.830078 c +7.341558 4.830078 6.728102 5.136502 6.314001 5.447078 c +6.105952 5.603114 5.950729 5.758654 5.849528 5.872505 c +5.799157 5.929173 5.762889 5.974701 5.740838 6.003644 c +5.729833 6.018087 5.722438 6.028310 5.718665 6.033616 c +5.715932 6.037502 l +5.511385 6.340178 5.100525 6.421325 4.796125 6.218391 c +4.490538 6.014668 4.407963 5.601789 4.611687 5.296203 c +5.165000 5.665078 l +4.611687 5.296203 4.611857 5.295947 4.612031 5.295686 c +4.612401 5.295135 l +4.613211 5.293926 l +4.615123 5.291091 l +4.620114 5.283784 l +4.634774 5.262849 l +4.646626 5.246182 4.662668 5.224178 4.682913 5.197606 c +4.723361 5.144518 4.780844 5.072859 4.855473 4.988901 c +5.004272 4.821503 5.224049 4.602041 5.516000 4.383078 c +6.101899 3.943654 6.988443 3.500078 8.165001 3.500078 c +8.637335 3.500078 9.084862 3.556022 9.502834 3.672907 c +9.499994 3.465117 9.499997 3.238721 9.500001 2.992584 c +9.500001 2.992556 l +9.500001 2.965078 l +9.500001 2.265078 l +9.500001 1.974079 9.499484 1.795822 9.488585 1.662432 c +9.483507 1.600277 9.477124 1.562618 9.471726 1.539696 c +9.469157 1.528788 9.466998 1.522051 9.465687 1.518375 c +9.464426 1.514845 9.463658 1.513325 9.463488 1.512991 c +9.431371 1.449957 9.380122 1.398708 9.317088 1.366590 c +9.316753 1.366421 9.315233 1.365652 9.311703 1.364391 c +9.308028 1.363081 9.301291 1.360922 9.290383 1.358353 c +9.267460 1.352955 9.229801 1.346571 9.167646 1.341494 c +9.070562 1.333561 8.949712 1.331128 8.778924 1.330392 c +8.584664 1.330091 8.380329 1.330078 8.165000 1.330078 c +7.797731 1.330078 7.500001 1.032347 7.500001 0.665077 c +7.500001 0.297808 7.797731 0.000078 8.165000 0.000078 c +8.189899 0.000078 l +8.207291 0.000078 8.224624 0.000078 8.241899 0.000078 c +8.565001 0.000078 l +8.589002 0.000078 l +8.589076 0.000078 l +8.658995 0.000074 8.727019 0.000071 8.792893 0.000378 c +9.654441 0.001719 10.357803 0.009060 10.946046 0.049196 c +11.677188 0.099081 12.278939 0.201841 12.832902 0.431300 c +14.220985 1.006264 15.323814 2.109093 15.898778 3.497176 c +16.128237 4.051139 16.230997 4.652890 16.280882 5.384032 c +16.321011 5.972157 16.328358 6.675352 16.329700 7.536662 c +16.330009 7.602722 16.330006 7.670947 16.330002 7.741076 c +16.330002 7.765078 l +16.330000 8.140180 l +16.330000 8.165078 l +16.330002 10.165077 l +16.330002 10.165615 16.330000 10.166151 16.330000 10.166687 c +16.330000 10.865078 l +16.330000 10.893884 l +16.330000 10.893948 l +16.330006 11.709566 16.330009 12.362215 16.286919 12.889624 c +16.242687 13.431005 16.149757 13.898714 15.930539 14.328953 c +15.579163 15.018567 15.018489 15.579241 14.328876 15.930616 c +13.898637 16.149834 13.430927 16.242764 12.889546 16.286997 c +12.362127 16.330088 11.709462 16.330084 10.893822 16.330078 c +10.865000 16.330078 l +5.465001 16.330078 l +h +14.958070 5.537087 m +14.896574 5.527458 14.835731 5.520794 14.775951 5.515910 c +14.581818 5.500050 14.348466 5.500062 14.089046 5.500076 c +14.089024 5.500076 l +14.065001 5.500078 l +13.365002 5.500078 l +12.793976 5.500078 12.405701 5.499560 12.105556 5.475039 c +11.813177 5.451149 11.663464 5.407837 11.558924 5.354571 c +11.307728 5.226581 11.103498 5.022351 10.975508 4.771154 c +10.922241 4.666615 10.878929 4.516901 10.855041 4.224522 c +10.830518 3.924378 10.830001 3.536103 10.830001 2.965078 c +10.830001 2.265078 l +10.830002 2.241066 l +10.830017 1.981632 10.830030 1.748270 10.814168 1.554127 c +10.809284 1.494348 10.802620 1.433504 10.792991 1.372008 c +10.814003 1.373334 10.834842 1.374701 10.855511 1.376111 c +11.518936 1.421376 11.959040 1.508917 12.323933 1.660061 c +13.386129 2.100037 14.230042 2.943949 14.670017 4.006145 c +14.821161 4.371038 14.908702 4.811142 14.953967 5.474567 c +14.955378 5.495236 14.956744 5.516075 14.958070 5.537087 c +h +6.665000 9.915077 m +6.665000 9.224722 6.217285 8.665077 5.665000 8.665077 c +5.112716 8.665077 4.665000 9.224722 4.665000 9.915077 c +4.665000 10.605433 5.112716 11.165077 5.665000 11.165077 c +6.217285 11.165077 6.665000 10.605433 6.665000 9.915077 c +h +11.665001 9.915077 m +11.665001 9.224722 11.217285 8.665077 10.665001 8.665077 c +10.112716 8.665077 9.665001 9.224722 9.665001 9.915077 c +9.665001 10.605433 10.112716 11.165077 10.665001 11.165077 c +11.217285 11.165077 11.665001 10.605433 11.665001 9.915077 c +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.830078 0.839844 cm +0.000000 0.000000 0.000000 scn +5.330000 8.665078 m +5.330000 9.032348 5.032269 9.330078 4.665000 9.330078 c +4.297730 9.330078 4.000000 9.032348 4.000000 8.665078 c +4.000000 5.330078 l +0.665000 5.330078 l +0.297731 5.330078 0.000000 5.032348 0.000000 4.665078 c +0.000000 4.297809 0.297731 4.000078 0.665000 4.000078 c +4.000000 4.000078 l +4.000000 0.665078 l +4.000000 0.297809 4.297730 0.000078 4.665000 0.000078 c +5.032269 0.000078 5.330000 0.297809 5.330000 0.665078 c +5.330000 4.000078 l +8.665000 4.000078 l +9.032269 4.000078 9.330000 4.297809 9.330000 4.665078 c +9.330000 5.032348 9.032269 5.330078 8.665000 5.330078 c +5.330000 5.330078 l +5.330000 8.665078 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 7178 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000007268 00000 n +0000007291 00000 n +0000007464 00000 n +0000007538 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +7597 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleDot.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleDot.imageset/Contents.json new file mode 100644 index 0000000000..29572eb880 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleDot.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "dot.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleDot.imageset/dot.png b/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleDot.imageset/dot.png new file mode 100644 index 0000000000..3ac5dcbcb5 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleDot.imageset/dot.png differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleGlow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleGlow.imageset/Contents.json new file mode 100644 index 0000000000..6c8dfa9d38 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleGlow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "glow.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleGlow.imageset/glow.png b/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleGlow.imageset/glow.png new file mode 100644 index 0000000000..d3be506a28 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Media Editor/ParticleGlow.imageset/glow.png differ diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2b478c99e6..c366748421 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -14106,6 +14106,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } + func enqueueStickerFile(_ file: TelegramMediaFile) { + let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + + let replyMessageSubject = self.presentationInterfaceState.interfaceState.replyMessageSubject + self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + self.sendMessages([message].map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }) + + Queue.mainQueue().after(3.0) { + if let message = self.chatDisplayNode.historyNode.lastVisbleMesssage(), let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, file.isSticker { + self.context.engine.stickers.addRecentlyUsedSticker(file: file) + } + } + } + func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true) { if !canSendMessagesToChat(self.presentationInterfaceState) { return diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 8772e663cc..1b5ec39d4a 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -1715,69 +1715,121 @@ extension ChatControllerImpl { } func openStickerEditor() { - let mainController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { - return nil - }) -// controller.forceSourceRect = true -// controller.getSourceRect = getSourceRect - mainController.requestController = { [weak self, weak mainController] _, present in - guard let self else { - return - } - let mediaPickerController = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: nil, threadTitle: nil, chatLocation: nil, subject: .assets(nil, .createSticker)) - mediaPickerController.customSelection = { [weak self, weak mainController] controller, result in - guard let self else { + var dismissImpl: (() -> Void)? + let mainController = self.context.sharedContext.makeStickerMediaPickerScreen( + context: self.context, + getSourceRect: { return .zero }, + completion: { [weak self] result, transitionView, transitionRect, transitionImage, transitionOut, dismissed in + guard let self, let asset = result as? PHAsset else { return } - if let result = result as? PHAsset { - controller.updateHiddenMediaId(result.localIdentifier) - if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { - let editorController = MediaEditorScreen( - context: self.context, - mode: .stickerEditor, - subject: .single(.asset(result)), - transitionIn: .gallery( - MediaEditorScreen.TransitionIn.GalleryTransitionIn( - sourceView: transitionView, - sourceRect: transitionView.bounds, - sourceImage: controller.transitionImage(for: result.localIdentifier) - ) - ), - transitionOut: { finished, isNew in - if !finished { - return MediaEditorScreen.TransitionOut( - destinationView: transitionView, - destinationRect: transitionView.bounds, - destinationCornerRadius: 0.0 - ) - } - return nil - }, completion: { [weak self, weak mainController] result, commit in - mainController?.dismiss() - - Queue.mainQueue().after(0.1) { - commit({}) - if let mediaResult = result.media, case let .image(image, _) = mediaResult { - self?.enqueueStickerImage(image, isMemoji: false) - } - } - } as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void + let editorController = MediaEditorScreen( + context: self.context, + mode: .stickerEditor, + subject: .single(.asset(asset)), + transitionIn: .gallery( + MediaEditorScreen.TransitionIn.GalleryTransitionIn( + sourceView: transitionView, + sourceRect: transitionRect, + sourceImage: transitionImage ) - editorController.dismissed = { [weak controller] in - controller?.updateHiddenMediaId(nil) + ), + transitionOut: { finished, isNew in + if !finished { + return MediaEditorScreen.TransitionOut( + destinationView: transitionView, + destinationRect: transitionView.bounds, + destinationCornerRadius: 0.0 + ) } - self.push(editorController) - -// completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), transitionOut, { [weak controller] in -// controller?.updateHiddenMediaId(nil) -// }) - } - } - } - present(mediaPickerController, mediaPickerController.mediaPickerContext) + return nil + }, completion: { [weak self] result, commit in + dismissImpl?() + self?.chatDisplayNode.dismissInput() + + Queue.mainQueue().after(0.1) { + commit({}) + if case let .sticker(file) = result.media { + self?.enqueueStickerFile(file) + } + } + } as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void + ) + self.push(editorController) + + + }, + dismissed: {} + ) + dismissImpl = { [weak mainController] in + mainController?.dismiss() } mainController.navigationPresentation = .flatModal mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.push(mainController) } + +// func openStickerEditor() { +// let mainController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { +// return nil +// }) +//// controller.forceSourceRect = true +//// controller.getSourceRect = getSourceRect +// mainController.requestController = { [weak self, weak mainController] _, present in +// guard let self else { +// return +// } +// let mediaPickerController = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: nil, threadTitle: nil, chatLocation: nil, subject: .assets(nil, .createSticker)) +// mediaPickerController.customSelection = { [weak self, weak mainController] controller, result in +// guard let self else { +// return +// } +// if let result = result as? PHAsset { +// controller.updateHiddenMediaId(result.localIdentifier) +// if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { +// let editorController = MediaEditorScreen( +// context: self.context, +// mode: .stickerEditor, +// subject: .single(.asset(result)), +// transitionIn: .gallery( +// MediaEditorScreen.TransitionIn.GalleryTransitionIn( +// sourceView: transitionView, +// sourceRect: transitionView.bounds, +// sourceImage: controller.transitionImage(for: result.localIdentifier) +// ) +// ), +// transitionOut: { finished, isNew in +// if !finished { +// return MediaEditorScreen.TransitionOut( +// destinationView: transitionView, +// destinationRect: transitionView.bounds, +// destinationCornerRadius: 0.0 +// ) +// } +// return nil +// }, completion: { [weak self, weak mainController] result, commit in +// mainController?.dismiss() +// self?.chatDisplayNode.dismissInput() +// +// Queue.mainQueue().after(0.1) { +// commit({}) +// if case let .sticker(file) = result.media { +// self?.enqueueStickerFile(file) +// } +// } +// } as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void +// ) +// editorController.dismissed = { [weak controller] in +// controller?.updateHiddenMediaId(nil) +// } +// self.push(editorController) +// } +// } +// } +// present(mediaPickerController, mediaPickerController.mediaPickerContext) +// } +// mainController.navigationPresentation = .flatModal +// mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) +// self.push(mainController) +// } } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index f454f44991..b834c54245 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2293,10 +2293,14 @@ public final class SharedAccountContextImpl: SharedAccountContext { return controller } - public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController { - return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, parentNavigationController: parentNavigationController, sendSticker: sendSticker) + public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], isEditing: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController { + return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, isEditing: isEditing, parentNavigationController: parentNavigationController, sendSticker: sendSticker) } +// func makeStickerEditorScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, initialSticker: TelegramMediaFile?, targetStickerPack: StickerPackReference?) -> ViewController { +// +// } + public func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController { return mediaPickerController(context: context, hasSearch: hasSearch, completion: completion) } @@ -2304,6 +2308,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController { return storyMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed, groupsPresented: groupsPresented) } + + public func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController { + return stickerMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed) + } public func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController { return proxySettingsController(accountManager: sharedContext.accountManager, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData) diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 2c6b97fd5b..aefb623683 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -618,6 +618,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon } media = .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers) } + default: + break } } else if let existingMedia { media = .existing(media: existingMedia._asMedia())