diff --git a/Telegram/BUILD b/Telegram/BUILD index 61efb4a8e0..26d56c83f1 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -156,6 +156,7 @@ swift_library( ":AppIntentVocabularyResources", ":InfoPlistStringResources", "//submodules/LegacyComponents:LegacyComponentsResources", + "//submodules/LegacyComponents:LegacyComponentsAssets", "//submodules/OverlayStatusController:OverlayStatusControllerResources", "//submodules/PasswordSetupUI:PasswordSetupUIResources", "//submodules/PasswordSetupUI:PasswordSetupUIAssets", diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift index f7cb2ab2b2..84445b572e 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift @@ -60,12 +60,12 @@ public enum AnimatedStickerPlaybackMode { case still(AnimatedStickerPlaybackPosition) } -private final class AnimatedStickerFrame { - let data: Data - let type: AnimationRendererFrameType - let width: Int - let height: Int - let bytesPerRow: Int +public final class AnimatedStickerFrame { + public let data: Data + public let type: AnimationRendererFrameType + public let width: Int + public let height: Int + public let bytesPerRow: Int let index: Int let isLastFrame: Bool @@ -80,7 +80,7 @@ private final class AnimatedStickerFrame { } } -private protocol AnimatedStickerFrameSource: class { +public protocol AnimatedStickerFrameSource: class { var frameRate: Int { get } var frameCount: Int { get } @@ -97,7 +97,7 @@ private final class AnimatedStickerFrameSourceWrapper { } @available(iOS 9.0, *) -private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource { +public final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource { private let queue: Queue private var data: Data private var dataComplete: Bool @@ -107,15 +107,15 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource let width: Int let bytesPerRow: Int let height: Int - let frameRate: Int - let frameCount: Int + public let frameRate: Int + public let frameCount: Int private var frameIndex: Int private let initialOffset: Int private var offset: Int var decodeBuffer: Data var frameBuffer: Data - init?(queue: Queue, data: Data, complete: Bool, notifyUpdated: @escaping () -> Void) { + public init?(queue: Queue, data: Data, complete: Bool, notifyUpdated: @escaping () -> Void) { self.queue = queue self.data = data self.dataComplete = complete @@ -179,7 +179,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource assert(self.queue.isCurrent()) } - func takeFrame() -> AnimatedStickerFrame? { + public func takeFrame() -> AnimatedStickerFrame? { var frameData: Data? var isLastFrame = false @@ -259,7 +259,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource self.dataComplete = complete } - func skipToEnd() { + public func skipToEnd() { } } @@ -310,13 +310,13 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource } } -private final class AnimatedStickerFrameQueue { +public final class AnimatedStickerFrameQueue { private let queue: Queue private let length: Int private let source: AnimatedStickerFrameSource private var frames: [AnimatedStickerFrame] = [] - init(queue: Queue, length: Int, source: AnimatedStickerFrameSource) { + public init(queue: Queue, length: Int, source: AnimatedStickerFrameSource) { self.queue = queue self.length = length self.source = source @@ -326,7 +326,7 @@ private final class AnimatedStickerFrameQueue { assert(self.queue.isCurrent()) } - func take() -> AnimatedStickerFrame? { + public func take() -> AnimatedStickerFrame? { if self.frames.isEmpty { if let frame = self.source.takeFrame() { self.frames.append(frame) @@ -340,7 +340,7 @@ private final class AnimatedStickerFrameQueue { } } - func generateFramesIfNeeded() { + public func generateFramesIfNeeded() { if self.frames.isEmpty { if let frame = self.source.takeFrame() { self.frames.append(frame) diff --git a/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift b/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift index 1c171dac83..87ef1f37aa 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift @@ -2,7 +2,7 @@ import Foundation import SwiftSignalKit import AsyncDisplayKit -enum AnimationRendererFrameType { +public enum AnimationRendererFrameType { case argb case yuva } diff --git a/submodules/LegacyComponents/BUILD b/submodules/LegacyComponents/BUILD index 074269a828..81a3745ef8 100644 --- a/submodules/LegacyComponents/BUILD +++ b/submodules/LegacyComponents/BUILD @@ -7,6 +7,13 @@ filegroup( visibility = ["//visibility:public"], ) +filegroup( + name = "LegacyComponentsAssets", + srcs = glob(["LegacyImages.xcassets/**"]), + visibility = ["//visibility:public"], +) + + objc_library( name = "LegacyComponents", enable_modules = True, diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/Contents.json new file mode 100644 index 0000000000..1c0f9b65ca --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_addsticker.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/ic_editor_addsticker.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/ic_editor_addsticker.pdf new file mode 100644 index 0000000000..4a0e3ff1bd Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/ic_editor_addsticker.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddText.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddText.imageset/Contents.json new file mode 100644 index 0000000000..94e17b900c --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddText.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_addtext.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddText.imageset/ic_editor_addtext.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddText.imageset/ic_editor_addtext.pdf new file mode 100644 index 0000000000..2c18f3d469 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddText.imageset/ic_editor_addtext.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/Contents.json new file mode 100644 index 0000000000..e5bcd2364e --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_tools.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/ic_editor_tools.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/ic_editor_tools.pdf new file mode 100644 index 0000000000..e7db29f833 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/ic_editor_tools.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AspectRatio.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AspectRatio.imageset/Contents.json new file mode 100644 index 0000000000..0d1e607d97 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AspectRatio.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_frame.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AspectRatio.imageset/ic_editor_frame.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AspectRatio.imageset/ic_editor_frame.pdf new file mode 100644 index 0000000000..a90d3a404b Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AspectRatio.imageset/ic_editor_frame.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/Contents.json new file mode 100644 index 0000000000..a6c3c0e5d8 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_blur.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/ic_editor_blur.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/ic_editor_blur.pdf new file mode 100644 index 0000000000..0dd29d3021 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/ic_editor_blur.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/Contents.json new file mode 100644 index 0000000000..2bfc036bdc --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_brushtype.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/ic_editor_brushtype.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/ic_editor_brushtype.pdf new file mode 100644 index 0000000000..d27572a0bb Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/ic_editor_brushtype.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Cancel.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Cancel.imageset/Contents.json new file mode 100644 index 0000000000..23eab49dc2 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Cancel.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_close (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Cancel.imageset/ic_editor_close (2).pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Cancel.imageset/ic_editor_close (2).pdf new file mode 100644 index 0000000000..593b189576 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Cancel.imageset/ic_editor_close (2).pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/Contents.json new file mode 100644 index 0000000000..efa7620e6b --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_check (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/ic_editor_check (2).pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/ic_editor_check (2).pdf new file mode 100644 index 0000000000..b3af47d0bd Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/ic_editor_check (2).pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Crop.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Crop.imageset/Contents.json new file mode 100644 index 0000000000..0f73679ee4 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Crop.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_crop.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Crop.imageset/ic_editor_crop.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Crop.imageset/ic_editor_crop.pdf new file mode 100644 index 0000000000..021d1e8ee8 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Crop.imageset/ic_editor_crop.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Curves.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Curves.imageset/Contents.json new file mode 100644 index 0000000000..56265f74f6 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Curves.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_curves.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Curves.imageset/ic_editor_curves.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Curves.imageset/ic_editor_curves.pdf new file mode 100644 index 0000000000..ce7be3acd2 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Curves.imageset/ic_editor_curves.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/Contents.json new file mode 100644 index 0000000000..3076fdf67b --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_brush.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/ic_editor_brush.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/ic_editor_brush.pdf new file mode 100644 index 0000000000..f79859caaa Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/ic_editor_brush.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eraser.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eraser.imageset/Contents.json new file mode 100644 index 0000000000..f0739f4ea5 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eraser.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_eracer.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eraser.imageset/ic_editor_eracer.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eraser.imageset/ic_editor_eracer.pdf new file mode 100644 index 0000000000..eb85b03cc8 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eraser.imageset/ic_editor_eracer.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/Contents.json new file mode 100644 index 0000000000..2031d3ca22 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_eyedropper.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/ic_editor_eyedropper.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/ic_editor_eyedropper.pdf new file mode 100644 index 0000000000..249385c9ad Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/ic_editor_eyedropper.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Flip.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Flip.imageset/Contents.json new file mode 100644 index 0000000000..fba3f941e3 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Flip.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_flip.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Flip.imageset/ic_editor_flip.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Flip.imageset/ic_editor_flip.pdf new file mode 100644 index 0000000000..e9279f10b9 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Flip.imageset/ic_editor_flip.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Font.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Font.imageset/Contents.json new file mode 100644 index 0000000000..88687252ce --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Font.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_font.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Font.imageset/ic_editor_font.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Font.imageset/ic_editor_font.pdf new file mode 100644 index 0000000000..6aed45f0fd Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Font.imageset/ic_editor_font.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Mute.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Mute.imageset/Contents.json new file mode 100644 index 0000000000..3b0593300b --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Mute.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_muted.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Mute.imageset/ic_editor_muted.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Mute.imageset/ic_editor_muted.pdf new file mode 100644 index 0000000000..f5f0a80c37 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Mute.imageset/ic_editor_muted.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/Contents.json new file mode 100644 index 0000000000..93ee70ef42 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_play.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/ic_editor_play.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/ic_editor_play.pdf new file mode 100644 index 0000000000..e068b7fafe Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/ic_editor_play.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/Contents.json new file mode 100644 index 0000000000..7d9bd740b5 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "send.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/send.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/send.pdf new file mode 100644 index 0000000000..246d25b72c Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/send.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Rotate.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Rotate.imageset/Contents.json new file mode 100644 index 0000000000..fc6d70a162 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Rotate.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_rotate.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Rotate.imageset/ic_editor_rotate.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Rotate.imageset/ic_editor_rotate.pdf new file mode 100644 index 0000000000..f12547069d Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Rotate.imageset/ic_editor_rotate.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Tint.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Tint.imageset/Contents.json new file mode 100644 index 0000000000..7a5487de72 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Tint.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_tint.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Tint.imageset/ic_editor_tint.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Tint.imageset/ic_editor_tint.pdf new file mode 100644 index 0000000000..1da45fcd21 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Tint.imageset/ic_editor_tint.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Undo.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Undo.imageset/Contents.json new file mode 100644 index 0000000000..b73e4b1233 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Undo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_undo.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Undo.imageset/ic_editor_undo.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Undo.imageset/ic_editor_undo.pdf new file mode 100644 index 0000000000..f87a630323 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Undo.imageset/ic_editor_undo.pdf differ diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/Contents.json new file mode 100644 index 0000000000..e81de9ed61 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_unmuted.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/ic_editor_unmuted.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/ic_editor_unmuted.pdf new file mode 100644 index 0000000000..c78e550ee8 Binary files /dev/null and b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/ic_editor_unmuted.pdf differ diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h index d584d8d03e..54dac0dba1 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h @@ -264,6 +264,8 @@ #import #import #import +#import +#import #import #import #import diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h index 54638ed711..0436432211 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h @@ -10,6 +10,7 @@ @class TGViewController; @class TGAttachmentCameraView; @protocol TGModernGalleryTransitionHostScrollView; +@protocol TGPhotoPaintStickersContext; @interface TGAttachmentCarouselCollectionView : UICollectionView @@ -22,6 +23,7 @@ @property (nonatomic, readonly) TGMediaSelectionContext *selectionContext; @property (nonatomic, readonly) TGMediaEditingContext *editingContext; @property (nonatomic, strong) TGSuggestionContext *suggestionContext; +@property (nonatomic, strong) id stickersContext; @property (nonatomic) bool allowCaptions; @property (nonatomic) bool allowCaptionEntities; @property (nonatomic) bool inhibitDocumentCaptions; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h index 348bab118a..a70d99bb97 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h @@ -9,6 +9,8 @@ @class TGMediaAssetsPickerController; @class TGViewController; +@protocol TGPhotoPaintStickersContext; + typedef enum { TGMediaAssetsControllerSendMediaIntent, @@ -49,6 +51,7 @@ typedef enum @property (nonatomic, readonly) TGMediaEditingContext *editingContext; @property (nonatomic, readonly) TGMediaSelectionContext *selectionContext; @property (nonatomic, strong) TGSuggestionContext *suggestionContext; +@property (nonatomic, strong) id stickersContext; @property (nonatomic, assign) bool localMediaCacheEnabled; @property (nonatomic, assign) bool captionsEnabled; @property (nonatomic, assign) bool allowCaptionEntities; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h index f3b89a11fb..ebecb48fae 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h @@ -81,7 +81,8 @@ - (SSignal *)timersUpdatedSignal; - (UIImage *)paintingImageForItem:(NSObject *)item; -- (bool)setPaintingData:(NSData *)data image:(UIImage *)image forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video; +- (UIImage *)stillPaintingImageForItem:(NSObject *)item; +- (bool)setPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)image forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video; - (void)clearPaintingData; - (SSignal *)facesForItem:(NSObject *)item; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h index 8cd104554b..560181ff6f 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h @@ -8,6 +8,8 @@ @class TGMediaPickerSelectionGestureRecognizer; @class TGMediaAssetsPallete; +@protocol TGPhotoPaintStickersContext; + @interface TGMediaPickerController : TGViewController { TGMediaPickerLayoutMetrics *_layoutMetrics; @@ -18,6 +20,7 @@ } @property (nonatomic, strong) TGSuggestionContext *suggestionContext; +@property (nonatomic, strong) id stickersContext; @property (nonatomic, assign) bool localMediaCacheEnabled; @property (nonatomic, assign) bool captionsEnabled; @property (nonatomic, assign) bool allowCaptionEntities; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h index 40d8da8921..b65cc68ae5 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h @@ -15,6 +15,8 @@ @class TGMediaSelectionContext; @protocol TGMediaSelectableItem; +@protocol TGPhotoPaintStickersContext; + @class TGSuggestionContext; @interface TGMediaPickerGalleryModel : TGModernGalleryModel @@ -44,6 +46,7 @@ @property (nonatomic, readonly) TGMediaSelectionContext *selectionContext; @property (nonatomic, strong) TGSuggestionContext *suggestionContext; +@property (nonatomic, strong) id stickersContext; - (instancetype)initWithContext:(id)context items:(NSArray *)items focusItem:(id)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions hasSelectionPanel:(bool)hasSelectionPanel hasCamera:(bool)hasCamera recipientName:(NSString *)recipientName; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h index f35fd05706..5868cb7e5a 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h @@ -11,6 +11,8 @@ @class TGMediaAssetFetchResult; @class TGMediaAssetMomentList; +@protocol TGPhotoPaintStickersContext; + @interface TGMediaPickerModernGalleryMixin : NSObject @property (nonatomic, weak, readonly) TGMediaPickerGalleryModel *galleryModel; @@ -29,9 +31,9 @@ @property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t)); -- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder; +- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder stickersContext:(id)stickersContext; -- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder; +- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder stickersContext:(id)stickersContext; - (void)present; - (void)updateWithFetchResult:(TGMediaAssetFetchResult *)fetchResult; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h index aa5023b0fe..6f9446c8c7 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h @@ -12,11 +12,12 @@ @end +@protocol TGPhotoPaintEntityRenderer; @interface TGMediaVideoConverter : NSObject -+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher; -+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio; ++ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id)entityRenderer; ++ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio entityRenderer:(id)entityRenderer; + (SSignal *)hashForAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments; + (NSUInteger)estimatedSizeForPreset:(TGMediaVideoConversionPreset)preset duration:(NSTimeInterval)duration hasAudio:(bool)hasAudio; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryZoomableScrollView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryZoomableScrollView.h index d3df62565a..490bff8166 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryZoomableScrollView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryZoomableScrollView.h @@ -7,4 +7,6 @@ @property (nonatomic, copy) void (^singleTapped)(); @property (nonatomic, copy) void (^doubleTapped)(CGPoint point); +- (instancetype)initWithFrame:(CGRect)frame hasDoubleTap:(bool)hasDoubleTap; + @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h index 5c6b95d5da..99c180874b 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h @@ -16,7 +16,11 @@ @property (nonatomic, readonly) NSData *data; @property (nonatomic, readonly) UIImage *image; -+ (instancetype)dataWithPaintingData:(NSData *)data image:(UIImage *)image entities:(NSArray *)entities undoManager:(TGPaintUndoManager *)undoManager; +@property (nonatomic, readonly) UIImage *stillImage; + ++ (instancetype)dataWithPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)stillImage entities:(NSArray *)entities undoManager:(TGPaintUndoManager *)undoManager; + ++ (instancetype)dataWithPaintingImagePath:(NSString *)imagePath entities:(NSArray *)entities; + (instancetype)dataWithPaintingImagePath:(NSString *)imagePath; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h index ae25ee29ad..f3fadff59c 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h @@ -11,6 +11,8 @@ @class TGSuggestionContext; @class TGPhotoEditorController; +@protocol TGPhotoPaintStickersContext; + typedef enum { TGPhotoEditorControllerGenericIntent = 0, TGPhotoEditorControllerAvatarIntent = (1 << 0), @@ -24,6 +26,7 @@ typedef enum { @property (nonatomic, strong) TGSuggestionContext *suggestionContext; @property (nonatomic, strong) TGMediaEditingContext *editingContext; +@property (nonatomic, strong) id stickersContext; @property (nonatomic, copy) UIView *(^beginTransitionIn)(CGRect *referenceFrame, UIView **parentView); @property (nonatomic, copy) void (^finishedTransitionIn)(void); diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorInterfaceAssets.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorInterfaceAssets.h index a5c2d3e8ec..7708afa843 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorInterfaceAssets.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorInterfaceAssets.h @@ -18,7 +18,6 @@ + (UIColor *)editorButtonSelectionBackgroundColor; -+ (UIImage *)captionIcon; + (UIImage *)cropIcon; + (UIImage *)toolsIcon; + (UIImage *)rotateIcon; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintEntity.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintEntity.h index fa0c9294de..02017daf79 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintEntity.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintEntity.h @@ -7,6 +7,7 @@ } @property (nonatomic, assign) NSInteger uuid; +@property (nonatomic, readonly) bool animated; @property (nonatomic, assign) CGPoint position; @property (nonatomic, assign) CGFloat angle; @property (nonatomic, assign) CGFloat scale; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickerEntity.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickerEntity.h index b3ae077796..2971ed54ed 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickerEntity.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickerEntity.h @@ -4,11 +4,11 @@ @interface TGPhotoPaintStickerEntity : TGPhotoPaintEntity -@property (nonatomic, readonly) TGDocumentMediaAttachment *document; +@property (nonatomic, readonly) NSData *document; @property (nonatomic, readonly) NSString *emoji; @property (nonatomic, readonly) CGSize baseSize; -- (instancetype)initWithDocument:(TGDocumentMediaAttachment *)document baseSize:(CGSize)baseSize; +- (instancetype)initWithDocument:(id)document baseSize:(CGSize)baseSize animated:(bool)animated; - (instancetype)initWithEmoji:(NSString *)emoji; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h new file mode 100644 index 0000000000..0fabec411e --- /dev/null +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h @@ -0,0 +1,25 @@ +#import +#import +#import + +@class TGPaintingData; + +@protocol TGPhotoPaintEntityRenderer + +- (void)entitiesForTime:(CMTime)time size:(CGSize)size completion:(void(^)(NSArray *))completion; + +@end + +@protocol TGPhotoPaintStickerRenderView + +- (UIImage *)image; + +@end + +@protocol TGPhotoPaintStickersContext + +- (UIView *)stickerViewForDocument:(id)document; + +@property (nonatomic, copy) void(^presentStickersController)(void(^)(id, bool, UIView *, CGRect)); + +@end diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintTextEntity.h similarity index 85% rename from submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.h rename to submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintTextEntity.h index 4eb3ceffca..0874828b5c 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintTextEntity.h @@ -1,6 +1,7 @@ #import -#import "TGPaintSwatch.h" -#import "TGPhotoPaintFont.h" + +@class TGPaintSwatch; +@class TGPhotoPaintFont; @interface TGPhotoPaintTextEntity : TGPhotoPaintEntity @@ -11,6 +12,8 @@ @property (nonatomic, assign) CGFloat maxWidth; @property (nonatomic, assign) bool stroke; +@property (nonatomic, strong) UIImage *renderImage; + - (instancetype)initWithText:(NSString *)text font:(TGPhotoPaintFont *)font swatch:(TGPaintSwatch *)swatch baseFontSize:(CGFloat)baseFontSize maxWidth:(CGFloat)maxWidth stroke:(bool)stroke; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h index 87b404a2d5..015071de7d 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h @@ -3,9 +3,9 @@ typedef NS_OPTIONS(NSUInteger, TGPhotoEditorTab) { TGPhotoEditorNoneTab = 0, TGPhotoEditorCropTab = 1 << 0, - TGPhotoEditorStickerTab = 1 << 1, - TGPhotoEditorPaintTab = 1 << 2, - TGPhotoEditorEraserTab = 1 << 3, + TGPhotoEditorPaintTab = 1 << 1, + TGPhotoEditorEraserTab = 1 << 2, + TGPhotoEditorStickerTab = 1 << 3, TGPhotoEditorTextTab = 1 << 4, TGPhotoEditorToolsTab = 1 << 5, TGPhotoEditorRotateTab = 1 << 6, diff --git a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h index 87ffa71589..1ee363c2f6 100755 --- a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h +++ b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h @@ -57,4 +57,6 @@ typedef struct GPUTextureOptions { - (NSUInteger)bytesPerRow; - (GLubyte *)byteBuffer; ++ (void)setMark:(BOOL)mark; + @end diff --git a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m index 448ebf8df8..cf00a03f29 100755 --- a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m +++ b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m @@ -33,13 +33,18 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); #pragma mark - #pragma mark Initialization and teardown +static BOOL mark = false; ++ (void)setMark:(BOOL)mark_ { + mark = mark_; +} + - (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture { if (!(self = [super init])) { return nil; } - + _mark = mark; _textureOptions = fboTextureOptions; _size = framebufferSize; framebufferReferenceCount = 0; @@ -67,7 +72,7 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); { return nil; } - + _mark = mark; GPUTextureOptions defaultTextureOptions; defaultTextureOptions.minFilter = GL_LINEAR; defaultTextureOptions.magFilter = GL_LINEAR; @@ -89,6 +94,7 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); - (id)initWithSize:(CGSize)framebufferSize { + _mark = mark; GPUTextureOptions defaultTextureOptions; defaultTextureOptions.minFilter = GL_LINEAR; defaultTextureOptions.magFilter = GL_LINEAR; @@ -136,10 +142,8 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - // By default, all framebuffers on iOS 5.0+ devices are backed by texture caches, using one shared cache if ([GPUImageContext supportsFastTextureUpload]) { -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE CVOpenGLESTextureCacheRef coreVideoTextureCache = [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache]; // Code originally sourced from http://allmybrain.com/2011/12/08/rendering-to-a-texture-with-ios-5-texture-cache-api/ @@ -180,7 +184,6 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _textureOptions.wrapT); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(renderTexture), 0); -#endif } else { @@ -275,8 +278,10 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); if (framebufferReferenceCount < 1) { [[GPUImageContext sharedFramebufferCache] returnFramebufferToCache:self]; - } else if (framebufferReferenceCount == 1) { - fixer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(fixTick) interval:0.3 repeat:false]; + [fixer invalidate]; + fixer = nil; + } else if (framebufferReferenceCount == 1 && self.mark) { + fixer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(fixTick) interval:0.35 repeat:false]; } } @@ -336,7 +341,6 @@ void dataProviderUnlockCallback (void *info, __unused const void *data, __unused CGDataProviderRef dataProvider = NULL; if ([GPUImageContext supportsFastTextureUpload]) { -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE NSUInteger paddedWidthOfImage = (NSUInteger)(CVPixelBufferGetBytesPerRow(renderTarget) / 4.0); NSUInteger paddedBytesForImage = paddedWidthOfImage * (int)_size.height * 4; @@ -346,8 +350,6 @@ void dataProviderUnlockCallback (void *info, __unused const void *data, __unused rawImagePixels = (GLubyte *)CVPixelBufferGetBaseAddress(renderTarget); dataProvider = CGDataProviderCreateWithData((__bridge_retained void*)self, rawImagePixels, paddedBytesForImage, dataProviderUnlockCallback); [[GPUImageContext sharedFramebufferCache] addFramebufferToActiveImageCaptureList:self]; // In case the framebuffer is swapped out on the filter, need to have a strong reference to it somewhere for it to hang on while the image is in existence -#else -#endif } else { @@ -362,10 +364,7 @@ void dataProviderUnlockCallback (void *info, __unused const void *data, __unused if ([GPUImageContext supportsFastTextureUpload]) { -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, CVPixelBufferGetBytesPerRow(renderTarget), defaultRGBColorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, dataProvider, NULL, NO, kCGRenderingIntentDefault); -#else -#endif } else { diff --git a/submodules/LegacyComponents/Sources/GPUImageFramebufferCache.m b/submodules/LegacyComponents/Sources/GPUImageFramebufferCache.m index 422af4473a..627331b2cb 100755 --- a/submodules/LegacyComponents/Sources/GPUImageFramebufferCache.m +++ b/submodules/LegacyComponents/Sources/GPUImageFramebufferCache.m @@ -94,7 +94,6 @@ { // Nothing in the cache, create a new framebuffer to use framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture]; - framebufferFromCache.mark = mark; } else { @@ -120,7 +119,6 @@ if (framebufferFromCache == nil) { framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture]; - framebufferFromCache.mark = mark; } } }); diff --git a/submodules/LegacyComponents/Sources/GPUImageOutput.h b/submodules/LegacyComponents/Sources/GPUImageOutput.h index c8f3178004..2f26d33a50 100755 --- a/submodules/LegacyComponents/Sources/GPUImageOutput.h +++ b/submodules/LegacyComponents/Sources/GPUImageOutput.h @@ -1,21 +1,7 @@ #import "GPUImageContext.h" #import "GPUImageFramebuffer.h" -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE #import -#else -// For now, just redefine this on the Mac -typedef NS_ENUM(NSInteger, UIImageOrientation) { - UIImageOrientationUp, // default orientation - UIImageOrientationDown, // 180 deg rotation - UIImageOrientationLeft, // 90 deg CCW - UIImageOrientationRight, // 90 deg CW - UIImageOrientationUpMirrored, // as above but image mirrored along other axis. horizontal flip - UIImageOrientationDownMirrored, // horizontal flip - UIImageOrientationLeftMirrored, // vertical flip - UIImageOrientationRightMirrored, // vertical flip -}; -#endif void runOnMainQueueWithoutDeadlocking(void (^block)(void)); void runSynchronouslyOnVideoProcessingQueue(void (^block)(void)); @@ -24,17 +10,6 @@ void runSynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void void runAsynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void)); void reportAvailableMemoryForGPUImage(NSString *tag); -/** GPUImage's base source object - - Images or frames of video are uploaded from source objects, which are subclasses of GPUImageOutput. These include: - - - GPUImageVideoCamera (for live video from an iOS camera) - - GPUImageStillCamera (for taking photos with the camera) - - GPUImagePicture (for still images) - - GPUImageMovie (for movies) - - Source objects upload still image frames to OpenGL ES as textures, then hand those textures off to the next objects in the processing chain. - */ @interface GPUImageOutput : NSObject { GPUImageFramebuffer *outputFramebuffer; @@ -56,7 +31,6 @@ void reportAvailableMemoryForGPUImage(NSString *tag); @property(nonatomic) BOOL enabled; @property(readwrite, nonatomic) GPUTextureOptions outputTextureOptions; -/// @name Managing targets - (void)setInputFramebufferForTarget:(id)target atIndex:(NSInteger)inputTextureIndex; - (GPUImageFramebuffer *)framebufferForOutput; - (void)removeOutputFramebuffer; @@ -106,15 +80,8 @@ void reportAvailableMemoryForGPUImage(NSString *tag); - (void)useNextFrameForImageCapture; - (CGImageRef)newCGImageFromCurrentlyProcessedOutput; -// Platform-specific image output methods -// If you're trying to use these methods, remember that you need to set -useNextFrameForImageCapture before running -processImage or running video and calling any of these methods, or you will get a nil image -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE - (UIImage *)imageFromCurrentFramebuffer; - (UIImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation; -#else -- (NSImage *)imageFromCurrentFramebuffer; -- (NSImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation; -#endif - (BOOL)providesMonochromeOutput; diff --git a/submodules/LegacyComponents/Sources/GPUImageTextureInput.h b/submodules/LegacyComponents/Sources/GPUImageTextureInput.h new file mode 100755 index 0000000000..bd987f5e0c --- /dev/null +++ b/submodules/LegacyComponents/Sources/GPUImageTextureInput.h @@ -0,0 +1,16 @@ +#import "GPUImageOutput.h" +#import + +@interface GPUImageTextureInput : GPUImageOutput +{ + CGSize textureSize; +} + +- (instancetype)initWithTexture:(GLuint)newInputTexture size:(CGSize)newTextureSize; +- (instancetype)initWithCIImage:(CIImage *)ciImage; + +- (void)processTextureWithFrameTime:(CMTime)frameTime synchronous:(bool)synchronous; + +- (CGSize)textureSize; + +@end diff --git a/submodules/LegacyComponents/Sources/GPUImageTextureInput.m b/submodules/LegacyComponents/Sources/GPUImageTextureInput.m new file mode 100755 index 0000000000..12cec3e377 --- /dev/null +++ b/submodules/LegacyComponents/Sources/GPUImageTextureInput.m @@ -0,0 +1,94 @@ +#import "GPUImageTextureInput.h" + +@implementation GPUImageTextureInput + +#pragma mark - +#pragma mark Initialization and teardown + +- (instancetype)initWithTexture:(GLuint)newInputTexture size:(CGSize)newTextureSize +{ + if (!(self = [super init])) + { + return nil; + } + + runSynchronouslyOnVideoProcessingQueue(^{ + [GPUImageContext useImageProcessingContext]; + }); + + textureSize = newTextureSize; + + runSynchronouslyOnVideoProcessingQueue(^{ + outputFramebuffer = [[GPUImageFramebuffer alloc] initWithSize:newTextureSize overriddenTexture:newInputTexture]; + }); + + return self; +} + +- (instancetype)initWithCIImage:(CIImage *)ciImage +{ + EAGLContext *context = [[GPUImageContext sharedImageProcessingContext] context]; + [EAGLContext setCurrentContext:[[GPUImageContext sharedImageProcessingContext] context]]; + + GLsizei backingWidth = ciImage.extent.size.width; + GLsizei backingHeight = ciImage.extent.size.height; + GLuint outputTexture, defaultFramebuffer; + + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, &outputTexture); + glBindTexture(GL_TEXTURE_2D, outputTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + + glActiveTexture(GL_TEXTURE1); + glGenFramebuffers(1, &defaultFramebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); + + glBindTexture(GL_TEXTURE_2D, outputTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, backingWidth, backingHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, outputTexture, 0); + + NSAssert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, @"Incomplete filter FBO: %d", glCheckFramebufferStatus(GL_FRAMEBUFFER)); + + glBindTexture(GL_TEXTURE_2D, 0); + + ciImage = [ciImage imageByApplyingTransform:CGAffineTransformConcat(CGAffineTransformMakeScale(1.0f, -1.0f), CGAffineTransformMakeTranslation(0.0f, ciImage.extent.size.height))]; + + CIContext *ciContext = [CIContext contextWithEAGLContext:context options:@{kCIContextWorkingColorSpace: [NSNull null]}]; + [ciContext drawImage:ciImage inRect:ciImage.extent fromRect:ciImage.extent]; + + if (self = [self initWithTexture:outputTexture size:ciImage.extent.size]) { + textureSize = ciImage.extent.size; + } + return self; +} + +- (void)processTextureWithFrameTime:(CMTime)frameTime synchronous:(bool)synchronous +{ + void (^block)(void) = ^ + { + for (id currentTarget in targets) + { + NSInteger indexOfObject = [targets indexOfObject:currentTarget]; + NSInteger targetTextureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue]; + + [currentTarget setInputSize:textureSize atIndex:targetTextureIndex]; + [currentTarget setInputFramebuffer:outputFramebuffer atIndex:targetTextureIndex]; + [currentTarget newFrameReadyAtTime:frameTime atIndex:targetTextureIndex]; + } + }; + + if (synchronous) + runSynchronouslyOnVideoProcessingQueue(block); + else + runAsynchronouslyOnVideoProcessingQueue(block); +} + +- (CGSize)textureSize { + return textureSize; +} + +@end diff --git a/submodules/LegacyComponents/Sources/GPUImageTextureOutput.h b/submodules/LegacyComponents/Sources/GPUImageTextureOutput.h new file mode 100755 index 0000000000..51bcd814f4 --- /dev/null +++ b/submodules/LegacyComponents/Sources/GPUImageTextureOutput.h @@ -0,0 +1,24 @@ +#import +#import +#import "GPUImageContext.h" + +@protocol GPUImageTextureOutputDelegate; + +@interface GPUImageTextureOutput : NSObject +{ + GPUImageFramebuffer *firstInputFramebuffer; +} + +@property(readwrite, unsafe_unretained, nonatomic) id delegate; +@property(readonly) GLuint texture; +@property(nonatomic) BOOL enabled; + +- (CIImage *)CIImageWithSize:(CGSize)size; + +- (void)doneWithTexture; + +@end + +@protocol GPUImageTextureOutputDelegate +- (void)newFrameReadyFromTextureOutput:(GPUImageTextureOutput *)callbackTextureOutput; +@end diff --git a/submodules/LegacyComponents/Sources/GPUImageTextureOutput.m b/submodules/LegacyComponents/Sources/GPUImageTextureOutput.m new file mode 100755 index 0000000000..c99012be38 --- /dev/null +++ b/submodules/LegacyComponents/Sources/GPUImageTextureOutput.m @@ -0,0 +1,91 @@ +#import "GPUImageTextureOutput.h" + +@implementation GPUImageTextureOutput + +@synthesize delegate = _delegate; +@synthesize texture = _texture; +@synthesize enabled; + +#pragma mark - +#pragma mark Initialization and teardown + +- (id)init; +{ + if (!(self = [super init])) + { + return nil; + } + + self.enabled = YES; + + return self; +} + +- (void)doneWithTexture; +{ + [firstInputFramebuffer unlock]; +} + +#pragma mark - +#pragma mark GPUImageInput protocol + +- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex; +{ + [_delegate newFrameReadyFromTextureOutput:self]; +} + +- (NSInteger)nextAvailableTextureIndex; +{ + return 0; +} + +- (CIImage *)CIImageWithSize:(CGSize)size +{ + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CIImage *image = [[CIImage alloc] initWithTexture:self.texture size:size flipped:true colorSpace:colorSpace]; + CGColorSpaceRelease(colorSpace); + return image; +} + +// TODO: Deal with the fact that the texture changes regularly as a result of the caching +- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex; +{ + firstInputFramebuffer = newInputFramebuffer; + [firstInputFramebuffer lock]; + + _texture = [firstInputFramebuffer texture]; +} + +- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex; +{ +} + +- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex; +{ +} + +- (CGSize)maximumOutputSize; +{ + return CGSizeZero; +} + +- (void)endProcessing +{ +} + +- (BOOL)shouldIgnoreUpdatesToThisTarget; +{ + return NO; +} + +- (BOOL)wantsMonochromeInput; +{ + return NO; +} + +- (void)setCurrentlyReceivingMonochromeInput:(BOOL)newValue; +{ + +} + +@end diff --git a/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m b/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m index cbed53166d..494622075b 100644 --- a/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m +++ b/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m @@ -152,5 +152,9 @@ UIImage *TGComponentsImageNamed(NSString *name) { } NSString *TGComponentsPathForResource(NSString *name, NSString *type) { - return [resourcesBundle() pathForResource:name ofType:type]; + NSBundle *bundle = resourcesBundle(); + if (bundle == nil) { + bundle = getAppBundle(); + } + return [bundle pathForResource:name ofType:type]; } diff --git a/submodules/LegacyComponents/Sources/PGPhotoEditor.h b/submodules/LegacyComponents/Sources/PGPhotoEditor.h index a0205ea305..d0a5f31af3 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoEditor.h +++ b/submodules/LegacyComponents/Sources/PGPhotoEditor.h @@ -37,11 +37,13 @@ - (void)setImage:(UIImage *)image forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored fullSize:(bool)fullSize; - (void)setVideoAsset:(AVAsset *)asset; +- (void)setCIImage:(CIImage *)ciImage; - (void)processAnimated:(bool)animated completion:(void (^)(void))completion; - (void)createResultImageWithCompletion:(void (^)(UIImage *image))completion; - (UIImage *)currentResultImage; +- (CIImage *)currentResultCIImage; - (bool)hasDefaultCropping; diff --git a/submodules/LegacyComponents/Sources/PGPhotoEditor.m b/submodules/LegacyComponents/Sources/PGPhotoEditor.m index 3bad0f1aa4..cb7eebfc56 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoEditor.m +++ b/submodules/LegacyComponents/Sources/PGPhotoEditor.m @@ -10,6 +10,9 @@ #import "PGPhotoEditorView.h" #import "PGPhotoEditorPicture.h" +#import "GPUImageTextureInput.h" +#import "GPUImageTextureOutput.h" + #import #import #import @@ -44,6 +47,8 @@ NSArray *_currentProcessChain; GPUImageOutput *_finalFilter; + GPUImageTextureOutput *_textureOutput; + PGPhotoHistogram *_currentHistogram; PGPhotoHistogramGenerator *_histogramGenerator; @@ -167,6 +172,21 @@ _fullSize = true; } +- (void)setCIImage:(CIImage *)ciImage { + [_toolComposer invalidate]; + _currentProcessChain = nil; + + [_currentInput removeAllTargets]; + GPUImageTextureInput *input = [[GPUImageTextureInput alloc] initWithCIImage:ciImage]; + _currentInput = input; + + if (_textureOutput == nil) { + _textureOutput = [[GPUImageTextureOutput alloc] init]; + } + + _fullSize = true; +} + #pragma mark - Properties - (CGSize)rotatedCropSize @@ -201,7 +221,7 @@ - (void)processAnimated:(bool)animated capture:(bool)capture synchronous:(bool)synchronous completion:(void (^)(void))completion { - if (self.previewOutput == nil) + if (self.previewOutput == nil && ![_currentInput isKindOfClass:[GPUImageTextureInput class]]) return; if (self.forVideo) { @@ -210,16 +230,21 @@ [self updateProcessChain]; GPUImageOutput *currentInput = _currentInput; - - if (!_playing) { - _playing = true; - [_videoQueue dispatch:^{ - if ([currentInput isKindOfClass:[PGVideoMovie class]]) { - [(PGVideoMovie *)currentInput startProcessing]; - } - }]; + if ([currentInput isKindOfClass:[PGVideoMovie class]]) { + if (!_playing) { + _playing = true; + [_videoQueue dispatch:^{ + if ([currentInput isKindOfClass:[PGVideoMovie class]]) { + [(PGVideoMovie *)currentInput startProcessing]; + } + }]; + } + } else if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) { + [(GPUImageTextureInput *)currentInput processTextureWithFrameTime:kCMTimeZero synchronous:synchronous]; + if (completion != nil) + completion(); } - }]; + } synchronous:synchronous]; return; } @@ -281,6 +306,8 @@ } - (void)updateProcessChain { + [GPUImageFramebuffer setMark:self.forVideo]; + NSMutableArray *processChain = [NSMutableArray array]; for (PGPhotoTool *tool in _toolComposer.advancedTools) @@ -319,10 +346,17 @@ } _finalFilter = lastFilter; - [_finalFilter addTarget:previewOutput.imageView]; + if (_textureOutput != nil) { + [_finalFilter addTarget:_textureOutput]; + } + + if (previewOutput != nil) { + [_finalFilter addTarget:previewOutput.imageView]; + } - if (!self.forVideo) + if (!self.forVideo) { [_finalFilter addTarget:_histogramGenerator]; + } } } @@ -349,6 +383,18 @@ return image; } +- (CIImage *)currentResultCIImage { + __block CIImage *image = nil; + GPUImageOutput *currentInput = _currentInput; + [self processAnimated:false capture:false synchronous:true completion:^ + { + if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) { + image = [_textureOutput CIImageWithSize:[(GPUImageTextureInput *)currentInput textureSize]]; + } + }]; + return image; +} + #pragma mark - Editor Values - (void)_importAdjustments:(id)adjustments diff --git a/submodules/LegacyComponents/Sources/PGVideoMovie.m b/submodules/LegacyComponents/Sources/PGVideoMovie.m index 6fed7835a9..49ecafb5c2 100755 --- a/submodules/LegacyComponents/Sources/PGVideoMovie.m +++ b/submodules/LegacyComponents/Sources/PGVideoMovie.m @@ -14,9 +14,9 @@ GLfloat kColorConversion601FullRangeDefault[] = { }; GLfloat kColorConversion709Default[] = { - 1.164, 1.164, 1.164, - 0.0, -0.213, 2.112, - 1.793, -0.533, 0.0, + 1, 1, 1, + 0, -.21482, 2.12798, + 1.28033, -.38059, 0, }; GLfloat *kColorConversion601 = kColorConversion601Default; @@ -124,7 +124,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN #pragma mark Initialization and teardown -- (instancetype)initWithAsset:(AVAsset *)asset; +- (instancetype)initWithAsset:(AVAsset *)asset { if (!(self = [super init])) { @@ -138,7 +138,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN return self; } -- (void)yuvConversionSetup; +- (void)yuvConversionSetup { if ([GPUImageContext supportsFastTextureUpload]) { @@ -147,7 +147,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN _preferredConversion = kColorConversion709; isFullYUVRange = YES; - yuvConversionProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kYUVFullRangeConversionForLAFragmentShaderString]; + yuvConversionProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kYUVVideoRangeConversionForRGFragmentShaderString]; if (!yuvConversionProgram.initialized) { @@ -213,7 +213,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN { NSError *error = nil; AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:self.asset error:&error]; - + NSMutableDictionary *outputSettings = [NSMutableDictionary dictionary]; if ([GPUImageContext supportsFastTextureUpload]) { [outputSettings setObject:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) forKey:(id)kCVPixelBufferPixelFormatTypeKey]; @@ -224,7 +224,6 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN isFullYUVRange = NO; } - // Maybe set alwaysCopiesSampleData to NO on iOS 5.0 for faster video decoding AVAssetReaderTrackOutput *readerVideoTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:[[self.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] outputSettings:outputSettings]; readerVideoTrackOutput.alwaysCopiesSampleData = NO; [assetReader addOutput:readerVideoTrackOutput]; @@ -485,8 +484,6 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN } } - - CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); [GPUImageContext useImageProcessingContext]; @@ -510,7 +507,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN glActiveTexture(GL_TEXTURE4); if ([GPUImageContext deviceSupportsRedTextures]) { - err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], movieFrame, NULL, GL_TEXTURE_2D, GL_LUMINANCE, bufferWidth, bufferHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0, &luminanceTextureRef); + err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], movieFrame, NULL, GL_TEXTURE_2D, GL_RED_EXT, bufferWidth, bufferHeight, GL_RED_EXT, GL_UNSIGNED_BYTE, 0, &luminanceTextureRef); } else { @@ -531,7 +528,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN glActiveTexture(GL_TEXTURE5); if ([GPUImageContext deviceSupportsRedTextures]) { - err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], movieFrame, NULL, GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, bufferWidth/2, bufferHeight/2, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 1, &chrominanceTextureRef); + err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], movieFrame, NULL, GL_TEXTURE_2D, GL_RG_EXT, bufferWidth/2, bufferHeight/2, GL_RG_EXT, GL_UNSIGNED_BYTE, 1, &chrominanceTextureRef); } else { @@ -556,6 +553,8 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN NSInteger targetTextureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue]; [currentTarget setInputSize:CGSizeMake(bufferWidth, bufferHeight) atIndex:targetTextureIndex]; [currentTarget setInputFramebuffer:outputFramebuffer atIndex:targetTextureIndex]; + +// [currentTarget setInputRotation:kGPUImageRotateLeft atIndex:targetTextureIndex]; } [outputFramebuffer unlock]; diff --git a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m index 7b36327c5b..3298c0bd39 100644 --- a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m +++ b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m @@ -801,7 +801,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; if ([cell isKindOfClass:[TGAttachmentAssetCell class]]) thumbnailImage = cell.imageView.image; - TGMediaPickerModernGalleryMixin *mixin = [[TGMediaPickerModernGalleryMixin alloc] initWithContext:_context item:asset fetchResult:_fetchResult parentController:self.parentController thumbnailImage:thumbnailImage selectionContext:_selectionContext editingContext:_editingContext suggestionContext:self.suggestionContext hasCaptions:(_allowCaptions && !_forProfilePhoto) allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:_inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:self.asFile itemsLimit:TGAttachmentDisplayedAssetLimit recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule reminder:self.reminder]; + TGMediaPickerModernGalleryMixin *mixin = [[TGMediaPickerModernGalleryMixin alloc] initWithContext:_context item:asset fetchResult:_fetchResult parentController:self.parentController thumbnailImage:thumbnailImage selectionContext:_selectionContext editingContext:_editingContext suggestionContext:self.suggestionContext hasCaptions:(_allowCaptions && !_forProfilePhoto) allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:_inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:self.asFile itemsLimit:TGAttachmentDisplayedAssetLimit recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule reminder:self.reminder stickersContext:self.stickersContext]; mixin.presentScheduleController = self.presentScheduleController; __weak TGAttachmentCarouselItemView *weakSelf = self; mixin.thumbnailSignalForItem = ^SSignal *(id item) diff --git a/submodules/LegacyComponents/Sources/TGCameraPhotoPreviewController.m b/submodules/LegacyComponents/Sources/TGCameraPhotoPreviewController.m index aa650c39ee..1f88d4701b 100644 --- a/submodules/LegacyComponents/Sources/TGCameraPhotoPreviewController.m +++ b/submodules/LegacyComponents/Sources/TGCameraPhotoPreviewController.m @@ -135,7 +135,7 @@ CGRect containerFrame = self.view.bounds; CGSize fittedSize = TGScaleToSize(_image.size, containerFrame.size); - _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:self.view.bounds]; + _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:self.view.bounds hasDoubleTap:true]; _scrollView.clipsToBounds = false; _scrollView.delegate = self; _scrollView.showsHorizontalScrollIndicator = false; diff --git a/submodules/LegacyComponents/Sources/TGClipboardMenu.m b/submodules/LegacyComponents/Sources/TGClipboardMenu.m index 27484b495b..c646e301c6 100644 --- a/submodules/LegacyComponents/Sources/TGClipboardMenu.m +++ b/submodules/LegacyComponents/Sources/TGClipboardMenu.m @@ -42,6 +42,7 @@ TGClipboardPreviewItemView *previewItem = [[TGClipboardPreviewItemView alloc] initWithContext:context images:images]; __weak TGClipboardPreviewItemView *weakPreviewItem = previewItem; + previewItem.suggestionContext = suggestionContext; previewItem.parentController = parentController; previewItem.allowCaptions = hasCaption; previewItem.hasTimer = hasTimer; diff --git a/submodules/LegacyComponents/Sources/TGFont.mm b/submodules/LegacyComponents/Sources/TGFont.mm index 446a717b0b..9c66d82e67 100644 --- a/submodules/LegacyComponents/Sources/TGFont.mm +++ b/submodules/LegacyComponents/Sources/TGFont.mm @@ -117,8 +117,8 @@ UIFont *TGFixedSystemFontOfSize(CGFloat size) + (UIFont *)roundedFontOfSize:(CGFloat)size { if (@available(iOSApplicationExtension 13.0, iOS 13.0, *)) { - UIFontDescriptor *descriptor = [UIFont systemFontOfSize: size].fontDescriptor; - descriptor = [descriptor fontDescriptorWithDesign: UIFontDescriptorSystemDesignRounded]; + UIFontDescriptor *descriptor = [UIFont boldSystemFontOfSize: size].fontDescriptor; + descriptor = [descriptor fontDescriptorWithDesign:UIFontDescriptorSystemDesignRounded]; return [UIFont fontWithDescriptor:descriptor size:size]; } else { return [UIFont fontWithName:@".SFCompactRounded-Semibold" size:size]; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 8c6fd58736..44aee76b8b 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -110,6 +110,7 @@ pickerController.pallete = strongController.pallete; } pickerController.suggestionContext = strongController.suggestionContext; + pickerController.stickersContext = strongController.stickersContext; pickerController.localMediaCacheEnabled = strongController.localMediaCacheEnabled; pickerController.captionsEnabled = strongController.captionsEnabled; pickerController.allowCaptionEntities = strongController.allowCaptionEntities; @@ -149,6 +150,12 @@ self.pickerController.suggestionContext = suggestionContext; } +- (void)setStickersContext:(id)stickersContext +{ + _stickersContext = stickersContext; + self.pickerController.stickersContext = stickersContext; +} + - (void)setCaptionsEnabled:(bool)captionsEnabled { _captionsEnabled = captionsEnabled; @@ -875,7 +882,11 @@ if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting) { CGRect scaledCropRect = CGRectMake(adjustments.cropRect.origin.x * image.size.width / adjustments.originalSize.width, adjustments.cropRect.origin.y * image.size.height / adjustments.originalSize.height, adjustments.cropRect.size.width * image.size.width / adjustments.originalSize.width, adjustments.cropRect.size.height * image.size.height / adjustments.originalSize.height); - return TGPhotoEditorCrop(image, adjustments.paintingData.image, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, targetSize, sourceSize, resize); + UIImage *paintingImage = adjustments.paintingData.stillImage; + if (paintingImage == nil) { + paintingImage = adjustments.paintingData.image; + } + return TGPhotoEditorCrop(image, paintingImage, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, targetSize, sourceSize, resize); } return image; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m index ad92688326..346f2481df 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m @@ -323,7 +323,7 @@ - (TGMediaPickerModernGalleryMixin *)_galleryMixinForContext:(id)context item:(id)item thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities inhibitDocumentCaptions:(bool)inhibitDocumentCaptions asFile:(bool)asFile { - return [[TGMediaPickerModernGalleryMixin alloc] initWithContext:context item:item fetchResult:_fetchResult parentController:self thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:asFile itemsLimit:0 recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule reminder:self.reminder]; + return [[TGMediaPickerModernGalleryMixin alloc] initWithContext:context item:item fetchResult:_fetchResult parentController:self thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:asFile itemsLimit:0 recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule reminder:self.reminder stickersContext:self.stickersContext]; } - (TGMediaPickerModernGalleryMixin *)galleryMixinForIndexPath:(NSIndexPath *)indexPath previewMode:(bool)previewMode outAsset:(TGMediaAsset **)outAsset diff --git a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m index f9ddc6324d..fc5f82f9e0 100644 --- a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m +++ b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m @@ -78,6 +78,7 @@ TGMemoryImageCache *_thumbnailImageCache; TGMemoryImageCache *_paintingImageCache; + TGMemoryImageCache *_stillPaintingImageCache; TGMemoryImageCache *_originalImageCache; TGMemoryImageCache *_originalThumbnailImageCache; @@ -86,6 +87,7 @@ NSURL *_fullSizeResultsUrl; NSURL *_paintingDatasUrl; NSURL *_paintingImagesUrl; + NSURL *_stillPaintingImagesUrl; NSURL *_videoPaintingImagesUrl; NSMutableArray *_storeVideoPaintingImages; @@ -131,6 +133,9 @@ _paintingImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] imageSoftMemoryLimit] hardMemoryLimit:[[self class] imageHardMemoryLimit]]; + _stillPaintingImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] imageSoftMemoryLimit] + hardMemoryLimit:[[self class] imageHardMemoryLimit]]; + _originalImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] originalImageSoftMemoryLimit] hardMemoryLimit:[[self class] originalImageHardMemoryLimit]]; _originalThumbnailImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] thumbnailImageSoftMemoryLimit] @@ -145,6 +150,9 @@ _paintingImagesUrl = [NSURL fileURLWithPath:[[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:[NSString stringWithFormat:@"paintingimages/%@", _contextId]]]; [[NSFileManager defaultManager] createDirectoryAtPath:_paintingImagesUrl.path withIntermediateDirectories:true attributes:nil error:nil]; + _stillPaintingImagesUrl = [NSURL fileURLWithPath:[[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:@"stillpaintingimages"]]; + [[NSFileManager defaultManager] createDirectoryAtPath:_stillPaintingImagesUrl.path withIntermediateDirectories:true attributes:nil error:nil]; + _videoPaintingImagesUrl = [NSURL fileURLWithPath:[[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:@"videopaintingimages"]]; [[NSFileManager defaultManager] createDirectoryAtPath:_videoPaintingImagesUrl.path withIntermediateDirectories:true attributes:nil error:nil]; @@ -340,6 +348,27 @@ return result; } +- (UIImage *)stillPaintingImageForItem:(NSObject *)item +{ + NSString *itemId = [self _contextualIdForItemId:item.uniqueIdentifier]; + if (itemId == nil) + return nil; + + UIImage *result = [_stillPaintingImageCache imageForKey:itemId attributes:NULL]; + if (result == nil) + { + NSURL *imageUrl = [_stillPaintingImagesUrl URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", [TGStringUtils md5:itemId]]]; + UIImage *diskImage = [UIImage imageWithContentsOfFile:imageUrl.path]; + if (diskImage != nil) + { + result = diskImage; + [_stillPaintingImageCache setImage:result forKey:itemId attributes:NULL]; + } + } + + return result; +} + #pragma mark - Caption - (NSString *)captionForItem:(id)item @@ -602,7 +631,7 @@ [_queue dispatch:block]; } -- (bool)setPaintingData:(NSData *)data image:(UIImage *)image forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video +- (bool)setPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)stillImage forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video { NSString *itemId = [self _contextualIdForItemId:item.uniqueIdentifier]; @@ -624,10 +653,21 @@ if (dataSuccess && dataOutUrl != NULL) *dataOutUrl = dataUrl; - + if (video) [_storeVideoPaintingImages addObject:imageUrl]; + if (stillImage != nil) { + NSURL *stillImageUrl = [_stillPaintingImagesUrl URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", [TGStringUtils md5:itemId]]]; + [_stillPaintingImageCache setImage:stillImage forKey:itemId attributes:NULL]; + + NSData *stillImageData = UIImagePNGRepresentation(stillImage); + [stillImageData writeToURL:stillImageUrl options:NSDataWritingAtomic error:nil]; + + if (video) + [_storeVideoPaintingImages addObject:stillImageUrl]; + } + return (image == nil || imageSuccess) && (data == nil || dataSuccess); } diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index 7bcdea25b5..d08466c329 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -164,7 +164,7 @@ if (recipientName.length > 0) { - _arrowView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"PhotoPickerArrow")]; + _arrowView = [[UIImageView alloc] initWithImage: TGTintedImage([UIImage imageNamed:@"Editor/Recipient"], UIColor.whiteColor)]; _arrowView.alpha = 0.45f; [_wrapperView addSubview:_arrowView]; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m index e6ca8b1ca5..9942634dac 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m @@ -387,6 +387,7 @@ TGPhotoEditorControllerIntent intent = isVideo ? TGPhotoEditorControllerVideoIntent : TGPhotoEditorControllerGenericIntent; TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:_context item:item.editableMediaItem intent:intent adjustments:editorValues caption:caption screenImage:screenImage availableTabs:_interfaceView.currentTabs selectedTab:tab]; controller.editingContext = _editingContext; + controller.stickersContext = _stickersContext; self.editorController = controller; controller.suggestionContext = self.suggestionContext; controller.willFinishEditing = ^(id adjustments, id temporaryRep, bool hasChanges) diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m index 2c9535ec3d..e8d0082b1d 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m @@ -166,7 +166,8 @@ _actionButton.highlightImage = highlightImage; - _progressView = [[TGMessageImageViewOverlayView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)]; + _progressView = [[TGMessageImageViewOverlayView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)]; + [_progressView setRadius:60.0]; _progressView.userInteractionEnabled = false; [_progressView setPlay]; [_actionButton addSubview:_progressView]; @@ -864,7 +865,8 @@ if (progressVisible && _progressView == nil) { - _progressView = [[TGMessageImageViewOverlayView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 50.0f, 50.0f)]; + _progressView = [[TGMessageImageViewOverlayView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 60.0f, 60.0f)]; + [_progressView setRadius:60.0]; _progressView.userInteractionEnabled = false; _progressView.frame = (CGRect){{CGFloor((self.frame.size.width - _progressView.frame.size.width) / 2.0f), CGFloor((self.frame.size.height - _progressView.frame.size.height) / 2.0f)}, _progressView.frame.size}; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m b/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m index a2e2b5292b..b62ee3b8b3 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m @@ -41,17 +41,17 @@ @implementation TGMediaPickerModernGalleryMixin -- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder +- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder stickersContext:(id)stickersContext { - return [self initWithContext:context item:item fetchResult:fetchResult momentList:nil parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:recipientName hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule reminder:reminder]; + return [self initWithContext:context item:item fetchResult:fetchResult momentList:nil parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:recipientName hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule reminder:reminder stickersContext:stickersContext]; } -- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder +- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder stickersContext:(id)stickersContext { - return [self initWithContext:context item:item fetchResult:nil momentList:momentList parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:nil hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule reminder:reminder]; + return [self initWithContext:context item:item fetchResult:nil momentList:momentList parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:nil hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule reminder:reminder stickersContext:stickersContext]; } -- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder +- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder stickersContext:(id)stickersContext { self = [super init]; if (self != nil) @@ -85,6 +85,7 @@ TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:[_windowManager context] items:galleryItems focusItem:focusItem selectionContext:selectionContext editingContext:editingContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions hasSelectionPanel:true hasCamera:false recipientName:recipientName]; _galleryModel = model; + model.stickersContext = stickersContext; model.inhibitMute = inhibitMute; model.controller = modernGallery; model.suggestionContext = suggestionContext; diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index 6e2f86967d..4501fea098 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -3,13 +3,18 @@ #import #import +#import "GPUImageContext.h" + #import "LegacyComponentsInternal.h" #import "TGImageUtils.h" #import "TGPhotoEditorUtils.h" +#import "PGPhotoEditor.h" +#import "TGPhotoPaintEntity.h" #import "TGVideoEditAdjustments.h" #import "TGPaintingData.h" +#import "TGPhotoPaintStickersContext.h" @interface TGMediaVideoConversionPresetSettings () @@ -68,6 +73,8 @@ @property (nonatomic, readonly) TGMediaSampleBufferProcessor *videoProcessor; @property (nonatomic, readonly) TGMediaSampleBufferProcessor *audioProcessor; +@property (nonatomic, readonly) id entityRenderer; + @property (nonatomic, readonly) CMTimeRange timeRange; @property (nonatomic, readonly) CGSize dimensions; @property (nonatomic, readonly) UIImage *coverImage; @@ -79,7 +86,7 @@ - (instancetype)addImageGenerator:(AVAssetImageGenerator *)imageGenerator; - (instancetype)addCoverImage:(UIImage *)coverImage; -- (instancetype)contextWithAssetReader:(AVAssetReader *)assetReader assetWriter:(AVAssetWriter *)assetWriter videoProcessor:(TGMediaSampleBufferProcessor *)videoProcessor audioProcessor:(TGMediaSampleBufferProcessor *)audioProcessor timeRange:(CMTimeRange)timeRange dimensions:(CGSize)dimensions; +- (instancetype)contextWithAssetReader:(AVAssetReader *)assetReader assetWriter:(AVAssetWriter *)assetWriter videoProcessor:(TGMediaSampleBufferProcessor *)videoProcessor audioProcessor:(TGMediaSampleBufferProcessor *)audioProcessor timeRange:(CMTimeRange)timeRange dimensions:(CGSize)dimensions entityRenderer:(id)entityRenderer; @end @@ -93,12 +100,12 @@ @implementation TGMediaVideoConverter -+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher ++ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id)entityRenderer { - return [self convertAVAsset:avAsset adjustments:adjustments watcher:watcher inhibitAudio:false]; + return [self convertAVAsset:avAsset adjustments:adjustments watcher:watcher inhibitAudio:false entityRenderer:entityRenderer]; } -+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio ++ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio entityRenderer:(id)entityRenderer { SQueue *queue = [[SQueue alloc] init]; @@ -146,7 +153,7 @@ } } - if (![self setupAssetReaderWriterForAVAsset:avAsset outputURL:outputUrl preset:preset adjustments:adjustments inhibitAudio:inhibitAudio conversionContext:context error:&error]) + if (![self setupAssetReaderWriterForItem:avAsset outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:inhibitAudio conversionContext:context error:&error]) { [subscriber putError:error]; return; @@ -204,6 +211,92 @@ }]; } ++ (SSignal *)renderUIImage:(UIImage *)image adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id)entityRenderer +{ + SQueue *queue = [[SQueue alloc] init]; + + return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) + { + SAtomic *context = [[SAtomic alloc] initWithValue:[TGMediaVideoConversionContext contextWithQueue:queue subscriber:subscriber]]; + NSURL *outputUrl = [self _randomTemporaryURL]; + + [queue dispatch:^ + { + if (((TGMediaVideoConversionContext *)context.value).cancelled) + return; + + TGMediaVideoConversionPreset preset = TGMediaVideoConversionPresetAnimation; + + NSError *error = nil; + + NSString *outputPath = outputUrl.path; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:outputPath]) + { + [fileManager removeItemAtPath:outputPath error:&error]; + if (error != nil) + { + [subscriber putError:error]; + return; + } + } + + if (![self setupAssetReaderWriterForItem:image outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:true conversionContext:context error:&error]) + { + [subscriber putError:error]; + return; + } + + TGDispatchAfter(1.0, queue._dispatch_queue, ^ + { + if (watcher != nil) + [watcher setupWithFileURL:outputUrl]; + }); + + [self processWithConversionContext:context completionBlock:^ + { + TGMediaVideoConversionContext *resultContext = context.value; + [resultContext.imageGenerator generateCGImagesAsynchronouslyForTimes:@[ [NSValue valueWithCMTime:kCMTimeZero] ] completionHandler:^(__unused CMTime requestedTime, CGImageRef _Nullable image, __unused CMTime actualTime, AVAssetImageGeneratorResult result, __unused NSError * _Nullable error) + { + UIImage *coverImage = nil; + if (result == AVAssetImageGeneratorSucceeded) + coverImage = [UIImage imageWithCGImage:image]; + + __block TGMediaVideoConversionResult *contextResult = nil; + [context modify:^id(TGMediaVideoConversionContext *resultContext) + { + id liveUploadData = nil; + if (watcher != nil) + liveUploadData = [watcher fileUpdated:true]; + + contextResult = [TGMediaVideoConversionResult resultWithFileURL:outputUrl fileSize:0 duration:CMTimeGetSeconds(resultContext.timeRange.duration) dimensions:resultContext.dimensions coverImage:coverImage liveUploadData:liveUploadData]; + return [resultContext finishedContext]; + }]; + + [subscriber putNext:contextResult]; + [subscriber putCompletion]; + }]; + }]; + }]; + + return [[SBlockDisposable alloc] initWithBlock:^ + { + [queue dispatch:^ + { + [context modify:^id(TGMediaVideoConversionContext *currentContext) + { + if (currentContext.finished) + return currentContext; + + [currentContext.videoProcessor cancel]; + + return [currentContext cancelledContext]; + }]; + }]; + }]; + }]; +} + + (CGSize)dimensionsFor:(CGSize)dimensions adjustments:(TGMediaVideoEditAdjustments *)adjustments preset:(TGMediaVideoConversionPreset)preset { CGRect transformedRect = CGRectMake(0.0f, 0.0f, dimensions.width, dimensions.height); @@ -221,7 +314,7 @@ return outputDimensions; } -+ (AVAssetReaderVideoCompositionOutput *)setupVideoCompositionOutputWithAVAsset:(AVAsset *)avAsset composition:(AVMutableComposition *)composition videoTrack:(AVAssetTrack *)videoTrack preset:(TGMediaVideoConversionPreset)preset adjustments:(TGMediaVideoEditAdjustments *)adjustments timeRange:(CMTimeRange)timeRange outputSettings:(NSDictionary **)outputSettings dimensions:(CGSize *)dimensions conversionContext:(SAtomic *)conversionContext ++ (AVAssetReaderVideoCompositionOutput *)setupVideoCompositionOutputWithAVAsset:(AVAsset *)avAsset composition:(AVMutableComposition *)composition videoTrack:(AVAssetTrack *)videoTrack preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments timeRange:(CMTimeRange)timeRange outputSettings:(NSDictionary **)outputSettings dimensions:(CGSize *)dimensions conversionContext:(SAtomic *)conversionContext { CGSize transformedSize = CGRectApplyAffineTransform((CGRect){CGPointZero, videoTrack.naturalSize}, videoTrack.preferredTransform).size;; CGRect transformedRect = CGRectMake(0, 0, transformedSize.width, transformedSize.height); @@ -240,8 +333,81 @@ if (TGOrientationIsSideward(adjustments.cropOrientation, NULL)) outputDimensions = CGSizeMake(outputDimensions.height, outputDimensions.width); + + AVMutableCompositionTrack *trimVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + [trimVideoTrack insertTimeRange:timeRange ofTrack:videoTrack atTime:kCMTimeZero error:NULL]; - AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition]; + UIImage *overlayImage = nil; + if (adjustments.paintingData.imagePath != nil) + overlayImage = [UIImage imageWithContentsOfFile:adjustments.paintingData.imagePath]; + + bool hasAnimation = false; + for (TGPhotoPaintEntity *entity in adjustments.paintingData.entities) { + if (entity.animated) { + hasAnimation = true; + break; + } + } + if (!hasAnimation) { + entityRenderer = nil; + } + + AVMutableVideoComposition *videoComposition; + if (entityRenderer != nil || adjustments.toolsApplied) { + PGPhotoEditor *editor = nil; + CIContext *ciContext = nil; + if (adjustments.toolsApplied) { + editor = [[PGPhotoEditor alloc] initWithOriginalSize:adjustments.originalSize adjustments:adjustments forVideo:true enableStickers:true]; + ciContext = [CIContext contextWithEAGLContext:[[GPUImageContext sharedImageProcessingContext] context]]; + } + + __block CIImage *overlayCIImage = nil; + videoComposition = [AVMutableVideoComposition videoCompositionWithAsset:avAsset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) { + __block CIImage *resultImage = request.sourceImage; + + if (editor != nil) { + [editor setCIImage:resultImage]; + resultImage = editor.currentResultCIImage; + } + + if (overlayImage != nil && overlayImage.size.width > 0.0) { + if (overlayCIImage == nil) { + overlayCIImage = [[CIImage alloc] initWithImage:overlayImage]; + CGFloat scale = request.sourceImage.extent.size.width / overlayCIImage.extent.size.width; + overlayCIImage = [overlayCIImage imageByApplyingTransform:CGAffineTransformMakeScale(scale, scale)]; + } + resultImage = [overlayCIImage imageByCompositingOverImage:resultImage]; + } + + if (entityRenderer != nil) { + [entityRenderer entitiesForTime:request.compositionTime size:request.sourceImage.extent.size completion:^(NSArray *images) { + for (CIImage *image in images) { + resultImage = [image imageByCompositingOverImage:resultImage]; + } + [request finishWithImage:resultImage context:ciContext]; + }]; + } else { + [request finishWithImage:resultImage context:ciContext]; + } + }]; + } else { + videoComposition = [AVMutableVideoComposition videoComposition]; + + bool mirrored = false; + UIImageOrientation videoOrientation = TGVideoOrientationForAsset(avAsset, &mirrored); + CGAffineTransform transform = TGVideoTransformForOrientation(videoOrientation, videoTrack.naturalSize, cropRect, mirrored); + CGAffineTransform rotationTransform = TGVideoTransformForCrop(adjustments.cropOrientation, cropRect.size, adjustments.cropMirrored); + CGAffineTransform finalTransform = CGAffineTransformConcat(transform, rotationTransform); + + AVMutableVideoCompositionLayerInstruction *transformer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:trimVideoTrack]; + [transformer setTransform:finalTransform atTime:kCMTimeZero]; + + AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; + instruction.timeRange = CMTimeRangeMake(kCMTimeZero, timeRange.duration); + instruction.layerInstructions = [NSArray arrayWithObject:transformer]; + videoComposition.instructions = [NSArray arrayWithObject:instruction]; + } + if (videoTrack.nominalFrameRate > 0) videoComposition.frameDuration = CMTimeMake(1, (int32_t)videoTrack.nominalFrameRate); else if (CMTimeCompare(videoTrack.minFrameDuration, kCMTimeZero) == 1) @@ -258,60 +424,39 @@ videoComposition.renderSize = [self _renderSizeWithCropSize:cropRect.size rotateSideward:TGOrientationIsSideward(adjustments.cropOrientation, NULL)]; if (videoComposition.renderSize.width < FLT_EPSILON || videoComposition.renderSize.height < FLT_EPSILON) return nil; - - AVMutableCompositionTrack *trimVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; - [trimVideoTrack insertTimeRange:timeRange ofTrack:videoTrack atTime:kCMTimeZero error:NULL]; - - bool mirrored = false; - UIImageOrientation videoOrientation = TGVideoOrientationForAsset(avAsset, &mirrored); - CGAffineTransform transform = TGVideoTransformForOrientation(videoOrientation, videoTrack.naturalSize, cropRect, mirrored); - CGAffineTransform rotationTransform = TGVideoTransformForCrop(adjustments.cropOrientation, cropRect.size, adjustments.cropMirrored); - CGAffineTransform finalTransform = CGAffineTransformConcat(transform, rotationTransform); - - AVMutableVideoCompositionLayerInstruction *transformer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:trimVideoTrack]; - [transformer setTransform:finalTransform atTime:kCMTimeZero]; - - AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; - instruction.timeRange = CMTimeRangeMake(kCMTimeZero, timeRange.duration); - instruction.layerInstructions = [NSArray arrayWithObject:transformer]; - videoComposition.instructions = [NSArray arrayWithObject:instruction]; - - UIImage *overlayImage = nil; - if (adjustments.paintingData.imagePath != nil) - overlayImage = [UIImage imageWithContentsOfFile:adjustments.paintingData.imagePath]; - - if (overlayImage != nil) + + if (overlayImage != nil && entityRenderer == nil) { CALayer *parentLayer = [CALayer layer]; parentLayer.frame = CGRectMake(0, 0, videoComposition.renderSize.width, videoComposition.renderSize.height); - + CALayer *videoLayer = [CALayer layer]; videoLayer.frame = parentLayer.frame; [parentLayer addSublayer:videoLayer]; - + CGSize parentSize = parentLayer.bounds.size; if (TGOrientationIsSideward(adjustments.cropOrientation, NULL)) parentSize = CGSizeMake(parentSize.height, parentSize.width); - + CGSize size = CGSizeMake(parentSize.width * transformedSize.width / cropRect.size.width, parentSize.height * transformedSize.height / cropRect.size.height); CGPoint origin = CGPointMake(-parentSize.width / cropRect.size.width * cropRect.origin.x, -parentSize.height / cropRect.size.height * (transformedSize.height - cropRect.size.height - cropRect.origin.y)); - + CALayer *rotationLayer = [CALayer layer]; rotationLayer.frame = CGRectMake(0, 0, parentSize.width, parentSize.height); [parentLayer addSublayer:rotationLayer]; - + UIImageOrientation orientation = TGMirrorSidewardOrientation(adjustments.cropOrientation); CATransform3D layerTransform = CATransform3DMakeTranslation(rotationLayer.frame.size.width / 2.0f, rotationLayer.frame.size.height / 2.0f, 0.0f); layerTransform = CATransform3DRotate(layerTransform, TGRotationForOrientation(orientation), 0.0f, 0.0f, 1.0f); layerTransform = CATransform3DTranslate(layerTransform, -parentLayer.bounds.size.width / 2.0f, -parentLayer.bounds.size.height / 2.0f, 0.0f); rotationLayer.transform = layerTransform; rotationLayer.frame = parentLayer.frame; - + CALayer *overlayLayer = [CALayer layer]; overlayLayer.contents = (id)overlayImage.CGImage; overlayLayer.frame = CGRectMake(origin.x, origin.y, size.width, size.height); [rotationLayer addSublayer:overlayLayer]; - + videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer]; } @@ -335,83 +480,113 @@ return output; } -+ (bool)setupAssetReaderWriterForAVAsset:(AVAsset *)avAsset outputURL:(NSURL *)outputURL preset:(TGMediaVideoConversionPreset)preset adjustments:(TGMediaVideoEditAdjustments *)adjustments inhibitAudio:(bool)inhibitAudio conversionContext:(SAtomic *)outConversionContext error:(NSError **)error ++ (bool)setupAssetReaderWriterForItem:(id)item outputURL:(NSURL *)outputURL preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments inhibitAudio:(bool)inhibitAudio conversionContext:(SAtomic *)outConversionContext error:(NSError **)error { - TGMediaSampleBufferProcessor *videoProcessor = nil; - TGMediaSampleBufferProcessor *audioProcessor = nil; - - AVAssetTrack *audioTrack = [[avAsset tracksWithMediaType:AVMediaTypeAudio] firstObject]; - AVAssetTrack *videoTrack = [[avAsset tracksWithMediaType:AVMediaTypeVideo] firstObject]; - if (videoTrack == nil) - return false; - - CGSize dimensions = CGSizeZero; - CMTimeRange timeRange = videoTrack.timeRange; - if (adjustments.trimApplied) - { - NSTimeInterval duration = CMTimeGetSeconds(videoTrack.timeRange.duration); - if (adjustments.trimEndValue < duration) - { - timeRange = adjustments.trimTimeRange; - } - else - { - timeRange = CMTimeRangeMake(CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC), CMTimeMakeWithSeconds(duration - adjustments.trimStartValue, NSEC_PER_SEC)); - } - } - timeRange = CMTimeRangeMake(CMTimeAdd(timeRange.start, CMTimeMake(10, 100)), CMTimeSubtract(timeRange.duration, CMTimeMake(10, 100))); - - NSDictionary *outputSettings = nil; - AVMutableComposition *composition = [AVMutableComposition composition]; - AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:avAsset composition:composition videoTrack:videoTrack preset:preset adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; - if (output == nil) - return false; - - AVAssetReader *assetReader = [[AVAssetReader alloc] initWithAsset:composition error:error]; - if (assetReader == nil) - return false; - - AVAssetWriter *assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeMPEG4 error:error]; - if (assetWriter == nil) - return false; - - [assetReader addOutput:output]; - - AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:outputSettings]; - - NSDictionary *sourcePixelBufferAttributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey, - [NSNumber numberWithInt:dimensions.width], kCVPixelBufferWidthKey, - [NSNumber numberWithInt:dimensions.height], kCVPixelBufferHeightKey, - nil]; - - AVAssetWriterInputPixelBufferAdaptor *pixelBufferInput = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:input sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary]; - - [assetWriter addInput:input]; - - videoProcessor = [[TGMediaSampleBufferProcessor alloc] initWithAssetReaderOutput:output assetWriterInput:input]; - - if (!inhibitAudio && [TGMediaVideoConversionPresetSettings keepAudioForPreset:preset] && audioTrack != nil) - { - AVMutableCompositionTrack *trimAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; - [trimAudioTrack insertTimeRange:timeRange ofTrack:audioTrack atTime:kCMTimeZero error:NULL]; - if (trimAudioTrack == nil) + if ([item isKindOfClass:[AVAsset class]]) { + TGMediaSampleBufferProcessor *videoProcessor = nil; + TGMediaSampleBufferProcessor *audioProcessor = nil; + + AVAsset *avAsset = (AVAsset *)item; + + AVAssetTrack *audioTrack = [[avAsset tracksWithMediaType:AVMediaTypeAudio] firstObject]; + AVAssetTrack *videoTrack = [[avAsset tracksWithMediaType:AVMediaTypeVideo] firstObject]; + if (videoTrack == nil) + return false; + + CGSize dimensions = CGSizeZero; + CMTimeRange timeRange = videoTrack.timeRange; + if (adjustments.trimApplied) + { + NSTimeInterval duration = CMTimeGetSeconds(videoTrack.timeRange.duration); + if (adjustments.trimEndValue < duration) + { + timeRange = adjustments.trimTimeRange; + } + else + { + timeRange = CMTimeRangeMake(CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC), CMTimeMakeWithSeconds(duration - adjustments.trimStartValue, NSEC_PER_SEC)); + } + } + timeRange = CMTimeRangeMake(CMTimeAdd(timeRange.start, CMTimeMake(10, 100)), CMTimeSubtract(timeRange.duration, CMTimeMake(10, 100))); + + NSDictionary *outputSettings = nil; + AVMutableComposition *composition = [AVMutableComposition composition]; + AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:avAsset composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; + if (output == nil) + return false; + + AVAssetReader *assetReader = [[AVAssetReader alloc] initWithAsset:composition error:error]; + if (assetReader == nil) + return false; + + AVAssetWriter *assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeMPEG4 error:error]; + if (assetWriter == nil) return false; - AVAssetReaderOutput *output = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:trimAudioTrack outputSettings:@{ AVFormatIDKey: @(kAudioFormatLinearPCM) }]; [assetReader addOutput:output]; - AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:[TGMediaVideoConversionPresetSettings audioSettingsForPreset:preset]]; + AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:outputSettings]; [assetWriter addInput:input]; - audioProcessor = [[TGMediaSampleBufferProcessor alloc] initWithAssetReaderOutput:output assetWriterInput:input]; + videoProcessor = [[TGMediaSampleBufferProcessor alloc] initWithAssetReaderOutput:output assetWriterInput:input]; + + if (!inhibitAudio && [TGMediaVideoConversionPresetSettings keepAudioForPreset:preset] && audioTrack != nil) + { + AVMutableCompositionTrack *trimAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; + [trimAudioTrack insertTimeRange:timeRange ofTrack:audioTrack atTime:kCMTimeZero error:NULL]; + if (trimAudioTrack == nil) + return false; + + AVAssetReaderOutput *output = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:trimAudioTrack outputSettings:@{ AVFormatIDKey: @(kAudioFormatLinearPCM) }]; + [assetReader addOutput:output]; + + AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:[TGMediaVideoConversionPresetSettings audioSettingsForPreset:preset]]; + [assetWriter addInput:input]; + + audioProcessor = [[TGMediaSampleBufferProcessor alloc] initWithAssetReaderOutput:output assetWriterInput:input]; + } + + [outConversionContext modify:^id(TGMediaVideoConversionContext *currentContext) + { + return [currentContext contextWithAssetReader:assetReader assetWriter:assetWriter videoProcessor:videoProcessor audioProcessor:audioProcessor timeRange:timeRange dimensions:dimensions entityRenderer:entityRenderer]; + }]; + + return true; + } else if ([item isKindOfClass:[UIImage class]]) { + TGMediaSampleBufferProcessor *videoProcessor = nil; + + CGSize dimensions = CGSizeZero; + NSDictionary *outputSettings = nil; + CMTimeRange timeRange = CMTimeRangeMake(CMTimeMakeWithSeconds(0.0, NSEC_PER_SEC), CMTimeMakeWithSeconds(4.0, NSEC_PER_SEC)); + AVMutableComposition *composition = [AVMutableComposition composition]; + AVAssetTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:composition composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; + if (output == nil) + return false; + + AVAssetReader *assetReader = [[AVAssetReader alloc] initWithAsset:composition error:error]; + if (assetReader == nil) + return false; + + AVAssetWriter *assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeMPEG4 error:error]; + if (assetWriter == nil) + return false; + + [assetReader addOutput:output]; + + AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:outputSettings]; + [assetWriter addInput:input]; + + videoProcessor = [[TGMediaSampleBufferProcessor alloc] initWithAssetReaderOutput:output assetWriterInput:input]; + + [outConversionContext modify:^id(TGMediaVideoConversionContext *currentContext) + { + return [currentContext contextWithAssetReader:assetReader assetWriter:assetWriter videoProcessor:videoProcessor audioProcessor:nil timeRange:timeRange dimensions:dimensions entityRenderer:entityRenderer]; + }]; + + return true; } - - [outConversionContext modify:^id(TGMediaVideoConversionContext *currentContext) - { - return [currentContext contextWithAssetReader:assetReader assetWriter:assetWriter videoProcessor:videoProcessor audioProcessor:audioProcessor timeRange:timeRange dimensions:dimensions]; - }]; - - return true; + return false; } + (void)processWithConversionContext:(SAtomic *)context_ completionBlock:(void (^)(void))completionBlock @@ -865,6 +1040,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, context->_dimensions = _dimensions; context->_coverImage = _coverImage; context->_imageGenerator = _imageGenerator; + context->_entityRenderer = _entityRenderer; return context; } @@ -883,6 +1059,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, context->_dimensions = _dimensions; context->_coverImage = _coverImage; context->_imageGenerator = _imageGenerator; + context->_entityRenderer = _entityRenderer; return context; } @@ -900,6 +1077,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, context->_dimensions = _dimensions; context->_coverImage = _coverImage; context->_imageGenerator = imageGenerator; + context->_entityRenderer = _entityRenderer; return context; } @@ -917,10 +1095,11 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, context->_dimensions = _dimensions; context->_coverImage = coverImage; context->_imageGenerator = _imageGenerator; + context->_entityRenderer = _entityRenderer; return context; } -- (instancetype)contextWithAssetReader:(AVAssetReader *)assetReader assetWriter:(AVAssetWriter *)assetWriter videoProcessor:(TGMediaSampleBufferProcessor *)videoProcessor audioProcessor:(TGMediaSampleBufferProcessor *)audioProcessor timeRange:(CMTimeRange)timeRange dimensions:(CGSize)dimensions +- (instancetype)contextWithAssetReader:(AVAssetReader *)assetReader assetWriter:(AVAssetWriter *)assetWriter videoProcessor:(TGMediaSampleBufferProcessor *)videoProcessor audioProcessor:(TGMediaSampleBufferProcessor *)audioProcessor timeRange:(CMTimeRange)timeRange dimensions:(CGSize)dimensions entityRenderer:(id)entityRenderer { TGMediaVideoConversionContext *context = [[TGMediaVideoConversionContext alloc] init]; context->_queue = _queue; @@ -934,6 +1113,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, context->_dimensions = dimensions; context->_coverImage = _coverImage; context->_imageGenerator = _imageGenerator; + context->_entityRenderer = entityRenderer; return context; } diff --git a/submodules/LegacyComponents/Sources/TGMessageImageViewOverlayView.m b/submodules/LegacyComponents/Sources/TGMessageImageViewOverlayView.m index 52e88791cb..7d02043b76 100644 --- a/submodules/LegacyComponents/Sources/TGMessageImageViewOverlayView.m +++ b/submodules/LegacyComponents/Sources/TGMessageImageViewOverlayView.m @@ -702,7 +702,7 @@ const NSInteger TGMessageImageViewOverlayParticlesCount = 40; CGFloat offset = round(diameter * 0.06f); CGFloat verticalOffset = 0.0f; CGFloat alpha = 0.8f; - UIColor *iconColor = TGColorWithHexAndAlpha(0xff000000, 0.45f); + UIColor *iconColor = TGColorWithHexAndAlpha(0xffffffff, 1.0f); if (diameter <= 25.0f + FLT_EPSILON) { offset = round(50.0f * 0.06f) - 1.0f; verticalOffset += 0.5f; @@ -730,16 +730,11 @@ const NSInteger TGMessageImageViewOverlayParticlesCount = 40; } else { - CGContextSetFillColorWithColor(context, TGColorWithHexAndAlpha(0xffffffff, alpha).CGColor); + CGContextSetFillColorWithColor(context, TGColorWithHexAndAlpha(0x00000000, 0.3).CGColor); CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, diameter, diameter)); - - CGContextBeginPath(context); - CGContextMoveToPoint(context, offset + floor((diameter - width) / 2.0f), verticalOffset + floor((diameter - height) / 2.0f)); - CGContextAddLineToPoint(context, offset + floor((diameter - width) / 2.0f) + width, verticalOffset + floor(diameter / 2.0f)); - CGContextAddLineToPoint(context, offset + floor((diameter - width) / 2.0f), verticalOffset + floor((diameter + height) / 2.0f)); - CGContextClosePath(context); - CGContextSetFillColorWithColor(context, iconColor.CGColor); - CGContextFillPath(context); + + UIImage *iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Play"], iconColor); + [iconImage drawAtPoint:CGPointMake(floor((diameter - iconImage.size.width) / 2.0f), floor((diameter - iconImage.size.height) / 2.0f)) blendMode:kCGBlendModeNormal alpha:1.0f]; } break; diff --git a/submodules/LegacyComponents/Sources/TGModernGalleryZoomableItemView.m b/submodules/LegacyComponents/Sources/TGModernGalleryZoomableItemView.m index b72a4ee361..41c86a393f 100644 --- a/submodules/LegacyComponents/Sources/TGModernGalleryZoomableItemView.m +++ b/submodules/LegacyComponents/Sources/TGModernGalleryZoomableItemView.m @@ -24,7 +24,7 @@ _containerView = [[TGModernGalleryImageItemContainerView alloc] initWithFrame:_internalContainerView.bounds]; [_internalContainerView addSubview:_containerView]; - _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:_containerView.bounds]; + _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:_containerView.bounds hasDoubleTap:true]; _scrollView.delegate = self; _scrollView.showsHorizontalScrollIndicator = false; _scrollView.showsVerticalScrollIndicator = false; diff --git a/submodules/LegacyComponents/Sources/TGModernGalleryZoomableScrollView.m b/submodules/LegacyComponents/Sources/TGModernGalleryZoomableScrollView.m index 8b3a5bcc11..2eb54f6508 100644 --- a/submodules/LegacyComponents/Sources/TGModernGalleryZoomableScrollView.m +++ b/submodules/LegacyComponents/Sources/TGModernGalleryZoomableScrollView.m @@ -3,25 +3,48 @@ #import "TGDoubleTapGestureRecognizer.h" @interface TGModernGalleryZoomableScrollView () - +{ + bool _hasDoubleTap; +} @end @implementation TGModernGalleryZoomableScrollView -- (instancetype)initWithFrame:(CGRect)frame +- (instancetype)initWithFrame:(CGRect)frame hasDoubleTap:(bool)hasDoubleTap { self = [super initWithFrame:frame]; if (self != nil) { - TGDoubleTapGestureRecognizer *recognizer = [[TGDoubleTapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapGesture:)]; - recognizer.consumeSingleTap = true; - [self addGestureRecognizer:recognizer]; + _hasDoubleTap = hasDoubleTap; + if (hasDoubleTap) { + TGDoubleTapGestureRecognizer *recognizer = [[TGDoubleTapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapGesture:)]; + recognizer.consumeSingleTap = true; + [self addGestureRecognizer:recognizer]; + } else { + self.panGestureRecognizer.minimumNumberOfTouches = 2; + } _normalZoomScale = 1.0f; } return self; } +- (void)setContentInset:(UIEdgeInsets)contentInset { + if (_hasDoubleTap) { + [super setContentInset:contentInset]; + } else { + [super setContentInset:UIEdgeInsetsZero]; + } +} + +- (UIEdgeInsets)adjustedContentInset { + if (_hasDoubleTap) { + return [super adjustedContentInset]; + } else { + return UIEdgeInsetsZero; + } +} + - (void)doubleTapGesture:(TGDoubleTapGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateRecognized) diff --git a/submodules/LegacyComponents/Sources/TGPaintBrush.m b/submodules/LegacyComponents/Sources/TGPaintBrush.m index e492ed7ffd..67ca1ad9ce 100644 --- a/submodules/LegacyComponents/Sources/TGPaintBrush.m +++ b/submodules/LegacyComponents/Sources/TGPaintBrush.m @@ -2,7 +2,7 @@ #import -const CGSize TGPaintBrushTextureSize = { 256.0f, 256.0f }; +const CGSize TGPaintBrushTextureSize = { 384.0f, 384.0f }; const CGSize TGPaintBrushPreviewTextureSize = { 64.0f, 64.0f }; @interface TGPaintBrush () diff --git a/submodules/LegacyComponents/Sources/TGPaintCanvas.m b/submodules/LegacyComponents/Sources/TGPaintCanvas.m index 1ba27b520a..c750b4208e 100644 --- a/submodules/LegacyComponents/Sources/TGPaintCanvas.m +++ b/submodules/LegacyComponents/Sources/TGPaintCanvas.m @@ -22,6 +22,8 @@ CGAffineTransform _canvasTransform; CGRect _dirtyRect; + CGRect _visibleRect; + TGPaintInput *_input; TGPaintPanGestureRecognizer *_gestureRecognizer; bool _beganDrawing; @@ -40,6 +42,8 @@ if (self != nil) { self.contentScaleFactor = _screenScale; + self.multipleTouchEnabled = true; + self.exclusiveTouch = true; _state = [[TGPaintState alloc] init]; @@ -82,10 +86,18 @@ - (void)setFrame:(CGRect)frame { [super setFrame:frame]; + _visibleRect = self.bounds; [self _updateTransform]; } +- (void)setBounds:(CGRect)bounds +{ + [super setBounds:bounds]; + + _visibleRect = bounds; +} + - (void)_updateTransform { CGAffineTransform transform = CGAffineTransformIdentity; @@ -112,7 +124,7 @@ _gestureRecognizer = [[TGPaintPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; _gestureRecognizer.delegate = self; _gestureRecognizer.minimumNumberOfTouches = 1; - _gestureRecognizer.maximumNumberOfTouches = 1; + _gestureRecognizer.maximumNumberOfTouches = 2; __weak TGPaintCanvas *weakSelf = self; _gestureRecognizer.shouldRecognizeTap = ^bool @@ -162,7 +174,7 @@ } } -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)__unused gestureRecognizer +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (self.shouldDraw != nil) return self.shouldDraw(); @@ -172,9 +184,9 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { - if (gestureRecognizer == _gestureRecognizer && ([otherGestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UIRotationGestureRecognizer class]])) - return false; - +// if (gestureRecognizer == _gestureRecognizer && ([otherGestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UIRotationGestureRecognizer class]])) +// return false; +// return true; } @@ -264,16 +276,18 @@ - (CGRect)visibleRect { - return self.bounds; + return _visibleRect; } - (void)layoutSubviews { [super layoutSubviews]; - [_buffers update]; - - [self draw]; + [self.painting performSynchronouslyInContext:^{ + [_buffers update]; + + [self draw]; + }]; } #pragma mark - GL Setup diff --git a/submodules/LegacyComponents/Sources/TGPaintInput.m b/submodules/LegacyComponents/Sources/TGPaintInput.m index edd7dacfb0..ed38085d09 100644 --- a/submodules/LegacyComponents/Sources/TGPaintInput.m +++ b/submodules/LegacyComponents/Sources/TGPaintInput.m @@ -165,11 +165,13 @@ - (void)gestureCanceled:(UIGestureRecognizer *)recognizer { - TGPaintCanvas *canvas = (TGPaintCanvas *) recognizer.view; - TGPainting *painting = canvas.painting; + TGPaintCanvas *canvas = (TGPaintCanvas *) recognizer.view; + TGPainting *painting = canvas.painting; - painting.activePath = nil; - [canvas draw]; + [painting performAsynchronouslyInContext:^{ + painting.activePath = nil; + [canvas draw]; + }]; } - (void)paintPath:(TGPaintPath *)path inCanvas:(TGPaintCanvas *)canvas diff --git a/submodules/LegacyComponents/Sources/TGPaintPanGestureRecognizer.m b/submodules/LegacyComponents/Sources/TGPaintPanGestureRecognizer.m index 3686b96699..15fb86a144 100644 --- a/submodules/LegacyComponents/Sources/TGPaintPanGestureRecognizer.m +++ b/submodules/LegacyComponents/Sources/TGPaintPanGestureRecognizer.m @@ -16,7 +16,11 @@ - (void)touchesMoved:(NSSet *)inTouches withEvent:(UIEvent *)event { _touches = [inTouches copy]; - [super touchesMoved:inTouches withEvent:event]; + if (inTouches.count > 1) { + self.state = UIGestureRecognizerStateCancelled; + } else { + [super touchesMoved:inTouches withEvent:event]; + } } - (void)touchesEnded:(NSSet *)inTouches withEvent:(UIEvent *)event diff --git a/submodules/LegacyComponents/Sources/TGPaintRender.m b/submodules/LegacyComponents/Sources/TGPaintRender.m index 34718daf90..d23fc0adc2 100644 --- a/submodules/LegacyComponents/Sources/TGPaintRender.m +++ b/submodules/LegacyComponents/Sources/TGPaintRender.m @@ -168,7 +168,8 @@ typedef struct for (f = state.remainder; f <= distance; f += step, pressure += pressureStep) { CGFloat alpha = boldenFirst ? boldenedAlpha : state.alpha; - CGFloat brushSize = MIN(brushWeight, brushWeight - pressure * brushWeight * 0.55f); + CGFloat brushSize = brushWeight; +// CGFloat brushSize = MIN(brushWeight, brushWeight - pressure * brushWeight * 0.55f); [state addPoint:start size:brushSize angle:vectorAngle alpha:alpha index:i]; start = TGPaintAddPoints(start, TGPaintMultiplyPoint(unitVector, step)); diff --git a/submodules/LegacyComponents/Sources/TGPainting.h b/submodules/LegacyComponents/Sources/TGPainting.h index da659750fb..b2834dc781 100644 --- a/submodules/LegacyComponents/Sources/TGPainting.h +++ b/submodules/LegacyComponents/Sources/TGPainting.h @@ -27,6 +27,7 @@ - (instancetype)initWithSize:(CGSize)size undoManager:(TGPaintUndoManager *)undoManager imageData:(NSData *)imageData; +- (void)performSynchronouslyInContext:(void (^)(void))block; - (void)performAsynchronouslyInContext:(void (^)(void))block; - (void)paintStroke:(TGPaintPath *)path clearBuffer:(bool)clearBuffer completion:(void (^)(void))completion; diff --git a/submodules/LegacyComponents/Sources/TGPainting.m b/submodules/LegacyComponents/Sources/TGPainting.m index cce8fe48b3..bfc20bcd67 100644 --- a/submodules/LegacyComponents/Sources/TGPainting.m +++ b/submodules/LegacyComponents/Sources/TGPainting.m @@ -145,6 +145,15 @@ #pragma mark - +- (void)performSynchronouslyInContext:(void (^)(void))block +{ + [_queue dispatch:^ + { + [EAGLContext setCurrentContext:self.context]; + block(); + } synchronous:true]; +} + - (void)performAsynchronouslyInContext:(void (^)(void))block { [_queue dispatch:^ diff --git a/submodules/LegacyComponents/Sources/TGPaintingData.m b/submodules/LegacyComponents/Sources/TGPaintingData.m index 0f30568609..2cffd2f833 100644 --- a/submodules/LegacyComponents/Sources/TGPaintingData.m +++ b/submodules/LegacyComponents/Sources/TGPaintingData.m @@ -11,24 +11,34 @@ @interface TGPaintingData () { UIImage *_image; + UIImage *_stillImage; NSData *_data; UIImage *(^_imageRetrievalBlock)(void); + UIImage *(^_stillImageRetrievalBlock)(void); } @end @implementation TGPaintingData -+ (instancetype)dataWithPaintingData:(NSData *)data image:(UIImage *)image entities:(NSArray *)entities undoManager:(TGPaintUndoManager *)undoManager ++ (instancetype)dataWithPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)stillImage entities:(NSArray *)entities undoManager:(TGPaintUndoManager *)undoManager { TGPaintingData *paintingData = [[TGPaintingData alloc] init]; paintingData->_data = data; paintingData->_image = image; + paintingData->_stillImage = stillImage; paintingData->_entities = entities; paintingData->_undoManager = undoManager; return paintingData; } ++ (instancetype)dataWithPaintingImagePath:(NSString *)imagePath entities:(NSArray *)entities { + TGPaintingData *paintingData = [[TGPaintingData alloc] init]; + paintingData->_imagePath = imagePath; + paintingData->_entities = entities; + return paintingData; +} + + (instancetype)dataWithPaintingImagePath:(NSString *)imagePath { TGPaintingData *paintingData = [[TGPaintingData alloc] init]; @@ -44,7 +54,7 @@ NSURL *imageUrl = nil; NSData *compressedData = TGPaintGZipDeflate(data.data); - [context setPaintingData:compressedData image:data.image forItem:item dataUrl:&dataUrl imageUrl:&imageUrl forVideo:video]; + [context setPaintingData:compressedData image:data.image stillImage:data.stillImage forItem:item dataUrl:&dataUrl imageUrl:&imageUrl forVideo:video]; __weak TGMediaEditingContext *weakContext = context; [[SQueue mainQueue] dispatch:^ @@ -61,6 +71,15 @@ return nil; }; + + data->_stillImageRetrievalBlock = ^UIImage * + { + __strong TGMediaEditingContext *strongContext = weakContext; + if (strongContext != nil) + return [strongContext stillPaintingImageForItem:item]; + + return nil; + }; }]; }]; } @@ -99,6 +118,16 @@ return nil; } +- (UIImage *)stillImage +{ + if (_stillImage != nil) + return _stillImage; + else if (_stillImageRetrievalBlock != nil) + return _stillImageRetrievalBlock(); + else + return nil; +} + - (NSArray *)stickers { NSMutableSet *stickers = [[NSMutableSet alloc] init]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m index cb89ad2dfc..fbe55c5f8a 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m @@ -845,6 +845,7 @@ case TGPhotoEditorPaintTab: { TGPhotoPaintController *paintController = [[TGPhotoPaintController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView]; + paintController.stickersContext = _stickersContext; paintController.toolbarLandscapeSize = TGPhotoEditorToolbarSize; paintController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView, bool *noTransitionView) @@ -1408,12 +1409,12 @@ [_currentTabController prepareTransitionOutSaving:true]; TGPaintingData *paintingData = _photoEditor.paintingData; + bool saving = true; if ([_currentTabController isKindOfClass:[TGPhotoPaintController class]]) { TGPhotoPaintController *paintController = (TGPhotoPaintController *)_currentTabController; paintingData = [paintController paintingData]; - _photoEditor.paintingData = paintingData; if (paintingData != nil) @@ -1484,27 +1485,40 @@ UIImage *image = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef); - CGSize thumbnailSize = TGPhotoThumbnailSizeForCurrentScreen(); - thumbnailSize.width = CGCeil(thumbnailSize.width); - thumbnailSize.height = CGCeil(thumbnailSize.height); - - CGSize fillSize = TGScaleToFillSize(videoDimensions, thumbnailSize); - - UIImage *thumbnailImage = nil; + UIImage *paintingImage = adjustments.paintingData.stillImage; + if (paintingImage == nil) { + paintingImage = adjustments.paintingData.image; + } + CGSize fillSize = TGScaleToFillSize(videoDimensions, image.size); + UIImage *fullImage = nil; UIGraphicsBeginImageContextWithOptions(fillSize, true, 0.0f); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetInterpolationQuality(context, kCGInterpolationMedium); [image drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)]; + [paintingImage drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)]; - if (adjustments.paintingData.image != nil) - [adjustments.paintingData.image drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)]; + fullImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + CGSize thumbnailSize = TGPhotoThumbnailSizeForCurrentScreen(); + thumbnailSize.width = CGCeil(thumbnailSize.width); + thumbnailSize.height = CGCeil(thumbnailSize.height); + + fillSize = TGScaleToFillSize(videoDimensions, thumbnailSize); + UIImage *thumbnailImage = nil; + UIGraphicsBeginImageContextWithOptions(fillSize, true, 0.0f); + context = UIGraphicsGetCurrentContext(); + CGContextSetInterpolationQuality(context, kCGInterpolationMedium); + + [image drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)]; + [paintingImage drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)]; thumbnailImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - [_editingContext setImage:image thumbnailImage:thumbnailImage forItem:_item synchronous:true]; + [_editingContext setImage:fullImage thumbnailImage:thumbnailImage forItem:_item synchronous:true]; }]; }]; } diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m index 5d0c996697..4c0bf212de 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m @@ -23,6 +23,11 @@ return UIColorRGBA(0x000000, 0.7f); } ++ (UIColor *)toolbarIconColor +{ + return [UIColor whiteColor]; +} + + (UIColor *)accentColor { TGMediaAssetsPallete *pallete = nil; @@ -47,74 +52,69 @@ return UIColorRGB(0xd1d1d1); } -+ (UIImage *)captionIcon -{ - return TGComponentsImageNamed(@"PhotoEditorCaption.png"); -} - + (UIImage *)cropIcon { - return TGComponentsImageNamed(@"PhotoEditorCrop.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Crop"], [self toolbarIconColor]); } + (UIImage *)toolsIcon { - return TGComponentsImageNamed(@"PhotoEditorTools.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Adjustments"], [self toolbarIconColor]); } + (UIImage *)rotateIcon { - return TGComponentsImageNamed(@"PhotoEditorRotateIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Rotate"], [self toolbarIconColor]); } + (UIImage *)paintIcon { - return TGComponentsImageNamed(@"PhotoEditorPaint.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Drawing"], [self toolbarIconColor]); } + (UIImage *)stickerIcon { - return TGComponentsImageNamed(@"PaintStickersIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/AddSticker"], [self toolbarIconColor]); } + (UIImage *)textIcon { - return TGComponentsImageNamed(@"PaintTextIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/AddText"], [self toolbarIconColor]); } + (UIImage *)eraserIcon { - return TGComponentsImageNamed(@"PaintEraserIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Eraser"], [self toolbarIconColor]); } + (UIImage *)mirrorIcon { - return TGComponentsImageNamed(@"PhotoEditorMirrorIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Flip"], [self toolbarIconColor]); } + (UIImage *)aspectRatioIcon { - return TGComponentsImageNamed(@"PhotoEditorAspectRatioIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/AspectRatio"], [self toolbarIconColor]); } + (UIImage *)aspectRatioActiveIcon { - return TGTintedImage(TGComponentsImageNamed(@"PhotoEditorAspectRatioIcon.png"), [self accentColor]); + return TGTintedImage([UIImage imageNamed:@"Editor/AspectRatio"], [self accentColor]); } + (UIImage *)tintIcon { - return TGComponentsImageNamed(@"PhotoEditorTintIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Tint"], [self toolbarIconColor]); } + (UIImage *)blurIcon { - return TGComponentsImageNamed(@"PhotoEditorBlurIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Blur"], [self toolbarIconColor]); } + (UIImage *)curvesIcon { - return TGComponentsImageNamed(@"PhotoEditorCurvesIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Curves"], [self toolbarIconColor]); } + (UIImage *)gifBackgroundImage @@ -207,9 +207,13 @@ + (UIImage *)qualityIconForPreset:(TGMediaVideoConversionPreset)preset { - UIImage *background = TGComponentsImageNamed(@"PhotoEditorQuality"); + CGSize size = CGSizeMake(27.0f, 22.0f); + CGRect rect = CGRectInset(CGRectMake(0.0f, 0.0f, size.width, size.height), 1.0, 1.0); + UIGraphicsBeginImageContextWithOptions(size, false, 0.0f); - UIGraphicsBeginImageContextWithOptions(background.size, false, 0.0f); + CGContextRef context = UIGraphicsGetCurrentContext(); + + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:5.0f]; NSString *label = @""; switch (preset) @@ -239,12 +243,15 @@ break; } - [background drawAtPoint:CGPointZero]; - + CGContextAddPath(context, path.CGPath); + CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); + CGContextSetLineWidth(context, 2.0f - TGScreenPixel); + CGContextStrokePath(context); + UIFont *font = [TGFont roundedFontOfSize:11]; - CGSize size = [label sizeWithFont:font]; + CGSize textSize = [label sizeWithFont:font]; [[UIColor whiteColor] setFill]; - [label drawInRect:CGRectMake(floor(background.size.width - size.width) / 2.0f, 8.0f, size.width, size.height) withFont:font]; + [label drawInRect:CGRectMake(floor(size.width - textSize.width) / 2.0f, 4.0f, textSize.width, textSize.height) withFont:font]; UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m b/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m index bb5567cc99..ad373bcab0 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m @@ -172,7 +172,7 @@ { [self performTransitionInWithCompletion:nil]; } - else if (_delayedImage != nil) + else if (_delayedImage != nil && [_delayedImage isKindOfClass:[UIImage class]]) { UIImageView *transitionView = [[UIImageView alloc] initWithFrame:_snapshotView.frame]; transitionView.image = ((UIImageView *)_snapshotView).image; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.h b/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.h index 6d321c5829..fa0909d874 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.h +++ b/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.h @@ -15,7 +15,7 @@ - (void)handlePinch:(UIPinchGestureRecognizer *)gestureRecognizer; - (void)handleRotate:(UIRotationGestureRecognizer *)gestureRecognizer; -- (UIImage *)imageInRect:(CGRect)rect background:(UIImage *)background; +- (UIImage *)imageInRect:(CGRect)rect background:(UIImage *)background still:(bool)still; - (bool)isTrackingAnyEntityView; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m b/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m index 242bc35188..f10718f5d0 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m @@ -219,7 +219,7 @@ return nil; } -- (UIImage *)imageInRect:(CGRect)rect background:(UIImage *)background +- (UIImage *)imageInRect:(CGRect)rect background:(UIImage *)background still:(bool)still { if (self.subviews.count < 2) return nil; @@ -241,13 +241,15 @@ { TGPhotoStickerEntityView *stickerView = (TGPhotoStickerEntityView *)view; UIImage *image = stickerView.image; - CGSize fittedSize = TGScaleToSize(image.size, view.bounds.size); - - CGContextTranslateCTM(context, view.bounds.size.width / 2.0f, view.bounds.size.height / 2.0f); - if (stickerView.isMirrored) - CGContextScaleCTM(context, -1, 1); - - [image drawInRect:CGRectMake(-fittedSize.width / 2.0f, -fittedSize.height / 2.0f, fittedSize.width, fittedSize.height)]; + if (image != nil) { + CGSize fittedSize = TGScaleToSize(image.size, view.bounds.size); + + CGContextTranslateCTM(context, view.bounds.size.width / 2.0f, view.bounds.size.height / 2.0f); + if (stickerView.isMirrored) + CGContextScaleCTM(context, -1, 1); + + [image drawInRect:CGRectMake(-fittedSize.width / 2.0f, -fittedSize.height / 2.0f, fittedSize.width, fittedSize.height)]; + } }]; } else if ([view isKindOfClass:[TGPhotoTextEntityView class]]) diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintActionsView.m b/submodules/LegacyComponents/Sources/TGPhotoPaintActionsView.m index 1ea991476d..8b3ca16860 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintActionsView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintActionsView.m @@ -2,6 +2,7 @@ #import "LegacyComponentsInternal.h" #import "TGFont.h" +#import "TGImageUtils.h" #import @@ -24,7 +25,7 @@ _undoButton.adjustsImageWhenDisabled = false; _undoButton.enabled = false; _undoButton.exclusiveTouch = true; - [_undoButton setImage:TGComponentsImageNamed(@"PaintUndoIcon") forState:UIControlStateNormal]; + [_undoButton setImage:TGTintedImage([UIImage imageNamed:@"Editor/Undo"], [UIColor whiteColor]) forState:UIControlStateNormal]; [_undoButton addTarget:self action:@selector(undoButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:_undoButton]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintController.h b/submodules/LegacyComponents/Sources/TGPhotoPaintController.h index d343bc4453..613341b8b2 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintController.h +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintController.h @@ -5,8 +5,12 @@ @class PGPhotoEditor; @class TGPhotoEditorPreviewView; +@protocol TGPhotoPaintStickersContext; + @interface TGPhotoPaintController : TGPhotoEditorTabController +@property (nonatomic, strong) id stickersContext; + - (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView; - (TGPaintingData *)paintingData; diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintController.m b/submodules/LegacyComponents/Sources/TGPhotoPaintController.m index e8a25a309f..3e808558a6 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintController.m @@ -12,7 +12,6 @@ #import #import -#import #import "TGMenuSheetController.h" @@ -28,6 +27,8 @@ #import "TGPaintingWrapperView.h" #import "TGPaintState.h" #import "TGPaintBrushPreview.h" +#import "TGPaintSwatch.h" +#import "TGPhotoPaintFont.h" #import #import "PGPhotoEditor.h" @@ -53,7 +54,7 @@ const CGFloat TGPhotoPaintTopPanelSize = 44.0f; const CGFloat TGPhotoPaintBottomPanelSize = 79.0f; const CGSize TGPhotoPaintingLightMaxSize = { 1280.0f, 1280.0f }; -const CGSize TGPhotoPaintingMaxSize = { 1600.0f, 1600.0f }; +const CGSize TGPhotoPaintingMaxSize = { 1920.0f, 1920.0f }; const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; @@ -63,6 +64,9 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; TGObserverProxy *_keyboardWillChangeFrameProxy; CGFloat _keyboardHeight; + TGModernGalleryZoomableScrollView *_scrollView; + UIView *_scrollContentView; + UIButton *_containerView; TGPhotoPaintSparseView *_wrapperView; UIView *_portraitToolsWrapperView; @@ -76,7 +80,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; TGPaintCanvas *_canvasView; TGPaintBrushPreview *_brushPreview; - UIView *_scrollView; + CGSize _previousSize; + UIView *_contentView; UIView *_contentWrapperView; UIView *_dimView; @@ -110,14 +115,9 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; TGMenuContainerView *_menuContainerView; TGPaintingData *_resultData; - - AVPlayer *_player; - SMetaDisposable *_playerItemDisposable; - id _playerStartedObserver; - id _playerReachedEndObserver; - + UIImage *_stillImage; + TGPaintingWrapperView *_paintingWrapperView; - TGModernGalleryVideoView *_videoView; SMetaDisposable *_faceDetectorDisposable; NSArray *_faces; @@ -176,18 +176,30 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; { [_actionHandle reset]; [_faceDetectorDisposable dispose]; - [_playerItemDisposable dispose]; } - (void)loadView { [super loadView]; self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:self.view.bounds hasDoubleTap:false]; + if (iosMajorVersion() >= 11) { + _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } + _scrollView.contentInset = UIEdgeInsetsZero; + _scrollView.delegate = self; + _scrollView.showsHorizontalScrollIndicator = false; + _scrollView.showsVerticalScrollIndicator = false; + [self.view addSubview:_scrollView]; + + _scrollContentView = [[UIView alloc] initWithFrame:self.view.bounds]; + [_scrollView addSubview:_scrollContentView]; _containerView = [[UIButton alloc] initWithFrame:self.view.bounds]; _containerView.clipsToBounds = true; [_containerView addTarget:self action:@selector(containerPressed) forControlEvents:UIControlEventTouchUpInside]; - [self.view addSubview:_containerView]; + [_scrollContentView addSubview:_containerView]; _pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)]; _pinchGestureRecognizer.delegate = self; @@ -215,14 +227,14 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; }; [_containerView addSubview:_paintingWrapperView]; - _scrollView = [[UIView alloc] init]; - _scrollView.clipsToBounds = true; - _scrollView.userInteractionEnabled = false; - [_containerView addSubview:_scrollView]; + _contentView = [[UIView alloc] init]; + _contentView.clipsToBounds = true; + _contentView.userInteractionEnabled = false; + [_containerView addSubview:_contentView]; _contentWrapperView = [[UIView alloc] init]; _contentWrapperView.userInteractionEnabled = false; - [_scrollView addSubview:_contentWrapperView]; + [_contentView addSubview:_contentWrapperView]; _entitiesContainerView = [[TGPhotoEntitiesContainerView alloc] init]; _entitiesContainerView.clipsToBounds = true; @@ -591,6 +603,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (void)_clearCurrentSelection { + _scrollView.pinchGestureRecognizer.enabled = true; _currentEntityView = nil; if (_entitySelectionView != nil) { @@ -622,6 +635,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; UIImage *image = _painting.isEmpty ? nil : [_painting imageWithSize:fittedSize andData:&data]; NSMutableArray *entities = [[NSMutableArray alloc] init]; + bool hasAnimatedEntities = false; + UIImage *stillImage = nil; if (image == nil && _entitiesContainerView.entitiesCount < 1) { _resultData = nil; @@ -629,20 +644,46 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; } else if (_entitiesContainerView.entitiesCount > 0) { - image = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:image]; - for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) { if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) continue; TGPhotoPaintEntity *entity = [view entity]; - if (entity != nil) + if (entity != nil) { + if (entity.animated) { + hasAnimatedEntities = true; + } [entities addObject:entity]; + } + } + + if (hasAnimatedEntities) { + for (TGPhotoPaintEntity *entity in entities) { + if ([entity isKindOfClass:[TGPhotoPaintTextEntity class]]) { + TGPhotoPaintTextEntity *textEntity = (TGPhotoPaintTextEntity *)entity; + for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) + { + if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) + continue; + + if (view.entityUUID == textEntity.uuid) { + textEntity.renderImage = [(TGPhotoTextEntityView *)view image]; + break; + } + } + } + } + } + + if (!hasAnimatedEntities) { + image = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:image still:false]; + } else { + stillImage = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:image still:true]; } } - _resultData = [TGPaintingData dataWithPaintingData:data image:image entities:entities undoManager:_undoManager]; + _resultData = [TGPaintingData dataWithPaintingData:data image:image stillImage:stillImage entities:entities undoManager:_undoManager]; return _resultData; } @@ -678,6 +719,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; _currentEntityView = view; [self updateSettingsButton]; + _scrollView.pinchGestureRecognizer.enabled = _currentEntityView == nil; + if (view != nil) { [_currentEntityView.superview bringSubviewToFront:_currentEntityView]; @@ -946,7 +989,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (TGPhotoStickerEntityView *)_createStickerViewWithEntity:(TGPhotoPaintStickerEntity *)entity { - TGPhotoStickerEntityView *stickerView = [[TGPhotoStickerEntityView alloc] initWithEntity:entity]; + TGPhotoStickerEntityView *stickerView = [[TGPhotoStickerEntityView alloc] initWithEntity:entity context:self.stickersContext]; [self _commonEntityViewSetup:stickerView entity:entity]; [_entitiesContainerView addSubview:stickerView]; @@ -987,61 +1030,70 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (void)presentStickersView { - if (_stickersView == nil) - { - TGPhotoStickersView *view = [[TGPhotoStickersView alloc] initWithContext:_context frame:self.view.bounds]; - view.parentViewController = self; - - __weak TGPhotoPaintController *weakSelf = self; - __weak TGPhotoStickersView *weakStickersView = view; - view.stickerSelected = ^(TGDocumentMediaAttachment *document, CGPoint transitionPoint, TGPhotoStickersView *stickersView, UIView *snapshotView) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf createNewStickerWithDocument:document transitionPoint:transitionPoint stickersView:stickersView snapshotView:snapshotView]; - }; - view.dismissed = ^ - { - __strong TGPhotoStickersView *strongStickersView = weakStickersView; - if (strongStickersView != nil) - [strongStickersView removeFromSuperview]; - }; - - _stickersView = view; - } + __weak TGPhotoPaintController *weakSelf = self; + _stickersContext.presentStickersController(^(id document, bool animated, UIView *view, CGRect rect) { + __strong TGPhotoPaintController *strongSelf = weakSelf; + if (strongSelf != nil) { + UIView *snapshot = [view snapshotViewAfterScreenUpdates:false]; + [strongSelf createNewStickerWithDocument:document animated:animated transitionPoint:CGPointZero stickersView:nil snapshotView:nil]; + } + }); - if ([_context currentSizeClass] == UIUserInterfaceSizeClassCompact) - { - _stickersView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _stickersView.frame = self.view.bounds; - _stickersView.safeAreaInset = self.controllerSafeAreaInset; - [self.parentViewController.view addSubview:_stickersView]; - } - else - { - _settingsView = _stickersView; - [_stickersView sizeToFit]; - - UIView *wrapper = [self settingsViewWrapper]; - wrapper.userInteractionEnabled = true; - [wrapper addSubview:_stickersView]; - - [self viewWillLayoutSubviews]; - } - - _stickersView.outerView = self.parentViewController.view; - _stickersView.targetView = _contentWrapperView; - - [_stickersView present]; +// if (_stickersView == nil) +// { +// TGPhotoStickersView *view = [[TGPhotoStickersView alloc] initWithContext:_context frame:self.view.bounds]; +// view.parentViewController = self; +// +// __weak TGPhotoPaintController *weakSelf = self; +// __weak TGPhotoStickersView *weakStickersView = view; +// view.stickerSelected = ^(TGDocumentMediaAttachment *document, CGPoint transitionPoint, TGPhotoStickersView *stickersView, UIView *snapshotView) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) +// [strongSelf createNewStickerWithDocument:document transitionPoint:transitionPoint stickersView:stickersView snapshotView:snapshotView]; +// }; +// view.dismissed = ^ +// { +// __strong TGPhotoStickersView *strongStickersView = weakStickersView; +// if (strongStickersView != nil) +// [strongStickersView removeFromSuperview]; +// }; +// +// _stickersView = view; +// } +// +// if ([_context currentSizeClass] == UIUserInterfaceSizeClassCompact) +// { +// _stickersView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; +// _stickersView.frame = self.view.bounds; +// _stickersView.safeAreaInset = self.controllerSafeAreaInset; +// [self.parentViewController.view addSubview:_stickersView]; +// } +// else +// { +// _settingsView = _stickersView; +// [_stickersView sizeToFit]; +// +// UIView *wrapper = [self settingsViewWrapper]; +// wrapper.userInteractionEnabled = true; +// [wrapper addSubview:_stickersView]; +// +// [self viewWillLayoutSubviews]; +// } +// +// _stickersView.outerView = self.parentViewController.view; +// _stickersView.targetView = _contentWrapperView; +// +// [_stickersView present]; } -- (void)createNewStickerWithDocument:(TGDocumentMediaAttachment *)document transitionPoint:(CGPoint)transitionPoint stickersView:(TGPhotoStickersView *)stickersView snapshotView:(UIView *)snapshotView +- (void)createNewStickerWithDocument:(id)document animated:(bool)animated transitionPoint:(CGPoint)transitionPoint stickersView:(TGPhotoStickersView *)stickersView snapshotView:(UIView *)snapshotView { - TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:document baseSize:[self _stickerBaseSizeForCurrentPainting]]; + TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:document baseSize:[self _stickerBaseSizeForCurrentPainting] animated:animated]; [self _setStickerEntityPosition:entity]; TGPhotoStickerEntityView *stickerView = [self _createStickerViewWithEntity:entity]; - stickerView.hidden = true; +// stickerView.hidden = true; CGFloat rotation = entity.angle - [self startRotation]; @@ -1059,6 +1111,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; [self selectEntityView:stickerView]; _entitySelectionView.alpha = 0.0f; + [_entitySelectionView fadeIn]; + [self _registerEntityRemovalUndo:entity]; [self updateActionsView]; } @@ -1118,7 +1172,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; _dimView.alpha = 1.0f; }; - _scrollView.userInteractionEnabled = true; + _contentView.userInteractionEnabled = true; _contentWrapperView.userInteractionEnabled = true; if (iosMajorVersion() >= 7) @@ -1141,7 +1195,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (void)sendTextEntityViewBack { - _scrollView.userInteractionEnabled = false; + _contentView.userInteractionEnabled = false; _contentWrapperView.userInteractionEnabled = false; _dimView.userInteractionEnabled = false; @@ -1226,7 +1280,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (CGFloat)_brushBaseWeightForCurrentPainting { - return 25.0f / TGPhotoPaintingMaxSize.width * _painting.size.width; + return 15.0f / TGPhotoPaintingMaxSize.width * _painting.size.width; } - (CGFloat)_brushWeightRangeForCurrentPainting @@ -1489,14 +1543,106 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (void)resetScrollView { - CGSize contentSize = [self fittedContentSize]; + CGSize fittedContentSize = [self fittedContentSize]; CGRect fittedCropRect = [self fittedCropRect:false]; + _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, fittedContentSize.width, fittedContentSize.height); - _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, contentSize.width, contentSize.height); - - CGFloat scale = _scrollView.bounds.size.width / fittedCropRect.size.width; + CGFloat scale = _contentView.bounds.size.width / fittedCropRect.size.width; _contentWrapperView.transform = CGAffineTransformMakeScale(scale, scale); - _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, _scrollView.bounds.size.width, _scrollView.bounds.size.height); + _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, _contentView.bounds.size.width, _contentView.bounds.size.height); + + CGSize contentSize = [self contentSize]; + _scrollView.minimumZoomScale = 1.0f; + _scrollView.maximumZoomScale = 1.0f; + _scrollView.normalZoomScale = 1.0f; + _scrollView.zoomScale = 1.0f; + _scrollView.contentSize = contentSize; + [self contentView].frame = CGRectMake(0.0f, 0.0f, contentSize.width, contentSize.height); + + [self adjustZoom]; + _scrollView.zoomScale = _scrollView.normalZoomScale; +} + +- (void)scrollViewWillBeginZooming:(UIScrollView *)__unused scrollView withView:(UIView *)__unused view +{ +} + +- (void)scrollViewDidZoom:(UIScrollView *)__unused scrollView +{ + [self adjustZoom]; +} + +- (void)scrollViewDidEndZooming:(UIScrollView *)__unused scrollView withView:(UIView *)__unused view atScale:(CGFloat)__unused scale +{ + [self adjustZoom]; + + if (_scrollView.zoomScale < _scrollView.normalZoomScale - FLT_EPSILON) + { + [TGHacks setAnimationDurationFactor:0.5f]; + [_scrollView setZoomScale:_scrollView.normalZoomScale animated:true]; + [TGHacks setAnimationDurationFactor:1.0f]; + } +} + +- (UIView *)contentView +{ + return _scrollContentView; +} + +- (CGSize)contentSize +{ + return _scrollView.frame.size; +} + +- (UIView *)viewForZoomingInScrollView:(UIScrollView *)__unused scrollView +{ + return [self contentView]; +} + +- (void)adjustZoom +{ + CGSize contentSize = [self contentSize]; + CGSize boundsSize = _scrollView.frame.size; + if (contentSize.width < FLT_EPSILON || contentSize.height < FLT_EPSILON || boundsSize.width < FLT_EPSILON || boundsSize.height < FLT_EPSILON) + return; + + CGFloat scaleWidth = boundsSize.width / contentSize.width; + CGFloat scaleHeight = boundsSize.height / contentSize.height; + CGFloat minScale = MIN(scaleWidth, scaleHeight); + CGFloat maxScale = MAX(scaleWidth, scaleHeight); + maxScale = MAX(maxScale, minScale * 3.0f); + + if (ABS(maxScale - minScale) < 0.01f) + maxScale = minScale; + + + if (iosMajorVersion() >= 11) { + _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } + _scrollView.contentInset = UIEdgeInsetsZero; + + if (_scrollView.minimumZoomScale != 0.05f) + _scrollView.minimumZoomScale = 0.05f; + if (_scrollView.normalZoomScale != minScale) + _scrollView.normalZoomScale = minScale; + if (_scrollView.maximumZoomScale != maxScale) + _scrollView.maximumZoomScale = maxScale; + + CGRect contentFrame = [self contentView].frame; + + if (boundsSize.width > contentFrame.size.width) + contentFrame.origin.x = (boundsSize.width - contentFrame.size.width) / 2.0f; + else + contentFrame.origin.x = 0; + + if (boundsSize.height > contentFrame.size.height) + contentFrame.origin.y = (boundsSize.height - contentFrame.size.height) / 2.0f; + else + contentFrame.origin.y = 0; + + [self contentView].frame = contentFrame; + + _scrollView.scrollEnabled = ABS(_scrollView.zoomScale - _scrollView.normalZoomScale) > FLT_EPSILON; } #pragma mark - Gestures @@ -1513,6 +1659,9 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)__unused gestureRecognizer { + if (gestureRecognizer == _pinchGestureRecognizer && _currentEntityView == nil) { + return false; + } return !_canvasView.isTracking; } @@ -1615,15 +1764,13 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; [_contentWrapperView addSubview:_entitiesContainerView]; [self resetScrollView]; - - [self setupVideoPlaybackIfNeeded]; } - (void)prepareForCustomTransitionOut { _previewView.hidden = true; _canvasView.hidden = true; - _scrollView.hidden = true; + _contentView.hidden = true; [UIView animateWithDuration:0.3f animations:^ { _portraitToolsWrapperView.alpha = 0.0f; @@ -1651,8 +1798,6 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; if (completion != nil) completion(); }]; - - [_player pause]; } - (CGRect)transitionOutSourceFrameForReferenceFrame:(CGRect)referenceFrame orientation:(UIInterfaceOrientation)orientation @@ -1701,13 +1846,13 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; UIView *canvasSnapshotView = [_paintingWrapperView resizableSnapshotViewFromRect:[_paintingWrapperView convertRect:previewView.bounds fromView:previewView] afterScreenUpdates:false withCapInsets:UIEdgeInsetsZero]; canvasSnapshotView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - canvasSnapshotView.transform = _scrollView.transform; + canvasSnapshotView.transform = _contentView.transform; canvasSnapshotView.frame = snapshotView.bounds; [snapshotView addSubview:canvasSnapshotView]; UIView *entitiesSnapshotView = [_contentWrapperView resizableSnapshotViewFromRect:[_contentWrapperView convertRect:previewView.bounds fromView:previewView] afterScreenUpdates:false withCapInsets:UIEdgeInsetsZero]; entitiesSnapshotView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - entitiesSnapshotView.transform = _scrollView.transform; + entitiesSnapshotView.transform = _contentView.transform; entitiesSnapshotView.frame = snapshotView.bounds; [snapshotView addSubview:entitiesSnapshotView]; @@ -1728,21 +1873,19 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; POPSpringAnimation *previewAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame]; previewAnimation.fromValue = [NSValue valueWithCGRect:previewView.frame]; previewAnimation.toValue = [NSValue valueWithCGRect:targetFrame]; - if (_videoView == nil) - [animations addObject:previewAnimation]; + [animations addObject:previewAnimation]; POPSpringAnimation *previewAlphaAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewAlpha]; previewAlphaAnimation.fromValue = @(previewView.alpha); previewAlphaAnimation.toValue = @(0.0f); - if (_videoView == nil) - [animations addObject:previewAnimation]; + [animations addObject:previewAnimation]; POPSpringAnimation *entitiesAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewCenter]; - entitiesAnimation.fromValue = [NSValue valueWithCGPoint:_scrollView.center]; + entitiesAnimation.fromValue = [NSValue valueWithCGPoint:_contentView.center]; entitiesAnimation.toValue = [NSValue valueWithCGPoint:targetCenter]; [animations addObject:entitiesAnimation]; - CGFloat targetEntitiesScale = targetFrame.size.width / _scrollView.frame.size.width; + CGFloat targetEntitiesScale = targetFrame.size.width / _contentView.frame.size.width; POPSpringAnimation *entitiesScaleAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewScaleXY]; entitiesScaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)]; entitiesScaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(targetEntitiesScale, targetEntitiesScale)]; @@ -1782,9 +1925,9 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; [previewView pop_addAnimation:previewAnimation forKey:@"frame"]; [previewView pop_addAnimation:previewAlphaAnimation forKey:@"alpha"]; - [_scrollView pop_addAnimation:entitiesAnimation forKey:@"frame"]; - [_scrollView pop_addAnimation:entitiesScaleAnimation forKey:@"scale"]; - [_scrollView pop_addAnimation:entitiesAlphaAnimation forKey:@"alpha"]; + [_contentView pop_addAnimation:entitiesAnimation forKey:@"frame"]; + [_contentView pop_addAnimation:entitiesScaleAnimation forKey:@"scale"]; + [_contentView pop_addAnimation:entitiesAlphaAnimation forKey:@"alpha"]; [_paintingWrapperView pop_addAnimation:paintingAnimation forKey:@"frame"]; [_paintingWrapperView pop_addAnimation:paintingScaleAnimation forKey:@"scale"]; @@ -1792,7 +1935,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; if (saving) { - _scrollView.hidden = true; + _contentView.hidden = true; _paintingWrapperView.hidden = true; previewView.hidden = true; } @@ -1902,10 +2045,16 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; _landscapeToolsWrapperView.hidden = true; orientation = UIInterfaceOrientationPortrait; } - + CGSize referenceSize = [self referenceViewSize]; CGFloat screenSide = MAX(referenceSize.width, referenceSize.height) + 2 * TGPhotoPaintBottomPanelSize; + bool sizeUpdated = false; + if (!CGSizeEqualToSize(referenceSize, _previousSize)) { + sizeUpdated = true; + _previousSize = referenceSize; + } + CGFloat panelToolbarPortraitSize = TGPhotoPaintBottomPanelSize + TGPhotoEditorToolbarSize; CGFloat panelToolbarLandscapeSize = TGPhotoPaintBottomPanelSize + self.toolbarLandscapeSize; @@ -2035,9 +2184,15 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; CGFloat rotation = _photoEditor.cropRotation; CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(TGRotationForOrientation(cropOrientation)); - _scrollView.transform = rotationTransform; - _scrollView.frame = previewFrame; - [self resetScrollView]; + _contentView.transform = rotationTransform; + _contentView.frame = previewFrame; + + _scrollView.frame = self.view.bounds; + + if (sizeUpdated) { + [self resetScrollView]; + } + [self adjustZoom]; _paintingWrapperView.transform = CGAffineTransformMakeRotation(TGRotationForOrientation(cropOrientation)); _paintingWrapperView.frame = previewFrame; @@ -2063,7 +2218,6 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; _selectionContainerView.transform = CGAffineTransformRotate(rotationTransform, rotation); _selectionContainerView.frame = previewFrame; - _videoView.frame = originalFrame; _containerView.frame = CGRectMake(containerFrame.origin.x, containerFrame.origin.y + offsetHeight, containerFrame.size.width, containerFrame.size.height); } @@ -2113,104 +2267,20 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; } completion:nil]; } -#pragma mark - Video Playback - -- (void)setupVideoPlaybackIfNeeded -{ - if ((![self.item isKindOfClass:[TGMediaAsset class]] || !((TGMediaAsset *)self.item).isVideo) && ![self.item isKindOfClass:[AVAsset class]]) - return; - - SSignal *itemSignal = [self.item isKindOfClass:[TGMediaAsset class]] ? [TGMediaAssetImageSignals playerItemForVideoAsset:(TGMediaAsset *)self.item] : [SSignal single:[AVPlayerItem playerItemWithAsset:((AVAsset *)self.item)]]; - ; - - __weak TGPhotoPaintController *weakSelf = self; - [_playerItemDisposable setDisposable:[[itemSignal deliverOn:[SQueue mainQueue]] startWithNext:^(AVPlayerItem *playerItem) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_player = [AVPlayer playerWithPlayerItem:playerItem]; - strongSelf->_player.actionAtItemEnd = AVPlayerActionAtItemEndNone; - strongSelf->_player.muted = true; - - NSTimeInterval startPosition = 0.0f; - if (strongSelf->_photoEditor.trimStartValue > DBL_EPSILON) - startPosition = strongSelf->_photoEditor.trimStartValue; - - CMTime targetTime = CMTimeMakeWithSeconds(startPosition, NSEC_PER_SEC); - [strongSelf->_player.currentItem seekToTime:targetTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; - - [strongSelf _setupPlaybackStartedObserver]; - - strongSelf->_videoView = [[TGModernGalleryVideoView alloc] initWithFrame:strongSelf->_previewView.frame player:strongSelf->_player]; - strongSelf->_videoView.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; - strongSelf->_videoView.playerLayer.opaque = false; - strongSelf->_videoView.playerLayer.backgroundColor = nil; - [strongSelf->_paintingWrapperView insertSubview:strongSelf->_videoView atIndex:0]; - - [strongSelf->_player play]; - - [strongSelf updateLayout:strongSelf.interfaceOrientation]; - }]]; -} - -- (void)_setupPlaybackStartedObserver -{ - CMTime startTime = CMTimeMake(10, 100); - if (_photoEditor.trimStartValue > DBL_EPSILON) - startTime = CMTimeMakeWithSeconds(_photoEditor.trimStartValue + 0.1, NSEC_PER_SEC); - - __weak TGPhotoPaintController *weakSelf = self; - _playerStartedObserver = [_player addBoundaryTimeObserverForTimes:@[[NSValue valueWithCMTime:startTime]] queue:NULL usingBlock:^ - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf->_player removeTimeObserver:strongSelf->_playerStartedObserver]; - strongSelf->_playerStartedObserver = nil; - - if (CMTimeGetSeconds(strongSelf->_player.currentItem.duration) > 0) - [strongSelf _setupPlaybackReachedEndObserver]; - }]; -} - -- (void)_setupPlaybackReachedEndObserver -{ - CMTime endTime = CMTimeSubtract(_player.currentItem.duration, CMTimeMake(10, 100)); - if (_photoEditor.trimEndValue > DBL_EPSILON && _photoEditor.trimEndValue < CMTimeGetSeconds(_player.currentItem.duration)) - endTime = CMTimeMakeWithSeconds(_photoEditor.trimEndValue - 0.1, NSEC_PER_SEC); - - CMTime startTime = CMTimeMake(5, 100); - if (_photoEditor.trimStartValue > DBL_EPSILON) - startTime = CMTimeMakeWithSeconds(_photoEditor.trimStartValue + 0.05, NSEC_PER_SEC); - - __weak TGPhotoPaintController *weakSelf = self; - _playerReachedEndObserver = [_player addBoundaryTimeObserverForTimes:@[[NSValue valueWithCMTime:endTime]] queue:NULL usingBlock:^ - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf->_player seekToTime:startTime]; - }]; -} - -#pragma mark - Face Detection - - (void)_setStickerEntityPosition:(TGPhotoPaintStickerEntity *)entity { TGDocumentMediaAttachment *document = entity.document; TGDocumentAttributeSticker *sticker = nil; - for (id attribute in document.attributes) - { - if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) - { - sticker = (TGDocumentAttributeSticker *)attribute; - break; - } - } +// for (id attribute in document.attributes) +// { +// if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) +// { +// sticker = (TGDocumentAttributeSticker *)attribute; +// break; +// } +// } - TGPhotoMaskPosition *position = [self _positionForMask:sticker documentId:document.documentId]; + TGPhotoMaskPosition *position = nil; // [self _positionForMask:sticker documentId:document.documentId]; if (position != nil) { entity.position = position.center; diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintSettingsView.m b/submodules/LegacyComponents/Sources/TGPhotoPaintSettingsView.m index f8380bb730..b39238cf07 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintSettingsView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintSettingsView.m @@ -147,15 +147,15 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f; switch (icon) { case TGPhotoPaintSettingsViewIconBrush: - iconImage = TGComponentsImageNamed(@"PaintBrushIcon"); + iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Brush"], [UIColor whiteColor]); break; case TGPhotoPaintSettingsViewIconText: - iconImage = TGComponentsImageNamed(@"PaintTextSettingsIcon"); + iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Font"], [UIColor whiteColor]); break; case TGPhotoPaintSettingsViewIconMirror: - iconImage = TGComponentsImageNamed(@"PaintMirrorIcon"); + iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Flip"], [UIColor whiteColor]); break; } diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintStickerEntity.m b/submodules/LegacyComponents/Sources/TGPhotoPaintStickerEntity.m index 68d7ab38a7..2b2e463413 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintStickerEntity.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintStickerEntity.m @@ -4,13 +4,16 @@ @implementation TGPhotoPaintStickerEntity -- (instancetype)initWithDocument:(TGDocumentMediaAttachment *)document baseSize:(CGSize)baseSize +@synthesize animated = _animated; + +- (instancetype)initWithDocument:(NSData *)document baseSize:(CGSize)baseSize animated:(bool)animated { self = [super init]; if (self != nil) { _document = document; _baseSize = baseSize; + _animated = animated; self.scale = 1.0; } return self; @@ -22,6 +25,7 @@ if (self != nil) { _emoji = emoji; + _animated = false; self.scale = 1.0f; } return self; @@ -31,7 +35,7 @@ { TGPhotoPaintStickerEntity *entity = nil; if (_document != nil) - entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:self.document baseSize:self.baseSize]; + entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:self.document baseSize:self.baseSize animated:self.animated]; else if (_emoji != nil) entity = [[TGPhotoPaintStickerEntity alloc] initWithEmoji:self.emoji]; else @@ -55,7 +59,7 @@ return false; TGPhotoPaintStickerEntity *entity = (TGPhotoPaintStickerEntity *)object; - return entity.uuid == self.uuid && [entity.document isEqual:self.document] && CGSizeEqualToSize(entity.baseSize, self.baseSize) && CGPointEqualToPoint(entity.position, self.position) && fabs(entity.scale - self.scale) < FLT_EPSILON && fabs(entity.angle - self.angle) < FLT_EPSILON && entity.mirrored == self.mirrored; + return entity.uuid == self.uuid && CGSizeEqualToSize(entity.baseSize, self.baseSize) && CGPointEqualToPoint(entity.position, self.position) && fabs(entity.scale - self.scale) < FLT_EPSILON && fabs(entity.angle - self.angle) < FLT_EPSILON && entity.mirrored == self.mirrored; } @end diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.m b/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.m index ddc83ca5f9..7663338ae6 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.m @@ -1,5 +1,8 @@ #import "TGPhotoPaintTextEntity.h" +#import "TGPhotoPaintFont.h" +#import "TGPaintSwatch.h" + @implementation TGPhotoPaintTextEntity - (instancetype)initWithText:(NSString *)text font:(TGPhotoPaintFont *)font swatch:(TGPaintSwatch *)swatch baseFontSize:(CGFloat)baseFontSize maxWidth:(CGFloat)maxWidth stroke:(bool)stroke @@ -30,6 +33,10 @@ return entity; } +- (bool)animated { + return false; +} + - (BOOL)isEqual:(id)object { if (object == self) diff --git a/submodules/LegacyComponents/Sources/TGPhotoQualityController.m b/submodules/LegacyComponents/Sources/TGPhotoQualityController.m index 5df162adfc..348146e4b1 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoQualityController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoQualityController.m @@ -664,7 +664,7 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f; { return [[[[[SSignal single:avAsset] delay:delay onQueue:[SQueue concurrentDefaultQueue]] mapToSignal:^SSignal *(AVAsset *avAsset) { - return [TGMediaVideoConverter convertAVAsset:avAsset adjustments:adjustments watcher:nil inhibitAudio:true]; + return [TGMediaVideoConverter convertAVAsset:avAsset adjustments:adjustments watcher:nil inhibitAudio:true entityRenderer:nil]; }] onError:^(__unused id error) { delay = 1.0; }] retryIf:^bool(__unused id error) diff --git a/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.h b/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.h index b29daf8353..6e8cc7cd00 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.h +++ b/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.h @@ -5,13 +5,14 @@ @end +@protocol TGPhotoPaintStickersContext; @interface TGPhotoStickerEntityView : TGPhotoPaintEntityView @property (nonatomic, readonly) TGPhotoPaintStickerEntity *entity; @property (nonatomic, readonly) bool isMirrored; -- (instancetype)initWithEntity:(TGPhotoPaintStickerEntity *)entity; +- (instancetype)initWithEntity:(TGPhotoPaintStickerEntity *)entity context:(id)context; - (void)mirror; - (UIImage *)image; diff --git a/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.m b/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.m index a3b62cac08..21635b65d1 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.m @@ -4,14 +4,13 @@ #import #import +#import #import "TGDocumentMediaAttachment.h" #import "TGStringUtils.h" #import "TGImageUtils.h" #import "TGColor.h" -#import "TGImageView.h" - const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; @interface UIView (OpaquePixel) @@ -33,9 +32,10 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; @interface TGPhotoStickerEntityView () { - TGImageView *_imageView; - - TGDocumentMediaAttachment *_document; + UIView *_stickerView; + + id _document; + bool _animated; bool _mirrored; CGSize _baseSize; @@ -45,7 +45,7 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; @implementation TGPhotoStickerEntityView -- (instancetype)initWithEntity:(TGPhotoPaintStickerEntity *)entity +- (instancetype)initWithEntity:(TGPhotoPaintStickerEntity *)entity context:(id)context { self = [super initWithFrame:CGRectMake(0.0f, 0.0f, entity.baseSize.width, entity.baseSize.height)]; if (self != nil) @@ -54,58 +54,24 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; _baseSize = entity.baseSize; _mirrored = entity.isMirrored; - _imageView = [[TGImageView alloc] init]; - _imageView.contentMode = UIViewContentModeScaleAspectFit; - _imageView.expectExtendedEdges = true; - [self addSubview:_imageView]; + _stickerView = [context stickerViewForDocument:entity.document]; + [self addSubview:_stickerView]; - TGDocumentMediaAttachment *sticker = entity.document; - _document = sticker; + _document = entity.document; + _animated = entity.animated; - CGSize imageSize = CGSizeZero; - bool isSticker = false; - for (id attribute in sticker.attributes) - { - if ([attribute isKindOfClass:[TGDocumentAttributeImageSize class]]) - imageSize = ((TGDocumentAttributeImageSize *)attribute).size; - else if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) - isSticker = true; - } + CGSize imageSize = CGSizeMake(512.0f, 512.0f); CGSize displaySize = [self fittedSizeForSize:imageSize maxSize:CGSizeMake(512.0f, 512.0f)]; - - NSMutableString *imageUri = [[NSMutableString alloc] init]; - [imageUri appendString:@"sticker://?"]; - if (sticker.documentId != 0) - { - [imageUri appendFormat:@"&documentId=%" PRId64, sticker.documentId]; - - TGMediaOriginInfo *originInfo = sticker.originInfo ?: [TGMediaOriginInfo mediaOriginInfoForDocumentAttachment:sticker]; - if (originInfo != nil) - [imageUri appendFormat:@"&origin_info=%@", [originInfo stringRepresentation]]; - } - else - { - [imageUri appendFormat:@"&localDocumentId=%" PRId64, sticker.localDocumentId]; - } - [imageUri appendFormat:@"&accessHash=%" PRId64, sticker.accessHash]; - [imageUri appendFormat:@"&datacenterId=%d", (int)sticker.datacenterId]; - [imageUri appendFormat:@"&fileName=%@", [TGStringUtils stringByEscapingForURL:sticker.fileName]]; - [imageUri appendFormat:@"&size=%d", (int)sticker.size]; - [imageUri appendFormat:@"&width=%d&height=%d", (int)displaySize.width, (int)displaySize.height]; - [imageUri appendFormat:@"&mime-type=%@", [TGStringUtils stringByEscapingForURL:sticker.mimeType]]; - [imageUri appendString:@"&inhibitBlur=1"]; - - _imageView.frame = CGRectMake(CGFloor((self.frame.size.width - displaySize.width) / 2.0f), CGFloor((self.frame.size.height - displaySize.height) / 2.0f), displaySize.width, displaySize.height); + + _stickerView.frame = CGRectMake(CGFloor((self.frame.size.width - displaySize.width) / 2.0f), CGFloor((self.frame.size.height - displaySize.height) / 2.0f), displaySize.width, displaySize.height); CGFloat scale = displaySize.width > displaySize.height ? self.frame.size.width / displaySize.width : self.frame.size.height / displaySize.height; _defaultTransform = CATransform3DMakeScale(scale, scale, 1.0f); - _imageView.layer.transform = _defaultTransform; + _stickerView.layer.transform = _defaultTransform; if (_mirrored) - _imageView.layer.transform = CATransform3DRotate(_defaultTransform, M_PI, 0, 1, 0); - - [_imageView loadUri:imageUri withOptions:@{}]; + _stickerView.layer.transform = CATransform3DRotate(_defaultTransform, M_PI, 0, 1, 0); } return self; } @@ -113,7 +79,7 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; - (TGPhotoPaintStickerEntity *)entity { - TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:_document baseSize:_baseSize]; + TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:_document baseSize:_baseSize animated:_animated]; entity.uuid = _entityUUID; entity.position = self.center; entity.scale = self.scale; @@ -154,11 +120,7 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; - (bool)precisePointInside:(CGPoint)point { - CGPoint imagePoint = [_imageView convertPoint:point fromView:self]; - if (![_imageView pointInside:[_imageView convertPoint:point fromView:self] withEvent:nil]) - return false; - - return [_imageView isOpaqueAtPoint:imagePoint]; + return [_stickerView pointInside:[_stickerView convertPoint:point fromView:self] withEvent:nil]; } - (void)mirror @@ -170,29 +132,29 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; CATransform3D startTransform = _defaultTransform; if (!_mirrored) { - startTransform = _imageView.layer.transform; + startTransform = _stickerView.layer.transform; } CATransform3D targetTransform = CATransform3DRotate(_defaultTransform, 0, 0, 1, 0); if (_mirrored) { targetTransform = CATransform3DRotate(_defaultTransform, M_PI, 0, 1, 0); - targetTransform.m34 = -1.0f / _imageView.frame.size.width; + targetTransform.m34 = -1.0f / _stickerView.frame.size.width; } [UIView animateWithDuration:0.25 animations:^ { - _imageView.layer.transform = targetTransform; + _stickerView.layer.transform = targetTransform; }]; } else { - _imageView.layer.transform = CATransform3DRotate(_defaultTransform, _mirrored ? M_PI : 0, 0, 1, 0); + _stickerView.layer.transform = CATransform3DRotate(_defaultTransform, _mirrored ? M_PI : 0, 0, 1, 0); } } - (UIImage *)image { - return _imageView.currentImage; + return [_stickerView image]; } - (TGPhotoPaintEntitySelectionView *)createSelectionView diff --git a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.h b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.h index a297ab44c0..678a6dbf8d 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.h +++ b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.h @@ -11,6 +11,7 @@ @interface TGPhotoTextEntityView : TGPhotoPaintEntityView @property (nonatomic, readonly) TGPhotoPaintTextEntity *entity; +@property (nonatomic, readonly) UIImage *image; @property (nonatomic, readonly) bool isEmpty; diff --git a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m index 057d9253f2..21417f2db9 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m @@ -1,5 +1,8 @@ #import "TGPhotoTextEntityView.h" +#import "TGPaintSwatch.h" +#import "TGPhotoPaintFont.h" + #import "TGColor.h" #import "LegacyComponentsInternal.h" @@ -102,6 +105,20 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; return entity; } +- (UIImage *)image { + CGRect rect = self.bounds; + + UIGraphicsBeginImageContextWithOptions(CGSizeMake(rect.size.width, rect.size.height), false, 1.0f); + CGContextRef context = UIGraphicsGetCurrentContext(); + + [self drawViewHierarchyInRect:rect afterScreenUpdates:false]; + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return image; +} + - (bool)isEditing { return _textView.isFirstResponder; diff --git a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m index 2d15f274b7..274776bdf2 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m @@ -2,6 +2,7 @@ #import "LegacyComponentsInternal.h" #import "TGFont.h" +#import "TGImageUtils.h" #import "TGModernButton.h" #import "TGPhotoEditorButton.h" @@ -49,7 +50,7 @@ switch (backButton) { case TGPhotoEditorBackButtonCancel: - cancelImage = TGComponentsImageNamed(@"PhotoPickerCancelIcon"); + cancelImage = TGTintedImage([UIImage imageNamed:@"Editor/Cancel"], [UIColor whiteColor]); break; default: @@ -65,7 +66,7 @@ switch (doneButton) { case TGPhotoEditorDoneButtonCheck: - doneImage = TGComponentsImageNamed(@"PhotoPickerDoneIcon"); + doneImage = TGTintedImage([UIImage imageNamed:@"Editor/Commit"], [UIColor whiteColor]); break; default: diff --git a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m index 62cb623234..d787723785 100644 --- a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m +++ b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m @@ -4,6 +4,9 @@ #import "TGPaintingData.h" +#import "TGPhotoPaintStickerEntity.h" +#import "TGPhotoPaintTextEntity.h" + const NSTimeInterval TGVideoEditMinimumTrimmableDuration = 1.0; const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; @@ -68,12 +71,50 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; } if (dictionary[@"originalSize"]) adjustments->_originalSize = [dictionary[@"originalSize"] CGSizeValue]; - if (dictionary[@"paintingImagePath"]) + if (dictionary[@"entities"]) { + NSMutableArray *entities = [[NSMutableArray alloc] init]; + + for (NSDictionary *dict in dictionary[@"entities"]) { + if ([dict[@"type"] isEqualToString:@"sticker"]) { + TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:dict[@"data"] baseSize:[dict[@"baseSize"] CGSizeValue] animated:[dict[@"animated"] boolValue]]; + entity.uuid = [dict[@"uuid"] integerValue]; + entity.position = [dict[@"position"] CGPointValue]; + entity.scale = [dict[@"scale"] floatValue]; + entity.angle = [dict[@"angle"] floatValue]; + entity.mirrored = [dict[@"mirrored"] boolValue]; + [entities addObject:entity]; + } else if ([dict[@"type"] isEqualToString:@"text"]) { + UIImage *renderImage = [[UIImage alloc] initWithData:dict[@"data"]]; + if (renderImage != nil) { + TGPhotoPaintTextEntity *entity = [[TGPhotoPaintTextEntity alloc] initWithText:nil font:nil swatch:nil baseFontSize:0.0 maxWidth:0.0 stroke:false]; + entity.uuid = [dict[@"uuid"] integerValue]; + entity.position = [dict[@"position"] CGPointValue]; + entity.scale = [dict[@"scale"] floatValue]; + entity.angle = [dict[@"angle"] floatValue]; + entity.renderImage = renderImage; + [entities addObject:entity]; + } + } + } + + adjustments->_paintingData = [TGPaintingData dataWithPaintingImagePath:dictionary[@"paintingImagePath"] entities:entities]; + } else if (dictionary[@"paintingImagePath"]) { adjustments->_paintingData = [TGPaintingData dataWithPaintingImagePath:dictionary[@"paintingImagePath"]]; + } if (dictionary[@"sendAsGif"]) adjustments->_sendAsGif = [dictionary[@"sendAsGif"] boolValue]; if (dictionary[@"preset"]) adjustments->_preset = (TGMediaVideoConversionPreset)[dictionary[@"preset"] integerValue]; + if (dictionary[@"tools"]) { + NSMutableDictionary *tools = [[NSMutableDictionary alloc] init]; + for (NSString *key in dictionary[@"tools"]) { + id value = dictionary[@"tools"][key]; + if ([value isKindOfClass:[NSNumber class]]) { + tools[key] = value; + } + } + adjustments->_toolValues = tools; + } return adjustments; } @@ -102,6 +143,7 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; adjustments->_paintingData = _paintingData; adjustments->_sendAsGif = _sendAsGif; adjustments->_preset = preset; + adjustments->_toolValues = _toolValues; if (maxDuration > DBL_EPSILON) { @@ -135,8 +177,59 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; dict[@"originalSize"] = [NSValue valueWithCGSize:self.originalSize]; - if (self.paintingData.imagePath != nil) - dict[@"paintingImagePath"] = self.paintingData.imagePath; + if (self.toolValues.count > 0) { + NSMutableDictionary *tools = [[NSMutableDictionary alloc] init]; + for (NSString *key in self.toolValues) { + id value = self.toolValues[key]; + if ([value isKindOfClass:[NSNumber class]]) { + tools[key] = value; + } + } + dict[@"tools"] = tools; + } + + if (self.paintingData != nil) { + if (self.paintingData.imagePath != nil) { + dict[@"paintingImagePath"] = self.paintingData.imagePath; + } + + NSMutableArray *entities = [[NSMutableArray alloc] init]; + + if (self.paintingData.entities != nil) { + for (TGPhotoPaintEntity *entity in self.paintingData.entities) { + if ([entity isKindOfClass:[TGPhotoPaintStickerEntity class]]) { + TGPhotoPaintStickerEntity *stickerEntity = (TGPhotoPaintStickerEntity *)entity; + NSMutableDictionary *sticker = [[NSMutableDictionary alloc] init]; + sticker[@"type"] = @"sticker"; + sticker[@"baseSize"] = [NSValue valueWithCGSize:stickerEntity.baseSize]; + sticker[@"uuid"] = @(stickerEntity.uuid); + sticker[@"data"] = stickerEntity.document; + sticker[@"position"] = [NSValue valueWithCGPoint:stickerEntity.position]; + sticker[@"scale"] = @(stickerEntity.scale); + sticker[@"angle"] = @(stickerEntity.angle); + sticker[@"mirrored"] = @(stickerEntity.mirrored); + sticker[@"animated"] = @(stickerEntity.animated); + [entities addObject:sticker]; + } else if ([entity isKindOfClass:[TGPhotoPaintTextEntity class]]) { + TGPhotoPaintTextEntity *textEntity = (TGPhotoPaintTextEntity *)entity; + NSMutableDictionary *text = [[NSMutableDictionary alloc] init]; + if (textEntity.renderImage != nil) { + text[@"type"] = @"text"; + text[@"uuid"] = @(textEntity.uuid); + text[@"data"] = UIImagePNGRepresentation(textEntity.renderImage); + text[@"position"] = [NSValue valueWithCGPoint:textEntity.position]; + text[@"scale"] = @(textEntity.scale); + text[@"angle"] = @(textEntity.angle); + [entities addObject:text]; + } + } + } + } + + if (entities.count > 0) { + dict[@"entities"] = entities; + } + } dict[@"sendAsGif"] = @(self.sendAsGif); diff --git a/submodules/LegacyMediaPickerUI/BUILD b/submodules/LegacyMediaPickerUI/BUILD index 4a258f2a6e..1ae531d0cf 100644 --- a/submodules/LegacyMediaPickerUI/BUILD +++ b/submodules/LegacyMediaPickerUI/BUILD @@ -22,6 +22,9 @@ swift_library( "//submodules/SearchPeerMembers:SearchPeerMembers", "//submodules/SaveToCameraRoll:SaveToCameraRoll", "//submodules/LegacyMediaPickerUI/LegacyImageProcessors:LegacyImageProcessors", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/StickerResources:StickerResources", ], visibility = [ "//visibility:public", diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index 6cb8d27bfe..d267bba071 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -59,7 +59,7 @@ public enum LegacyAttachmentMenuMediaEditing { case file } -public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController { +public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> Void, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController { let defaultVideoPreset = defaultVideoPresetForContext(context) UserDefaults.standard.set(defaultVideoPreset.rawValue as NSNumber, forKey: "TG_preferredVideoPreset_v0") @@ -106,9 +106,19 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO selectionLimit = 10 } + let paintStickersContext = LegacyPaintStickersContext(context: context) + paintStickersContext.presentStickersController = { completion in + presentStickers({ file, animated, view, rect in + let coder = PostboxEncoder() + coder.encodeRootObject(file) + completion?(coder.makeData(), animated, view, rect) + }) + } + if canSendImageOrVideo { let carouselItem = TGAttachmentCarouselItemView(context: parentController.context, camera: PGCamera.cameraAvailable(), selfPortrait: false, forProfilePhoto: false, assetType: TGMediaAssetAnyType, saveEditedPhotos: !isSecretChat && saveEditedPhotos, allowGrouping: editMediaOptions == nil && allowGrouping, allowSelection: editMediaOptions == nil, allowEditing: true, document: false, selectionLimit: selectionLimit)! carouselItemView = carouselItem + carouselItem.stickersContext = paintStickersContext carouselItem.suggestionContext = legacySuggestionContext(context: context, peerId: peer.id) carouselItem.recipientName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) carouselItem.cameraPressed = { [weak controller, weak parentController] cameraView in diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickerView.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickerView.swift new file mode 100644 index 0000000000..60b4f7193f --- /dev/null +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickerView.swift @@ -0,0 +1,118 @@ +import UIKit +import Display +import SyncCore +import TelegramCore +import AccountContext +import SwiftSignalKit +import AnimatedStickerNode +import TelegramAnimatedStickerNode +import StickerResources +import LegacyComponents + +class LegacyPaintStickerView: UIView, TGPhotoPaintStickerRenderView { + private let context: AccountContext + private let file: TelegramMediaFile + private var currentSize: CGSize? + private var dimensions: CGSize? + + private let imageNode: TransformImageNode + private var animationNode: AnimatedStickerNode? + + private var didSetUpAnimationNode = false + private let stickerFetchedDisposable = MetaDisposable() + + init(context: AccountContext, file: TelegramMediaFile) { + self.context = context + self.file = file + + self.imageNode = TransformImageNode() + + super.init(frame: CGRect()) + + self.addSubnode(self.imageNode) + + self.setup() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.stickerFetchedDisposable.dispose() + } + + func image() -> UIImage! { + if self.imageNode.contents != nil { + return UIImage(cgImage: self.imageNode.contents as! CGImage) + } else { + return nil + } + } + + private func setup() { + if let dimensions = self.file.dimensions { + if self.file.isAnimatedSticker { + if self.animationNode == nil { + let animationNode = AnimatedStickerNode() + self.animationNode = animationNode + animationNode.started = { [weak self] in + self?.imageNode.isHidden = true + } + self.addSubnode(animationNode) + } + let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512) + self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, file: self.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0)))) + self.updateVisibility() + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, 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, file: self.file, small: false, synchronousLoad: false)) + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, fileReference: stickerPackFileReference(self.file), resource: chatMessageStickerResource(file: self.file, small: false)).start()) + } + + self.dimensions = dimensions.cgSize + self.setNeedsLayout() + } + } + + func updateVisibility() { + if !self.didSetUpAnimationNode { + self.didSetUpAnimationNode = true + + self.animationNode?.visibility = true + let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512) + let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0)) + self.animationNode?.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + let size = self.bounds.size + + if size.width > 0 && self.currentSize != size { + self.currentSize = size + + 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) + } + } + } + } +} diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift new file mode 100644 index 0000000000..78df4321f2 --- /dev/null +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -0,0 +1,316 @@ +import LegacyComponents +import Display +import Postbox +import SwiftSignalKit +import SyncCore +import TelegramCore +import AccountContext +import AnimatedStickerNode +import TelegramAnimatedStickerNode +import YuvConversion +import StickerResources + +protocol LegacyPaintEntity { + var position: CGPoint { get } + var scale: CGFloat { get } + var angle: CGFloat { get } + var baseSize: CGSize? { get } + + func image(for time: CMTime, completion: @escaping (CIImage?) -> Void) +} + +private class LegacyPaintStickerEntity: LegacyPaintEntity { + var position: CGPoint { + return self.entity.position + } + + var scale: CGFloat { + return self.entity.scale + } + + var angle: CGFloat { + return self.entity.angle + } + + var baseSize: CGSize? { + return self.entity.baseSize + } + + let account: Account + let file: TelegramMediaFile + let entity: TGPhotoPaintStickerEntity + let animated: Bool + + var source: AnimatedStickerNodeSource? + var frameSource: AnimatedStickerFrameSource? + var frameQueue: QueueLocalObject? + + let queue = Queue() + let disposable = MetaDisposable() + + let imagePromise = Promise() + + init?(account: Account, entity: TGPhotoPaintStickerEntity) { + let decoder = PostboxDecoder(buffer: MemoryBuffer(data: entity.document)) + if let file = decoder.decodeRootObject() as? TelegramMediaFile { + self.account = account + self.entity = entity + self.file = file + self.animated = file.isAnimatedSticker + + if file.isAnimatedSticker { + self.source = AnimatedStickerResourceSource(account: account, resource: file.resource) + if let source = self.source { + let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512) + let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0)) + self.disposable.set((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: {})! + + let frameQueue = QueueLocalObject(queue: queue, generate: { + return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource) + }) + + strongSelf.frameQueue = frameQueue + strongSelf.frameSource = frameSource + } + } + })) + } + } else { + self.disposable.set((chatMessageSticker(account: self.account, 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)) + } + } + })) + } + } else { + return nil + } + } + + deinit { + self.disposable.dispose() + } + + private func render(width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType) -> CIImage? { + let calculatedBytesPerRow = (4 * Int(width) + 15) & (~15) + assert(bytesPerRow == calculatedBytesPerRow) + + let image = generateImagePixel(CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, pixelGenerator: { _, pixelData, bytesPerRow in + switch type { + case .yuva: + data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + decodeYUVAToRGBA(bytes, pixelData, Int32(width), Int32(height), Int32(bytesPerRow)) + } + case .argb: + data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + memcpy(pixelData, bytes, data.count) + } + } + }) + + if let image = image { + return CIImage(image: image) + } else { + return nil + } + } + + var cachedCIImage: CIImage? + func image(for time: CMTime, completion: @escaping (CIImage?) -> Void) { + if self.animated { + let frameQueue = self.frameQueue + self.queue.async { + guard let frameQueue = frameQueue else { + completion(nil) + return + } + let maybeFrame = frameQueue.syncWith { frameQueue in + return frameQueue.take() + } + if let maybeFrame = maybeFrame, let frame = maybeFrame { + let image = self.render(width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type) + completion(image) + } else { + completion(nil) + } + frameQueue.with { frameQueue in + frameQueue.generateFramesIfNeeded() + } + } + } else { + self.queue.async { + var image: CIImage? + if let cachedImage = self.cachedCIImage { + image = cachedImage + completion(image) + } else { + let _ = (self.imagePromise.get() + |> deliverOn(self.queue)).start(next: { [weak self] image in + if let strongSelf = self { + strongSelf.cachedCIImage = CIImage(image: image) + completion(strongSelf.cachedCIImage) + } + }) + } + } + } + } +} + +private class LegacyPaintTextEntity: LegacyPaintEntity { + var position: CGPoint { + return self.entity.position + } + + var scale: CGFloat { + return self.entity.scale + } + + var angle: CGFloat { + return self.entity.angle + } + + var baseSize: CGSize? { + return nil + } + + let entity: TGPhotoPaintTextEntity + + init(entity: TGPhotoPaintTextEntity) { + self.entity = entity + } + + var cachedCIImage: CIImage? + func image(for time: CMTime, completion: @escaping (CIImage?) -> Void) { + var image: CIImage? + if let cachedImage = self.cachedCIImage { + image = cachedImage + } else if let renderImage = entity.renderImage { + image = CIImage(image: renderImage) + self.cachedCIImage = image + } + completion(image) + } +} + +public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRenderer { + private let account: Account + private let queue = Queue() + + private let entities: [LegacyPaintEntity] + private let originalSize: CGSize + private let cropRect: CGRect? + + public init(account: Account, adjustments: TGMediaEditAdjustments) { + self.account = account + self.originalSize = adjustments.originalSize + self.cropRect = adjustments.cropRect.isEmpty ? nil : adjustments.cropRect + + var entities: [LegacyPaintEntity] = [] + if let paintingData = adjustments.paintingData, let paintingEntities = paintingData.entities { + for paintingEntity in paintingEntities { + if let sticker = paintingEntity as? TGPhotoPaintStickerEntity { + if let entity = LegacyPaintStickerEntity(account: account, entity: sticker) { + entities.append(entity) + } + } else if let text = paintingEntity as? TGPhotoPaintTextEntity { + entities.append(LegacyPaintTextEntity(entity: text)) + } + } + } + self.entities = entities + + super.init() + } + + deinit { + + } + + public func entities(for time: CMTime, size: CGSize, completion: (([CIImage]?) -> Void)!) { + let entities = self.entities +// let originalSize = self.originalSize +// let cropRect = self.cropRect + + self.queue.async { + if entities.isEmpty { + completion(nil) + } else { + let count = Atomic(value: 1) + let images = Atomic<[CIImage]>(value: []) + let maybeFinalize = { + let count = count.modify { current -> Int in + return current - 1 + } + if count == 0 { + completion(images.with { $0 }) + } + } + for entity in entities { + let _ = count.modify { current -> Int in + return current + 1 + } + entity.image(for: time, completion: { image in + if var image = image { + let paintingScale = max(size.width, size.height) / 1920.0 + + var transform = CGAffineTransform(translationX: -image.extent.midX, y: -image.extent.midY) + image = image.transformed(by: transform) + + var scale = entity.scale * paintingScale + if let baseSize = entity.baseSize { + scale *= baseSize.width / image.extent.size.width + } + + transform = CGAffineTransform(translationX: entity.position.x * paintingScale, y: size.height - entity.position.y * paintingScale) + transform = transform.rotated(by: CGFloat.pi * 2 - entity.angle) + transform = transform.scaledBy(x: scale, y: scale) + + image = image.transformed(by: transform) + let _ = images.modify { current in + var updated = current + updated.append(image) + return updated + } + } + maybeFinalize() + }) + } + maybeFinalize() + } + } + } +} + +public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersContext { + public var presentStickersController: ((((Any?, Bool, UIView?, CGRect) -> Void)?) -> Void)! + + private let context: AccountContext + + public init(context: AccountContext) { + self.context = context + } + + public func stickerView(forDocument document: Any!) -> (UIView & TGPhotoPaintStickerRenderView)! { + if let data = document as? Data{ + let decoder = PostboxDecoder(buffer: MemoryBuffer(data: data)) + if let file = decoder.decodeRootObject() as? TelegramMediaFile { + return LegacyPaintStickerView(context: self.context, file: file) + } else { + return nil + } + } else { + return nil + } + } +} diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift index 47581bc752..70501d7344 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift @@ -203,37 +203,37 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! ThemeAutoNightSettingsControllerArguments switch self { - case let .modeSystem(theme, title, value): + case let .modeSystem(_, title, value): return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.system) }) - case let .modeDisabled(theme, title, value): + case let .modeDisabled(_, title, value): return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.none) }) - case let .modeTimeBased(theme, title, value): + case let .modeTimeBased(_, title, value): return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.timeBased) }) - case let .modeBrightness(theme, title, value): + case let .modeBrightness(_, title, value): return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.brightness) }) - case let .settingsHeader(theme, title): + case let .settingsHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) - case let .timeBasedAutomaticLocation(theme, title, value): + case let .timeBasedAutomaticLocation(_, title, value): return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updateTimeBasedAutomatic(value) }) - case let .timeBasedAutomaticLocationValue(theme, title, value): + case let .timeBasedAutomaticLocationValue(_, title, value): return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: title, titleColor: .accent, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: { arguments.updateTimeBasedAutomaticLocation() }) - case let .timeBasedManualFrom(theme, title, value): + case let .timeBasedManualFrom(_, title, value): return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openTimeBasedManual(.from) }) - case let .timeBasedManualTo(theme, title, value): + case let .timeBasedManualTo(_, title, value): return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openTimeBasedManual(.to) }) @@ -241,9 +241,9 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry { return ThemeSettingsBrightnessItem(theme: theme, value: Int32(value * 100.0), sectionId: self.section, updated: { value in arguments.updateAutomaticBrightness(Double(value) / 100.0) }) - case let .settingInfo(theme, text): + case let .settingInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) - case let .themeHeader(theme, title): + case let .themeHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .themeItem(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers): return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, allThemes: allThemes, displayUnsupported: false, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, currentTheme: currentTheme, updatedTheme: { theme in diff --git a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift index 0c0215dbb1..58b62d7b59 100644 --- a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift +++ b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift @@ -225,7 +225,7 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation public func descriptionStringForMessage(contentSettings: ContentSettings, message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> (String, Bool) { if !message.text.isEmpty { - return (message.text, false) + return (foldLineBreaks(message.text), false) } return stringForMediaKind(messageContentKind(contentSettings: contentSettings, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId), strings: strings) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index fbcb3dd245..dfbaca2704 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -5880,7 +5880,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } strongSelf.chatDisplayNode.dismissInput() - + var bannedSendMedia: (Int32, Bool)? var canSendPolls = true if let channel = peer as? TelegramChannel { @@ -6064,6 +6064,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) }) } + }, presentStickers: { [weak self] completion in + if let strongSelf = self { + let controller = DrawingStickersScreen(context: strongSelf.context, selectSticker: { fileReference, node, rect in + completion(fileReference.media, fileReference.media.isAnimatedSticker, node.view, rect) + return true + }) + strongSelf.present(controller, in: .window(.root)) + } }, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) }) diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index 6cf9c17d20..55bd83627e 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -18,24 +18,24 @@ import GalleryUI import OverlayStatusController import PresentationDataUtils -private struct PeerSpecificPackData { +struct PeerSpecificPackData { let peer: Peer let info: StickerPackCollectionInfo let items: [ItemCollectionItem] } -private enum CanInstallPeerSpecificPack { +enum CanInstallPeerSpecificPack { case none case available(peer: Peer, dismissed: Bool) } -private struct ChatMediaInputPanelTransition { +struct ChatMediaInputPanelTransition { let deletions: [ListViewDeleteItem] let insertions: [ListViewInsertItem] let updates: [ListViewUpdateItem] } -private struct ChatMediaInputGridTransition { +struct ChatMediaInputGridTransition { let deletions: [Int] let insertions: [GridNodeInsertItem] let updates: [GridNodeUpdateItem] @@ -46,7 +46,7 @@ private struct ChatMediaInputGridTransition { let animated: Bool } -private func preparedChatMediaInputPanelEntryTransition(context: AccountContext, from fromEntries: [ChatMediaInputPanelEntry], to toEntries: [ChatMediaInputPanelEntry], inputNodeInteraction: ChatMediaInputNodeInteraction) -> ChatMediaInputPanelTransition { +func preparedChatMediaInputPanelEntryTransition(context: AccountContext, from fromEntries: [ChatMediaInputPanelEntry], to toEntries: [ChatMediaInputPanelEntry], inputNodeInteraction: ChatMediaInputNodeInteraction) -> ChatMediaInputPanelTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -56,7 +56,7 @@ private func preparedChatMediaInputPanelEntryTransition(context: AccountContext, return ChatMediaInputPanelTransition(deletions: deletions, insertions: insertions, updates: updates) } -private func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemCollectionsView, from fromEntries: [ChatMediaInputGridEntry], to toEntries: [ChatMediaInputGridEntry], update: StickerPacksCollectionUpdate, interfaceInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingInteraction: TrendingPaneInteraction) -> ChatMediaInputGridTransition { +func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemCollectionsView, from fromEntries: [ChatMediaInputGridEntry], to toEntries: [ChatMediaInputGridEntry], update: StickerPacksCollectionUpdate, interfaceInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingInteraction: TrendingPaneInteraction) -> ChatMediaInputGridTransition { var stationaryItems: GridNodeStationaryItems = .none var scrollToItem: GridNodeScrollToItem? var animated = false @@ -163,9 +163,11 @@ private func preparedChatMediaInputGridEntryTransition(account: Account, view: I return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, updateOpaqueState: opaqueState, animated: animated) } -private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, hasUnreadTrending: Bool?, theme: PresentationTheme) -> [ChatMediaInputPanelEntry] { +func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, hasGifs: Bool = true, hasUnreadTrending: Bool?, theme: PresentationTheme) -> [ChatMediaInputPanelEntry] { var entries: [ChatMediaInputPanelEntry] = [] - entries.append(.recentGifs(theme)) + if hasGifs { + entries.append(.recentGifs(theme)) + } if let hasUnreadTrending = hasUnreadTrending { entries.append(.trending(hasUnreadTrending, theme)) } @@ -215,7 +217,7 @@ private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers return entries } -private func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, strings: PresentationStrings, theme: PresentationTheme) -> [ChatMediaInputGridEntry] { +func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, strings: PresentationStrings, theme: PresentationTheme) -> [ChatMediaInputGridEntry] { var entries: [ChatMediaInputGridEntry] = [] if view.lower == nil { @@ -297,7 +299,7 @@ private func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: return entries } -private enum StickerPacksCollectionPosition: Equatable { +enum StickerPacksCollectionPosition: Equatable { case initial case scroll(aroundIndex: ItemCollectionViewEntryIndex?) case navigate(index: ItemCollectionViewEntryIndex?, collectionId: ItemCollectionId?) @@ -322,7 +324,7 @@ private enum StickerPacksCollectionPosition: Equatable { } } -private enum StickerPacksCollectionUpdate { +enum StickerPacksCollectionUpdate { case initial case generic case scroll @@ -353,7 +355,7 @@ final class ChatMediaInputNodeInteraction { } } -private func clipScrollPosition(_ position: StickerPacksCollectionPosition) -> StickerPacksCollectionPosition { +func clipScrollPosition(_ position: StickerPacksCollectionPosition) -> StickerPacksCollectionPosition { switch position { case let .scroll(index): if let index = index, index.collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue || index.collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue { @@ -365,13 +367,12 @@ private func clipScrollPosition(_ position: StickerPacksCollectionPosition) -> S return position } -private enum ChatMediaInputPaneType { +enum ChatMediaInputPaneType { case gifs case stickers - //case trending } -private struct ChatMediaInputPaneArrangement { +struct ChatMediaInputPaneArrangement { let panes: [ChatMediaInputPaneType] let currentIndex: Int let indexTransition: CGFloat @@ -385,7 +386,7 @@ private struct ChatMediaInputPaneArrangement { } } -private final class CollectionListContainerNode: ASDisplayNode { +final class CollectionListContainerNode: ASDisplayNode { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for subview in self.view.subviews { if let result = subview.hitTest(point.offsetBy(dx: -subview.frame.minX, dy: -subview.frame.minY), with: event) { diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift new file mode 100644 index 0000000000..2be1d2579c --- /dev/null +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -0,0 +1,901 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import TelegramUIPreferences +import MergeLists +import StickerPackPreviewUI +import OverlayStatusController +import PresentationDataUtils +import SearchBarNode +import UndoUI +import SegmentedControlNode + +private final class DrawingStickersScreenNode: ViewControllerTracingNode { + private let context: AccountContext + private var presentationData: PresentationData + private let selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? + private var searchItemContext = StickerPaneSearchGlobalItemContext() + private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)> + + private let controllerInteraction: ChatControllerInteraction + private var inputNodeInteraction: ChatMediaInputNodeInteraction! + + private let collectionListPanel: ASDisplayNode + private let collectionListContainer: CollectionListContainerNode + + private let blurView: UIView + + private let segmentedControlNode: SegmentedControlNode + private let cancelButton: HighlightableButtonNode + + private let listView: ListView + + private var searchContainerNode: PaneSearchContainerNode? + private let searchContainerNodeLoadedDisposable = MetaDisposable() + + private let stickerPane: ChatMediaInputStickerPane + + private let itemCollectionsViewPosition = Promise() + private var currentStickerPacksCollectionPosition: StickerPacksCollectionPosition? + private var currentView: ItemCollectionsView? + + private var paneArrangement: ChatMediaInputPaneArrangement + private var initializedArrangement = false + + private var validLayout: ContainerViewLayout? + + private var disposable = MetaDisposable() + + private let _ready = Promise() + var ready: Promise { + return self._ready + } + private var didSetReady: Bool = false + + fileprivate var dismiss: (() -> Void)? + + init(context: AccountContext, selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?) { + self.context = context + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.selectSticker = selectSticker + + self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) + + var selectStickerImpl: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? + + self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in + return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, node, rect in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + }, presentController: { _, _ in }, navigationController: { + return nil + }, chatControllerNode: { + return nil + }, reactionContainerNode: { + return nil + }, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in + }, canSetupReply: { _ in + return .none + }, navigateToFirstDateMessage: { _ in + }, requestRedeliveryOfFailedMessages: { _ in + }, addContact: { _ in + }, rateCall: { _, _ in + }, requestSelectMessagePollOptions: { _, _ in + }, requestOpenMessagePollResults: { _, _ in + }, openAppStorePage: { + }, displayMessageTooltip: { _, _, _, _ in + }, seekToTimecode: { _, _, _ in + }, scheduleCurrentMessage: { + }, sendScheduledMessagesNow: { _ in + }, editScheduledMessagesTime: { _ in + }, performTextSelectionAction: { _, _, _ in + }, updateMessageLike: { _, _ in + }, openMessageReactions: { _ in + }, displaySwipeToReplyHint: { + }, dismissReplyMarkupMessage: { _ in + }, openMessagePollResults: { _, _ in + }, openPollCreation: { _ in + }, displayPollSolution: { _, _ in + }, displayPsa: { _, _ in + }, displayDiceTooltip: { _ in + }, animateDiceSuccess: { + }, requestMessageUpdate: { _ in + }, cancelInteractiveKeyboardGestures: { + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, + pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: true)) + + self.blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) + + let segmentedTheme = SegmentedControlTheme(backgroundColor: UIColor(rgb: 0x2c2d2d), foregroundColor: UIColor(rgb: 0x656565), shadowColor: UIColor.clear, textColor: .white, dividerColor: .white) + + self.segmentedControlNode = SegmentedControlNode(theme: segmentedTheme, items: [SegmentedControlItem(title: "Stickers"), SegmentedControlItem(title: "Masks")], selectedIndex: 0) + + self.cancelButton = HighlightableButtonNode() + self.cancelButton.setAttributedTitle(NSAttributedString(string: self.presentationData.strings.Common_Cancel, font: Font.regular(17.0), textColor: .white), for: .normal) + + self.collectionListPanel = ASDisplayNode() + self.collectionListPanel.clipsToBounds = true + + self.collectionListPanel.backgroundColor = UIColor(rgb: 0x151515) + + self.collectionListContainer = CollectionListContainerNode() + self.collectionListContainer.clipsToBounds = true + + self.listView = ListView() + self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0) + + var paneDidScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void)? + self.stickerPane = ChatMediaInputStickerPane(theme: self.presentationData.theme, strings: self.presentationData.strings, paneDidScroll: { pane, state, transition in + paneDidScrollImpl?(pane, state, transition) + }, fixPaneScroll: { pane, state in + // fixPaneScrollImpl?(pane, state) + }) + + self.paneArrangement = ChatMediaInputPaneArrangement(panes: [.stickers], currentIndex: 0, indexTransition: 0.0) + + super.init() + + self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) + + self.view.addSubview(self.blurView) + + self.inputNodeInteraction = ChatMediaInputNodeInteraction(navigateToCollectionId: { [weak self] collectionId in + if let strongSelf = self, let currentView = strongSelf.currentView, (collectionId != strongSelf.inputNodeInteraction.highlightedItemCollectionId || true) { + var index: Int32 = 0 + if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue { + strongSelf.setCurrentPane(.gifs, transition: .animated(duration: 0.25, curve: .spring)) + } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue { + strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen( + context: strongSelf.context, + sendSticker: { + fileReference, sourceNode, sourceRect in + if let strongSelf = self { + return strongSelf.controllerInteraction.sendSticker(fileReference, false, sourceNode, sourceRect) + } else { + return false + } + } + )) + } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue { + strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace) + strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId) + strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId))) + } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue { + strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace) + strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId) + strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId))) + } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue { + strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring)) + strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId) + strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId))) + } else { + strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring)) + for (id, _, _) in currentView.collectionInfos { + if id.namespace == collectionId.namespace { + if id == collectionId { + let itemIndex = ItemCollectionViewEntryIndex.lowerBound(collectionIndex: index, collectionId: id) + strongSelf.currentStickerPacksCollectionPosition = .navigate(index: itemIndex, collectionId: nil) + strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: itemIndex, collectionId: nil))) + break + } + index += 1 + } + } + } + } + }, openSettings: { [weak self] in + if let strongSelf = self { +// let controller = installedStickerPacksController(context: context, mode: .modal) +// controller.navigationPresentation = .modal +// strongSelf.controllerInteraction.navigationController()?.pushViewController(controller) + } + }, toggleSearch: { [weak self] value, searchMode in + if let strongSelf = self { + if let searchMode = searchMode, value { + var searchContainerNode: PaneSearchContainerNode? + if let current = strongSelf.searchContainerNode { + searchContainerNode = current + } else { + searchContainerNode = PaneSearchContainerNode(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction, inputNodeInteraction: strongSelf.inputNodeInteraction, mode: searchMode, trendingGifsPromise: Promise(nil), cancel: { + self?.searchContainerNode?.deactivate() + self?.inputNodeInteraction.toggleSearch(false, nil) + }) + strongSelf.searchContainerNode = searchContainerNode + } + if let searchContainerNode = searchContainerNode { + strongSelf.searchContainerNodeLoadedDisposable.set((searchContainerNode.ready + |> deliverOnMainQueue).start(next: { + if let strongSelf = self { + strongSelf.controllerInteraction.updateInputMode { current in + switch current { + case let .media(mode, _): + return .media(mode: mode, expanded: .search(searchMode)) + default: + return current + } + } + } + })) + } + } else { + strongSelf.controllerInteraction.updateInputMode { current in + switch current { + case let .media(mode, _): + return .media(mode: mode, expanded: nil) + default: + return current + } + } + } + } + }, openPeerSpecificSettings: { [weak self] in + }, dismissPeerSpecificSettings: { [weak self] in + }, clearRecentlyUsedStickers: { [weak self] in + if let strongSelf = self { + let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: strongSelf.presentationData.theme, fontSize: strongSelf.presentationData.listsFontSize)) + var items: [ActionSheetItem] = [] + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Stickers_ClearRecent, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + let _ = (context.account.postbox.transaction { transaction in + clearRecentlyUsedStickers(transaction: transaction) + }).start() + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.controllerInteraction.presentController(actionSheet, nil) + } + }) + self.inputNodeInteraction.stickerSettings = ChatInterfaceStickerSettings(loopAnimatedStickers: true) + + + self.collectionListPanel.addSubnode(self.listView) + self.collectionListContainer.addSubnode(self.collectionListPanel) + self.addSubnode(self.collectionListContainer) + + self.addSubnode(self.segmentedControlNode) + self.addSubnode(self.cancelButton) + + let trendingInteraction = TrendingPaneInteraction(installPack: { [weak self] info in + }, openPack: { [weak self] info in + }, getItemIsPreviewed: { item in + return false + }, openSearch: { + }) + + let itemCollectionsView = self.itemCollectionsViewPosition.get() + |> distinctUntilChanged + |> mapToSignal { position -> Signal<(ItemCollectionsView, StickerPacksCollectionUpdate), NoError> in + switch position { + case .initial: + var firstTime = true + return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: nil, count: 50) + |> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in + let update: StickerPacksCollectionUpdate + if firstTime { + firstTime = false + update = .initial + } else { + update = .generic + } + return (view, update) + } + case let .scroll(aroundIndex): + var firstTime = true + return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: aroundIndex, count: 300) + |> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in + let update: StickerPacksCollectionUpdate + if firstTime { + firstTime = false + update = .scroll + } else { + update = .generic + } + return (view, update) + } + case let .navigate(index, collectionId): + var firstTime = true + return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: index, count: 300) + |> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in + let update: StickerPacksCollectionUpdate + if firstTime { + firstTime = false + update = .navigate(index, collectionId) + } else { + update = .generic + } + return (view, update) + } + } + } + + let controllerInteraction = self.controllerInteraction + let inputNodeInteraction = self.inputNodeInteraction! + + let previousEntries = Atomic<([ChatMediaInputPanelEntry], [ChatMediaInputGridEntry])>(value: ([], [])) + let previousView = Atomic(value: nil) + let transitionQueue = Queue() + let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get()) + |> map { viewAndUpdate, trendingPacks, themeAndStrings -> (ItemCollectionsView, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in + let (view, viewUpdate) = viewAndUpdate + let previous = previousView.swap(view) + var update = viewUpdate + if previous === view { + update = .generic + } + let (theme, strings) = themeAndStrings + + var savedStickers: OrderedItemListView? + var recentStickers: OrderedItemListView? + for orderedView in view.orderedItemListsViews { + if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers { + recentStickers = orderedView + } else if orderedView.collectionId == Namespaces.OrderedItemList.CloudSavedStickers { + savedStickers = orderedView + } + } + + var installedPacks = Set() + for info in view.collectionInfos { + installedPacks.insert(info.0) + } + + var hasUnreadTrending: Bool? + for pack in trendingPacks { + if hasUnreadTrending == nil { + hasUnreadTrending = false + } + if pack.unread { + hasUnreadTrending = true + break + } + } + + let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasGifs: false, hasUnreadTrending: hasUnreadTrending, theme: theme) + var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, strings: strings, theme: theme) + + if view.higher == nil { + var hasTopSeparator = true + if gridEntries.count == 1, case .search = gridEntries[0] { + hasTopSeparator = false + } + + var index = 0 + for item in trendingPacks { + if !installedPacks.contains(item.info.id) { + gridEntries.append(.trending(TrendingPanePackEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread, topSeparator: hasTopSeparator))) + hasTopSeparator = true + index += 1 + } + } + } + + let (previousPanelEntries, previousGridEntries) = previousEntries.swap((panelEntries, gridEntries)) + return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: inputNodeInteraction), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty) + } + + self.disposable.set((transitions + |> deliverOnMainQueue).start(next: { [weak self] (view, panelTransition, panelFirstTime, gridTransition, gridFirstTime) in + if let strongSelf = self { + strongSelf.currentView = view + strongSelf.enqueuePanelTransition(panelTransition, firstTime: panelFirstTime, thenGridTransition: gridTransition, gridFirstTime: gridFirstTime) + if !strongSelf.initializedArrangement { + strongSelf.initializedArrangement = true + var currentPane = strongSelf.paneArrangement.panes[strongSelf.paneArrangement.currentIndex] + if view.entries.isEmpty { + //currentPane = .trending + } + if currentPane != strongSelf.paneArrangement.panes[strongSelf.paneArrangement.currentIndex] { + strongSelf.setCurrentPane(currentPane, transition: .immediate) + } + } + } + })) + + self.stickerPane.gridNode.visibleItemsUpdated = { [weak self] visibleItems in + if let strongSelf = self { + var topVisibleCollectionId: ItemCollectionId? + + if let topVisibleSection = visibleItems.topSectionVisible as? ChatMediaInputStickerGridSection { + topVisibleCollectionId = topVisibleSection.collectionId + } else if let topVisible = visibleItems.topVisible { + if let item = topVisible.1 as? ChatMediaInputStickerGridItem { + topVisibleCollectionId = item.index.collectionId + } else if let _ = topVisible.1 as? StickerPanePeerSpecificSetupGridItem { + topVisibleCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0) + } + } + if let collectionId = topVisibleCollectionId { +// if strongSelf.inputNodeInteraction.highlightedItemCollectionId != collectionId { +// strongSelf.setHighlightedItemCollectionId(collectionId) +// } + } + + if let currentView = strongSelf.currentView, let (topIndex, topItem) = visibleItems.top, let (bottomIndex, bottomItem) = visibleItems.bottom { + if topIndex <= 10 && currentView.lower != nil { + let position: StickerPacksCollectionPosition = clipScrollPosition(.scroll(aroundIndex: (topItem as! ChatMediaInputStickerGridItem).index)) + if strongSelf.currentStickerPacksCollectionPosition != position { + strongSelf.currentStickerPacksCollectionPosition = position + strongSelf.itemCollectionsViewPosition.set(.single(position)) + } + } else if bottomIndex >= visibleItems.count - 10 && currentView.higher != nil { + var position: StickerPacksCollectionPosition? + if let bottomItem = bottomItem as? ChatMediaInputStickerGridItem { + position = clipScrollPosition(.scroll(aroundIndex: bottomItem.index)) + } + + if let position = position, strongSelf.currentStickerPacksCollectionPosition != position { + strongSelf.currentStickerPacksCollectionPosition = position + strongSelf.itemCollectionsViewPosition.set(.single(position)) + } + } + } + } + } + + self.currentStickerPacksCollectionPosition = .initial + self.itemCollectionsViewPosition.set(.single(.initial)) + + self.stickerPane.inputNodeInteraction = self.inputNodeInteraction + + paneDidScrollImpl = { [weak self] pane, state, transition in + self?.updatePaneDidScroll(pane: pane, state: state, transition: transition) + } + + selectStickerImpl = { [weak self] fileReference, node, rect in + return self?.selectSticker?(fileReference, node, rect) ?? false + } + } + + deinit { + self.disposable.dispose() + } + + @objc private func cancelPressed() { + self.dismiss?() + } + + private func setHighlightedItemCollectionId(_ collectionId: ItemCollectionId) { + if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue { + if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs { + self.inputNodeInteraction.highlightedItemCollectionId = collectionId + } + } else { + self.inputNodeInteraction.highlightedStickerItemCollectionId = collectionId + if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .stickers { + self.inputNodeInteraction.highlightedItemCollectionId = collectionId + } + } + var ensuredNodeVisible = false + var firstVisibleCollectionId: ItemCollectionId? + self.listView.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMediaInputStickerPackItemNode { + if firstVisibleCollectionId == nil { + firstVisibleCollectionId = itemNode.currentCollectionId + } + itemNode.updateIsHighlighted() + if itemNode.currentCollectionId == collectionId { + self.listView.ensureItemNodeVisible(itemNode) + ensuredNodeVisible = true + } + } else if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode { + itemNode.updateIsHighlighted() + if itemNode.currentCollectionId == collectionId { + self.listView.ensureItemNodeVisible(itemNode) + ensuredNodeVisible = true + } + } else if let itemNode = itemNode as? ChatMediaInputRecentGifsItemNode { + itemNode.updateIsHighlighted() + if itemNode.currentCollectionId == collectionId { + self.listView.ensureItemNodeVisible(itemNode) + ensuredNodeVisible = true + } + } else if let itemNode = itemNode as? ChatMediaInputTrendingItemNode { + itemNode.updateIsHighlighted() + if itemNode.currentCollectionId == collectionId { + self.listView.ensureItemNodeVisible(itemNode) + ensuredNodeVisible = true + } + } else if let itemNode = itemNode as? ChatMediaInputPeerSpecificItemNode { + itemNode.updateIsHighlighted() + if itemNode.currentCollectionId == collectionId { + self.listView.ensureItemNodeVisible(itemNode) + ensuredNodeVisible = true + } + } + } + + if let currentView = self.currentView, let firstVisibleCollectionId = firstVisibleCollectionId, !ensuredNodeVisible { + let targetIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == collectionId }) + let firstVisibleIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == firstVisibleCollectionId }) + if let targetIndex = targetIndex, let firstVisibleIndex = firstVisibleIndex { + let toRight = targetIndex > firstVisibleIndex + self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [], scrollToItem: ListViewScrollToItem(index: targetIndex, position: toRight ? .bottom(0.0) : .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: toRight ? .Down : .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil) + } + } + } + + private func setCurrentPane(_ pane: ChatMediaInputPaneType, transition: ContainedViewLayoutTransition, collectionIdHint: Int32? = nil) { + var transition = transition + + if let index = self.paneArrangement.panes.firstIndex(of: pane), index != self.paneArrangement.currentIndex { + let previousGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs + //let previousTrendingPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending + self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index) + let updatedGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs + //let updatedTrendingPanelIsActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending + + /*if updatedTrendingPanelIsActive != previousTrendingPanelWasActive { + transition = .immediate + }*/ + +// if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout { +// let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: transition, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible) +// self.updateAppearanceTransition(transition: transition) +// } +// if updatedGifPanelWasActive != previousGifPanelWasActive { +// self.gifPaneIsActiveUpdated(updatedGifPanelWasActive) +// } + switch pane { + case .gifs: + self.setHighlightedItemCollectionId(ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue, id: 0)) + case .stickers: + if let highlightedStickerCollectionId = self.inputNodeInteraction.highlightedStickerItemCollectionId { + self.setHighlightedItemCollectionId(highlightedStickerCollectionId) + } else if let collectionIdHint = collectionIdHint { + self.setHighlightedItemCollectionId(ItemCollectionId(namespace: collectionIdHint, id: 0)) + } + /*case .trending: + self.setHighlightedItemCollectionId(ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0))*/ + } + /*if updatedTrendingPanelIsActive != previousTrendingPanelWasActive { + self.controllerInteraction.updateInputMode { current in + switch current { + case let .media(mode, _): + if updatedTrendingPanelIsActive { + return .media(mode: mode, expanded: .content) + } else { + return .media(mode: mode, expanded: nil) + } + default: + return current + } + } + }*/ + } else { +// if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout { +// let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible) +// } + } + } + + private func currentCollectionListPanelOffset() -> CGFloat { + let paneOffsets = self.paneArrangement.panes.map { pane -> CGFloat in + return self.stickerPane.collectionListPanelOffset + } + + let mainOffset = paneOffsets[self.paneArrangement.currentIndex] + if self.paneArrangement.indexTransition.isZero { + return mainOffset + } else { + var sideOffset: CGFloat? + if self.paneArrangement.indexTransition < 0.0 { + if self.paneArrangement.currentIndex != 0 { + sideOffset = paneOffsets[self.paneArrangement.currentIndex - 1] + } + } else { + if self.paneArrangement.currentIndex != paneOffsets.count - 1 { + sideOffset = paneOffsets[self.paneArrangement.currentIndex + 1] + } + } + if let sideOffset = sideOffset { + let interpolator = CGFloat.interpolator() + let value = interpolator(mainOffset, sideOffset, abs(self.paneArrangement.indexTransition)) as! CGFloat + return value + } else { + return mainOffset + } + } + } + + func updateLayout(width: CGFloat, topInset: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) { + var searchMode: ChatMediaInputSearchMode? + + let wasVisible = true + + var displaySearch = false + let separatorHeight = UIScreenPixel + let panelHeight: CGFloat + + var isExpanded: Bool = true +// switch expanded { +// case .content: + panelHeight = maximumHeight +// case let .search(mode): +// panelHeight = maximumHeight +// displaySearch = true +// searchMode = mode +// } + self.stickerPane.collectionListPanelOffset = 0.0 + + var cancelSize = self.cancelButton.measure(CGSize(width: width, height: .greatestFiniteMagnitude)) + cancelSize.width += 16.0 * 2.0 + self.cancelButton.frame = CGRect(origin: CGPoint(x: width - cancelSize.width, y: topInset + 5.0), size: cancelSize) + + let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: width - cancelSize.width - 16.0 * 2.0), transition: transition) + transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: 16.0, y: topInset), size: controlSize)) + + if displaySearch { + if let searchContainerNode = self.searchContainerNode { + let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: -inputPanelHeight), size: CGSize(width: width, height: panelHeight + inputPanelHeight)) + if searchContainerNode.supernode != nil { + transition.updateFrame(node: searchContainerNode, frame: containerFrame) + searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition) + } else { + self.searchContainerNode = searchContainerNode + self.insertSubnode(searchContainerNode, belowSubnode: self.collectionListContainer) + searchContainerNode.frame = containerFrame + searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: .immediate) + var placeholderNode: PaneSearchBarPlaceholderNode? + if let searchMode = searchMode { + switch searchMode { + case .sticker: + self.stickerPane.gridNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? PaneSearchBarPlaceholderNode { + placeholderNode = itemNode + } + } + default: + break + } + } + + if let placeholderNode = placeholderNode { + searchContainerNode.animateIn(from: placeholderNode, transition: transition, completion: { [weak self] in + }) + } + } + } + } + + let contentVerticalOffset: CGFloat = displaySearch ? -(inputPanelHeight + 41.0) : 0.0 + + let collectionListPanelOffset: CGFloat = 0.0 + + transition.updateFrame(view: self.blurView, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: maximumHeight))) + + transition.updateFrame(node: self.collectionListContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: maximumHeight + contentVerticalOffset - 41.0 - bottomInset), size: CGSize(width: width, height: max(0.0, 41.0 + UIScreenPixel + bottomInset)))) + transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: 41.0 + bottomInset))) + + self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0, height: width) + transition.updatePosition(node: self.listView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0 + 2.0)) + + let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) + let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: 41.0, height: width), insets: UIEdgeInsets(top: 4.0 + leftInset, left: 0.0, bottom: 4.0 + rightInset, right: 0.0), duration: duration, curve: curve) + + self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + + var visiblePanes: [(ChatMediaInputPaneType, CGFloat)] = [] + + var paneIndex = 0 + for pane in self.paneArrangement.panes { + let paneOrigin = CGFloat(paneIndex - self.paneArrangement.currentIndex) * width - self.paneArrangement.indexTransition * width + if paneOrigin.isLess(than: width) && CGFloat(0.0).isLess(than: (paneOrigin + width)) { + visiblePanes.append((pane, paneOrigin)) + } + paneIndex += 1 + } + + for (pane, paneOrigin) in visiblePanes { + let paneFrame = CGRect(origin: CGPoint(x: paneOrigin + leftInset, y: topInset + 54.0), size: CGSize(width: width - leftInset - rightInset, height: panelHeight - topInset)) + switch pane { + case .stickers: + if self.stickerPane.supernode == nil { + self.insertSubnode(self.stickerPane, belowSubnode: self.collectionListContainer) + self.stickerPane.frame = CGRect(origin: CGPoint(x: width, y: topInset + 54.0), size: CGSize(width: width, height: panelHeight - topInset)) + } + if self.stickerPane.frame != paneFrame { + self.stickerPane.layer.removeAnimation(forKey: "position") + transition.updateFrame(node: self.stickerPane, frame: paneFrame) + } + default: + break + } + } + + self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 0.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: true, deviceMetrics: deviceMetrics, transition: transition) + + if !displaySearch, let searchContainerNode = self.searchContainerNode { + self.searchContainerNode = nil + self.searchContainerNodeLoadedDisposable.set(nil) + + var paneIsEmpty = false + var placeholderNode: PaneSearchBarPlaceholderNode? + if let searchMode = searchMode { + switch searchMode { + case .sticker: + paneIsEmpty = true + self.stickerPane.gridNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? PaneSearchBarPlaceholderNode { + placeholderNode = itemNode + } + if let _ = itemNode as? ChatMediaInputStickerGridItemNode { + paneIsEmpty = false + } + } + default: + break + } + } + if let placeholderNode = placeholderNode { + searchContainerNode.animateOut(to: placeholderNode, animateOutSearchBar: !paneIsEmpty, transition: transition, completion: { [weak searchContainerNode] in + searchContainerNode?.removeFromSupernode() + }) + } else { + searchContainerNode.removeFromSupernode() + } + } + +// if let panRecognizer = self.panRecognizer, panRecognizer.isEnabled != !displaySearch { +// panRecognizer.isEnabled = !displaySearch +// } +// + + return (standardInputHeight, max(0.0, panelHeight - standardInputHeight)) + } + + private func enqueuePanelTransition(_ transition: ChatMediaInputPanelTransition, firstTime: Bool, thenGridTransition gridTransition: ChatMediaInputGridTransition, gridFirstTime: Bool) { + var options = ListViewDeleteAndInsertOptions() + if firstTime { + options.insert(.Synchronous) + options.insert(.LowLatency) + } else { + options.insert(.AnimateInsertion) + } + self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in + if let strongSelf = self { + strongSelf.enqueueGridTransition(gridTransition, firstTime: gridFirstTime) + if !strongSelf.didSetReady { + strongSelf.didSetReady = true + strongSelf._ready.set(.single(true)) + } + } + }) + } + + private func enqueueGridTransition(_ transition: ChatMediaInputGridTransition, firstTime: Bool) { + var itemTransition: ContainedViewLayoutTransition = .immediate + if transition.animated { + itemTransition = .animated(duration: 0.3, curve: .spring) + } + self.stickerPane.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: itemTransition, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset, updateOpaqueState: transition.updateOpaqueState), completion: { _ in }) + } + + private func updatePaneDidScroll(pane: ChatMediaInputPane, state: ChatMediaInputPaneScrollState, transition: ContainedViewLayoutTransition) { + pane.collectionListPanelOffset = 0.0 + +// let collectionListPanelOffset = self.currentCollectionListPanelOffset() +// +// self.updateAppearanceTransition(transition: transition) +// transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: collectionListPanelOffset), size: self.collectionListPanel.bounds.size)) +// transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0 + collectionListPanelOffset), size: self.collectionListSeparator.bounds.size)) +// transition.updatePosition(node: self.listView, position: CGPoint(x: self.listView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0)) + } + + func animateIn() { + self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = layout + + let insets = layout.insets(options: [.statusBar]) + let height = layout.size.height + + let _ = self.updateLayout(width: layout.size.width, topInset: insets.top, leftInset: insets.left, rightInset: insets.right, bottomInset: insets.bottom, standardInputHeight: height, inputHeight: height, maximumHeight: height, inputPanelHeight: height, transition: transition, deviceMetrics: layout.deviceMetrics, isVisible: true) + } +} + +final class DrawingStickersScreen: ViewController { + private let context: AccountContext + private let selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? + + private var controllerNode: DrawingStickersScreenNode { + return self.displayNode as! DrawingStickersScreenNode + } + + private var presentationData: PresentationData + private var presentationDataDisposable: Disposable? + + private var didPlayPresentationAnimation = false + + private let _ready = Promise() + override public var ready: Promise { + return self._ready + } + + public init(context: AccountContext, selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? = nil) { + self.context = context + self.selectSticker = selectSticker + + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + + super.init(navigationBarPresentationData: nil) + + self.navigationPresentation = .modal + + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + + self.presentationDataDisposable = (context.sharedContext.presentationData + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + let previous = strongSelf.presentationData + strongSelf.presentationData = presentationData + + if previous.theme !== presentationData.theme || previous.strings !== presentationData.strings { + strongSelf.updatePresentationData() + } + } + }) + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.presentationDataDisposable?.dispose() + } + + private func updatePresentationData() { + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + } + + override public func loadDisplayNode() { + self.displayNode = DrawingStickersScreenNode( + context: self.context, + selectSticker: self.selectSticker.flatMap { [weak self] selectSticker in + return { file, sourceNode, sourceRect in + if selectSticker(file, sourceNode, sourceRect) { + self?.dismiss() + return true + } else { + return false + } + } + } + ) + (self.displayNode as! DrawingStickersScreenNode).dismiss = { [weak self] in + self?.dismiss() + } + self._ready.set(self.controllerNode.ready.get()) + + super.displayNodeDidLoad() + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !self.didPlayPresentationAnimation { + self.didPlayPresentationAnimation = true + (self.displayNode as! DrawingStickersScreenNode).animateIn() + } + } + +// override public func inFocusUpdated(isInFocus: Bool) { +// self.controllerNode.inFocusUpdated(isInFocus: isInFocus) +// } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, navigationHeight: 0.0, transition: transition) + } +} diff --git a/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift b/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift index dec88cecf3..8a31c87ffb 100644 --- a/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift +++ b/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift @@ -7,6 +7,7 @@ import SyncCore import LegacyComponents import FFMpegBinding import LocalMediaResources +import LegacyMediaPickerUI private final class AVURLAssetCopyItem: MediaResourceDataFetchCopyLocalItem { private let url: URL @@ -65,9 +66,9 @@ struct VideoConversionConfiguration { } static func with(appConfiguration: AppConfiguration) -> VideoConversionConfiguration { - #if DEBUG - return VideoConversionConfiguration(remuxToFMp4: true) - #endif +// #if DEBUG +// return VideoConversionConfiguration(remuxToFMp4: true) +// #endif if let data = appConfiguration.data, let conversion = data["video_conversion"] as? [String: Any] { let remuxToFMp4 = conversion["remux_fmp4"] as? Bool ?? VideoConversionConfiguration.defaultValue.remuxToFMp4 @@ -233,8 +234,8 @@ private final class FetchVideoLibraryMediaResourceContext { private let throttlingContext = FetchVideoLibraryMediaResourceContext() -public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibraryMediaResource) -> Signal { - return postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) +public func fetchVideoLibraryMediaResource(account: Account, resource: VideoLibraryMediaResource) -> Signal { + return account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> take(1) |> map { view in return view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue @@ -286,6 +287,7 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr } } let updatedSize = Atomic(value: 0) + let entityRenderer = adjustments.flatMap { LegacyPaintEntityRenderer(account: account, adjustments: $0) } let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in var value = stat() if stat(path, &value) == 0 { @@ -299,7 +301,7 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) }*/ } - }))! + }), entityRenderer: entityRenderer)! let signalDisposable = signal.start(next: { next in if let result = next as? TGMediaVideoConversionResult { var value = stat() @@ -352,8 +354,8 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr } } -func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideoMediaResource) -> Signal { - return postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) +func fetchLocalFileVideoMediaResource(account: Account, resource: LocalFileVideoMediaResource) -> Signal { + return account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> take(1) |> map { view in return view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue @@ -377,10 +379,11 @@ func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideo } } let updatedSize = Atomic(value: 0) + let entityRenderer = adjustments.flatMap { LegacyPaintEntityRenderer(account: account, adjustments: $0) } let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in var value = stat() if stat(path, &value) == 0 { - /*if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { var range: Range? let _ = updatedSize.modify { updatedSize in range = updatedSize ..< Int(value.st_size) @@ -388,26 +391,26 @@ func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideo } //print("size = \(Int(value.st_size)), range: \(range!)") subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) - }*/ + } } - }))! + }), entityRenderer: entityRenderer)! let signalDisposable = signal.start(next: { next in if let result = next as? TGMediaVideoConversionResult { var value = stat() if stat(result.fileURL.path, &value) == 0 { - if config.remuxToFMp4 { - let tempFile = TempBox.shared.tempFile(fileName: "video.mp4") - if FFMpegRemuxer.remux(result.fileURL.path, to: tempFile.path) { - let _ = try? FileManager.default.removeItem(atPath: result.fileURL.path) - subscriber.putNext(.moveTempFile(file: tempFile)) - } else { - TempBox.shared.dispose(tempFile) - subscriber.putNext(.moveLocalFile(path: result.fileURL.path)) - } - } else { - subscriber.putNext(.moveLocalFile(path: result.fileURL.path)) - } - /*if let data = try? Data(contentsOf: result.fileURL, options: [.mappedRead]) { +// if config.remuxToFMp4 { +// let tempFile = TempBox.shared.tempFile(fileName: "video.mp4") +// if FFMpegRemuxer.remux(result.fileURL.path, to: tempFile.path) { +// let _ = try? FileManager.default.removeItem(atPath: result.fileURL.path) +// subscriber.putNext(.moveTempFile(file: tempFile)) +// } else { +// TempBox.shared.dispose(tempFile) +// subscriber.putNext(.moveLocalFile(path: result.fileURL.path)) +// } +// } else { +// subscriber.putNext(.moveLocalFile(path: result.fileURL.path)) +// } + if let data = try? Data(contentsOf: result.fileURL, options: [.mappedRead]) { var range: Range? let _ = updatedSize.modify { updatedSize in range = updatedSize ..< Int(value.st_size) @@ -417,7 +420,7 @@ func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideo subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) subscriber.putNext(.replaceHeader(data: data, range: 0 ..< 1024)) subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: true)) - }*/ + } } subscriber.putCompletion() } diff --git a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift index c0859cb01a..9c3afbb6e3 100644 --- a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift +++ b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift @@ -19,9 +19,9 @@ public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerC } }, fetchResource: { account, resource, ranges, _ in if let resource = resource as? VideoLibraryMediaResource { - return fetchVideoLibraryMediaResource(postbox: account.postbox, resource: resource) + return fetchVideoLibraryMediaResource(account: account, resource: resource) } else if let resource = resource as? LocalFileVideoMediaResource { - return fetchLocalFileVideoMediaResource(postbox: account.postbox, resource: resource) + return fetchLocalFileVideoMediaResource(account: account, resource: resource) } else if let resource = resource as? LocalFileGifMediaResource { return fetchLocalFileGifMediaResource(resource: resource) } else if let photoLibraryResource = resource as? PhotoLibraryMediaResource {