[WIP] Stickers editor

This commit is contained in:
Ilya Laktyushin 2024-04-09 00:50:37 +04:00
parent ff5307a254
commit 70eef8ecac
16 changed files with 204 additions and 81 deletions

View File

@ -11904,3 +11904,5 @@ Sorry for the inconvenience.";
"Conversation.ViewStickers" = "VIEW STICKERS";
"Conversation.ViewEmojis" = "VIEW EMOJIS";
"MediaEditor.StickersTooMuch" = "Sorry, you've reached the maximum number of stickers in this set. Try a different one.";

View File

@ -314,7 +314,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
if stickerItem.file.isAnimatedSticker || stickerItem.file.isVideoSticker {
let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
if stickerItem.file.isVideoSticker {
self.imageNode.setSignal(chatMessageSticker(account: context.account, userLocation: .other, file: stickerItem.file, small: true))
self.imageNode.setSignal(chatMessageSticker(account: context.account, userLocation: .other, file: stickerItem.file, small: true, fetched: true))
} else {
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .other, file: stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
}

View File

@ -1250,6 +1250,7 @@ private final class StickerPackContainer: ASDisplayNode {
let presentationData = self.presentationData
let updatedPresentationData = self.controller?.updatedPresentationData
let navigationController = self.controller?.parentNavigationController as? NavigationController
let sendSticker = self.controller?.sendSticker
var dismissImpl: (() -> Void)?
let mainController = context.sharedContext.makeStickerMediaPickerScreen(
@ -1275,7 +1276,7 @@ private final class StickerPackContainer: ASDisplayNode {
|> deliverOnMainQueue).start(completed: {
commit()
let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: nil, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil)
let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: sendSticker, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil)
(navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
Queue.mainQueue().after(0.1) {
@ -1301,6 +1302,7 @@ private final class StickerPackContainer: ASDisplayNode {
let presentationData = self.presentationData
let updatedPresentationData = self.controller?.updatedPresentationData
let navigationController = self.controller?.parentNavigationController as? NavigationController
let sendSticker = self.controller?.sendSticker
let context = self.context
let controller = self.context.sharedContext.makeStickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData, completion: { file in
@ -1323,7 +1325,7 @@ private final class StickerPackContainer: ASDisplayNode {
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
let _ = (context.engine.stickers.addStickerToStickerSet(packReference: packReference, sticker: sticker)
|> deliverOnMainQueue).start(completed: {
let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: nil, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil)
let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: sendSticker, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil)
(navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
Queue.mainQueue().after(0.1) {
@ -1345,8 +1347,10 @@ private final class StickerPackContainer: ASDisplayNode {
}
let context = self.context
let presentationData = self.presentationData
let updatedPresentationData = self.controller?.updatedPresentationData
let navigationController = self.controller?.parentNavigationController as? NavigationController
let sendSticker = self.controller?.sendSticker
self.controller?.dismiss()
@ -1369,8 +1373,12 @@ private final class StickerPackContainer: ASDisplayNode {
|> deliverOnMainQueue).start(completed: {
commit()
let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: nil, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil)
let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: sendSticker, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil)
(navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
Queue.mainQueue().after(0.1) {
packController.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: "Sticker updated.", undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
}
})
}
)
@ -2645,7 +2653,7 @@ public final class StickerPackScreenImpl: ViewController, StickerPackScreen {
private let initialSelectedStickerPackIndex: Int
fileprivate weak var parentNavigationController: NavigationController?
private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
fileprivate let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
private let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?
fileprivate var controllerNode: StickerPackScreenNode {

View File

@ -162,7 +162,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
self.activateBadgeAction?()
}
public typealias AsyncLayout = (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ titleBadge: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize, _ animationCache: AnimationCache, _ animationRenderer: MultiAnimationRenderer) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)))
public typealias AsyncLayout = (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ titleBadge: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: ([Media], ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize, _ animationCache: AnimationCache, _ animationRenderer: MultiAnimationRenderer) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)))
public func makeProgress() -> Promise<Bool> {
let progress = Promise<Bool>()
@ -302,7 +302,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
var mediaAndFlags = mediaAndFlags
if let mediaAndFlagsValue = mediaAndFlags {
if mediaAndFlagsValue.0 is TelegramMediaStory || mediaAndFlagsValue.0 is WallpaperPreviewMedia {
if mediaAndFlagsValue.0.first is TelegramMediaStory || mediaAndFlagsValue.0.first is WallpaperPreviewMedia {
var flags = mediaAndFlagsValue.1
flags.remove(.preferMediaInline)
mediaAndFlags = (mediaAndFlagsValue.0, flags)
@ -315,50 +315,52 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
}
var contentMediaInline = false
if let (media, flags) = mediaAndFlags {
if let (mediaArray, flags) = mediaAndFlags {
contentMediaInline = flags.contains(.preferMediaInline)
if let file = media as? TelegramMediaFile {
if file.mimeType == "application/x-tgtheme-ios", let size = file.size, size < 16 * 1024 {
contentMediaValue = file
} else if file.isInstantVideo {
contentMediaValue = file
} else if file.isVideo {
contentMediaValue = file
} else if file.isSticker || file.isAnimatedSticker {
contentMediaValue = file
} else {
contentFileValue = file
}
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file) {
contentMediaAutomaticDownload = .full
} else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) {
contentMediaAutomaticDownload = .prefetch
}
if file.isAnimated {
contentMediaAutomaticPlayback = context.sharedContext.energyUsageSettings.autoplayGif
} else if file.isVideo && context.sharedContext.energyUsageSettings.autoplayVideo {
var willDownloadOrLocal = false
if case .full = contentMediaAutomaticDownload {
willDownloadOrLocal = true
if let media = mediaArray.first {
if let file = media as? TelegramMediaFile {
if file.mimeType == "application/x-tgtheme-ios", let size = file.size, size < 16 * 1024 {
contentMediaValue = file
} else if file.isInstantVideo {
contentMediaValue = file
} else if file.isVideo {
contentMediaValue = file
} else if file.isSticker || file.isAnimatedSticker {
contentMediaValue = file
} else {
willDownloadOrLocal = context.account.postbox.mediaBox.completedResourcePath(file.resource) != nil
contentFileValue = file
}
if willDownloadOrLocal {
contentMediaAutomaticPlayback = true
contentMediaAspectFilled = true
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file) {
contentMediaAutomaticDownload = .full
} else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) {
contentMediaAutomaticDownload = .prefetch
}
if file.isAnimated {
contentMediaAutomaticPlayback = context.sharedContext.energyUsageSettings.autoplayGif
} else if file.isVideo && context.sharedContext.energyUsageSettings.autoplayVideo {
var willDownloadOrLocal = false
if case .full = contentMediaAutomaticDownload {
willDownloadOrLocal = true
} else {
willDownloadOrLocal = context.account.postbox.mediaBox.completedResourcePath(file.resource) != nil
}
if willDownloadOrLocal {
contentMediaAutomaticPlayback = true
contentMediaAspectFilled = true
}
}
} else if let _ = media as? TelegramMediaImage {
contentMediaValue = media
} else if let _ = media as? TelegramMediaWebFile {
contentMediaValue = media
} else if let _ = media as? WallpaperPreviewMedia {
contentMediaValue = media
} else if let _ = media as? TelegramMediaStory {
contentMediaValue = media
}
} else if let _ = media as? TelegramMediaImage {
contentMediaValue = media
} else if let _ = media as? TelegramMediaWebFile {
contentMediaValue = media
} else if let _ = media as? WallpaperPreviewMedia {
contentMediaValue = media
} else if let _ = media as? TelegramMediaStory {
contentMediaValue = media
}
}
@ -939,7 +941,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
self.context = context
self.message = message
self.media = mediaAndFlags?.0
self.media = mediaAndFlags?.0.first
self.theme = presentationData.theme
self.mainColor = mainColor

View File

@ -50,7 +50,7 @@ public final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessag
} else {
text = item.message.text
}
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
let mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)? = nil
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer)

View File

@ -45,7 +45,7 @@ public final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubble
let title: String = item.message.text.contains("\n") ? item.presentationData.strings.Channel_AdminLog_MessagePreviousLinks : item.presentationData.strings.Channel_AdminLog_MessagePreviousLink
let text: String = item.message.text
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
let mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)? = nil
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer)

View File

@ -50,7 +50,7 @@ public final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBub
} else {
text = item.message.text
}
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
let mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)? = nil
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer)

View File

@ -67,16 +67,16 @@ public final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNod
var title: String?
var text: String?
var mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)?
var mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)?
if let game = game {
title = game.title
text = game.description
if let file = game.file {
mediaAndFlags = (file, [.preferMediaBeforeText])
mediaAndFlags = ([file], [.preferMediaBeforeText])
} else if let image = game.image {
mediaAndFlags = (image, [.preferMediaBeforeText])
mediaAndFlags = ([image], [.preferMediaBeforeText])
}
}

View File

@ -52,7 +52,7 @@ public final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContent
var title: String?
var subtitle: NSAttributedString? = nil
var text: String?
var mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)?
var mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)?
var automaticDownloadSettings = item.controllerInteraction.automaticMediaDownloadSettings
if let invoice = invoice {
@ -61,7 +61,7 @@ public final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContent
if let image = invoice.photo {
automaticDownloadSettings = MediaAutoDownloadSettings.defaultSettings
mediaAndFlags = (image, [.preferMediaBeforeText])
mediaAndFlags = ([image], [.preferMediaBeforeText])
} else {
let invoiceLabel = item.presentationData.strings.Message_InvoiceLabel
var invoiceText = "\(formatCurrencyAmount(invoice.totalAmount, currency: invoice.currency)) "

View File

@ -242,7 +242,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
var text: String?
var entities: [MessageTextEntity]?
var titleBadge: String?
var mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)?
var mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)?
var badge: String?
var actionIcon: ChatMessageAttachedContentActionIcon?
@ -307,9 +307,9 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
if let file = mainMedia as? TelegramMediaFile, webpage.type != "telegram_theme" {
if let embedUrl = webpage.embedUrl, !embedUrl.isEmpty {
if automaticPlayback {
mediaAndFlags = (file, [.preferMediaBeforeText])
mediaAndFlags = ([file], [.preferMediaBeforeText])
} else {
mediaAndFlags = (webpage.image ?? file, [.preferMediaBeforeText])
mediaAndFlags = ([webpage.image ?? file], [.preferMediaBeforeText])
}
} else if webpage.type == "telegram_background" {
var colors: [UInt32] = []
@ -321,12 +321,12 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
intensity = intensityValue
}
let media = WallpaperPreviewMedia(content: .file(file: file, colors: colors, rotation: rotation, intensity: intensity, false, false))
mediaAndFlags = (media, [.preferMediaAspectFilled])
mediaAndFlags = ([media], [.preferMediaAspectFilled])
if let fileSize = file.size {
badge = dataSizeString(fileSize, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData))
}
} else {
mediaAndFlags = (file, [])
mediaAndFlags = ([file], [])
}
} else if let image = mainMedia as? TelegramMediaImage {
if let type = webpage.type, ["photo", "video", "embed", "gif", "document", "telegram_album"].contains(type) {
@ -338,13 +338,13 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
} else if let embedUrl = webpage.embedUrl, !embedUrl.isEmpty {
flags.insert(.preferMediaBeforeText)
}
mediaAndFlags = (image, flags)
mediaAndFlags = ([image], flags)
} else if let _ = largestImageRepresentation(image.representations)?.dimensions {
let flags = ChatMessageAttachedContentNodeMediaFlags()
mediaAndFlags = (image, flags)
mediaAndFlags = ([image], flags)
}
} else if let story = mainMedia as? TelegramMediaStory {
mediaAndFlags = (story, [.preferMediaBeforeText, .titleBeforeMedia])
mediaAndFlags = ([story], [.preferMediaBeforeText, .titleBeforeMedia])
if let storyItem = item.message.associatedStories[story.storyId]?.get(Stories.StoredItem.self), case let .item(itemValue) = storyItem {
text = itemValue.text
entities = itemValue.entities
@ -372,7 +372,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
}
if let content = content {
let media = WallpaperPreviewMedia(content: content)
mediaAndFlags = (media, [])
mediaAndFlags = ([media], [])
}
} else if type == "telegram_theme" {
var file: TelegramMediaFile?
@ -397,10 +397,10 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
}
if let file = file {
let media = WallpaperPreviewMedia(content: .file(file: file, colors: [], rotation: nil, intensity: nil, true, isSupported))
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags())
mediaAndFlags = ([media], ChatMessageAttachedContentNodeMediaFlags())
} else if let settings = settings {
let media = WallpaperPreviewMedia(content: .themeSettings(settings))
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags())
mediaAndFlags = ([media], ChatMessageAttachedContentNodeMediaFlags())
}
}
}
@ -479,6 +479,12 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
break
}
}
for attribute in webpage.attributes {
if case let .stickerPack(stickerPack) = attribute, !stickerPack.files.isEmpty {
mediaAndFlags = (stickerPack.files, .preferMediaInline)
break
}
}
if defaultWebpageImageSizeIsSmall(webpage: webpage) {
mediaAndFlags?.1.insert(.preferMediaInline)
@ -514,7 +520,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
for media in item.message.media {
switch media {
case _ as TelegramMediaImage, _ as TelegramMediaFile, _ as TelegramMediaStory:
mediaAndFlags = (media, [.preferMediaInline])
mediaAndFlags = ([media], [.preferMediaInline])
default:
break
}

View File

@ -258,7 +258,7 @@ private final class MediaCutoutScreenComponent: Component {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if let controller = self.environment?.controller() as? MediaCutoutScreen, [.erase, .restore].contains(controller.mode), result == self.previewContainerView {
return nil//controller.previewView.superview
return nil
}
return result
}

View File

@ -2418,7 +2418,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
}
final class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate {
final class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate, UIScrollViewDelegate {
private weak var controller: MediaEditorScreen?
private let context: AccountContext
fileprivate var interaction: DrawingToolsInteraction?
@ -2438,6 +2438,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
fileprivate let toolValue: ComponentView<Empty>
fileprivate let previewContainerView: UIView
fileprivate let previewScrollView: UIScrollView
fileprivate let previewContentContainerView: PortalSourceView
private var transitionInView: UIImageView?
@ -2514,7 +2515,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.componentHost = ComponentView<ViewControllerComponentContainer.Environment>()
self.storyPreview = ComponentView<Empty>()
self.toolValue = ComponentView<Empty>()
self.previewContainerView = UIView()
self.previewContainerView.alpha = 0.0
self.previewContainerView.clipsToBounds = true
@ -2523,6 +2524,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.previewContainerView.layer.cornerCurve = .continuous
}
self.previewScrollView = UIScrollView()
self.previewScrollView.contentInsetAdjustmentBehavior = .never
self.previewScrollView.contentInset = .zero
self.previewScrollView.showsHorizontalScrollIndicator = false
self.previewScrollView.showsVerticalScrollIndicator = false
self.previewScrollView.panGestureRecognizer.minimumNumberOfTouches = 2
self.previewScrollView.isScrollEnabled = false
self.previewContentContainerView = PortalSourceView()
self.gradientView = UIImageView()
@ -2568,6 +2577,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.view.addSubview(self.backgroundDimView)
self.view.addSubview(self.containerView)
self.previewScrollView.delegate = self
self.containerView.addSubview(self.previewContainerView)
if case .stickerEditor = controller.mode {
@ -2597,7 +2609,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.previewContainerView.addSubview(self.gradientView)
}
self.previewContainerView.addSubview(self.previewContentContainerView)
self.previewContainerView.addSubview(self.previewScrollView)
self.previewScrollView.addSubview(self.previewContentContainerView)
self.previewContentContainerView.addSubview(self.previewView)
self.previewContentContainerView.addSubview(self.entitiesContainerView)
@ -3050,7 +3063,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
controller.stickerSelectedEmoji = emoji
let stickerEntity = DrawingStickerEntity(content: .file(.standalone(media: sticker), .sticker))
stickerEntity.referenceDrawingSize = storyDimensions
stickerEntity.scale = 4.0
stickerEntity.scale = 4.0 * 0.97
stickerEntity.position = CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0)
self.entitiesView.add(stickerEntity, announce: false)
}
@ -3262,6 +3275,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
}
)
Queue.mainQueue().after(0.1) {
self.previewScrollView.pinchGestureRecognizer?.isEnabled = false
}
}
@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
@ -4436,6 +4453,53 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
}
private func adjustPreviewZoom(updating: Bool = false) {
let minScale: CGFloat = 0.05
let maxScale: CGFloat = 3.0
if self.previewScrollView.minimumZoomScale != minScale {
self.previewScrollView.minimumZoomScale = minScale
}
if self.previewScrollView.maximumZoomScale != maxScale {
self.previewScrollView.maximumZoomScale = maxScale
}
let boundsSize = self.previewScrollView.frame.size
var contentFrame = self.previewContentContainerView.frame
if boundsSize.width > contentFrame.size.width {
contentFrame.origin.x = (boundsSize.width - contentFrame.size.width) / 2.0
} else {
contentFrame.origin.x = 0.0
}
if boundsSize.height > contentFrame.size.height {
contentFrame.origin.y = (boundsSize.height - contentFrame.size.height) / 2.0
} else {
contentFrame.origin.y = 0.0
}
self.previewContentContainerView.frame = contentFrame
if !updating {
self.stickerMaskDrawingView?.updateZoomScale(self.previewScrollView.zoomScale)
}
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
self.adjustPreviewZoom()
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
self.adjustPreviewZoom()
if scrollView.zoomScale < 1.0 {
scrollView.setZoomScale(1.0, animated: true)
}
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.previewContentContainerView
}
fileprivate var drawingScreen: DrawingScreen?
fileprivate var stickerScreen: StickerPickerScreen?
fileprivate weak var cutoutScreen: MediaCutoutScreen?
@ -4723,6 +4787,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
guard let mediaEditor = self.mediaEditor, let stickerMaskDrawingView = self.stickerMaskDrawingView, let stickerBackgroundView = self.stickerBackgroundView else {
return
}
if [.cutoutErase, .cutoutRestore].contains(mode) {
self.previewScrollView.isScrollEnabled = true
self.previewScrollView.pinchGestureRecognizer?.isEnabled = true
}
let cutoutController = MediaCutoutScreen(
context: self.context,
mode: cutoutMode,
@ -4746,6 +4816,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
cutoutController.dismissed = { [weak self] in
if let self {
self.previewScrollView.setZoomScale(1.0, animated: true)
self.previewScrollView.isScrollEnabled = false
self.previewScrollView.pinchGestureRecognizer?.isEnabled = false
self.animateInFromTool(inPlace: true)
}
}
@ -4911,8 +4984,18 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let previewFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - previewSize.width) / 2.0), y: topInset - bottomInputOffset + self.dismissOffset), size: previewSize)
transition.setFrame(view: self.previewContainerView, frame: previewFrame)
transition.setFrame(view: self.previewScrollView, frame: CGRect(origin: .zero, size: previewSize))
transition.setFrame(view: self.previewContentContainerView, frame: CGRect(origin: .zero, size: previewSize))
if self.previewScrollView.contentSize == .zero {
self.previewScrollView.zoomScale = 1.0
self.previewScrollView.contentSize = previewSize
}
if abs(self.previewContentContainerView.bounds.width - previewSize.width) > 1.0 {
transition.setFrame(view: self.previewContentContainerView, frame: CGRect(origin: .zero, size: previewSize))
}
self.adjustPreviewZoom(updating: true)
transition.setFrame(view: self.previewView, frame: CGRect(origin: .zero, size: previewSize))
let entitiesViewScale = previewSize.width / storyDimensions.width
@ -5125,6 +5208,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
public var completion: (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void = { _, _ in }
public var dismissed: () -> Void = { }
public var willDismiss: () -> Void = { }
public var sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
private var adminedChannels = Promise<[EnginePeer]>()
private var closeFriends = Promise<[EnginePeer]>()
@ -6367,9 +6451,25 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
contextItems.append(.custom(StickerPackListContextItem(context: self.context, packs: self.myStickerPacks, packSelected: { [weak self] pack in
guard let self else {
return
return true
}
if pack.count >= 120 {
let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.MediaEditor_StickersTooMuch, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in
if case .info = action, let self {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories, forceDark: true, dismissed: {
})
self.push(controller)
}
return false
})
self.hapticFeedback.error()
self.present(controller, in: .window(.root))
return false
} else {
self.uploadSticker(file, action: .addToStickerPack(pack: .id(id: pack.id.id, accessHash: pack.accessHash), title: pack.title))
return true
}
self.uploadSticker(file, action: .addToStickerPack(pack: .id(id: pack.id.id, accessHash: pack.accessHash), title: pack.title))
}), false))
let items = ContextController.Items(
@ -6552,7 +6652,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
self.present(controller, in: .window(.root))
}
private let stickerUploadDisposable = MetaDisposable()
private func uploadSticker(_ file: TelegramMediaFile, action: StickerAction) {
let context = self.context

View File

@ -13,9 +13,9 @@ import ContextUI
final class StickerPackListContextItem: ContextMenuCustomItem {
let context: AccountContext
let packs: [(StickerPackCollectionInfo, StickerPackItem?)]
let packSelected: (StickerPackCollectionInfo) -> Void
let packSelected: (StickerPackCollectionInfo) -> Bool
init(context: AccountContext, packs: [(StickerPackCollectionInfo, StickerPackItem?)], packSelected: @escaping (StickerPackCollectionInfo) -> Void) {
init(context: AccountContext, packs: [(StickerPackCollectionInfo, StickerPackItem?)], packSelected: @escaping (StickerPackCollectionInfo) -> Bool) {
self.context = context
self.packs = packs
self.packSelected = packSelected
@ -75,9 +75,9 @@ private final class StickerPackListContextItemNode: ASDisplayNode, ContextMenuCu
}
let action = ContextMenuActionItem(text: pack.title, textLayout: .singleLine, icon: { _ in nil }, iconSource: thumbnailIconSource, iconPosition: .left, action: { _, f in
f(.dismissWithoutContent)
item.packSelected(pack)
if item.packSelected(pack) {
f(.dismissWithoutContent)
}
})
let actionNode = ContextControllerActionsListActionItemNode(getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: action)
actionNodes.append(actionNode)

View File

@ -7642,6 +7642,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let editMessage = interfaceState.editMessage, let message = combinedInitialData.initialData?.associatedMessages[editMessage.messageId] {
let (updatedState, updatedPreviewQueryState) = updatedChatEditInterfaceMessageState(context: strongSelf.context, state: updated, message: message)
updated = updatedState
strongSelf.editingUrlPreviewQueryState?.1.dispose()
strongSelf.editingUrlPreviewQueryState = updatedPreviewQueryState
}
updated = updated.updatedSlowmodeState(slowmodeState)
@ -8979,6 +8980,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let (updatedState, updatedPreviewQueryState) = updatedChatEditInterfaceMessageState(context: strongSelf.context, state: updated, message: message)
updated = updatedState
strongSelf.editingUrlPreviewQueryState?.1.dispose()
strongSelf.editingUrlPreviewQueryState = updatedPreviewQueryState
updated = updated.updatedInputMode({ _ in

View File

@ -1772,6 +1772,9 @@ extension ChatControllerImpl {
}
} as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void
)
editorController.sendSticker = { [weak self] file, sourceView, sourceRect in
return self?.interfaceInteraction?.sendSticker(file, true, sourceView, sourceRect, nil, []) ?? false
}
self.push(editorController)
},
dismissed: {}

View File

@ -761,7 +761,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
if file.isAnimatedSticker {
thumbnailItem = .animated(EngineMediaResource(file.resource))
resourceReference = MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)
} else if let dimensions = file.dimensions, let resource = chatMessageStickerResource(file: file, small: false) as? TelegramMediaResource {
} else if let dimensions = file.dimensions, let resource = chatMessageStickerResource(file: file, small: true) as? TelegramMediaResource {
thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
resourceReference = MediaResourceReference.media(media: .standalone(media: file), resource: resource)
}