Camera and editor improvements

This commit is contained in:
Ilya Laktyushin 2023-05-31 15:24:07 +04:00
parent 9bd6dd35c7
commit 2b85ec7b5f
17 changed files with 372 additions and 160 deletions

View File

@ -21,11 +21,13 @@ public final class LottieAnimationComponent: Component {
public var name: String
public var mode: Mode
public var range: (CGFloat, CGFloat)?
public var waitForCompletion: Bool
public init(name: String, mode: Mode, range: (CGFloat, CGFloat)? = nil) {
public init(name: String, mode: Mode, range: (CGFloat, CGFloat)? = nil, waitForCompletion: Bool = true) {
self.name = name
self.mode = mode
self.range = range
self.waitForCompletion = waitForCompletion
}
public static func == (lhs: LottieAnimationComponent.AnimationItem, rhs: LottieAnimationComponent.AnimationItem) -> Bool {
@ -157,7 +159,7 @@ public final class LottieAnimationComponent: Component {
}
}
if let animationView = self.animationView, animationView.isAnimationPlaying {
if let animationView = self.animationView, animationView.isAnimationPlaying && component.animation.waitForCompletion {
updateComponent = false
self.currentCompletion = { [weak self] in
guard let strongSelf = self else {

View File

@ -149,10 +149,10 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
}
}
public static func encodeEntities(_ entities: [DrawingEntity], entitiesView: DrawingEntitiesView? = nil) -> Data? {
public static func encodeEntities(_ entities: [DrawingEntity], entitiesView: DrawingEntitiesView? = nil) -> [CodableDrawingEntity] {
let entities = entities
guard !entities.isEmpty else {
return nil
return []
}
if let entitiesView {
for entity in entities {
@ -161,7 +161,11 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
}
}
}
let codableEntities = entities.compactMap({ CodableDrawingEntity(entity: $0) })
return entities.compactMap({ CodableDrawingEntity(entity: $0) })
}
public static func encodeEntitiesData(_ entities: [DrawingEntity], entitiesView: DrawingEntitiesView? = nil) -> Data? {
let codableEntities = encodeEntities(entities, entitiesView: entitiesView)
if let data = try? JSONEncoder().encode(codableEntities) {
return data
} else {
@ -170,7 +174,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
}
var entitiesData: Data? {
return DrawingEntitiesView.encodeEntities(self.entities, entitiesView: self)
return DrawingEntitiesView.encodeEntitiesData(self.entities, entitiesView: self)
}
var hasChanges: Bool {

View File

@ -2392,6 +2392,8 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
}
func animateIn() {
self.entitiesView.selectEntity(nil)
if let view = self.componentHost.findTaggedView(tag: topGradientTag) {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
@ -2797,15 +2799,11 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
}
public func animateOut(_ completion: @escaping (() -> Void)) {
self.selectionContainerView.alpha = 0.0
self.entitiesView.selectEntity(nil)
self.node.animateOut(completion: {
completion()
})
//
// Queue.mainQueue().after(0.4) {
// self.node.isHidden = true
// }
}
private var orientation: UIInterfaceOrientation?
@ -2932,7 +2930,7 @@ public final class DrawingToolsInteraction {
self.activate()
}
func activate() {
public func activate() {
self.isActive = true
self.entitiesView.selectionContainerView = self.selectionContainerView

View File

@ -75,11 +75,11 @@ final class MediaPickerGridItem: GridItem {
}
}
private let maskImage = generateImage(CGSize(width: 1.0, height: 24.0), opaque: false, rotatedContext: { size, context in
private let maskImage = generateImage(CGSize(width: 1.0, height: 36.0), opaque: false, rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let gradientColors = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.6).cgColor] as CFArray
let gradientColors = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.45).cgColor] as CFArray
var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
@ -93,8 +93,10 @@ final class MediaPickerGridItemNode: GridItemNode {
var currentState: (PHFetchResult<PHAsset>, Int)?
var currentDraftState: (MediaEditorDraft, Int)?
var enableAnimations: Bool = true
var stories: Bool = false
private var selectable: Bool = false
private let backgroundNode: ASImageNode
private let imageNode: ImageNode
private var checkNode: InteractiveCheckNode?
private let gradientNode: ASImageNode
@ -115,6 +117,9 @@ final class MediaPickerGridItemNode: GridItemNode {
var selected: (() -> Void)?
override init() {
self.backgroundNode = ASImageNode()
self.backgroundNode.contentMode = .scaleToFill
self.imageNode = ImageNode()
self.imageNode.clipsToBounds = true
self.imageNode.contentMode = .scaleAspectFill
@ -273,8 +278,15 @@ final class MediaPickerGridItemNode: GridItemNode {
self.theme = theme
self.selectable = selectable
self.enableAnimations = enableAnimations
self.stories = stories
self.backgroundColor = theme.list.mediaPlaceholderColor
if stories {
if self.backgroundNode.supernode == nil {
self.insertSubnode(self.backgroundNode, at: 0)
}
}
if self.currentMediaState == nil || self.currentMediaState!.0.uniqueIdentifier != media.identifier || self.currentMediaState!.1 != index {
self.currentMediaState = (media.asset, index)
@ -290,9 +302,16 @@ final class MediaPickerGridItemNode: GridItemNode {
self.theme = theme
self.selectable = selectable
self.enableAnimations = enableAnimations
self.stories = stories
self.backgroundColor = theme.list.mediaPlaceholderColor
if stories {
if self.backgroundNode.supernode == nil {
self.insertSubnode(self.backgroundNode, at: 0)
}
}
if self.currentState == nil || self.currentState!.0 !== fetchResult || self.currentState!.1 != index {
let editingContext = interaction.editingState
let asset = fetchResult.object(at: index)
@ -334,6 +353,27 @@ final class MediaPickerGridItemNode: GridItemNode {
|> delay(0.03, queue: Queue.concurrentDefaultQueue())
)
if stories {
self.imageNode.contentUpdated = { [weak self] image in
if let self {
if self.backgroundNode.image == nil {
if let image, image.size.width > image.size.height {
self.imageNode.contentMode = .scaleAspectFit
Queue.concurrentDefaultQueue().async {
let colors = mediaEditorGetGradientColors(from: image)
let gradientImage = mediaEditorGenerateGradientImage(size: CGSize(width: 3.0, height: 128.0), colors: [colors.0, colors.1])
Queue.mainQueue().async {
self.backgroundNode.image = gradientImage
}
}
} else {
self.imageNode.contentMode = .scaleAspectFill
}
}
}
}
}
let originalSignal = assetImageSignal //assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false, synchronous: true)
let imageSignal: Signal<UIImage?, NoError> = editedSignal
|> mapToSignal { result in
@ -344,7 +384,7 @@ final class MediaPickerGridItemNode: GridItemNode {
}
}
self.imageNode.setSignal(imageSignal)
let spoilerSignal = Signal<Bool, NoError> { subscriber in
if let signal = editingContext.spoilerSignal(forIdentifier: asset.localIdentifier) {
let disposable = signal.start(next: { next in
@ -443,8 +483,9 @@ final class MediaPickerGridItemNode: GridItemNode {
override func layout() {
super.layout()
self.backgroundNode.frame = self.bounds
self.imageNode.frame = self.bounds.insetBy(dx: -1.0 + UIScreenPixel, dy: -1.0 + UIScreenPixel)
self.gradientNode.frame = CGRect(x: 0.0, y: self.bounds.height - 24.0, width: self.bounds.width, height: 24.0)
self.gradientNode.frame = CGRect(x: 0.0, y: self.bounds.height - 36.0, width: self.bounds.width, height: 36.0)
self.typeIconNode.frame = CGRect(x: 0.0, y: self.bounds.height - 20.0, width: 19.0, height: 19.0)
self.activateAreaNode.frame = self.bounds
@ -478,7 +519,20 @@ final class MediaPickerGridItemNode: GridItemNode {
}
func transitionImage() -> UIImage? {
return self.imageNode.image
if let backgroundImage = self.backgroundNode.image {
return generateImage(self.bounds.size, contextGenerator: { size, context in
if let cgImage = backgroundImage.cgImage {
context.draw(cgImage, in: CGRect(origin: .zero, size: size))
if let image = self.imageNode.image, let cgImage = image.cgImage {
let fittedSize = image.size.fitted(size)
let fittedFrame = CGRect(origin: CGPoint(x: (size.width - fittedSize.width) / 2.0, y: (size.height - fittedSize.height) / 2.0), size: fittedSize)
context.draw(cgImage, in: fittedFrame)
}
}
})
} else {
return self.imageNode.image
}
}
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {

View File

@ -1811,11 +1811,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
return
}
self.requestAttachmentMenuExpansion()
var embedded = true
if case .story = mode {
embedded = false
} else {
self.requestAttachmentMenuExpansion()
}
var updateNavigationStackImpl: ((AttachmentContainable) -> Void)?

View File

@ -1410,7 +1410,7 @@ final class AvatarEditorScreenComponent: Component {
let colors: [NSNumber] = state.selectedBackground.colors.map { Int32(bitPattern: $0) as NSNumber }
let entitiesData = DrawingEntitiesView.encodeEntities([entity])
let entitiesData = DrawingEntitiesView.encodeEntitiesData([entity])
let paintingData = TGPaintingData(
drawing: nil,

View File

@ -144,6 +144,8 @@ private final class CameraScreenComponent: CombinedComponent {
var cameraState = CameraState(mode: .photo, flashMode: .off, flashModeDidChange: false, recording: .none, duration: 0.0)
var swipeHint: CaptureControlsComponent.SwipeHint = .none
private let hapticFeedback = HapticFeedback()
init(context: AccountContext, camera: Camera, present: @escaping (ViewController) -> Void, completion: ActionSlot<Signal<CameraScreen.Result, NoError>>) {
self.context = context
self.camera = camera
@ -193,6 +195,22 @@ private final class CameraScreenComponent: CombinedComponent {
self.updated(transition: .spring(duration: 0.3))
}
func toggleFlashMode() {
if self.cameraState.flashMode == .off {
self.camera.setFlashMode(.on)
} else if self.cameraState.flashMode == .on {
self.camera.setFlashMode(.auto)
} else {
self.camera.setFlashMode(.off)
}
self.hapticFeedback.impact(.veryLight)
}
func togglePosition() {
self.camera.togglePosition()
self.hapticFeedback.impact(.veryLight)
}
func updateSwipeHint(_ hint: CaptureControlsComponent.SwipeHint) {
guard hint != self.swipeHint else {
return
@ -330,7 +348,8 @@ private final class CameraScreenComponent: CombinedComponent {
animation: LottieAnimationComponent.AnimationItem(
name: flashIconName,
mode: !state.cameraState.flashModeDidChange ? .still(position: .end) : .animating(loop: false),
range: nil
range: nil,
waitForCompletion: false
),
colors: [:],
size: CGSize(width: 40.0, height: 40.0)
@ -356,13 +375,7 @@ private final class CameraScreenComponent: CombinedComponent {
guard let state else {
return
}
if state.cameraState.flashMode == .off {
state.camera.setFlashMode(.on)
} else if state.cameraState.flashMode == .on {
state.camera.setFlashMode(.auto)
} else {
state.camera.setFlashMode(.off)
}
state.toggleFlashMode()
}
).tagged(flashButtonTag),
availableSize: CGSize(width: 40.0, height: 40.0),
@ -453,7 +466,7 @@ private final class CameraScreenComponent: CombinedComponent {
guard let state else {
return
}
state.camera.togglePosition()
state.togglePosition()
},
galleryTapped: {
guard let controller = environment.controller() as? CameraScreen else {
@ -882,7 +895,7 @@ public class CameraScreen: ViewController {
let transitionFraction = 1.0 - max(0.0, translation.x * -1.0) / self.frame.width
controller.updateTransitionProgress(transitionFraction, transition: .immediate)
} else if translation.y < -10.0 {
controller.presentGallery()
controller.presentGallery(fromGesture: true)
gestureRecognizer.isEnabled = false
gestureRecognizer.isEnabled = true
}
@ -1239,6 +1252,8 @@ public class CameraScreen: ViewController {
private var audioSessionDisposable: Disposable?
private let hapticFeedback = HapticFeedback()
public init(
context: AccountContext,
mode: Mode,
@ -1289,7 +1304,11 @@ public class CameraScreen: ViewController {
self.node.animateInFromEditor(toGallery: self.galleryController != nil)
}
func presentGallery() {
func presentGallery(fromGesture: Bool = false) {
if !fromGesture {
self.hapticFeedback.impact(.veryLight)
}
var didStopCameraCapture = false
let stopCameraCapture = { [weak self] in
guard !didStopCameraCapture, let self else {
@ -1344,9 +1363,15 @@ public class CameraScreen: ViewController {
guard !self.isDismissed else {
return
}
if !interactive {
self.hapticFeedback.impact(.veryLight)
}
self.node.camera.stopCapture(invalidate: true)
self.isDismissed = true
if animated {
self.statusBar.updateStatusBarStyle(.Ignore, animated: true)
if !interactive {
if let navigationController = self.navigationController as? NavigationController {
navigationController.updateRootContainerTransitionOffset(self.node.frame.width, transition: .immediate)
@ -1360,9 +1385,7 @@ public class CameraScreen: ViewController {
}
}
private var isTransitioning = false
public func updateTransitionProgress(_ transitionFraction: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) {
self.isTransitioning = true
let offsetX = floorToScreenPixels((1.0 - transitionFraction) * self.node.frame.width * -1.0)
transition.updateTransform(layer: self.node.backgroundView.layer, transform: CGAffineTransform(translationX: offsetX, y: 0.0))
transition.updateTransform(layer: self.node.containerView.layer, transform: CGAffineTransform(translationX: offsetX, y: 0.0))
@ -1382,7 +1405,6 @@ public class CameraScreen: ViewController {
}
public func completeWithTransitionProgress(_ transitionFraction: CGFloat, velocity: CGFloat, dismissing: Bool) {
self.isTransitioning = false
if dismissing {
if transitionFraction < 0.7 || velocity < -1000.0 {
self.statusBar.updateStatusBarStyle(.Ignore, animated: true)

View File

@ -107,6 +107,8 @@ public final class MediaEditor {
}
private let playerPlaybackStatePromise = Promise<(Double, Double, Bool)>((0.0, 0.0, false))
public var onFirstDisplay: () -> Void = {}
public func playerState(framesCount: Int) -> Signal<MediaEditorPlayerState?, NoError> {
return self.playerPromise.get()
|> mapToSignal { [weak self] player in
@ -228,6 +230,7 @@ public final class MediaEditor {
gradientColors: nil,
videoTrimRange: nil,
videoIsMuted: false,
videoIsFullHd: false,
drawing: nil,
entities: [],
toolValues: [:]
@ -267,21 +270,11 @@ public final class MediaEditor {
if let device = renderTarget.mtlDevice, CVMetalTextureCacheCreate(nil, nil, device, nil, &self.textureCache) != kCVReturnSuccess {
print("error")
}
func gradientColors(from image: UIImage) -> (UIColor, UIColor) {
let context = DrawingContext(size: CGSize(width: 1.0, height: 4.0), scale: 1.0, clear: false)!
context.withFlippedContext({ context in
if let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 4.0))
}
})
return (context.colorAt(CGPoint(x: 0.0, y: 0.0)), context.colorAt(CGPoint(x: 0.0, y: 3.0)))
}
let textureSource: Signal<(TextureSource, UIImage?, AVPlayer?, UIColor, UIColor), NoError>
switch subject {
case let .image(image, _):
let colors = gradientColors(from: image)
let colors = mediaEditorGetGradientColors(from: image)
textureSource = .single((ImageTextureSource(image: image, renderTarget: renderTarget), image, nil, colors.0, colors.1))
case let .draft(draft):
guard let image = UIImage(contentsOfFile: draft.path) else {
@ -291,7 +284,7 @@ public final class MediaEditor {
if let gradientColors = draft.values.gradientColors {
colors = (gradientColors.first!, gradientColors.last!)
} else {
colors = gradientColors(from: image)
colors = mediaEditorGetGradientColors(from: image)
}
textureSource = .single((ImageTextureSource(image: image, renderTarget: renderTarget), image, nil, colors.0, colors.1))
case let .video(path, _):
@ -304,7 +297,7 @@ public final class MediaEditor {
let playerItem = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: playerItem)
if let image {
let colors = gradientColors(from: UIImage(cgImage: image))
let colors = mediaEditorGetGradientColors(from: UIImage(cgImage: image))
subscriber.putNext((VideoTextureSource(player: player, renderTarget: renderTarget), nil, player, colors.0, colors.1))
} else {
subscriber.putNext((VideoTextureSource(player: player, renderTarget: renderTarget), nil, player, .black, .black))
@ -329,7 +322,7 @@ public final class MediaEditor {
}
}
if !degraded {
let colors = gradientColors(from: image)
let colors = mediaEditorGetGradientColors(from: image)
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil, resultHandler: { asset, _, _ in
if let asset {
let playerItem = AVPlayerItem(asset: asset)
@ -359,7 +352,7 @@ public final class MediaEditor {
}
}
if !degraded {
let colors = gradientColors(from: image)
let colors = mediaEditorGetGradientColors(from: image)
subscriber.putNext((ImageTextureSource(image: image, renderTarget: renderTarget), image, nil, colors.0, colors.1))
subscriber.putCompletion()
}
@ -377,7 +370,7 @@ public final class MediaEditor {
if let self {
let (source, image, player, topColor, bottomColor) = sourceAndColors
self.renderer.onNextRender = { [weak self] in
self?.previewView?.removeTransitionImage()
self?.onFirstDisplay()
}
self.renderer.textureSource = source
self.player = player
@ -446,6 +439,10 @@ public final class MediaEditor {
self.values = self.values.withUpdatedVideoIsMuted(videoIsMuted)
}
public func setVideoIsFullHd(_ videoIsFullHd: Bool) {
self.values = self.values.withUpdatedVideoIsFullHd(videoIsFullHd)
}
private var targetTimePosition: (CMTime, Bool)?
private var updatingTimePosition = false
public func seek(_ position: Double, andPlay play: Bool) {

View File

@ -12,7 +12,7 @@ import TelegramAnimatedStickerNode
import YuvConversion
import StickerResources
func mediaEditorGenerateGradientImage(size: CGSize, colors: [UIColor]) -> UIImage? {
public func mediaEditorGenerateGradientImage(size: CGSize, colors: [UIColor]) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 1.0)
if let context = UIGraphicsGetCurrentContext() {
let gradientColors = colors.map { $0.cgColor } as CFArray
@ -29,6 +29,16 @@ func mediaEditorGenerateGradientImage(size: CGSize, colors: [UIColor]) -> UIImag
return image
}
public func mediaEditorGetGradientColors(from image: UIImage) -> (UIColor, UIColor) {
let context = DrawingContext(size: CGSize(width: 5.0, height: 5.0), scale: 1.0, clear: false)!
context.withFlippedContext({ context in
if let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 5.0, height: 5.0))
}
})
return (context.colorAt(CGPoint(x: 2.0, y: 0.0)), context.colorAt(CGPoint(x: 2.0, y: 4.0)))
}
final class MediaEditorComposer {
let device: MTLDevice?
private let colorSpace: CGColorSpace
@ -201,7 +211,7 @@ private func makeEditorImageFrameComposition(inputImage: CIImage, gradientImage:
var initialScale: CGFloat
if mediaImage.extent.height > mediaImage.extent.width {
initialScale = dimensions.height / mediaImage.extent.height
initialScale = max(dimensions.width / mediaImage.extent.width, dimensions.height / mediaImage.extent.height)
} else {
initialScale = dimensions.width / mediaImage.extent.width
}

View File

@ -67,33 +67,4 @@ public final class MediaEditorPreviewView: MTKView, MTKViewDelegate, RenderTarge
}
self.renderer?.renderFrame()
}
private var transitionView: UIImageView?
public func setTransitionImage(_ image: UIImage) {
self.transitionView?.removeFromSuperview()
let transitionView = UIImageView(image: image)
transitionView.frame = self.bounds
self.addSubview(transitionView)
self.transitionView = transitionView
}
public func removeTransitionImage() {
if let transitionView = self.transitionView {
// transitionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak transitionView] _ in
//
// })
transitionView.removeFromSuperview()
self.transitionView = nil
}
}
public override func layoutSubviews() {
super.layoutSubviews()
if let transitionView = self.transitionView {
transitionView.frame = self.bounds
}
}
}

View File

@ -183,7 +183,7 @@ final class MediaEditorRenderer: TextureConsumer {
}
}
if self.renderTarget != nil {
let _ = self.outputRenderPass.process(input: texture, device: device, commandBuffer: commandBuffer)
self.outputRenderPass.process(input: texture, device: device, commandBuffer: commandBuffer)
}
self.finalTexture = texture
@ -191,14 +191,30 @@ final class MediaEditorRenderer: TextureConsumer {
if let self {
self.semaphore.signal()
#if targetEnvironment(simulator)
if let onNextRender = self.onNextRender {
self.onNextRender = nil
Queue.mainQueue().async {
onNextRender()
}
}
#endif
}
}
#if targetEnvironment(simulator)
#else
if let renderTarget = self.renderTarget, let drawable = renderTarget.drawable {
drawable.addPresentedHandler { [weak self] _ in
if let self, let onNextRender = self.onNextRender {
self.onNextRender = nil
Queue.mainQueue().async {
onNextRender()
}
}
}
}
#endif
if let _ = self.renderTarget {
commandBuffer.commit()

View File

@ -51,6 +51,7 @@ public final class MediaEditorValues: Codable {
case videoTrimRange
case videoIsMuted
case videoIsFullHd
case drawing
case entities
@ -68,6 +69,7 @@ public final class MediaEditorValues: Codable {
public let videoTrimRange: Range<Double>?
public let videoIsMuted: Bool
public let videoIsFullHd: Bool
public let drawing: UIImage?
public let entities: [CodableDrawingEntity]
@ -83,6 +85,7 @@ public final class MediaEditorValues: Codable {
gradientColors: [UIColor]?,
videoTrimRange: Range<Double>?,
videoIsMuted: Bool,
videoIsFullHd: Bool,
drawing: UIImage?,
entities: [CodableDrawingEntity],
toolValues: [EditorToolKey: Any]
@ -96,6 +99,7 @@ public final class MediaEditorValues: Codable {
self.gradientColors = gradientColors
self.videoTrimRange = videoTrimRange
self.videoIsMuted = videoIsMuted
self.videoIsFullHd = videoIsFullHd
self.drawing = drawing
self.entities = entities
self.toolValues = toolValues
@ -122,6 +126,7 @@ public final class MediaEditorValues: Codable {
self.videoTrimRange = try container.decodeIfPresent(Range<Double>.self, forKey: .videoTrimRange)
self.videoIsMuted = try container.decode(Bool.self, forKey: .videoIsMuted)
self.videoIsFullHd = try container.decode(Bool.self, forKey: .videoIsFullHd)
if let drawingData = try container.decodeIfPresent(Data.self, forKey: .drawing), let image = UIImage(data: drawingData) {
self.drawing = image
@ -175,31 +180,35 @@ public final class MediaEditorValues: Codable {
}
public func makeCopy() -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: offset, cropSize: self.cropSize, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: offset, cropSize: self.cropSize, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedVideoIsFullHd(_ videoIsFullHd: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedVideoTrimRange(_ videoTrimRange: Range<Double>) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: drawing, entities: entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: drawing, entities: entities, toolValues: self.toolValues)
}
func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, drawing: self.drawing, entities: self.entities, toolValues: toolValues)
}
}
@ -931,30 +940,40 @@ private let hasHEVCHardwareEncoder: Bool = {
return result == noErr
}()
public func recommendedVideoExportConfiguration(values: MediaEditorValues, frameRate: Float) -> MediaEditorVideoExport.Configuration {
public func recommendedVideoExportConfiguration(values: MediaEditorValues, forceFullHd: Bool = false, frameRate: Float) -> MediaEditorVideoExport.Configuration {
let compressionProperties: [String: Any]
let codecType: AVVideoCodecType
if hasHEVCHardwareEncoder {
codecType = AVVideoCodecType.hevc
compressionProperties = [
AVVideoAverageBitRateKey: 2000000,
AVVideoAverageBitRateKey: 3800000,
AVVideoProfileLevelKey: kVTProfileLevel_HEVC_Main_AutoLevel
]
} else {
codecType = AVVideoCodecType.h264
compressionProperties = [
AVVideoAverageBitRateKey: 2000000,
AVVideoAverageBitRateKey: 3800000,
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
]
}
let width: Int
let height: Int
if values.videoIsFullHd {
width = 1080
height = 1920
} else {
width = 720
height = 1280
}
let videoSettings: [String: Any] = [
AVVideoCodecKey: codecType,
AVVideoCompressionPropertiesKey: compressionProperties,
AVVideoWidthKey: 720,
AVVideoHeightKey: 1280
AVVideoWidthKey: width,
AVVideoHeightKey: height
]
let audioSettings: [String: Any] = [

View File

@ -131,6 +131,7 @@ class DefaultRenderPass: RenderPass {
final class OutputRenderPass: DefaultRenderPass {
weak var renderTarget: RenderTarget?
@discardableResult
override func process(input: MTLTexture, device: MTLDevice, commandBuffer: MTLCommandBuffer) -> MTLTexture? {
guard let renderTarget = self.renderTarget, let renderPassDescriptor = renderTarget.renderPassDescriptor else {
return nil
@ -154,10 +155,9 @@ final class OutputRenderPass: DefaultRenderPass {
renderCommandEncoder.endEncoding()
if let drawable = renderTarget.drawable {
if let drawable = renderTarget.drawable {
commandBuffer.present(drawable)
}
return nil
}
}

View File

@ -29,6 +29,7 @@ swift_library(
"//submodules/TelegramUI/Components/MediaEditor",
"//submodules/DrawingUI:DrawingUI",
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
"//submodules/Components/BundleIconComponent:BundleIconComponent",
"//submodules/TelegramUI/Components/MessageInputPanelComponent",
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
"//submodules/TooltipUI",

View File

@ -22,6 +22,7 @@ import AvatarNode
import ShareWithPeersScreen
import PresentationDataUtils
import ContextUI
import BundleIconComponent
enum DrawingScreenType {
case drawing
@ -172,6 +173,7 @@ final class MediaEditorScreenComponent: Component {
private let privacyButton = ComponentView<Empty>()
private let muteButton = ComponentView<Empty>()
private let saveButton = ComponentView<Empty>()
private let settingsButton = ComponentView<Empty>()
private var component: MediaEditorScreenComponent?
private weak var state: State?
@ -235,6 +237,11 @@ final class MediaEditorScreenComponent: Component {
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
}
if let view = self.settingsButton.view {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
}
if let view = self.privacyButton.view {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
@ -284,6 +291,11 @@ final class MediaEditorScreenComponent: Component {
transition.setScale(view: view, scale: 0.1)
}
if let view = self.settingsButton.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
}
if let view = self.privacyButton.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
@ -337,6 +349,11 @@ final class MediaEditorScreenComponent: Component {
transition.setScale(view: view, scale: 0.1)
}
if let view = self.settingsButton.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
}
if let view = self.privacyButton.view {
transition.setAlpha(view: view, alpha: 0.0)
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
@ -389,6 +406,11 @@ final class MediaEditorScreenComponent: Component {
transition.setScale(view: view, scale: 1.0)
}
if let view = self.settingsButton.view {
transition.setAlpha(view: view, alpha: 1.0)
transition.setScale(view: view, scale: 1.0)
}
if let view = self.privacyButton.view {
transition.setAlpha(view: view, alpha: 1.0)
view.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2)
@ -866,6 +888,44 @@ final class MediaEditorScreenComponent: Component {
}
}
if let _ = state.playerState {
let settingsButtonSize = self.settingsButton.update(
transition: transition,
component: AnyComponent(Button(
content: AnyComponent(
BundleIconComponent(
name: "Chat/Input/Media/EntityInputSettingsIcon",
tintColor: UIColor(rgb: 0xffffff)
)
),
action: {
if let controller = environment.controller() as? MediaEditorScreen {
controller.requestSettings()
}
}
)),
environment: {},
containerSize: CGSize(width: 44.0, height: 44.0)
)
let settingsButtonFrame = CGRect(
origin: CGPoint(x: floorToScreenPixels((availableSize.width - settingsButtonSize.width) / 2.0), y: environment.safeInsets.top + 20.0 - inputPanelOffset),
size: settingsButtonSize
)
if let settingsButtonView = self.settingsButton.view {
if settingsButtonView.superview == nil {
settingsButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
settingsButtonView.layer.shadowRadius = 4.0
settingsButtonView.layer.shadowColor = UIColor.black.cgColor
settingsButtonView.layer.shadowOpacity = 0.2
self.addSubview(settingsButtonView)
}
transition.setPosition(view: settingsButtonView, position: settingsButtonFrame.center)
transition.setBounds(view: settingsButtonView, bounds: CGRect(origin: .zero, size: settingsButtonFrame.size))
transition.setScale(view: settingsButtonView, scale: self.inputPanelExternalState.isEditing || isEditingTextEntity ? 0.01 : 1.0)
transition.setAlpha(view: settingsButtonView, alpha: self.inputPanelExternalState.isEditing || isEditingTextEntity ? 0.0 : 1.0)
}
}
return availableSize
}
}
@ -948,6 +1008,7 @@ public final class MediaEditorScreen: ViewController {
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
private let previewContainerView: UIView
private var transitionInView: UIImageView?
private let gradientView: UIImageView
private var gradientColorsDisposable: Disposable?
@ -1096,7 +1157,7 @@ public final class MediaEditorScreen: ViewController {
let mediaEntity = DrawingMediaEntity(content: subject.mediaContent, size: fittedSize)
mediaEntity.position = CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0)
if fittedSize.height > fittedSize.width {
mediaEntity.scale = storyDimensions.height / fittedSize.height
mediaEntity.scale = max(storyDimensions.width / fittedSize.width, storyDimensions.height / fittedSize.height)
} else {
mediaEntity.scale = storyDimensions.width / fittedSize.width
}
@ -1238,10 +1299,17 @@ public final class MediaEditorScreen: ViewController {
}
@objc func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {
if self.entitiesView.hasSelection {
self.entitiesView.selectEntity(nil)
let location = gestureRecognizer.location(in: self.view)
var entitiesHitTestResult = self.entitiesView.hitTest(self.view.convert(location, to: self.entitiesView), with: nil)
if entitiesHitTestResult is DrawingMediaEntityView {
entitiesHitTestResult = nil
}
if entitiesHitTestResult == nil {
if self.entitiesView.hasSelection {
self.entitiesView.selectEntity(nil)
}
self.view.endEditing(true)
}
self.view.endEditing(true)
}
func animateIn() {
@ -1252,9 +1320,27 @@ public final class MediaEditorScreen: ViewController {
view.animateIn(from: .camera)
}
case let .gallery(transitionIn):
if let transitionImage = transitionIn.sourceImage {
if let sourceImage = transitionIn.sourceImage {
self.previewContainerView.alpha = 1.0
self.previewView.setTransitionImage(transitionImage)
let transitionInView = UIImageView(image: sourceImage)
var initialScale: CGFloat
if sourceImage.size.height > sourceImage.size.width {
initialScale = max(self.previewContainerView.bounds.width / sourceImage.size.width, self.previewContainerView.bounds.height / sourceImage.size.height)
} else {
initialScale = self.previewContainerView.bounds.width / sourceImage.size.width
}
transitionInView.center = CGPoint(x: self.previewContainerView.bounds.width / 2.0, y: self.previewContainerView.bounds.height / 2.0)
transitionInView.transform = CGAffineTransformMakeScale(initialScale, initialScale)
self.previewContainerView.addSubview(transitionInView)
self.transitionInView = transitionInView
self.mediaEditor?.onFirstDisplay = { [weak self] in
if let self, let transitionInView = self.transitionInView {
transitionInView.removeFromSuperview()
self.transitionInView = nil
}
}
}
if let sourceView = transitionIn.sourceView {
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
@ -1265,7 +1351,7 @@ public final class MediaEditorScreen: ViewController {
let sourceScale = sourceLocalFrame.width / self.previewContainerView.frame.width
let sourceAspectRatio = sourceLocalFrame.height / sourceLocalFrame.width
let duration: Double = 0.5
let duration: Double = 0.4
self.previewContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: self.previewContainerView.center, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
self.previewContainerView.layer.animateScale(from: sourceScale, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
@ -1311,7 +1397,7 @@ public final class MediaEditorScreen: ViewController {
let transitionOutView = UIImageView(image: sourceImage)
var initialScale: CGFloat
if sourceImage.size.height > sourceImage.size.width {
initialScale = self.previewContainerView.bounds.height / sourceImage.size.height
initialScale = max(self.previewContainerView.bounds.width / sourceImage.size.width, self.previewContainerView.bounds.height / sourceImage.size.height)
} else {
initialScale = self.previewContainerView.bounds.width / sourceImage.size.width
}
@ -1560,6 +1646,7 @@ public final class MediaEditorScreen: ViewController {
self.interaction?.insertEntity(textEntity)
return
case .drawing:
self.interaction?.deactivate()
let controller = DrawingScreen(context: self.context, sourceHint: .storyEditor, size: self.previewContainerView.frame.size, originalSize: storyDimensions, isVideo: false, isAvatar: false, drawingView: self.drawingView, entitiesView: self.entitiesView, selectionContainerView: self.selectionContainerView, existingStickerPickerInputData: self.stickerPickerInputData)
self.drawingScreen = controller
self.drawingView.isUserInteractionEnabled = true
@ -1573,6 +1660,7 @@ public final class MediaEditorScreen: ViewController {
self?.animateInFromTool()
self?.entitiesView.selectEntity(nil)
self?.interaction?.activate()
}
controller.requestApply = { [weak controller, weak self] in
self?.drawingScreen = nil
@ -1589,6 +1677,7 @@ public final class MediaEditorScreen: ViewController {
}
self?.entitiesView.selectEntity(nil)
self?.interaction?.activate()
}
self.controller?.present(controller, in: .current)
self.animateOutToTool()
@ -2020,7 +2109,8 @@ public final class MediaEditorScreen: ViewController {
mediaEditor.stop()
let codableEntities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }.compactMap({ CodableDrawingEntity(entity: $0) })
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
if mediaEditor.resultIsVideo {
@ -2100,7 +2190,8 @@ public final class MediaEditorScreen: ViewController {
return
}
let codableEntities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }.compactMap({ CodableDrawingEntity(entity: $0) })
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
let tempVideoPath = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).mp4"
@ -2169,7 +2260,7 @@ public final class MediaEditorScreen: ViewController {
guard let self else {
return
}
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, frameRate: 60.0)
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, forceFullHd: true, frameRate: 60.0)
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).mp4"
let videoExport = MediaEditorVideoExport(account: self.context.account, subject: exportSubject, configuration: configuration, outputPath: outputPath)
self.videoExport = videoExport
@ -2201,6 +2292,10 @@ public final class MediaEditorScreen: ViewController {
}
}
func requestSettings() {
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)

View File

@ -15,6 +15,20 @@ private let borderHeight: CGFloat = 1.0 + UIScreenPixel
private let frameWidth: CGFloat = 24.0
private let minumumDuration: CGFloat = 1.0
private class VideoFrameLayer: SimpleShapeLayer {
private let stripeLayer = SimpleShapeLayer()
override func layoutSublayers() {
super.layoutSublayers()
if self.stripeLayer.superlayer == nil {
self.stripeLayer.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3).cgColor
self.addSublayer(self.stripeLayer)
}
self.stripeLayer.frame = CGRect(x: self.bounds.width - UIScreenPixel, y: 0.0, width: UIScreenPixel, height: self.bounds.height)
}
}
final class VideoScrubberComponent: Component {
typealias EnvironmentType = Empty
@ -87,8 +101,8 @@ final class VideoScrubberComponent: Component {
private let transparentFramesContainer = UIView()
private let opaqueFramesContainer = UIView()
private var transparentFrameLayers: [CALayer] = []
private var opaqueFrameLayers: [CALayer] = []
private var transparentFrameLayers: [VideoFrameLayer] = []
private var opaqueFrameLayers: [VideoFrameLayer] = []
private var component: VideoScrubberComponent?
private weak var state: EmptyComponentState?
@ -118,6 +132,15 @@ final class VideoScrubberComponent: Component {
context.fillPath()
})?.withRenderingMode(.alwaysTemplate)
let positionImage = generateImage(CGSize(width: 2.0, height: 42.0), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
let path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: CGSize(width: 2.0, height: 42.0)), cornerRadius: 1.0)
context.addPath(path.cgPath)
context.fillPath()
})
self.leftHandleView.image = handleImage
self.leftHandleView.isUserInteractionEnabled = true
self.leftHandleView.tintColor = .white
@ -127,6 +150,9 @@ final class VideoScrubberComponent: Component {
self.rightHandleView.isUserInteractionEnabled = true
self.rightHandleView.tintColor = .white
self.cursorView.image = positionImage
self.cursorView.isUserInteractionEnabled = true
self.borderView.image = generateImage(CGSize(width: 1.0, height: scrubberHeight), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
@ -151,7 +177,7 @@ final class VideoScrubberComponent: Component {
self.leftHandleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleLeftHandlePan(_:))))
self.rightHandleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleRightHandlePan(_:))))
//self.rightHandleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handlePositionHandlePan(_:))))
self.cursorView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handlePositionHandlePan(_:))))
}
required init?(coder: NSCoder) {
@ -234,6 +260,13 @@ final class VideoScrubberComponent: Component {
}
self.state?.updated(transition: transition)
}
@objc private func handlePositionHandlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let _ = self.component else {
return
}
//let location = gestureRecognizer.location(in: self)
}
func update(component: VideoScrubberComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
let previousFramesUpdateTimestamp = self.component?.framesUpdateTimestamp
@ -245,15 +278,15 @@ final class VideoScrubberComponent: Component {
if component.framesUpdateTimestamp != previousFramesUpdateTimestamp {
for i in 0 ..< component.frames.count {
let transparentFrameLayer: CALayer
let opaqueFrameLayer: CALayer
let transparentFrameLayer: VideoFrameLayer
let opaqueFrameLayer: VideoFrameLayer
if i >= self.transparentFrameLayers.count {
transparentFrameLayer = SimpleLayer()
transparentFrameLayer = VideoFrameLayer()
transparentFrameLayer.masksToBounds = true
transparentFrameLayer.contentsGravity = .resizeAspectFill
self.transparentFramesContainer.layer.addSublayer(transparentFrameLayer)
self.transparentFrameLayers.append(transparentFrameLayer)
opaqueFrameLayer = SimpleLayer()
opaqueFrameLayer = VideoFrameLayer()
opaqueFrameLayer.masksToBounds = true
opaqueFrameLayer.contentsGravity = .resizeAspectFill
self.opaqueFramesContainer.layer.addSublayer(opaqueFrameLayer)

View File

@ -191,41 +191,41 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
accountSettingsController.parentController = self
controllers.append(accountSettingsController)
tabBarController.cameraItemAndAction = (
UITabBarItem(title: "Camera", image: UIImage(bundleImageName: "Chat List/Tabs/IconCamera"), tag: 2),
{ [weak self] in
guard let self else {
return
}
let coordinator = self.openStoryCamera(
transitionIn: nil,
transitionedIn: { [weak self] in
guard let self, let rootTabController = self.rootTabController else {
return
}
if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController}) {
rootTabController.selectedIndex = index
}
},
transitionOut: { [weak self] finished in
guard let self else {
return nil
}
if finished {
if let chatListController = self.chatListController as? ChatListControllerImpl, let transitionView = chatListController.transitionViewForOwnStoryItem() {
return StoryCameraTransitionOut(
destinationView: transitionView,
destinationRect: transitionView.bounds,
destinationCornerRadius: transitionView.bounds.height / 2.0
)
}
}
return nil
}
)
coordinator?.animateIn()
}
)
// tabBarController.cameraItemAndAction = (
// UITabBarItem(title: "Camera", image: UIImage(bundleImageName: "Chat List/Tabs/IconCamera"), tag: 2),
// { [weak self] in
// guard let self else {
// return
// }
// let coordinator = self.openStoryCamera(
// transitionIn: nil,
// transitionedIn: { [weak self] in
// guard let self, let rootTabController = self.rootTabController else {
// return
// }
// if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController}) {
// rootTabController.selectedIndex = index
// }
// },
// transitionOut: { [weak self] finished in
// guard let self else {
// return nil
// }
// if finished {
// if let chatListController = self.chatListController as? ChatListControllerImpl, let transitionView = chatListController.transitionViewForOwnStoryItem() {
// return StoryCameraTransitionOut(
// destinationView: transitionView,
// destinationRect: transitionView.bounds,
// destinationCornerRadius: transitionView.bounds.height / 2.0
// )
// }
// }
// return nil
// }
// )
// coordinator?.animateIn()
// }
// )
tabBarController.setControllers(controllers, selectedIndex: restoreSettignsController != nil ? (controllers.count - 1) : (controllers.count - 2))
@ -400,16 +400,6 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
var attributes: [MessageAttribute] = []
let imageFlags: TelegramMediaImageFlags = []
// var stickerFiles: [TelegramMediaFile] = []
// if !stickers.isEmpty {
// for fileReference in stickers {
// stickerFiles.append(fileReference.media)
// }
// }
// if !stickerFiles.isEmpty {
// attributes.append(EmbeddedMediaStickersMessageAttribute(files: stickerFiles))
// imageFlags.insert(.hasStickers)
// }
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: imageFlags)
if let timeout, timeout > 0 && timeout <= 60 {