diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index b70a9509ea..bf9f321c04 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import CoreServices import Display import ComponentFlow import LegacyComponents @@ -921,7 +922,7 @@ private final class DrawingScreenComponent: CombinedComponent { } func addTextEntity() { - let textEntity = DrawingTextEntity(text: NSAttributedString(), style: .regular, font: .sanFrancisco, alignment: .center, fontSize: 1.0, color: DrawingColor(color: .white)) + let textEntity = DrawingTextEntity(text: NSAttributedString(), style: .regular, animation: .none, font: .sanFrancisco, alignment: .center, fontSize: 1.0, color: DrawingColor(color: .white)) self.insertEntity.invoke(textEntity) } @@ -934,7 +935,7 @@ private final class DrawingScreenComponent: CombinedComponent { self?.updateEntitiesPlayback.invoke(true) if let file = file { - let stickerEntity = DrawingStickerEntity(file: file) + let stickerEntity = DrawingStickerEntity(content: .file(file)) self?.insertEntity.invoke(stickerEntity) } else { self?.updateCurrentMode(.drawing) @@ -1085,6 +1086,7 @@ private final class DrawingScreenComponent: CombinedComponent { component: TextSettingsComponent( color: nil, style: DrawingTextStyle(style: textEntity.style), + animation: DrawingTextAnimation(animation: textEntity.animation), alignment: DrawingTextAlignment(alignment: textEntity.alignment), font: DrawingTextFont(font: textEntity.font), isEmojiKeyboard: false, @@ -1111,6 +1113,27 @@ private final class DrawingScreenComponent: CombinedComponent { } state?.updated(transition: .easeInOut(duration: 0.2)) }, + toggleAnimation: { [weak state, weak textEntity] in + guard let textEntity = textEntity else { + return + } + var nextAnimation: DrawingTextEntity.Animation + switch textEntity.animation { + case .none: + nextAnimation = .typing + case .typing: + nextAnimation = .wiggle + case .wiggle: + nextAnimation = .zoomIn + case .zoomIn: + nextAnimation = .none + } + textEntity.animation = nextAnimation + if let entityView = textEntity.currentEntityView { + entityView.update() + } + state?.updated(transition: .easeInOut(duration: 0.2)) + }, toggleAlignment: { [weak state, weak textEntity] in guard let textEntity = textEntity else { return @@ -1933,7 +1956,7 @@ private final class DrawingScreenComponent: CombinedComponent { } } -public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { +public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, UIDropInteractionDelegate { fileprivate final class Node: ViewControllerTracingNode { private weak var controller: DrawingScreen? private let context: AccountContext @@ -1942,7 +1965,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { private let performAction: ActionSlot private let updateToolState: ActionSlot private let updateSelectedEntity: ActionSlot - private let insertEntity: ActionSlot + fileprivate let insertEntity: ActionSlot private let deselectEntity: ActionSlot private let updateEntitiesPlayback: ActionSlot private let previewBrushSize: ActionSlot @@ -2683,6 +2706,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { TextSettingsComponent( color: textEntity.color, style: DrawingTextStyle(style: textEntity.style), + animation: DrawingTextAnimation(animation: textEntity.animation), alignment: DrawingTextAlignment(alignment: textEntity.alignment), font: DrawingTextFont(font: textEntity.font), isEmojiKeyboard: entityView.textView.inputView != nil, @@ -2731,6 +2755,29 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { strongSelf.containerLayoutUpdated(layout: layout, orientation: orientation, transition: .immediate) } }, + toggleAnimation: { [weak self] in + self?.dismissFontPicker() + guard let strongSelf = self, let entityView = strongSelf.entitiesView.selectedEntityView as? DrawingTextEntityView, let textEntity = entityView.entity as? DrawingTextEntity else { + return + } + var nextAnimation: DrawingTextEntity.Animation + switch textEntity.animation { + case .none: + nextAnimation = .typing + case .typing: + nextAnimation = .wiggle + case .wiggle: + nextAnimation = .zoomIn + case .zoomIn: + nextAnimation = .none + } + textEntity.animation = nextAnimation + entityView.update() + + if let (layout, orientation) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout: layout, orientation: orientation, transition: .immediate) + } + }, toggleAlignment: { [weak self] in self?.dismissFontPicker() guard let strongSelf = self, let entityView = strongSelf.entitiesView.selectedEntityView as? DrawingTextEntityView, let textEntity = entityView.entity as? DrawingTextEntity else { @@ -2893,6 +2940,9 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { self.displayNode = Node(controller: self, context: self.context) super.displayNodeDidLoad() + + let dropInteraction = UIDropInteraction(delegate: self) + self.view.addInteraction(dropInteraction) } public func generateResultData() -> TGPaintingData? { @@ -2944,15 +2994,17 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { var stickers: [Any] = [] for entity in self.entitiesView.entities { - if let sticker = entity as? DrawingStickerEntity { + if let sticker = entity as? DrawingStickerEntity, case let .file(file) = sticker.content { let coder = PostboxEncoder() - coder.encodeRootObject(sticker.file) + coder.encodeRootObject(file) stickers.append(coder.makeData()) } else if let text = entity as? DrawingTextEntity, let subEntities = text.renderSubEntities { for sticker in subEntities { - let coder = PostboxEncoder() - coder.encodeRootObject(sticker.file) - stickers.append(coder.makeData()) + if case let .file(file) = sticker.content { + let coder = PostboxEncoder() + coder.encodeRootObject(file) + stickers.append(coder.makeData()) + } } } } @@ -3004,4 +3056,44 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { self.orientation = orientation self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate) } + + @available(iOSApplicationExtension 11.0, iOS 11.0, *) + public func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool { + return session.hasItemsConforming(toTypeIdentifiers: [kUTTypeImage as String]) + } + + @available(iOSApplicationExtension 11.0, iOS 11.0, *) + public func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal { + //self.chatDisplayNode.updateDropInteraction(isActive: true) + + let operation: UIDropOperation + operation = .copy + return UIDropProposal(operation: operation) + } + + @available(iOSApplicationExtension 11.0, iOS 11.0, *) + public func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) { + session.loadObjects(ofClass: UIImage.self) { [weak self] imageItems in + guard let strongSelf = self else { + return + } + let images = imageItems as! [UIImage] + + //strongSelf.chatDisplayNode.updateDropInteraction(isActive: false) + if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 { + let entity = DrawingStickerEntity(content: .image(image)) + strongSelf.node.insertEntity.invoke(entity) + } + } + } + + @available(iOSApplicationExtension 11.0, iOS 11.0, *) + public func dropInteraction(_ interaction: UIDropInteraction, sessionDidExit session: UIDropSession) { + //self.chatDisplayNode.updateDropInteraction(isActive: false) + } + + @available(iOSApplicationExtension 11.0, iOS 11.0, *) + public func dropInteraction(_ interaction: UIDropInteraction, sessionDidEnd session: UIDropSession) { + //self.chatDisplayNode.updateDropInteraction(isActive: false) + } } diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift index 5f829a9e97..de673e2367 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift @@ -9,10 +9,14 @@ import StickerResources import AccountContext public final class DrawingStickerEntity: DrawingEntity, Codable { + public enum Content { + case file(TelegramMediaFile) + case image(UIImage) + } private enum CodingKeys: String, CodingKey { case uuid - case isAnimated case file + case image case referenceDrawingSize case position case scale @@ -21,8 +25,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { } public let uuid: UUID - public let isAnimated: Bool - public let file: TelegramMediaFile + public let content: Content public var referenceDrawingSize: CGSize public var position: CGPoint @@ -38,15 +41,22 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { } public var baseSize: CGSize { - let size = max(10.0, min(self.referenceDrawingSize.width, self.referenceDrawingSize.height) * 0.4) + let size = max(10.0, min(self.referenceDrawingSize.width, self.referenceDrawingSize.height) * 0.2) return CGSize(width: size, height: size) } - init(file: TelegramMediaFile) { + public var isAnimated: Bool { + switch self.content { + case let .file(file): + return file.isAnimatedSticker || file.isVideoSticker + case .image: + return false + } + } + + init(content: Content) { self.uuid = UUID() - self.isAnimated = file.isAnimatedSticker || file.isVideoSticker - - self.file = file + self.content = content self.referenceDrawingSize = .zero self.position = CGPoint() @@ -58,8 +68,13 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.uuid = try container.decode(UUID.self, forKey: .uuid) - self.isAnimated = try container.decode(Bool.self, forKey: .isAnimated) - self.file = try container.decode(TelegramMediaFile.self, forKey: .file) + if let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) { + self.content = .file(file) + } else if let imageData = try container.decodeIfPresent(Data.self, forKey: .image), let image = UIImage(data: imageData) { + self.content = .image(image) + } else { + fatalError() + } self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize) self.position = try container.decode(CGPoint.self, forKey: .position) self.scale = try container.decode(CGFloat.self, forKey: .scale) @@ -70,8 +85,12 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.uuid, forKey: .uuid) - try container.encode(self.isAnimated, forKey: .isAnimated) - try container.encode(self.file, forKey: .file) + switch self.content { + case let .file(file): + try container.encode(file, forKey: .file) + case let .image(image): + try container.encodeIfPresent(image.pngData(), forKey: .image) + } try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize) try container.encode(self.position, forKey: .position) try container.encode(self.scale, forKey: .scale) @@ -80,7 +99,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { } public func duplicate() -> DrawingEntity { - let newEntity = DrawingStickerEntity(file: self.file) + let newEntity = DrawingStickerEntity(content: self.content) newEntity.referenceDrawingSize = self.referenceDrawingSize newEntity.position = self.position newEntity.scale = self.scale @@ -108,7 +127,6 @@ final class DrawingStickerEntityView: DrawingEntityView { var started: ((Double) -> Void)? private var currentSize: CGSize? - private var dimensions: CGSize? private let imageNode: TransformImageNode private var animationNode: AnimatedStickerNode? @@ -139,46 +157,77 @@ final class DrawingStickerEntityView: DrawingEntityView { self.cachedDisposable.dispose() } - private var file: TelegramMediaFile { - return (self.entity as! DrawingStickerEntity).file + private var file: TelegramMediaFile? { + if case let .file(file) = self.stickerEntity.content { + return file + } else { + return nil + } + } + + private var image: UIImage? { + if case let .image(image) = self.stickerEntity.content { + return image + } else { + return nil + } + } + + private var dimensions: CGSize { + switch self.stickerEntity.content { + case let .file(file): + return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) + case let .image(image): + return image.size + } } private func setup() { - if let dimensions = self.file.dimensions { - if self.file.isAnimatedSticker || self.file.isVideoSticker || self.file.mimeType == "video/webm" { - if self.animationNode == nil { - let animationNode = DefaultAnimatedStickerNodeImpl() - animationNode.autoplay = false - self.animationNode = animationNode - animationNode.started = { [weak self, weak animationNode] in - self?.imageNode.isHidden = true - - if let animationNode = animationNode { - let _ = (animationNode.status - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] status in - self?.started?(status.duration) - }) + if let file = self.file { + if let dimensions = file.dimensions { + if file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm" { + if self.animationNode == nil { + let animationNode = DefaultAnimatedStickerNodeImpl() + animationNode.autoplay = false + self.animationNode = animationNode + animationNode.started = { [weak self, weak animationNode] in + self?.imageNode.isHidden = true + + if let animationNode = animationNode { + let _ = (animationNode.status + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] status in + self?.started?(status.duration) + }) + } } + self.addSubnode(animationNode) } - self.addSubnode(animationNode) + self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, userLocation: .other, file: file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0)))) + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: file.resource).start()) + } else { + if let animationNode = self.animationNode { + animationNode.visibility = false + self.animationNode = nil + animationNode.removeFromSupernode() + self.imageNode.isHidden = false + self.didSetUpAnimationNode = false + } + self.imageNode.setSignal(chatMessageSticker(account: self.context.account, userLocation: .other, file: file, small: false, synchronousLoad: false)) + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: false)).start()) } - let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512) - self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, userLocation: .other, file: self.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0)))) - self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(self.file), resource: self.file.resource).start()) - } else { - if let animationNode = self.animationNode { - animationNode.visibility = false - self.animationNode = nil - animationNode.removeFromSupernode() - self.imageNode.isHidden = false - self.didSetUpAnimationNode = false - } - self.imageNode.setSignal(chatMessageSticker(account: self.context.account, userLocation: .other, file: self.file, small: false, synchronousLoad: false)) - self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(self.file), resource: chatMessageStickerResource(file: self.file, small: false)).start()) + self.setNeedsLayout() } - - self.dimensions = dimensions.cgSize + } else if let image = self.image { + self.imageNode.setSignal(.single({ arguments -> DrawingContext? in + let context = DrawingContext(size: arguments.drawingSize, opaque: false, clear: true) + context?.withFlippedContext({ ctx in + if let cgImage = image.cgImage { + ctx.draw(cgImage, in: CGRect(origin: .zero, size: arguments.drawingSize)) + } + }) + return context + })) self.setNeedsLayout() } } @@ -215,15 +264,17 @@ final class DrawingStickerEntityView: DrawingEntityView { if self.isPlaying != isPlaying { self.isPlaying = isPlaying - if isPlaying && !self.didSetUpAnimationNode { - self.didSetUpAnimationNode = true - let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512) - let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0)) - let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.file.resource, isVideo: self.file.isVideoSticker || self.file.mimeType == "video/webm") - self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) - - self.cachedDisposable.set((source.cachedDataPath(width: 384, height: 384) - |> deliverOn(Queue.concurrentDefaultQueue())).start()) + if let file = self.file { + if isPlaying && !self.didSetUpAnimationNode { + self.didSetUpAnimationNode = true + let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) + let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0)) + let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker || file.mimeType == "video/webm") + self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) + + self.cachedDisposable.set((source.cachedDataPath(width: 384, height: 384) + |> deliverOn(Queue.concurrentDefaultQueue())).start()) + } } self.animationNode?.visibility = isPlaying } @@ -241,33 +292,29 @@ final class DrawingStickerEntityView: DrawingEntityView { let sideSize: CGFloat = size.width let boundingSize = CGSize(width: sideSize, height: sideSize) - if let dimensions = self.dimensions { - let imageSize = dimensions.aspectFitted(boundingSize) - self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() - self.imageNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) - if let animationNode = self.animationNode { - animationNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) - animationNode.updateLayout(size: imageSize) - - if !self.didApplyVisibility { - self.didApplyVisibility = true - self.applyVisibility() - } + + let imageSize = self.dimensions.aspectFitted(boundingSize) + self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() + self.imageNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) + if let animationNode = self.animationNode { + animationNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) + animationNode.updateLayout(size: imageSize) + + if !self.didApplyVisibility { + self.didApplyVisibility = true + self.applyVisibility() } - self.update(animated: false) } + self.update(animated: false) } } - + override func update(animated: Bool) { - guard let dimensions = self.stickerEntity.file.dimensions?.cgSize else { - return - } self.center = self.stickerEntity.position let size = self.stickerEntity.baseSize - self.bounds = CGRect(origin: .zero, size: dimensions.aspectFitted(size)) + self.bounds = CGRect(origin: .zero, size: self.dimensions.aspectFitted(size)) self.transform = CGAffineTransformScale(CGAffineTransformMakeRotation(self.stickerEntity.rotation), self.stickerEntity.scale, self.stickerEntity.scale) var transform = CATransform3DIdentity @@ -297,13 +344,13 @@ final class DrawingStickerEntityView: DrawingEntityView { self.pushIdentityTransformForMeasurement() selectionView.transform = .identity - let bounds = self.selectionBounds - let center = bounds.center + let maxSide = max(self.selectionBounds.width, self.selectionBounds.height) + let center = self.selectionBounds.center let scale = self.superview?.superview?.layer.value(forKeyPath: "transform.scale.x") as? CGFloat ?? 1.0 selectionView.center = self.convert(center, to: selectionView.superview) - selectionView.bounds = CGRect(origin: .zero, size: CGSize(width: (bounds.width * self.stickerEntity.scale) * scale + selectionView.selectionInset * 2.0, height: (bounds.height * self.stickerEntity.scale) * scale + selectionView.selectionInset * 2.0)) + selectionView.bounds = CGRect(origin: .zero, size: CGSize(width: (maxSide * self.stickerEntity.scale) * scale + selectionView.selectionInset * 2.0, height: (maxSide * self.stickerEntity.scale) * scale + selectionView.selectionInset * 2.0)) selectionView.transform = CGAffineTransformMakeRotation(self.stickerEntity.rotation) self.popIdentityTransformForMeasurement() diff --git a/submodules/DrawingUI/Sources/DrawingTextEntity.swift b/submodules/DrawingUI/Sources/DrawingTextEntity.swift index 21b63efde0..c14555de0d 100644 --- a/submodules/DrawingUI/Sources/DrawingTextEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingTextEntity.swift @@ -43,6 +43,7 @@ public final class DrawingTextEntity: DrawingEntity, Codable { case text case textAttributes case style + case animation case font case alignment case fontSize @@ -61,19 +62,13 @@ public final class DrawingTextEntity: DrawingEntity, Codable { case filled case semi case stroke - - init(style: DrawingTextEntity.Style) { - switch style { - case .regular: - self = .regular - case .filled: - self = .filled - case .semi: - self = .semi - case .stroke: - self = .stroke - } - } + } + + enum Animation: Codable { + case none + case typing + case wiggle + case zoomIn } enum Font: Codable { @@ -100,6 +95,9 @@ public final class DrawingTextEntity: DrawingEntity, Codable { public var uuid: UUID public var isAnimated: Bool { + if self.animation != .none { + return true + } var isAnimated = false self.text.enumerateAttributes(in: NSMakeRange(0, self.text.length), options: [], using: { attributes, range, _ in if let _ = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { @@ -111,6 +109,7 @@ public final class DrawingTextEntity: DrawingEntity, Codable { var text: NSAttributedString var style: Style + var animation: Animation var font: Font var alignment: Alignment var fontSize: CGFloat @@ -130,11 +129,12 @@ public final class DrawingTextEntity: DrawingEntity, Codable { public var renderImage: UIImage? public var renderSubEntities: [DrawingStickerEntity]? - init(text: NSAttributedString, style: Style, font: Font, alignment: Alignment, fontSize: CGFloat, color: DrawingColor) { + init(text: NSAttributedString, style: Style, animation: Animation, font: Font, alignment: Alignment, fontSize: CGFloat, color: DrawingColor) { self.uuid = UUID() self.text = text self.style = style + self.animation = animation self.font = font self.alignment = alignment self.fontSize = fontSize @@ -160,6 +160,7 @@ public final class DrawingTextEntity: DrawingEntity, Codable { self.text = attributedString self.style = try container.decode(Style.self, forKey: .style) + self.animation = try container.decode(Animation.self, forKey: .animation) self.font = try container.decode(Font.self, forKey: .font) self.alignment = try container.decode(Alignment.self, forKey: .alignment) self.fontSize = try container.decode(CGFloat.self, forKey: .fontSize) @@ -191,6 +192,7 @@ public final class DrawingTextEntity: DrawingEntity, Codable { try container.encode(textAttributes, forKey: .textAttributes) try container.encode(self.style, forKey: .style) + try container.encode(self.animation, forKey: .animation) try container.encode(self.font, forKey: .font) try container.encode(self.alignment, forKey: .alignment) try container.encode(self.fontSize, forKey: .fontSize) @@ -210,7 +212,7 @@ public final class DrawingTextEntity: DrawingEntity, Codable { } public func duplicate() -> DrawingEntity { - let newEntity = DrawingTextEntity(text: self.text, style: self.style, font: self.font, alignment: self.alignment, fontSize: self.fontSize, color: self.color) + let newEntity = DrawingTextEntity(text: self.text, style: self.style, animation: self.animation, font: self.font, alignment: self.alignment, fontSize: self.fontSize, color: self.color) newEntity.referenceDrawingSize = self.referenceDrawingSize newEntity.position = self.position newEntity.width = self.width @@ -700,7 +702,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { } let emojiTextPosition = emojiRect.center.offsetBy(dx: -textSize.width / 2.0, dy: -textSize.height / 2.0) - let entity = DrawingStickerEntity(file: file) + let entity = DrawingStickerEntity(content: .file(file)) entity.referenceDrawingSize = CGSize(width: itemSize * 2.5, height: itemSize * 2.5) entity.scale = scale entity.position = textPosition.offsetBy( diff --git a/submodules/DrawingUI/Sources/TextSettingsComponent.swift b/submodules/DrawingUI/Sources/TextSettingsComponent.swift index efebcef577..10d8c0bc62 100644 --- a/submodules/DrawingUI/Sources/TextSettingsComponent.swift +++ b/submodules/DrawingUI/Sources/TextSettingsComponent.swift @@ -27,6 +27,26 @@ enum DrawingTextStyle: Equatable { } } +enum DrawingTextAnimation: Equatable { + case none + case typing + case wiggle + case zoomIn + + init(animation: DrawingTextEntity.Animation) { + switch animation { + case .none: + self = .none + case .typing: + self = .typing + case .wiggle: + self = .wiggle + case .zoomIn: + self = .zoomIn + } + } +} + enum DrawingTextAlignment: Equatable { case left case center @@ -266,6 +286,7 @@ final class TextFontComponent: Component { final class TextSettingsComponent: CombinedComponent { let color: DrawingColor? let style: DrawingTextStyle + let animation: DrawingTextAnimation let alignment: DrawingTextAlignment let font: DrawingTextFont let isEmojiKeyboard: Bool @@ -277,6 +298,7 @@ final class TextSettingsComponent: CombinedComponent { let updateFastColorPickerPan: (CGPoint) -> Void let dismissFastColorPicker: () -> Void let toggleStyle: () -> Void + let toggleAnimation: () -> Void let toggleAlignment: () -> Void let presentFontPicker: () -> Void let toggleKeyboard: (() -> Void)? @@ -284,6 +306,7 @@ final class TextSettingsComponent: CombinedComponent { init( color: DrawingColor?, style: DrawingTextStyle, + animation: DrawingTextAnimation, alignment: DrawingTextAlignment, font: DrawingTextFont, isEmojiKeyboard: Bool, @@ -294,12 +317,14 @@ final class TextSettingsComponent: CombinedComponent { updateFastColorPickerPan: @escaping (CGPoint) -> Void = { _ in }, dismissFastColorPicker: @escaping () -> Void = {}, toggleStyle: @escaping () -> Void, + toggleAnimation: @escaping () -> Void, toggleAlignment: @escaping () -> Void, presentFontPicker: @escaping () -> Void, toggleKeyboard: (() -> Void)? ) { self.color = color self.style = style + self.animation = animation self.alignment = alignment self.font = font self.isEmojiKeyboard = isEmojiKeyboard @@ -310,6 +335,7 @@ final class TextSettingsComponent: CombinedComponent { self.updateFastColorPickerPan = updateFastColorPickerPan self.dismissFastColorPicker = dismissFastColorPicker self.toggleStyle = toggleStyle + self.toggleAnimation = toggleAnimation self.toggleAlignment = toggleAlignment self.presentFontPicker = presentFontPicker self.toggleKeyboard = toggleKeyboard @@ -322,6 +348,9 @@ final class TextSettingsComponent: CombinedComponent { if lhs.style != rhs.style { return false } + if lhs.animation != rhs.animation { + return false + } if lhs.alignment != rhs.alignment { return false } diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index 336f9326f3..f0533842ef 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -75,7 +75,7 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity { } let account: Account - let file: TelegramMediaFile + let file: TelegramMediaFile? let entity: DrawingStickerEntity let animated: Bool let durationPromise = Promise() @@ -95,47 +95,53 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity { init(account: Account, entity: DrawingStickerEntity) { self.account = account self.entity = entity - self.file = entity.file - self.animated = file.isAnimatedSticker || file.isVideoSticker - - if file.isAnimatedSticker || file.isVideoSticker { - self.source = AnimatedStickerResourceSource(account: account, resource: file.resource, isVideo: file.isVideoSticker) - if let source = self.source { - let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512) - let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384, height: 384)) - self.disposables.add((source.cachedDataPath(width: Int(fittedDimensions.width), height: Int(fittedDimensions.height)) + self.animated = entity.isAnimated + + switch entity.content { + case let .file(file): + self.file = file + if file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm" { + self.source = AnimatedStickerResourceSource(account: account, resource: file.resource, isVideo: file.isVideoSticker) + if let source = self.source { + let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) + let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384, height: 384)) + self.disposables.add((source.cachedDataPath(width: Int(fittedDimensions.width), height: Int(fittedDimensions.height)) |> deliverOn(self.queue)).start(next: { [weak self] path, complete in - if let strongSelf = self, complete { - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { - let queue = strongSelf.queue - let frameSource = AnimatedStickerCachedFrameSource(queue: queue, data: data, complete: complete, notifyUpdated: {})! - strongSelf.frameCount = frameSource.frameCount - strongSelf.frameRate = frameSource.frameRate - - let duration = Double(frameSource.frameCount) / Double(frameSource.frameRate) - strongSelf.totalDuration = duration - - strongSelf.durationPromise.set(.single(duration)) - - let frameQueue = QueueLocalObject(queue: queue, generate: { - return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource) - }) - strongSelf.frameQueue.set(.single(frameQueue)) + if let strongSelf = self, complete { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + let queue = strongSelf.queue + let frameSource = AnimatedStickerCachedFrameSource(queue: queue, data: data, complete: complete, notifyUpdated: {})! + strongSelf.frameCount = frameSource.frameCount + strongSelf.frameRate = frameSource.frameRate + + let duration = Double(frameSource.frameCount) / Double(frameSource.frameRate) + strongSelf.totalDuration = duration + + strongSelf.durationPromise.set(.single(duration)) + + let frameQueue = QueueLocalObject(queue: queue, generate: { + return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource) + }) + strongSelf.frameQueue.set(.single(frameQueue)) + } + } + })) + } + } else { + self.disposables.add((chatMessageSticker(account: self.account, userLocation: .other, file: file, small: false, fetched: true, onlyFullSize: true, thumbnail: false, synchronousLoad: false) + |> deliverOn(self.queue)).start(next: { [weak self] generator in + if let strongSelf = self { + let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: entity.baseSize, boundingSize: entity.baseSize, intrinsicInsets: UIEdgeInsets())) + let image = context?.generateImage() + if let image = image { + strongSelf.imagePromise.set(.single(image)) } } })) } - } else { - self.disposables.add((chatMessageSticker(account: self.account, userLocation: .other, file: self.file, small: false, fetched: true, onlyFullSize: true, thumbnail: false, synchronousLoad: false) - |> deliverOn(self.queue)).start(next: { [weak self] generator in - if let strongSelf = self { - let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: entity.baseSize, boundingSize: entity.baseSize, intrinsicInsets: UIEdgeInsets())) - let image = context?.generateImage() - if let image = image { - strongSelf.imagePromise.set(.single(image)) - } - } - })) + case let .image(image): + self.file = nil + self.imagePromise.set(.single(image)) } }