From 2c4c9a9d9b77e83cd75d32efcb539f8a7ab684c2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 20 Feb 2026 13:28:58 +0400 Subject: [PATCH] [WIP] Live photos --- .../Telegram-iOS/en.lproj/Localizable.strings | 3 + .../AccountContext/Sources/Premium.swift | 2 + .../Sources/Node/ChatListItem.swift | 2 +- .../Sources/Node/ChatListItemStrings.swift | 13 +- .../Items/UniversalVideoGalleryItem.swift | 4 + .../LegacyComponents/TGCameraCapturedVideo.h | 1 + .../LegacyComponents/TGMediaEditingContext.h | 14 + .../TGPhotoCaptionInputMixin.h | 9 + .../TGPhotoPaintStickersContext.h | 10 + .../LegacyComponents/TGVideoEditAdjustments.h | 2 + .../Sources/TGCameraCapturedVideo.m | 15 +- .../LegacyComponents/Sources/TGMediaAsset.m | 2 +- .../Sources/TGMediaAssetsController.m | 77 ++-- .../Sources/TGMediaEditingContext.m | 94 ++++ .../TGMediaPickerGalleryInterfaceView.m | 66 ++- .../TGMediaPickerGalleryPhotoItemView.h | 1 + .../TGMediaPickerGalleryPhotoItemView.m | 422 +++++++++++++++++- .../Sources/TGMediaPickerGalleryVideoItem.m | 5 +- .../TGMediaPickerGalleryVideoItemView.m | 54 ++- .../Sources/TGPhotoCaptionInputMixin.m | 29 +- .../Sources/TGVideoEditAdjustments.m | 15 + .../Sources/LegacyAttachmentMenu.swift | 5 +- .../Sources/LegacyMediaPickers.swift | 31 +- .../Sources/LegacyPaintStickersContext.swift | 1 + submodules/MediaPickerUI/BUILD | 1 + .../Sources/LegacyMediaPickerGallery.swift | 35 +- .../Sources/LivePhotoButton.swift | 187 ++++++++ .../Sources/ChannelAdminsController.swift | 2 +- .../PremiumUI/Sources/PremiumDemoScreen.swift | 25 ++ .../Sources/PremiumIntroScreen.swift | 24 +- .../Sources/State/Serialization.swift | 2 +- .../TelegramNotices/Sources/Notices.swift | 32 ++ .../PresentationResourcesItemList.swift | 2 +- .../Sources/AvatarEditorScreen.swift | 1 + .../Sources/CameraVideoSource.swift | 1 + .../Sources/ChatMessageBubbleItemNode.swift | 3 +- .../ChatMessageInteractiveMediaNode.swift | 9 + .../Sources/ChatRankInfoScreen.swift | 56 ++- .../MediaEditor/Sources/MediaEditor.swift | 1 + .../Sources/MediaEditorValues.swift | 69 +-- .../Sources/MediaEditorVideoExport.swift | 46 +- .../PeerInfoScreenPerformButtonAction.swift | 48 +- .../Sources/FetchVideoMediaResource.swift | 3 + .../Sources/VideoMessageCameraScreen.swift | 2 +- .../LiveBounce.imageset/Contents.json | 12 + .../LiveBounce.imageset/bounce.pdf | Bin 0 -> 7420 bytes .../LiveLoop.imageset/Contents.json | 12 + .../Media Editor/LiveLoop.imageset/loop.pdf | Bin 0 -> 7530 bytes .../LiveOff.imageset/Contents.json | 12 + .../Media Editor/LiveOff.imageset/liveoff.pdf | Bin 0 -> 7129 bytes .../LiveOn.imageset/Contents.json | 12 + .../Media Editor/LiveOn.imageset/live.pdf | Bin 0 -> 7144 bytes .../Media Editor/Scan.imageset/Contents.json | 12 + .../Media Editor/Scan.imageset/scandoc.pdf | Bin 0 -> 6081 bytes .../ChatControllerOpenDateContextMenu.swift | 4 +- .../Sources/Chat/ChatControllerPaste.swift | 1 + .../Sources/ChatControllerNode.swift | 4 + .../ChatInterfaceStateContextMenus.swift | 10 +- .../Sources/SharedAccountContext.swift | 4 + 59 files changed, 1355 insertions(+), 154 deletions(-) create mode 100644 submodules/MediaPickerUI/Sources/LivePhotoButton.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/LiveBounce.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/LiveBounce.imageset/bounce.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/LiveLoop.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/LiveLoop.imageset/loop.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/LiveOff.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/LiveOff.imageset/liveoff.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/LiveOn.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/LiveOn.imageset/live.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/Scan.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/Scan.imageset/scandoc.pdf diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 0ff62e025c..c3292dd630 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15815,3 +15815,6 @@ Error: %8$@"; "Notification.CopyProtection.StillEnabled" = "Sharing in this chat is still disabled"; "Gallery.CreateSticker" = "Create Sticker"; + +"Premium.CopyProtection" = "Disable Sharing"; +"Premium.CopyProtectionInfo" = "Prevent forwarding, saving and copying content in private chats."; diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index 74ee640f27..3dfd66de17 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -43,6 +43,7 @@ public enum PremiumIntroSource { case animatedEmoji case messageEffects case todo + case copyProtection case auth(String) case premiumGift(TelegramMediaFile) } @@ -83,6 +84,7 @@ public enum PremiumDemoSubject { case business case messageEffects case todo + case copyProtection case businessLocation case businessHours diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 8300e4270d..2b42d5c2e8 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -964,7 +964,7 @@ private final class ChatListMediaPreviewNode: ASDisplayNode { if file.isInstantVideo { isRound = true } - if file.isVideo && !file.isAnimated { + if file.isVideo && !file.isAnimated && !file.isLivePhoto { self.playIcon.isHidden = false } else { self.playIcon.isHidden = true diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index a356607d57..572d462d19 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -255,12 +255,21 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: break inner } else { if message.text.isEmpty { - messageText = strings.Message_Video + if flags.contains(.isLivePhoto) { + //TODO:localize + messageText = "Live Photo" + } else { + messageText = strings.Message_Video + } processed = true } else { if enableMediaEmoji { if !fileMedia.isAnimated { - messageText = "📹 \(messageText)" + if flags.contains(.isLivePhoto) { + messageText = "🖼 \(messageText)" + } else { + messageText = "📹 \(messageText)" + } } } processed = true diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index e038690c57..3b880ea6ac 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1376,6 +1376,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if let content = item.content as? NativeVideoContent { isAnimated = content.fileReference.media.isAnimated self.videoFramePreview = MediaPlayerFramePreview(postbox: item.context.account.postbox, userLocation: content.userLocation, userContentType: .video, fileReference: content.fileReference) + if content.fileReference.media.isLivePhoto { + disablePlayerControls = true + isAnimated = false + } } else if let _ = item.content as? SystemVideoContent { self._title.set(.single(item.presentationData.strings.Message_Video)) } else if let content = item.content as? WebEmbedVideoContent { diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraCapturedVideo.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraCapturedVideo.h index 724895304a..4231e350bb 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraCapturedVideo.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraCapturedVideo.h @@ -16,6 +16,7 @@ @property (nonatomic, readonly) NSString *uniformTypeIdentifier; - (instancetype)initWithURL:(NSURL *)url; +- (instancetype)initWithURL:(NSURL *)url isAnimation:(bool)isAnimation; - (instancetype)initWithAsset:(TGMediaAsset *)asset livePhoto:(bool)livePhoto; - (void)_cleanUp; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h index 6e8224ca8a..d84456a51a 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h @@ -18,6 +18,14 @@ @end +typedef NS_ENUM(NSUInteger, TGMediaLivePhotoMode) +{ + TGMediaLivePhotoModeOff = 0, + TGMediaLivePhotoModeLive, + TGMediaLivePhotoModeLoop, + TGMediaLivePhotoModeBounce +}; + @class TGPaintingData; @protocol TGMediaEditAdjustments @@ -105,6 +113,12 @@ - (void)setPrice:(NSNumber *)price forItem:(NSObject *)item; - (SSignal *)pricesUpdatedSignal; +- (TGMediaLivePhotoMode)livePhotoModeForItem:(NSObject *)item; +- (SSignal *)livePhotoModeSignalForItem:(NSObject *)item; +- (SSignal *)livePhotoModeForIdentifier:(NSString *)identifier; +- (void)setLivePhotoMode:(TGMediaLivePhotoMode)mode forItem:(NSObject *)item; +- (SSignal *)livePhotoModesUpdatedSignal; + - (UIImage *)paintingImageForItem:(NSObject *)item; - (UIImage *)stillPaintingImageForItem:(NSObject *)item; - (bool)setPaintingData:(NSData *)data entitiesData:(NSData *)entitiesData image:(UIImage *)image stillImage:(UIImage *)stillImage forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl entitiesDataUrl:(NSURL **)entitiesDataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h index 919084dd65..44d2e574b2 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h @@ -3,6 +3,7 @@ @protocol TGPhotoPaintStickersContext; @protocol TGCaptionPanelView; +@protocol TGLivePhotoButton; @interface TGPhotoCaptionInputMixin : NSObject @@ -12,6 +13,9 @@ @property (nonatomic, readonly) UIView *inputPanelView; @property (nonatomic, readonly) UIView *dismissView; +@property (nonatomic, readonly) id livePhotoButton; +@property (nonatomic, readonly) UIView *livePhotoButtonView; + @property (nonatomic, assign) UIInterfaceOrientation interfaceOrientation; @property (nonatomic, readonly) CGFloat keyboardHeight; @property (nonatomic, assign) CGFloat contentAreaHeight; @@ -26,6 +30,8 @@ @property (nonatomic, copy) void (^timerUpdated)(NSNumber *timeout); @property (nonatomic, copy) void (^captionIsAboveUpdated)(bool captionIsAbove); +@property (nonatomic, copy) void(^livePhotoModeUpdated)(NSUInteger mode); + @property (nonatomic, readonly) bool editing; - (void)createInputPanelIfNeeded; @@ -44,6 +50,9 @@ - (void)setTimeout:(int32_t)timeout isVideo:(bool)isVideo isCaptionAbove:(bool)isCaptionAbove; +- (void)setLivePhotoHidden:(bool)hidden; +- (void)setLivePhotoMode:(NSUInteger)mode; + - (void)updateLayoutWithFrame:(CGRect)frame edgeInsets:(UIEdgeInsets)edgeInsets animated:(bool)animated; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h index c2d4e20a76..05095af3af 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h @@ -24,6 +24,15 @@ @end +@protocol TGLivePhotoButton + +@property (nonatomic, readonly) UIView * _Nonnull view; + +@property (nonatomic, copy) void(^ _Nullable modeUpdated)(TGMediaLivePhotoMode mode); + +- (void)setLivePhotoMode:(TGMediaLivePhotoMode)mode; + +@end @protocol TGCaptionPanelView @@ -127,6 +136,7 @@ @protocol TGPhotoPaintStickersContext @property (nonatomic, copy) id _Nullable(^ _Nullable captionPanelView)(void); +@property (nonatomic, copy) id _Nullable(^ _Nullable livePhotoButton)(void); @property (nonatomic, copy) void (^ _Nullable editCover)(CGSize dimensions, void(^_Nonnull completion)(UIImage * _Nonnull)); diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h index be3bd2aa1d..c5925658a9 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h @@ -26,6 +26,7 @@ typedef enum @property (nonatomic, readonly) NSTimeInterval trimStartValue; @property (nonatomic, readonly) NSTimeInterval trimEndValue; @property (nonatomic, readonly) TGMediaVideoConversionPreset preset; +@property (nonatomic, readonly) bool bounce; @property (nonatomic, readonly) int64_t stickerPackId; @property (nonatomic, readonly) int64_t stickerPackAccessHash; @@ -46,6 +47,7 @@ typedef enum - (instancetype)editAdjustmentsWithPreset:(TGMediaVideoConversionPreset)preset maxDuration:(NSTimeInterval)maxDuration; - (instancetype)editAdjustmentsWithPreset:(TGMediaVideoConversionPreset)preset videoStartValue:(NSTimeInterval)videoStartValue trimStartValue:(NSTimeInterval)trimStartValue trimEndValue:(NSTimeInterval)trimEndValue; + (instancetype)editAdjustmentsWithOriginalSize:(CGSize)originalSize preset:(TGMediaVideoConversionPreset)preset; ++ (instancetype)editAdjustmentsWithOriginalSize:(CGSize)originalSize preset:(TGMediaVideoConversionPreset)preset bounce:(bool)bounce; + (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values preset:(TGMediaVideoConversionPreset)preset; + (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values preset:(TGMediaVideoConversionPreset)preset documentId:(int64_t)documentId colors:(NSArray *)colors; + (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values preset:(TGMediaVideoConversionPreset)preset stickerPackId:(int64_t)stickerPackId stickerPackAccessHash:(int64_t)stickerPackAccessHash documentId:(int64_t)documentId colors:(NSArray *)colors; diff --git a/submodules/LegacyComponents/Sources/TGCameraCapturedVideo.m b/submodules/LegacyComponents/Sources/TGCameraCapturedVideo.m index 1abb11bbfa..4c02fdc7ba 100644 --- a/submodules/LegacyComponents/Sources/TGCameraCapturedVideo.m +++ b/submodules/LegacyComponents/Sources/TGCameraCapturedVideo.m @@ -18,6 +18,7 @@ SVariable *_avAssetVariable; AVURLAsset *_cachedAVAsset; bool _livePhoto; + bool _isAnimation; } @end @@ -35,11 +36,21 @@ } } -- (instancetype)initWithURL:(NSURL *)url +- (instancetype)initWithURL:(NSURL *)url { + self = [self initWithURL:url isAnimation:false]; + if (self != nil) + { + } + return self; +} + +- (instancetype)initWithURL:(NSURL *)url isAnimation:(bool)isAnimation { self = [super init]; if (self != nil) { + _isAnimation = isAnimation; + if (![url.absoluteString.lowercaseString hasSuffix:@".mp4"]) { NSURL *pathUrl = [NSURL fileURLWithPath:[[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:@"videos"]]; [[NSFileManager defaultManager] createDirectoryAtPath:pathUrl.path withIntermediateDirectories:true attributes:nil error:nil]; @@ -81,7 +92,7 @@ } - (bool)isAnimation { - return _originalAsset != nil; + return _isAnimation || _originalAsset != nil; } - (AVAsset *)immediateAVAsset { diff --git a/submodules/LegacyComponents/Sources/TGMediaAsset.m b/submodules/LegacyComponents/Sources/TGMediaAsset.m index e30e05b9ab..87e2990abf 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAsset.m +++ b/submodules/LegacyComponents/Sources/TGMediaAsset.m @@ -143,7 +143,7 @@ - (SSignal *)actualVideoDuration { if (!self.isVideo) - return [SSignal fail:nil]; + return nil; if (_cachedDuration == nil) { diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index dcdf83da92..347a9c0d28 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -991,6 +991,7 @@ } NSAttributedString *caption = [editingContext captionForItem:asset]; + TGMediaLivePhotoMode livePhotoMode = [editingContext livePhotoModeForItem:asset]; if (editingContext.isForcedCaption) { if (grouping && num > 0) { @@ -1083,13 +1084,42 @@ if (isHighQualityPhoto) dict[@"hd"] = @true; + if (livePhotoMode != TGMediaLivePhotoModeOff) + dict[@"livePhoto"] = @(livePhotoMode); + id generatedItem = descriptionGenerator(dict, caption, nil, asset.identifier); return generatedItem; }]; SSignal *assetSignal = inlineSignal; SSignal *imageSignal = assetSignal; - if (adjustments.sendAsGif) + if (editingContext != nil) + { + imageSignal = [[[[[editingContext imageSignalForItem:asset withUpdates:true] filter:^bool(id result) + { + return result == nil || ([result isKindOfClass:[UIImage class]] && !((UIImage *)result).degraded); + }] take:1] mapToSignal:^SSignal *(id result) + { + if (result == nil) + { + return [SSignal fail:nil]; + } + else if ([result isKindOfClass:[UIImage class]]) + { + UIImage *image = (UIImage *)result; + image.edited = true; + return [SSignal single:image]; + } + + return [SSignal complete]; + }] onCompletion:^ + { + __strong TGMediaEditingContext *strongEditingContext = editingContext; + [strongEditingContext description]; + }]; + } + + if (livePhotoMode != TGMediaLivePhotoModeOff) { NSTimeInterval trimStartValue = 0.0; if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]]) { @@ -1130,12 +1160,18 @@ }]; SSignal *thumbnailSignal = trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal; + NSLog(@"%@", thumbnailSignal); TGMediaVideoConversionPreset preset = [TGMediaVideoConverter presetFromAdjustments:adjustments]; CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset]; + SSignal *livePhotoImageSignal = [imageSignal catch:^SSignal *(__unused id error) + { + return [TGMediaAssetImageSignals imageForAsset:asset imageType:TGMediaAssetImageTypeFullSize size:CGSizeZero]; + }]; + TGCameraCapturedVideo *videoAsset = [[TGCameraCapturedVideo alloc] initWithAsset:asset livePhoto:true]; - [signals addObject:[thumbnailSignal mapToSignal:^SSignal *(UIImage *image) + [signals addObject:[livePhotoImageSignal mapToSignal:^SSignal *(UIImage *image) { return [videoAsset.avAsset map:^id(AVURLAsset *avAsset) { NSTimeInterval duration = CMTimeGetSeconds(avAsset.duration); @@ -1151,6 +1187,15 @@ dict[@"duration"] = @(duration); dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions]; dict[@"adjustments"] = adjustments; + dict[@"coverImage"] = image; + + if (livePhotoMode == TGMediaLivePhotoModeBounce) { + dict[@"adjustments"] = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:dimensions preset:TGMediaVideoConversionPresetCompressedHigh bounce:true]; + } else if (livePhotoMode == TGMediaLivePhotoModeLoop) { + dict[@"adjustments"] = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:dimensions preset:TGMediaVideoConversionPresetCompressedHigh bounce:false]; + } else { + dict[@"livePhoto"] = @true; + } if (adjustments.paintingData.stickers.count > 0) dict[@"stickers"] = adjustments.paintingData.stickers; @@ -1165,7 +1210,7 @@ if (spoiler) { dict[@"spoiler"] = @true; } - + id generatedItem = descriptionGenerator(dict, caption, nil, asset.identifier); return generatedItem; }]; @@ -1176,32 +1221,6 @@ } else { - if (editingContext != nil) - { - imageSignal = [[[[[editingContext imageSignalForItem:asset withUpdates:true] filter:^bool(id result) - { - return result == nil || ([result isKindOfClass:[UIImage class]] && !((UIImage *)result).degraded); - }] take:1] mapToSignal:^SSignal *(id result) - { - if (result == nil) - { - return [SSignal fail:nil]; - } - else if ([result isKindOfClass:[UIImage class]]) - { - UIImage *image = (UIImage *)result; - image.edited = true; - return [SSignal single:image]; - } - - return [SSignal complete]; - }] onCompletion:^ - { - __strong TGMediaEditingContext *strongEditingContext = editingContext; - [strongEditingContext description]; - }]; - } - [signals addObject:[[imageSignal map:^NSDictionary *(UIImage *image) { NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; diff --git a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m index 0df58b712f..626fec405e 100644 --- a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m +++ b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m @@ -74,6 +74,16 @@ @end +@interface TGMediaLivePhotoModeUpdate : NSObject + +@property (nonatomic, readonly, strong) id item; +@property (nonatomic, readonly) TGMediaLivePhotoMode mode; + ++ (instancetype)livePhotoModeUpdateWithItem:(id)item mode:(TGMediaLivePhotoMode)mode; ++ (instancetype)livePhotoModeUpdate:(TGMediaLivePhotoMode)mode; + +@end + @interface TGModernCache (Private) @@ -92,6 +102,7 @@ NSMutableDictionary *_spoilers; NSMutableDictionary *_prices; + NSMutableDictionary *_livePhotoModes; SQueue *_queue; @@ -132,6 +143,7 @@ SPipe *_cropPipe; SPipe *_captionAbovePipe; SPipe *_highQualityPhotoPipe; + SPipe *_livePhotoModePipe; NSAttributedString *_forcedCaption; @@ -156,6 +168,7 @@ _timers = [[NSMutableDictionary alloc] init]; _spoilers = [[NSMutableDictionary alloc] init]; _prices = [[NSMutableDictionary alloc] init]; + _livePhotoModes = [[NSMutableDictionary alloc] init]; _imageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] imageSoftMemoryLimit] hardMemoryLimit:[[self class] imageHardMemoryLimit]]; @@ -213,6 +226,7 @@ _cropPipe = [[SPipe alloc] init]; _captionAbovePipe = [[SPipe alloc] init]; _highQualityPhotoPipe = [[SPipe alloc] init]; + _livePhotoModePipe = [[SPipe alloc] init]; } return self; } @@ -774,6 +788,67 @@ }]; } +#pragma mark - + +- (TGMediaLivePhotoMode)livePhotoModeForItem:(NSObject *)item { + NSString *itemId = [self _contextualIdForItemId:item.uniqueIdentifier]; + if (itemId == nil) + return TGMediaLivePhotoModeOff; + + return [self _livePhotoModeForItemId:itemId]; +} + +- (TGMediaLivePhotoMode)_livePhotoModeForItemId:(NSString *)itemId +{ + if (itemId == nil) + return TGMediaLivePhotoModeOff; + + return (TGMediaLivePhotoMode)[_livePhotoModes[itemId] unsignedIntValue]; +} + +- (SSignal *)livePhotoModeSignalForItem:(NSObject *)item { + SSignal *updateSignal = [[_livePhotoModePipe.signalProducer() filter:^bool(TGMediaLivePhotoModeUpdate *update) + { + return [update.item.uniqueIdentifier isEqualToString:item.uniqueIdentifier]; + }] map:^NSNumber *(TGMediaLivePhotoModeUpdate *update) + { + return @(update.mode); + }]; + + return [[SSignal single:@([self livePhotoModeForItem:item])] then:updateSignal]; +} + +- (SSignal *)livePhotoModeForIdentifier:(NSString *)identifier { + SSignal *updateSignal = [[_livePhotoModePipe.signalProducer() filter:^bool(TGMediaLivePhotoModeUpdate *update) + { + return [update.item.uniqueIdentifier isEqualToString:identifier]; + }] map:^NSNumber *(TGMediaLivePhotoModeUpdate *update) + { + return @(update.mode); + }]; + + return [[SSignal single:@([self _livePhotoModeForItemId:identifier])] then:updateSignal]; +} + +- (void)setLivePhotoMode:(TGMediaLivePhotoMode)mode forItem:(NSObject *)item { + NSString *itemId = [self _contextualIdForItemId:item.uniqueIdentifier]; + if (itemId == nil) + return; + + if (mode != TGMediaLivePhotoModeOff) + _livePhotoModes[itemId] = @(mode); + else + [_livePhotoModes removeObjectForKey:itemId]; + + _livePhotoModePipe.sink([TGMediaLivePhotoModeUpdate livePhotoModeUpdateWithItem:item mode:mode]); +} + +- (SSignal *)livePhotoModesUpdatedSignal { + return [_livePhotoModePipe.signalProducer() map:^id(__unused id value) + { + return @true; + }]; +} #pragma mark - @@ -1410,3 +1485,22 @@ } @end + +@implementation TGMediaLivePhotoModeUpdate + ++ (instancetype)livePhotoModeUpdateWithItem:(id)item mode:(TGMediaLivePhotoMode)mode +{ + TGMediaLivePhotoModeUpdate *update = [[TGMediaLivePhotoModeUpdate alloc] init]; + update->_item = item; + update->_mode = mode; + return update; +} + ++ (instancetype)livePhotoModeUpdate:(TGMediaLivePhotoMode)mode +{ + TGMediaLivePhotoModeUpdate *update = [[TGMediaLivePhotoModeUpdate alloc] init]; + update->_mode = mode; + return update; +} + +@end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index cbfef08d23..d8ffe716b6 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -281,7 +281,7 @@ if (_editingContext != nil) { _timersChangedDisposable = [_editingContext.timersUpdatedSignal startStrictWithNext:^(__unused NSNumber *next) - { + { __strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf; if (strongSelf == nil) return; @@ -290,7 +290,7 @@ } file:__FILE_NAME__ line:__LINE__]; _adjustmentsChangedDisposable = [_editingContext.adjustmentsUpdatedSignal startStrictWithNext:^(__unused NSNumber *next) - { + { __strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf; if (strongSelf == nil) return; @@ -396,6 +396,18 @@ [strongSelf->_editingContext setCaptionAbove:captionIsAbove]; }; + _captionMixin.livePhotoModeUpdated = ^(NSUInteger mode) { + __strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf; + if (strongSelf == nil) + return; + + if (![strongSelf->_currentItem conformsToProtocol:@protocol(TGModernGalleryEditableItem)]) + return; + + id galleryEditableItem = (id)strongSelf->_currentItem; + [strongSelf->_editingContext setLivePhotoMode:(TGMediaLivePhotoMode)mode forItem:galleryEditableItem.editableMediaItem]; + }; + _captionMixin.stickersContext = stickersContext; [_captionMixin createInputPanelIfNeeded]; @@ -696,6 +708,11 @@ if ([item.asset isKindOfClass:[TGCameraCapturedVideo class]] && ((TGCameraCapturedVideo *)item.asset).isAnimation) { sendableAsGif = false; } + } else if ([strongSelf->_currentItem isKindOfClass:[TGMediaPickerGalleryFetchResultItem class]]) { + TGMediaPickerGalleryFetchResultItem *item = (TGMediaPickerGalleryFetchResultItem *)strongSelf->_currentItem; + if ([item.asset isKindOfClass:[TGMediaAsset class]] && ((TGMediaAsset *)item.asset).type == TGMediaAssetPhotoType) { + sendableAsGif = false; + } } strongSelf->_muteButton.hidden = !sendableAsGif; @@ -710,6 +727,7 @@ } } file:__FILE_NAME__ line:__LINE__]]; + bool hasLivePhotoButton = false; UIImage *muteIcon = [TGPhotoEditorInterfaceAssets muteIcon]; UIImage *muteActiveIcon = [TGPhotoEditorInterfaceAssets muteActiveIcon]; if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]]) { @@ -721,11 +739,25 @@ muteActiveIcon = [TGPhotoEditorInterfaceAssets gifActiveIcon]; } } + } else if ([item isKindOfClass:[TGMediaPickerGalleryFetchResultItem class]]) { + TGMediaPickerGalleryFetchResultItem *photoGalleryItem = (TGMediaPickerGalleryFetchResultItem *)item; + if ([photoGalleryItem.asset isKindOfClass:[TGMediaAsset class]]) { + TGMediaAsset *asset = (TGMediaAsset *)photoGalleryItem.asset; + if (asset.subtypes & TGMediaAssetSubtypePhotoLive) { + hasLivePhotoButton = true; + } + } } [_muteButton setImage:muteIcon forState:UIControlStateNormal]; [_muteButton setImage:muteActiveIcon forState:UIControlStateSelected]; [_muteButton setImage:muteActiveIcon forState:UIControlStateSelected | UIControlStateHighlighted]; + if (@"".length == 0) { + hasLivePhotoButton = false; + } + + [_captionMixin setLivePhotoHidden:!hasLivePhotoButton]; + [self setNeedsLayout]; } @@ -1044,13 +1076,17 @@ [_adjustmentsDisposable setDisposable:[[[[galleryEditableItem.editingContext adjustmentsSignalForItem:editableMediaItem] mapToSignal:^SSignal *(id adjustments) { __strong id strongGalleryEditableItem = weakGalleryEditableItem; if (strongGalleryEditableItem != nil) { - return [[strongGalleryEditableItem.editingContext timerSignalForItem:editableMediaItem] map:^id(id timer) { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - if (adjustments != nil) - dict[@"adjustments"] = adjustments; - if (timer != nil) - dict[@"timer"] = timer; - return dict; + return [[strongGalleryEditableItem.editingContext timerSignalForItem:editableMediaItem] mapToSignal:^id(id timer) { + return [[strongGalleryEditableItem.editingContext livePhotoModeSignalForItem:editableMediaItem] map:^id(id livePhotoMode) { + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + if (adjustments != nil) + dict[@"adjustments"] = adjustments; + if (timer != nil) + dict[@"timer"] = timer; + if (livePhotoMode != nil) + dict[@"livePhotoMode"] = livePhotoMode; + return dict; + }]; }]; } else { return [SSignal never]; @@ -1063,6 +1099,12 @@ id adjustments = dict[@"adjustments"]; NSNumber *timer = dict[@"timer"]; + TGMediaLivePhotoMode livePhotoMode = (TGMediaLivePhotoMode)[dict[@"livePhotoMode"] unsignedIntValue]; + + [strongSelf->_captionMixin setLivePhotoMode:livePhotoMode]; + if ([strongSelf->_currentItemView isKindOfClass:[TGMediaPickerGalleryPhotoItemView class]]) { + [((TGMediaPickerGalleryPhotoItemView *)strongSelf->_currentItemView) setLivePhotoMode:livePhotoMode]; + } [strongSelf->_captionMixin setTimeout:[timer intValue] isVideo:editableMediaItem.isVideo isCaptionAbove:isCaptionAbove]; @@ -1404,6 +1446,7 @@ _landscapeToolbarView.alpha = alpha; _captionMixin.inputPanelView.alpha = alpha; _captionMixin.backgroundView.alpha = alpha; + _captionMixin.livePhotoButtonView.alpha = alpha; } completion:^(BOOL finished) { if (finished) @@ -1415,6 +1458,7 @@ _landscapeToolbarView.userInteractionEnabled = !hidden; _captionMixin.inputPanelView.userInteractionEnabled = !hidden; _captionMixin.backgroundView.userInteractionEnabled = !hidden; + _captionMixin.livePhotoButtonView.userInteractionEnabled = !hidden; } }]; @@ -1455,6 +1499,9 @@ _captionMixin.backgroundView.alpha = alpha; _captionMixin.backgroundView.userInteractionEnabled = !hidden; + + _captionMixin.livePhotoButtonView.alpha = alpha; + _captionMixin.livePhotoButtonView.userInteractionEnabled = !hidden; } if (hidden) @@ -1720,6 +1767,7 @@ || [view isDescendantOfView:_landscapeToolbarView] || [view isDescendantOfView:_selectedPhotosView] || [view isDescendantOfView:_captionMixin.inputPanelView] + || [view isDescendantOfView:_captionMixin.livePhotoButtonView] || ([view isDescendantOfView:_captionMixin.dismissView] && _captionMixin.dismissView.alpha > 0.0) || [view isKindOfClass:[TGMenuButtonView class]]) diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.h b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.h index a457b8f8bd..3c1046ac92 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.h +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.h @@ -8,6 +8,7 @@ @property (nonatomic, strong) TGModernGalleryImageItemImageView *imageView; +- (void)setLivePhotoMode:(TGMediaLivePhotoMode)mode; - (void)toggleSendAsGif; @end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.m index a3bd5c8892..cc41f33adf 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.m @@ -9,6 +9,7 @@ #import #import +#import #import #import @@ -26,8 +27,36 @@ #import #import "TGPaintFaceDetector.h" +#import +#import -@interface TGMediaPickerGalleryPhotoItemView () +@interface TGMediaPickerGalleryLivePhotoVideoView : UIView + +@property (nonatomic, strong) AVPlayer *player; + +@end + +@implementation TGMediaPickerGalleryLivePhotoVideoView + ++ (Class)layerClass +{ + return [AVPlayerLayer class]; +} + +- (AVPlayer *)player +{ + return ((AVPlayerLayer *)self.layer).player; +} + +- (void)setPlayer:(AVPlayer *)player +{ + ((AVPlayerLayer *)self.layer).player = player; + ((AVPlayerLayer *)self.layer).videoGravity = AVLayerVideoGravityResizeAspectFill; +} + +@end + +@interface TGMediaPickerGalleryPhotoItemView () { TGMediaPickerGalleryFetchResultItem *_fetchItem; SMetaDisposable *_facesDisposable; @@ -46,8 +75,26 @@ SMetaDisposable *_adjustmentsDisposable; SMetaDisposable *_attributesDisposable; + SMetaDisposable *_liveVideoItemDisposable; TGMenuContainerView *_tooltipContainerView; + + TGMediaLivePhotoMode _livePhotoMode; + + TGMediaPickerGalleryLivePhotoVideoView *_livePhotoVideoView; + AVPlayer *_livePhotoPlayer; + id _livePhotoDidPlayToEndObserver; + UILongPressGestureRecognizer *_livePhotoPressGestureRecognizer; + + bool _livePhotoIsLoadingPlayer; + bool _livePhotoPlaybackLooping; + bool _livePhotoIsHolding; + bool _livePhotoPendingPlayback; + bool _livePhotoPendingPlaybackLooping; + + CADisplayLink *_livePhotoBounceDisplayLink; + bool _livePhotoBounceManualReverse; + bool _livePhotoBouncePlayingBackwards; } @property (nonatomic, strong) TGMediaPickerGalleryPhotoItem *item; @@ -98,15 +145,21 @@ _fileInfoLabel.textColor = [UIColor whiteColor]; _adjustmentsDisposable = [[SMetaDisposable alloc] init]; + _liveVideoItemDisposable = [[SMetaDisposable alloc] init]; + + _livePhotoMode = TGMediaLivePhotoModeOff; } return self; } - (void)dealloc { + [self stopAndCleanupLivePhotoPlayback]; + [_adjustmentsDisposable dispose]; [_attributesDisposable dispose]; [_facesDisposable dispose]; + [_liveVideoItemDisposable dispose]; } - (void)setHiddenAsBeingEdited:(bool)hidden @@ -120,6 +173,9 @@ _imageView.hidden = false; [_imageView reset]; [self setProgressVisible:false value:0.0f animated:false]; + + [self stopAndCleanupLivePhotoPlayback]; + _livePhotoMode = TGMediaLivePhotoModeOff; } - (id)item { @@ -139,6 +195,9 @@ [super setItem:item synchronously:synchronously]; + [self stopAndCleanupLivePhotoPlayback]; + _livePhotoMode = TGMediaLivePhotoModeOff; + if (_entitiesView == nil) { _entitiesView = [item.stickersContext drawingEntitiesViewWithSize:item.asset.originalSize]; _entitiesView.userInteractionEnabled = false; @@ -246,6 +305,7 @@ } [strongSelf reset]; + [strongSelf layoutLivePhotoVideoView]; }]]; @@ -282,6 +342,11 @@ } } +- (void)setSafeAreaInset:(UIEdgeInsets)safeAreaInset +{ + _safeAreaInset = safeAreaInset; +} + - (void)_setTemporaryRepView:(UIView *)view { [_temporaryRepView removeFromSuperview]; @@ -353,6 +418,18 @@ } } +- (void)livePhotoModeButtonPressed +{ + TGMediaLivePhotoMode nextMode = (TGMediaLivePhotoMode)((_livePhotoMode + 1) % 4); + + + if (self.item.editingContext != nil) + { + id item = ((id)self.item).editableMediaItem; + [self.item.editingContext setLivePhotoMode:nextMode forItem:item]; + } +} + - (UIView *)footerView { if (((TGMediaPickerGalleryItem *)self.item).asFile) @@ -448,6 +525,7 @@ [super setFrame:frame]; [self layoutEntities]; + [self layoutLivePhotoVideoView]; } - (void)layoutEntities { @@ -509,4 +587,346 @@ _entitiesView.center = TGPaintAddPoints(boundsCenter, offset); } +- (bool)isCurrentAssetLivePhoto +{ + if (![self.item.asset isKindOfClass:[TGMediaAsset class]]) + return false; + + return (((TGMediaAsset *)self.item.asset).subtypes & TGMediaAssetSubtypePhotoLive) != 0; +} + +- (void)layoutLivePhotoVideoView +{ + if (_livePhotoVideoView != nil) + _livePhotoVideoView.frame = _imageView.bounds; +} + +- (void)setLivePhotoMode:(TGMediaLivePhotoMode)mode +{ + if (_livePhotoMode == mode) + return; + + _livePhotoMode = mode; + + if (_livePhotoMode == TGMediaLivePhotoModeLive) + { + [self ensureLivePhotoPressRecognizer]; + [self ensureLivePhotoPlayer]; + [self playLivePhotoVideoLooping:false fromStart:true]; + } + else if (_livePhotoMode == TGMediaLivePhotoModeLoop) + { + [self stopLivePhotoBounceIfNeeded]; + [self removeLivePhotoPressRecognizer]; + [self ensureLivePhotoPlayer]; + [self playLivePhotoVideoLooping:true fromStart:true]; + } + else if (_livePhotoMode == TGMediaLivePhotoModeBounce) + { + [self removeLivePhotoPressRecognizer]; + [self ensureLivePhotoPlayer]; + [self playLivePhotoVideoLooping:false fromStart:true]; + } + else + { + [self stopLivePhotoBounceIfNeeded]; + [self stopAndHideLivePhotoVideo:true]; + [self removeLivePhotoPressRecognizer]; + } +} + +- (void)ensureLivePhotoPlayer +{ + if (_livePhotoPlayer != nil || _livePhotoIsLoadingPlayer || ![self isCurrentAssetLivePhoto]) + return; + + _livePhotoIsLoadingPlayer = true; + + __weak TGMediaPickerGalleryPhotoItemView *weakSelf = self; + [_liveVideoItemDisposable setDisposable:[[[TGMediaAssetImageSignals playerItemForVideoAsset:(TGMediaAsset *)self.item.asset] deliverOn:[SQueue mainQueue]] startStrictWithNext:^(AVPlayerItem *playerItem) + { + __strong TGMediaPickerGalleryPhotoItemView *strongSelf = weakSelf; + if (strongSelf == nil || ![playerItem isKindOfClass:[AVPlayerItem class]]) + return; + + strongSelf->_livePhotoIsLoadingPlayer = false; + + [strongSelf removeLivePhotoPlaybackObserver]; + + strongSelf->_livePhotoPlayer = [AVPlayer playerWithPlayerItem:playerItem]; + strongSelf->_livePhotoPlayer.actionAtItemEnd = AVPlayerActionAtItemEndPause; + strongSelf->_livePhotoPlayer.muted = true; + + [strongSelf ensureLivePhotoVideoView]; + strongSelf->_livePhotoVideoView.player = strongSelf->_livePhotoPlayer; + + __weak TGMediaPickerGalleryPhotoItemView *observerWeakSelf = strongSelf; + strongSelf->_livePhotoDidPlayToEndObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:playerItem queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification *note) + { + __strong TGMediaPickerGalleryPhotoItemView *observerStrongSelf = observerWeakSelf; + [observerStrongSelf livePhotoPlayerItemDidPlayToEnd]; + }]; + + if (strongSelf->_livePhotoPendingPlayback) + { + bool shouldLoop = strongSelf->_livePhotoPendingPlaybackLooping; + strongSelf->_livePhotoPendingPlayback = false; + [strongSelf playLivePhotoVideoLooping:shouldLoop fromStart:true]; + } + } file:__FILE_NAME__ line:__LINE__]]; +} + +- (void)ensureLivePhotoVideoView +{ + if (_livePhotoVideoView != nil) + return; + + _livePhotoVideoView = [[TGMediaPickerGalleryLivePhotoVideoView alloc] initWithFrame:_imageView.bounds]; + _livePhotoVideoView.alpha = 0.0f; + _livePhotoVideoView.userInteractionEnabled = false; + [_imageView insertSubview:_livePhotoVideoView aboveSubview:_contentView]; +} + +- (void)ensureLivePhotoPressRecognizer +{ + if (_livePhotoPressGestureRecognizer != nil) + return; + + _livePhotoPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLivePhotoPress:)]; + _livePhotoPressGestureRecognizer.minimumPressDuration = 0.2f; + _livePhotoPressGestureRecognizer.cancelsTouchesInView = false; + _livePhotoPressGestureRecognizer.delegate = self; + [self.scrollView addGestureRecognizer:_livePhotoPressGestureRecognizer]; +} + +- (void)removeLivePhotoPressRecognizer +{ + if (_livePhotoPressGestureRecognizer == nil) + return; + + [self.scrollView removeGestureRecognizer:_livePhotoPressGestureRecognizer]; + _livePhotoPressGestureRecognizer = nil; + _livePhotoIsHolding = false; +} + +- (void)handleLivePhotoPress:(UILongPressGestureRecognizer *)gestureRecognizer +{ + if (_livePhotoMode != TGMediaLivePhotoModeLive || !self.gesturesEnabled) + return; + + switch (gestureRecognizer.state) + { + case UIGestureRecognizerStateBegan: + _livePhotoIsHolding = true; + [self playLivePhotoVideoLooping:true fromStart:true]; + break; + + case UIGestureRecognizerStateEnded: + case UIGestureRecognizerStateCancelled: + case UIGestureRecognizerStateFailed: + _livePhotoIsHolding = false; + [self stopAndHideLivePhotoVideo:true]; + break; + + default: + break; + } +} + +- (void)playLivePhotoVideoLooping:(bool)looping fromStart:(bool)fromStart +{ + if (_livePhotoMode == TGMediaLivePhotoModeOff || ![self isCurrentAssetLivePhoto]) + return; + + [self ensureLivePhotoPlayer]; + [self ensureLivePhotoVideoView]; + + if (_livePhotoPlayer == nil) + { + _livePhotoPendingPlayback = true; + _livePhotoPendingPlaybackLooping = looping; + return; + } + + _livePhotoPlaybackLooping = looping; + _livePhotoPendingPlayback = false; + + void (^playBlock)(void) = ^ + { + _livePhotoVideoView.alpha = 1.0f; + [_livePhotoPlayer play]; + }; + + if (fromStart) + { + [_livePhotoPlayer seekToTime:kCMTimeZero toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(__unused BOOL finished) + { + playBlock(); + }]; + } + else + { + playBlock(); + } +} + +- (void)livePhotoPlayerItemDidPlayToEnd +{ + if (_livePhotoMode == TGMediaLivePhotoModeLoop) + { + [self playLivePhotoVideoLooping:true fromStart:true]; + } + else if (_livePhotoMode == TGMediaLivePhotoModeBounce) + { + [self startLivePhotoBounceReversePlayback]; + } + else if (_livePhotoPlaybackLooping && _livePhotoIsHolding) + { + [self playLivePhotoVideoLooping:true fromStart:true]; + } + else + { + [self stopAndHideLivePhotoVideo:true]; + } +} + +- (void)stopAndHideLivePhotoVideo:(bool)animated +{ + [self stopLivePhotoBounceIfNeeded]; + + _livePhotoPlaybackLooping = false; + _livePhotoPendingPlayback = false; + _livePhotoPendingPlaybackLooping = false; + + [_livePhotoPlayer pause]; + + if (_livePhotoPlayer != nil) + [_livePhotoPlayer seekToTime:kCMTimeZero toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; + + if (_livePhotoVideoView == nil) + return; + + if (animated) + { + [UIView animateWithDuration:0.2f animations:^ + { + _livePhotoVideoView.alpha = 0.0f; + }]; + } + else + { + _livePhotoVideoView.alpha = 0.0f; + } +} + +- (void)removeLivePhotoPlaybackObserver +{ + if (_livePhotoDidPlayToEndObserver != nil) + { + [[NSNotificationCenter defaultCenter] removeObserver:_livePhotoDidPlayToEndObserver]; + _livePhotoDidPlayToEndObserver = nil; + } +} + +- (void)stopAndCleanupLivePhotoPlayback +{ + [self removeLivePhotoPressRecognizer]; + [self stopAndHideLivePhotoVideo:false]; + + [self removeLivePhotoPlaybackObserver]; + [_liveVideoItemDisposable setDisposable:nil]; + + _livePhotoPlayer = nil; + [_livePhotoVideoView removeFromSuperview]; + _livePhotoVideoView = nil; + _livePhotoIsLoadingPlayer = false; +} + +- (void)startLivePhotoBounceReversePlayback +{ + if (_livePhotoPlayer == nil || _livePhotoMode != TGMediaLivePhotoModeBounce || _livePhotoBouncePlayingBackwards) + return; + + AVPlayerItem *item = _livePhotoPlayer.currentItem; + if (item == nil) + return; + + _livePhotoBounceManualReverse = false; + _livePhotoBouncePlayingBackwards = true; + + if ([item canPlayReverse]) + { + //_livePhotoPlayer.rate = -1.0f; + [self startLivePhotoBounceMonitor]; + [_livePhotoPlayer playImmediatelyAtRate:-1.0]; + return; + } + + _livePhotoBounceManualReverse = true; + [_livePhotoPlayer pause]; + [self startLivePhotoBounceMonitor]; +} + +- (void)startLivePhotoBounceMonitor +{ + _livePhotoBounceDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(livePhotoBounceTick:)]; + _livePhotoBounceDisplayLink.preferredFramesPerSecond = 30; + + [_livePhotoBounceDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; +} + +- (void)stopLivePhotoBounceIfNeeded +{ + _livePhotoBouncePlayingBackwards = false; + + [_livePhotoBounceDisplayLink invalidate]; + _livePhotoBounceDisplayLink = nil; + _livePhotoBounceManualReverse = false; + + if (_livePhotoPlayer.rate < 0.0f) + [_livePhotoPlayer pause]; +} + +- (void)livePhotoBounceTick:(CADisplayLink *)displayLink +{ + if (_livePhotoPlayer == nil || _livePhotoMode != TGMediaLivePhotoModeBounce) + { + [self stopLivePhotoBounceIfNeeded]; + return; + } + + CMTime currentTime = _livePhotoPlayer.currentTime; + Float64 currentSeconds = CMTimeGetSeconds(currentTime); + if (!isfinite(currentSeconds)) + return; + + if (_livePhotoBounceManualReverse) + { + CGFloat step = MAX(1.0f / 45.0f, displayLink.duration); + Float64 targetSeconds = MAX(0.0, currentSeconds - step); + CMTime targetTime = CMTimeMakeWithSeconds(targetSeconds, NSEC_PER_SEC); + [_livePhotoPlayer seekToTime:targetTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; + currentSeconds = targetSeconds; + } + + bool reachedStart = currentSeconds <= 0.02; + if (!_livePhotoBounceManualReverse && _livePhotoPlayer.rate > -FLT_EPSILON && currentSeconds <= 0.12) + reachedStart = true; + + if (reachedStart) + { + _livePhotoBouncePlayingBackwards = false; + [self stopLivePhotoBounceIfNeeded]; + [self playLivePhotoVideoLooping:false fromStart:false]; + } +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)__unused otherGestureRecognizer +{ + if (gestureRecognizer == _livePhotoPressGestureRecognizer) + return true; + + return false; +} + @end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItem.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItem.m index f3500ece40..e9b209b13b 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItem.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItem.m @@ -85,9 +85,9 @@ - (TGPhotoEditorTab)toolbarTabs { - if ([self.asset isKindOfClass:[TGMediaAsset class]] && ((TGMediaAsset *)self.asset).subtypes & TGMediaAssetSubtypePhotoLive) { + if ([self.asset isKindOfClass:[TGCameraCapturedVideo class]] && ((TGCameraCapturedVideo *)self.asset).isAnimation) { return TGPhotoEditorCropTab | TGPhotoEditorPaintTab | TGPhotoEditorToolsTab; - } else if ([self.asset isKindOfClass:[TGCameraCapturedVideo class]] && ((TGCameraCapturedVideo *)self.asset).isAnimation) { + } else if ([self.asset isKindOfClass:[TGMediaAsset class]] && ((TGMediaAsset *)self.asset).type == TGMediaAssetPhotoType) { return TGPhotoEditorCropTab | TGPhotoEditorPaintTab | TGPhotoEditorToolsTab; } else { return TGPhotoEditorCropTab | TGPhotoEditorToolsTab | TGPhotoEditorPaintTab | TGPhotoEditorQualityTab; @@ -150,6 +150,7 @@ default: { + //backingItem = [[TGMediaPickerGalleryVideoItem alloc] initWithAsset:(id)asset]; backingItem = [[TGMediaPickerGalleryPhotoItem alloc] initWithAsset:(id)asset]; } break; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m index dc9f514390..46f243a2e9 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m @@ -228,7 +228,6 @@ UIView *scrubberBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, _headerView.frame.size.width, 64.0f)]; scrubberBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - //scrubberBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor]; [_scrubberPanelView addSubview:scrubberBackgroundView]; _scrubberView = [[TGMediaPickerGalleryVideoScrubber alloc] initWithFrame:CGRectMake(0.0f, _headerView.frame.size.height - 44.0f, _headerView.frame.size.width, 68.0f) cover:false]; @@ -284,7 +283,7 @@ if (_volumeOverlayFixView != nil) return; - UIWindow *keyWindow = self.window; //[UIApplication sharedApplication].keyWindow; + UIWindow *keyWindow = self.window; UIView *rootView = keyWindow.rootViewController.view; if (iosMajorVersion() < 13) { @@ -427,10 +426,6 @@ if (itemChanged) { [self _playerCleanup]; - -// if (!item.asFile) { -// [_facesDisposable setDisposable:[[TGPaintFaceDetector detectFacesInItem:item.editableMediaItem editingContext:item.editingContext] startStrictWithNext:nil file:__FILE_NAME__ line:__LINE__]]; -// } } _scrubberView.allowsTrimming = false; @@ -445,24 +440,33 @@ [_contentWrapperView addSubview:_entitiesView]; } + __weak TGMediaPickerGalleryVideoItemView *weakSelf = self; - [_videoDurationVar set:[[[item.durationSignal deliverOn:[SQueue mainQueue]] catch:^SSignal *(__unused id error) - { - __strong TGMediaPickerGalleryVideoItemView *strongSelf = weakSelf; - if (strongSelf != nil && [error isKindOfClass:[NSNumber class]]) - [strongSelf _setDownloadRequired]; - - return nil; - }] onNext:^(__unused id next) - { - __strong TGMediaPickerGalleryVideoItemView *strongSelf = weakSelf; - if (strongSelf != nil) + + SSignal *durationSignal = item.durationSignal; + if (durationSignal != nil) { + [_videoDurationVar set:[[[durationSignal deliverOn:[SQueue mainQueue]] catch:^SSignal *(__unused id error) { - strongSelf->_downloaded = true; - if (strongSelf->_currentAvailabilityObserver != nil) - strongSelf->_currentAvailabilityObserver(true); - } - }]]; + __strong TGMediaPickerGalleryVideoItemView *strongSelf = weakSelf; + if (strongSelf != nil && [error isKindOfClass:[NSNumber class]]) + [strongSelf _setDownloadRequired]; + + return nil; + }] onNext:^(__unused id next) + { + __strong TGMediaPickerGalleryVideoItemView *strongSelf = weakSelf; + if (strongSelf != nil) + { + strongSelf->_downloaded = true; + if (strongSelf->_currentAvailabilityObserver != nil) + strongSelf->_currentAvailabilityObserver(true); + } + }]]; + } else { + _downloaded = true; + if (_currentAvailabilityObserver != nil) + _currentAvailabilityObserver(true); + } SSignal *imageSignal = nil; if (item.asset != nil) @@ -470,6 +474,10 @@ SSignal *assetSignal = [SSignal single:nil]; if ([item.asset isKindOfClass:[TGMediaAsset class]]) { + if (((TGMediaAsset *)item.asset).type == TGMediaAssetPhotoType) { + [_scrubberPanelView setHidden:true]; + } + assetSignal = [TGMediaAssetImageSignals imageForAsset:(TGMediaAsset *)item.asset imageType:(item.immediateThumbnailImage != nil) ? TGMediaAssetImageTypeScreen : TGMediaAssetImageTypeFastScreen size:CGSizeMake(1280, 1280)]; } else @@ -745,7 +753,7 @@ - (bool)itemIsLivePhoto { if ([self.item.asset isKindOfClass:[TGMediaAsset class]]) - return ((TGMediaAsset *)self.item.asset).subtypes & TGMediaAssetSubtypePhotoLive; + return ((TGMediaAsset *)self.item.asset) ; return false; } diff --git a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m index 7645fcb095..ed137cd82c 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m +++ b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m @@ -10,7 +10,7 @@ { TGObserverProxy *_keyboardWillChangeFrameProxy; bool _editing; - + UIGestureRecognizer *_dismissTapRecognizer; CGRect _currentFrame; @@ -109,6 +109,22 @@ _backgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor]; //[parentView addSubview:_backgroundView]; [parentView addSubview:_inputPanelView]; + + if (_stickersContext && _stickersContext.livePhotoButton != nil) { + id livePhotoButton = nil; + livePhotoButton = _stickersContext.livePhotoButton(); + livePhotoButton.modeUpdated = ^(TGMediaLivePhotoMode mode) { + __strong TGPhotoCaptionInputMixin *strongSelf = weakSelf; + if (strongSelf.livePhotoModeUpdated != nil) { + strongSelf.livePhotoModeUpdated(mode); + } + }; + + _livePhotoButton = livePhotoButton; + + _livePhotoButtonView = livePhotoButton.view; + [parentView addSubview:_livePhotoButtonView]; + } } - (void)onAnimateOut { @@ -150,6 +166,14 @@ [_inputPanel setCaption:caption]; } +- (void)setLivePhotoHidden:(bool)hidden { + _livePhotoButtonView.hidden = hidden; +} + +- (void)setLivePhotoMode:(NSUInteger)mode { + [_livePhotoButton setLivePhotoMode:(TGMediaLivePhotoMode)mode]; +} + - (void)setTimeout:(int32_t)timeout isVideo:(bool)isVideo isCaptionAbove:(bool)isCaptionAbove { _currentIsCaptionAbove = isCaptionAbove; [_inputPanel setTimeout:timeout isVideo:isVideo isCaptionAbove:isCaptionAbove]; @@ -314,6 +338,9 @@ backgroundHeight += _keyboardHeight - edgeInsets.bottom; } + CGRect livePhotoButtonFrame = CGRectMake(edgeInsets.left + 16.0, edgeInsets.top + 160.0 + 32.0 - _keyboardHeight, 160.0, 18.0); + _livePhotoButtonView.frame = livePhotoButtonFrame; + CGRect panelFrame = CGRectMake(edgeInsets.left, panelY, frame.size.width, panelHeight); CGRect backgroundFrame = CGRectMake(edgeInsets.left, panelY, frame.size.width, backgroundHeight); diff --git a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m index 39232a34e1..88cfff3248 100644 --- a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m +++ b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m @@ -86,6 +86,8 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; } if (dictionary[@"sendAsGif"]) adjustments->_sendAsGif = [dictionary[@"sendAsGif"] boolValue]; + if (dictionary[@"bounce"]) + adjustments->_bounce = [dictionary[@"bounce"] boolValue]; if (dictionary[@"preset"]) adjustments->_preset = (TGMediaVideoConversionPreset)[dictionary[@"preset"] integerValue]; if (dictionary[@"tools"]) { @@ -126,6 +128,18 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; return adjustments; } ++ (instancetype)editAdjustmentsWithOriginalSize:(CGSize)originalSize preset:(TGMediaVideoConversionPreset)preset bounce:(bool)bounce +{ + TGVideoEditAdjustments *adjustments = [[[self class] alloc] init]; + adjustments->_originalSize = originalSize; + adjustments->_preset = preset; + adjustments->_sendAsGif = true; + adjustments->_bounce = bounce; + + return adjustments; +} + + + (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values preset:(TGMediaVideoConversionPreset)preset { TGVideoEditAdjustments *adjustments = [[[self class] alloc] init]; adjustments->_originalSize = values.originalSize; @@ -285,6 +299,7 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; } dict[@"sendAsGif"] = @(self.sendAsGif); + dict[@"bounce"] = @(self.bounce); if (self.preset != TGMediaVideoConversionPresetCompressedDefault) dict[@"preset"] = @(self.preset); diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index c6be7f81d8..b062edc22a 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -164,11 +164,12 @@ public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle: return } + let isGif = [.default, .caption].contains(mode) let item: TGMediaEditableItem & TGMediaSelectableItem if let image = UIImage(contentsOfFile: data.path) { item = TGCameraCapturedPhoto(existing: image) } else { - item = TGCameraCapturedVideo(url: URL(fileURLWithPath: data.path)) + item = TGCameraCapturedVideo(url: URL(fileURLWithPath: data.path), isAnimation: isGif) } let paintStickersContext = LegacyPaintStickersContext(context: context) @@ -206,7 +207,7 @@ public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle: present(legacyController, nil) - TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, withItem: item, paint: mode == .draw, adjustments: mode == .adjustments, recipientName: recipientName, stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: snapshots as [Any], immediate: transitionCompletion != nil, activateInput: mode == .caption, isGif: [.default, .caption].contains(mode), appeared: { + TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, withItem: item, paint: mode == .draw, adjustments: mode == .adjustments, recipientName: recipientName, stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: snapshots as [Any], immediate: transitionCompletion != nil, activateInput: mode == .caption, isGif: isGif, appeared: { transitionCompletion?() }, completion: { result, editingContext in let nativeGenerator = legacyAssetPickerItemGenerator() diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index 65dcb75c78..5b5b7d6be8 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -133,7 +133,7 @@ private enum LegacyAssetVideoData { private enum LegacyAssetItem { case image(data: LegacyAssetImageData, thumbnail: UIImage?, caption: NSAttributedString?, stickers: [FileMediaReference]) case file(data: LegacyAssetImageData, thumbnail: UIImage?, mimeType: String, name: String, caption: NSAttributedString?) - case video(data: LegacyAssetVideoData, thumbnail: UIImage?, cover: UIImage?, adjustments: TGVideoEditAdjustments?, caption: NSAttributedString?, asFile: Bool, asAnimation: Bool, stickers: [FileMediaReference]) + case video(data: LegacyAssetVideoData, thumbnail: UIImage?, cover: UIImage?, adjustments: TGVideoEditAdjustments?, caption: NSAttributedString?, asFile: Bool, asAnimation: Bool, stickers: [FileMediaReference], isLivePhoto: Bool) } private final class LegacyAssetItemWrapper: NSObject { @@ -183,7 +183,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.path if let url = url { let dimensions = image.size - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: 4.0), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: false, asAnimation: true, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: 4.0), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: false, asAnimation: true, stickers: stickers, isLivePhoto: false), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) } } else { result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, forceHd: forceHd, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) @@ -230,7 +230,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue! let duration = (dict["duration"]! as AnyObject).doubleValue! - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: tempFileUrl.path, dimensions: dimensions, duration: duration), thumbnail: thumbnail, cover: nil, adjustments: nil, caption: caption, asFile: false, asAnimation: true, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: tempFileUrl.path, dimensions: dimensions, duration: duration), thumbnail: thumbnail, cover: nil, adjustments: nil, caption: caption, asFile: false, asAnimation: true, stickers: [], isLivePhoto: false), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) return result } @@ -248,13 +248,13 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str if let asset = dict["asset"] as? TGMediaAsset { var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers, isLivePhoto: false), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) return result } else if let url = (dict["url"] as? String) ?? (dict["url"] as? URL)?.absoluteString { let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue! let duration = (dict["duration"]! as AnyObject).doubleValue! var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers, isLivePhoto: false), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) return result } } else if (dict["type"] as! NSString) == "cameraVideo" { @@ -265,13 +265,18 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str asFile = true } + var isLivePhoto = false + if let value = dict["livePhoto"] as? Bool { + isLivePhoto = value + } + let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.path if let url = url, let previewImage = dict["previewImage"] as? UIImage { let dimensions = previewImage.pixelSize() let duration = (dict["duration"]! as AnyObject).doubleValue! var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers, isLivePhoto: isLivePhoto), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) return result } } @@ -770,7 +775,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A default: break } - case let .video(data, thumbnail, cover, adjustments, caption, asFile, asAnimation, stickers): + case let .video(data, thumbnail, cover, adjustments, caption, asFile, asAnimation, stickers, isLivePhoto): var finalDimensions: CGSize var finalDuration: Double switch data { @@ -816,9 +821,10 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A var videoCover: TelegramMediaImage? if let cover { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - let coverSize = cover.size.aspectFitted(CGSize(width: 320.0, height: 320.0)) + let maxSide: CGFloat = isLivePhoto ? 1280.0 : 640.0 + let coverSize = cover.size.aspectFitted(CGSize(width: maxSide, height: maxSide)) let coverImage = TGScaleImageToPixelSize(cover, coverSize)! - if let coverData = coverImage.jpegData(compressionQuality: 0.6) { + if let coverData = coverImage.jpegData(compressionQuality: isLivePhoto ? 0.87 : 0.6) { account.postbox.mediaBox.storeResourceData(resource.id, data: coverData) videoCover = TelegramMediaImage( imageId: MediaId(namespace: 0, id: 0), @@ -891,7 +897,11 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A fileAttributes.append(.Animated) } if !asFile { - fileAttributes.append(.Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil, coverTime: nil, videoCodec: nil)) + var flags: TelegramMediaVideoFlags = [.supportsStreaming] + if isLivePhoto { + flags.insert(.isLivePhoto) + } + fileAttributes.append(.Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: flags, preloadSize: nil, coverTime: nil, videoCodec: nil)) if let adjustments = adjustments { if adjustments.sendAsGif { fileAttributes.append(.Animated) @@ -911,7 +921,6 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A } } - var attributes: [MessageAttribute] = [] var stickerFiles: [TelegramMediaFile] = [] diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index 5a3aa9211d..f10578a5ff 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -573,6 +573,7 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersContext { public var captionPanelView: (() -> TGCaptionPanelView?)? + public var livePhotoButton: (() -> TGLivePhotoButton?)? public var editCover: ((CGSize, @escaping (UIImage) -> Void) -> Void)? private let context: AccountContext diff --git a/submodules/MediaPickerUI/BUILD b/submodules/MediaPickerUI/BUILD index 046b14d0ac..496780dcb7 100644 --- a/submodules/MediaPickerUI/BUILD +++ b/submodules/MediaPickerUI/BUILD @@ -58,6 +58,7 @@ swift_library( "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/Components/BundleIconComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift index 507e1d781f..ce1352a1f8 100644 --- a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift +++ b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift @@ -101,7 +101,34 @@ enum LegacyMediaPickerGallerySource { case selection(item: TGMediaSelectableItem) } -func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, threadTitle: String?, chatLocation: ChatLocation?, isScheduledMessages: Bool, presentationData: PresentationData, source: LegacyMediaPickerGallerySource, immediateThumbnail: UIImage?, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, hasSilentPosting: Bool, hasSchedule: Bool, hasTimer: Bool, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (String) -> UIView?, completed: @escaping (TGMediaSelectableItem & TGMediaEditableItem, Bool, Int32?, @escaping () -> Void) -> Void, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void, finishedTransitionIn: @escaping () -> Void, willTransitionOut: @escaping () -> Void, dismissAll: @escaping () -> Void, editCover: @escaping (CGSize, @escaping (UIImage) -> Void) -> Void = { _, _ in }) -> TGModernGalleryController { +func presentLegacyMediaPickerGallery( + context: AccountContext, + peer: EnginePeer?, + threadTitle: String?, + chatLocation: ChatLocation?, + isScheduledMessages: Bool, + presentationData: PresentationData, + source: LegacyMediaPickerGallerySource, + immediateThumbnail: UIImage?, + selectionContext: TGMediaSelectionContext?, + editingContext: TGMediaEditingContext, + hasSilentPosting: Bool, + hasSchedule: Bool, + hasTimer: Bool, + updateHiddenMedia: @escaping (String?) -> Void, + initialLayout: ContainerViewLayout?, + transitionHostView: @escaping () -> UIView?, + transitionView: @escaping (String) -> UIView?, + completed: @escaping (TGMediaSelectableItem & TGMediaEditableItem, Bool, Int32?, @escaping () -> Void) -> Void, + presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, + presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, + getCaptionPanelView: @escaping () -> TGCaptionPanelView?, + present: @escaping (ViewController, Any?) -> Void, + finishedTransitionIn: @escaping () -> Void, + willTransitionOut: @escaping () -> Void, + dismissAll: @escaping () -> Void, + editCover: @escaping (CGSize, @escaping (UIImage) -> Void) -> Void = { _, _ in } +) -> TGModernGalleryController { let reminder = peer?.id == context.account.peerId let hasSilentPosting = hasSilentPosting && peer?.id != context.account.peerId var hasCoverButton = false @@ -118,6 +145,12 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, paintStickersContext.captionPanelView = { return getCaptionPanelView() } + paintStickersContext.livePhotoButton = { + let livePhotoButton = LivePhotoButton() + livePhotoButton.present = present + livePhotoButton.context = context + return livePhotoButton + } paintStickersContext.editCover = { dimensions, completion in editCover(dimensions, completion) } diff --git a/submodules/MediaPickerUI/Sources/LivePhotoButton.swift b/submodules/MediaPickerUI/Sources/LivePhotoButton.swift new file mode 100644 index 0000000000..3cce5fd9e7 --- /dev/null +++ b/submodules/MediaPickerUI/Sources/LivePhotoButton.swift @@ -0,0 +1,187 @@ +import Foundation +import UIKit +import Display +import LegacyComponents +import ComponentFlow +import GlassBackgroundComponent +import ContextUI +import TelegramPresentationData +import AccountContext +import BundleIconComponent + +final class LivePhotoButton: UIView, TGLivePhotoButton { + private let backgroundView: GlassContextExtractableContainer + private let icon = ComponentView() + private let label = ComponentView() + private let arrow = ComponentView() + private let button = HighlightTrackingButton() + + private var mode: TGMediaLivePhotoMode = .off + + public var modeUpdated: ((TGMediaLivePhotoMode) -> Void)? + + var context: AccountContext? + var present: ((ViewController, Any?) -> Void)? + + var view: UIView { + return self + } + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + return self.bounds.insetBy(dx: -16.0, dy: -16.0).contains(point) + } + + override init(frame: CGRect) { + self.backgroundView = GlassContextExtractableContainer() + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + self.backgroundView.contentView.addSubview(self.button) + + self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) + + self.update() + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + @objc private func buttonPressed() { + guard let context = self.context else { + return + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme) + + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: "Live", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LiveOn"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + self?.modeUpdated?(.live) + + f(.default) + }))) + items.append(.action(ContextMenuActionItem(text: "Loop", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LiveLoop"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + self?.modeUpdated?(.loop) + + f(.default) + }))) + items.append(.action(ContextMenuActionItem(text: "Bounce", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LiveBounce"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + self?.modeUpdated?(.bounce) + + f(.default) + }))) + items.append(.action(ContextMenuActionItem(text: "Live Off", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LiveOff"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + self?.modeUpdated?(.off) + + f(.default) + }))) + + let contextController = makeContextController(presentationData: presentationData, source: .reference(LivePhotoReferenceContentSource(sourceView: self.backgroundView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil) + self.present?(contextController, nil) + } + + func updateFrame(_ frame: CGRect) { + let transition: ContainedViewLayoutTransition + if self.frame.width.isZero { + transition = .immediate + } else { + transition = .animated(duration: 0.4, curve: .spring) + } + transition.updateFrame(view: self, frame: frame) + } + + func setLivePhotoMode(_ mode: TGMediaLivePhotoMode) { + self.mode = mode + self.update() + } + + func update() { + let iconName: String + let labelText: String + switch self.mode { + case .live: + labelText = "Live" + iconName = "Media Editor/LiveOn" + case .loop: + labelText = "Loop" + iconName = "Media Editor/LiveLoop" + case .bounce: + labelText = "Bounce" + iconName = "Media Editor/LiveBounce" + default: + labelText = "Live Off" + iconName = "Media Editor/LiveOff" + } + + let iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent( + BundleIconComponent(name: iconName, tintColor: .white) + ), + environment: {}, + containerSize: CGSize(width: 18.0, height: 18.0) + ) + let iconFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((18.0 - iconSize.height) / 2.0)), size: iconSize) + if let iconView = self.icon.view { + if iconView.superview == nil { + iconView.isUserInteractionEnabled = false + self.backgroundView.contentView.addSubview(iconView) + } + iconView.frame = iconFrame + } + + let labelSize = self.label.update( + transition: .immediate, + component: AnyComponent( + Text(text: labelText.uppercased(), font: Font.regular(12.0), color: .white) + ), + environment: {}, + containerSize: CGSize(width: 200.0, height: 18.0) + ) + let labelFrame = CGRect(origin: CGPoint(x: 19.0, y: floorToScreenPixels((18.0 - labelSize.height) / 2.0)), size: labelSize) + if let labelView = self.label.view { + if labelView.superview == nil { + labelView.isUserInteractionEnabled = false + self.backgroundView.contentView.addSubview(labelView) + } + labelView.frame = labelFrame + } + + let arrowSize = self.arrow.update( + transition: .immediate, + component: AnyComponent( + BundleIconComponent(name: "Media Editor/DownArrow", tintColor: .white) + ), + environment: {}, + containerSize: CGSize(width: 8.0, height: 5.0) + ) + let arrowFrame = CGRect(origin: CGPoint(x: 19.0 + labelSize.width + 2.0 + UIScreenPixel, y: floorToScreenPixels((18.0 - arrowSize.height) / 2.0)), size: arrowSize) + if let arrowView = self.arrow.view { + if arrowView.superview == nil { + arrowView.isUserInteractionEnabled = false + self.backgroundView.contentView.addSubview(arrowView) + } + arrowView.frame = arrowFrame + } + + let size = CGSize(width: 19.0 + labelSize.width + 16.0, height: 18.0) + self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: true, tintColor: .init(kind: .panel), isInteractive: true, transition: .immediate) + self.backgroundView.frame = CGRect(origin: .zero, size: size) + self.button.frame = CGRect(origin: .zero, size: size) + } +} + +private final class LivePhotoReferenceContentSource: ContextReferenceContentSource { + private let sourceView: UIView + + let forceDisplayBelowKeyboard = true + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index 02e02391da..565b3e0766 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -318,7 +318,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { label = .none } - return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: .text(peerText, .secondary), label: label, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap { EnginePeer.Presence($0) }, text: peerText.isEmpty ? .presence : .text(peerText, .secondary), label: label, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in arguments.removeAdmin(peerId) diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index 0d07cc43d6..f77c5ed42a 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -1109,6 +1109,26 @@ private final class DemoSheetContent: CombinedComponent { ) ) + availableItems[.copyProtection] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.copyProtection, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + model: .island, + videoFile: configuration.videos["pm_noforwards"], + decoration: .badgeStars + )), + title: strings.Premium_CopyProtection, + text: strings.Premium_CopyProtectionInfo, + textColor: textColor + ) + ) + ) + ) + let index: Int = 0 var items: [DemoPagerComponent.Item] = [] if let item = availableItems.first(where: { $0.value.content.id == component.subject as AnyHashable }) { @@ -1199,6 +1219,8 @@ private final class DemoSheetContent: CombinedComponent { text = strings.Premium_MessageEffectsInfo case .todo: text = strings.Premium_TodoInfo + case .copyProtection: + text = strings.Premium_CopyProtectionInfo default: text = "" } @@ -1473,6 +1495,7 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { case folderTags case messageEffects case todo + case copyProtection case businessLocation case businessHours @@ -1533,6 +1556,8 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { return .messageEffects case .todo: return .todo + case .copyProtection: + return .copyProtection case .businessLocation: return .businessLocation case .businessHours: diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 49ee25c73b..440b5926de 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -317,6 +317,12 @@ public enum PremiumSource: Equatable { } else { return false } + case .copyProtection: + if case .copyProtection = rhs { + return true + } else { + return false + } case let .auth(lhsPrice): if case let .auth(rhsPrice) = rhs, lhsPrice == rhsPrice { return true @@ -377,6 +383,7 @@ public enum PremiumSource: Equatable { case folderTags case messageEffects case todo + case copyProtection case auth(String) case premiumGift(TelegramMediaFile) @@ -474,6 +481,8 @@ public enum PremiumSource: Equatable { return "effects" case .todo: return "todo" + case .copyProtection: + return "pm_noforwards" case .auth: return "auth" case .premiumGift: @@ -507,6 +516,7 @@ public enum PremiumPerk: CaseIterable { case folderTags case messageEffects case todo + case copyProtection case businessLocation case businessHours @@ -542,7 +552,8 @@ public enum PremiumPerk: CaseIterable { .folderTags, .business, .messageEffects, - .todo + .todo, + .copyProtection ] } @@ -618,6 +629,8 @@ public enum PremiumPerk: CaseIterable { return "effects" case .todo: return "todo" + case .copyProtection: + return "pm_noforwards" case .business: return "business" case .businessLocation: @@ -689,6 +702,8 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_MessageEffects case .todo: return strings.Premium_Todo + case .copyProtection: + return strings.Premium_CopyProtection case .businessLocation: return strings.Business_Location case .businessHours: @@ -758,6 +773,8 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_MessageEffectsInfo case .todo: return strings.Premium_TodoInfo + case .copyProtection: + return strings.Premium_CopyProtectionInfo case .businessLocation: return strings.Business_LocationInfo case .businessHours: @@ -827,6 +844,8 @@ public enum PremiumPerk: CaseIterable { return "Premium/Perk/MessageEffects" case .todo: return "Premium/Perk/Todo" + case .copyProtection: + return "Premium/Perk/CopyProtection" case .businessLocation: return "Premium/BusinessPerk/Location" case .businessHours: @@ -856,6 +875,7 @@ struct PremiumIntroConfiguration { .lastSeen, .voiceToText, .fasterDownload, + .copyProtection, .translation, .todo, .animatedEmoji, @@ -2196,6 +2216,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { demoSubject = .messageEffects case .todo: demoSubject = .todo + case .copyProtection: + demoSubject = .copyProtection case .business: demoSubject = .business default: diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 400f7e2881..dc7b329822 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 223 + return 224 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 1fd68beefb..3c16eb47a1 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -206,6 +206,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case globalPostsSearch = 83 case giftAuctionTips = 84 case giftCraftingTips = 85 + case copyProtectionTips = 86 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -579,6 +580,10 @@ private struct ApplicationSpecificNoticeKeys { static func giftCraftingTips() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.giftCraftingTips.key) } + + static func copyProtectionTips() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.copyProtectionTips.key) + } } public struct ApplicationSpecificNotice { @@ -2546,4 +2551,31 @@ public struct ApplicationSpecificNotice { return Int(previousValue) } } + + public static func getCopyProtectionTips(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Int32 in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.copyProtectionTips())?.get(ApplicationSpecificCounterNotice.self) { + return value.value + } else { + return 0 + } + } + } + + public static func incrementCopyProtectionTips(accountManager: AccountManager, count: Int = 1) -> Signal { + return accountManager.transaction { transaction -> Int in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.copyProtectionTips())?.get(ApplicationSpecificCounterNotice.self) { + currentValue = value.value + } + let previousValue = currentValue + currentValue += Int32(count) + + if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.copyProtectionTips(), entry) + } + + return Int(previousValue) + } + } } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 3f6f51216c..8ea0ea147d 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -317,7 +317,7 @@ public struct PresentationResourcesItemList { public static func scanIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.itemListScanIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Scan"), color: theme.list.itemAccentColor) + return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Scan"), color: theme.list.itemAccentColor) }) } diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index 45bee7e333..00063fed48 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -1511,6 +1511,7 @@ final class AvatarEditorScreenComponent: Component { cropOrientation: nil, gradientColors: nil, videoTrimRange: nil, + videoBounce: false, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift index 1808012a40..c1d2974745 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift @@ -132,6 +132,7 @@ final class LiveStreamMediaSource { cropOrientation: nil, gradientColors: nil, videoTrimRange: nil, + videoBounce: false, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 29b84db9a9..b715e1108e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -6002,7 +6002,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI break case let .date(date, _): return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(stringForFullDate(timestamp: date, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat), rects: rects) else { + let fullDate = stringForEntityFormattedDate(timestamp: date, format: .full(timeFormat: .short, dateFormat: .long, dayOfWeek: false), strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat) + guard let self, let contentNode = self.contextContentNodeForLink(fullDate, rects: rects) else { return } item.controllerInteraction.longTap(.date(date), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index c5c9609e0f..10f3653aa4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -2861,6 +2861,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr state = .play(messageTheme.mediaOverlayControlColors.foregroundColor) } + if isSecretMedia { let remainingTime: Int32? if let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout, Int32(timeout) != viewOnceTimeout { @@ -2889,6 +2890,14 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } } + if let file = media as? TelegramMediaFile, file.isLivePhoto { + badgeContent = nil + if case .progress = state { + } else { + state = .none + } + } + if let statusNode = self.statusNode { var removeStatusNode = false if statusNode.state != .none && state == .none { diff --git a/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift b/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift index 8aa54f76c3..de9b8d5a7e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift @@ -32,6 +32,7 @@ private final class ChatRankInfoSheetContent: CombinedComponent { let userPeer: EnginePeer let role: ChatRankInfoScreenRole let rank: String + let canChange: Bool let getController: () -> ViewController? let dismiss: () -> Void @@ -41,6 +42,7 @@ private final class ChatRankInfoSheetContent: CombinedComponent { userPeer: EnginePeer, role: ChatRankInfoScreenRole, rank: String, + canChange: Bool, getController: @escaping () -> ViewController?, dismiss: @escaping () -> Void ) { @@ -49,6 +51,7 @@ private final class ChatRankInfoSheetContent: CombinedComponent { self.userPeer = userPeer self.role = role self.rank = rank + self.canChange = canChange self.getController = getController self.dismiss = dismiss } @@ -122,6 +125,8 @@ private final class ChatRankInfoSheetContent: CombinedComponent { let additionalTextString: String? = "Only admins can assign member tags in this group." var adminPreviewTag = "Admin Tag" var adminPreviewRole: ChatRankInfoScreenRole = .admin + + var canEdit = false switch context.component.role { case .creator: var rank = !context.component.rank.isEmpty ? context.component.rank : strings.Conversation_Owner @@ -146,6 +151,10 @@ private final class ChatRankInfoSheetContent: CombinedComponent { linkHasBackground = false titleString = "Member Tag" textString = "This grey tag [\(rank)]() is **\(context.component.userPeer.compactDisplayTitle)'s** member tag. **\(context.component.userPeer.compactDisplayTitle)** is a member of **\(context.component.chatPeer.compactDisplayTitle)**." + + if component.canChange { + canEdit = true + } } let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in @@ -368,20 +377,30 @@ private final class ChatRankInfoSheetContent: CombinedComponent { ) var buttonTitle: [AnyComponentWithIdentity] = [] - buttonTitle.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(LottieComponent( - content: LottieComponent.AppBundleContent(name: "anim_ok"), - color: theme.list.itemCheckColors.foregroundColor, - startingPosition: .begin, - size: CGSize(width: 28.0, height: 28.0), - playOnce: state.playButtonAnimation - )))) - buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ButtonTextContentComponent( - text: strings.CocoonInfo_Understood, - badge: 0, - textColor: theme.list.itemCheckColors.foregroundColor, - badgeBackground: theme.list.itemCheckColors.foregroundColor, - badgeForeground: theme.list.itemCheckColors.fillColor - )))) + if canEdit { + buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ButtonTextContentComponent( + text: "Add My Tag", + badge: 0, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor + )))) + } else { + buttonTitle.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "anim_ok"), + color: theme.list.itemCheckColors.foregroundColor, + startingPosition: .begin, + size: CGSize(width: 28.0, height: 28.0), + playOnce: state.playButtonAnimation + )))) + buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ButtonTextContentComponent( + text: strings.CocoonInfo_Understood, + badge: 0, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor + )))) + } let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let button = button.update( @@ -401,7 +420,7 @@ private final class ChatRankInfoSheetContent: CombinedComponent { action: { component.dismiss() - if let controller = controller() as? ChatRankInfoScreen { + if canEdit, let controller = controller() as? ChatRankInfoScreen { controller.completion?() } } @@ -430,6 +449,7 @@ final class ChatRankInfoSheetComponent: CombinedComponent { let userPeer: EnginePeer let role: ChatRankInfoScreenRole let rank: String + let canChange: Bool init( context: AccountContext, @@ -437,12 +457,14 @@ final class ChatRankInfoSheetComponent: CombinedComponent { userPeer: EnginePeer, role: ChatRankInfoScreenRole, rank: String, + canChange: Bool ) { self.context = context self.chatPeer = chatPeer self.userPeer = userPeer self.role = role self.rank = rank + self.canChange = canChange } static func ==(lhs: ChatRankInfoSheetComponent, rhs: ChatRankInfoSheetComponent) -> Bool { @@ -484,6 +506,7 @@ final class ChatRankInfoSheetComponent: CombinedComponent { userPeer: context.component.userPeer, role: context.component.role, rank: context.component.rank, + canChange: context.component.canChange, getController: controller, dismiss: { dismiss(true) @@ -572,7 +595,8 @@ public final class ChatRankInfoScreen: ViewControllerComponentContainer { chatPeer: chatPeer, userPeer: userPeer, role: role, - rank: rank + rank: rank, + canChange: canChange ), navigationBarAppearance: .none, statusBarStyle: .ignore, diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 5d8f928c93..3c8c774478 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -560,6 +560,7 @@ public final class MediaEditor { cropOrientation: nil, gradientColors: nil, videoTrimRange: nil, + videoBounce: false, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift index 480ffbe848..6fbf34e316 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift @@ -266,6 +266,9 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl if lhs.videoTrimRange != rhs.videoTrimRange { return false } + if lhs.videoBounce != rhs.videoBounce { + return false + } if lhs.videoIsMuted != rhs.videoIsMuted { return false } @@ -388,6 +391,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl case cropOrientation case gradientColors case videoTrimRange + case videoBounce case videoIsMuted case videoIsFullHd case videoIsMirrored @@ -575,6 +579,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl public let gradientColors: [UIColor]? public let videoTrimRange: Range? + public let videoBounce: Bool public let videoIsMuted: Bool public let videoIsFullHd: Bool public let videoIsMirrored: Bool @@ -644,6 +649,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl cropOrientation: MediaCropOrientation?, gradientColors: [UIColor]?, videoTrimRange: Range?, + videoBounce: Bool, videoIsMuted: Bool, videoIsFullHd: Bool, videoIsMirrored: Bool, @@ -683,6 +689,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl self.cropOrientation = cropOrientation self.gradientColors = gradientColors self.videoTrimRange = videoTrimRange + self.videoBounce = videoBounce self.videoIsMuted = videoIsMuted self.videoIsFullHd = videoIsFullHd self.videoIsMirrored = videoIsMirrored @@ -736,6 +743,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl } self.videoTrimRange = try container.decodeIfPresent(Range.self, forKey: .videoTrimRange) + self.videoBounce = try container.decodeIfPresent(Bool.self, forKey: .videoBounce) ?? false self.videoIsMuted = try container.decode(Bool.self, forKey: .videoIsMuted) self.videoIsFullHd = try container.decodeIfPresent(Bool.self, forKey: .videoIsFullHd) ?? false self.videoIsMirrored = try container.decodeIfPresent(Bool.self, forKey: .videoIsMirrored) ?? false @@ -809,6 +817,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl } try container.encodeIfPresent(self.videoTrimRange, forKey: .videoTrimRange) + try container.encode(self.videoBounce, forKey: .videoBounce) try container.encode(self.videoIsMuted, forKey: .videoIsMuted) try container.encode(self.videoIsFullHd, forKey: .videoIsFullHd) try container.encode(self.videoIsMirrored, forKey: .videoIsMirrored) @@ -856,125 +865,125 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl } public func makeCopy() -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedCropRect(cropRect: CGRect, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedVideoIsFullHd(_ videoIsFullHd: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedVideoIsMirrored(_ videoIsMirrored: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedVideoVolume(_ videoVolume: CGFloat?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideo(path: String?, isDual: Bool, positionChanges: [VideoPositionChange]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: path, additionalVideoIsDual: isDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: path, additionalVideoIsDual: isDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedAdditionalVideo(position: CGPoint, scale: CGFloat, rotation: CGFloat) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedAdditionalVideoPositionChanges(additionalVideoPositionChanges: [VideoPositionChange]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideoTrimRange(_ additionalVideoTrimRange: Range?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideoOffset(_ additionalVideoOffset: Double?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideoVolume(_ additionalVideoVolume: CGFloat?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedCollage(_ collage: [VideoCollageItem]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedVideoTrimRange(_ videoTrimRange: Range) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedMaskDrawing(maskDrawing: UIImage?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrack(_ audioTrack: MediaAudioTrack?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackTrimRange(_ audioTrackTrimRange: Range?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackOffset(_ audioTrackOffset: Double?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackVolume(_ audioTrackVolume: CGFloat?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackSamples(_ audioTrackSamples: MediaAudioTrackSamples?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedCollageTrackSamples(_ collageTrackSamples: MediaAudioTrackSamples?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedNightTheme(_ nightTheme: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedEntities(_ entities: [CodableDrawingEntity]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedCoverImageTimestamp(_ coverImageTimestamp: Double?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedCoverDimensions(_ coverDimensions: CGSize?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedQualityPreset(_ qualityPreset: MediaQualityPreset?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: qualityPreset) } public var resultDimensions: PixelDimensions { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift index 89bfd124cc..71ed1384ee 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift @@ -408,7 +408,11 @@ public final class MediaEditorVideoExport { duration = CMTime(seconds: 5.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) } } - self.durationValue = duration + if self.configuration.values.videoBounce, case .video = self.subject { + self.durationValue = CMTimeAdd(duration, duration) + } else { + self.durationValue = duration + } let _ = (combineLatest(signals) |> deliverOn(self.queue)).start(next: { [weak self] additionalInputs in @@ -497,17 +501,49 @@ public final class MediaEditorVideoExport { self.mainVideoScale = scale self.mainVideoOffset = offset self.textureRotation = rotation + var mainTimeRange = CMTimeRange(start: .zero, duration: asset.duration) + if let timeRange = self.configuration.timeRange { + mainTimeRange = timeRange + } else if isStory && asset.duration.seconds > 60.0 { + mainTimeRange = CMTimeRange(start: .zero, duration: CMTime(seconds: 60.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))) + } if let videoAssetTrack = asset.tracks(withMediaType: .video).first { if let compositionTrack = composition?.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) { mainVideoTrack = compositionTrack compositionTrack.preferredTransform = videoAssetTrack.preferredTransform - - try? compositionTrack.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: videoAssetTrack, at: .zero) + + try? compositionTrack.insertTimeRange(mainTimeRange, of: videoAssetTrack, at: .zero) + + if self.configuration.values.videoBounce { + let sourceFrameRate: Float + if videoAssetTrack.nominalFrameRate > 0.0 { + sourceFrameRate = videoAssetTrack.nominalFrameRate + } else if self.configuration.frameRate > 0.0 { + sourceFrameRate = self.configuration.frameRate + } else { + sourceFrameRate = 30.0 + } + let roundedFrameRate = max(1, Int(round(sourceFrameRate))) + let frameDuration = CMTime(value: 1, timescale: Int32(roundedFrameRate)) + + var insertionTime = mainTimeRange.duration + var cursor = CMTimeSubtract(CMTimeRangeGetEnd(mainTimeRange), frameDuration) + while CMTimeCompare(cursor, mainTimeRange.start) >= 0 { + let remaining = CMTimeSubtract(CMTimeRangeGetEnd(mainTimeRange), cursor) + let segmentDuration = CMTimeCompare(frameDuration, remaining) <= 0 ? frameDuration : remaining + if segmentDuration.seconds > 0.0 { + let segmentRange = CMTimeRange(start: cursor, duration: segmentDuration) + try? compositionTrack.insertTimeRange(segmentRange, of: videoAssetTrack, at: insertionTime) + insertionTime = CMTimeAdd(insertionTime, segmentDuration) + } + cursor = CMTimeSubtract(cursor, frameDuration) + } + } } } if let audioAssetTrack = asset.tracks(withMediaType: .audio).first, !self.configuration.values.videoIsMuted { if let compositionTrack = composition?.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) { - try? compositionTrack.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: audioAssetTrack, at: .zero) + try? compositionTrack.insertTimeRange(mainTimeRange, of: audioAssetTrack, at: .zero) if let volume = self.configuration.values.videoVolume, volume != 1.0 { let trackParameters = AVMutableAudioMixInputParameters(track: compositionTrack) @@ -517,7 +553,7 @@ public final class MediaEditorVideoExport { } } } - if let timeRange = self.configuration.timeRange { + if !self.configuration.values.videoBounce, let timeRange = self.configuration.timeRange { readerRange = timeRange } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift index d69c081a2a..38cbc24afb 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift @@ -12,6 +12,7 @@ import NotificationPeerExceptionController import NotificationExceptionsScreen import ShareController import TranslateUI +import TelegramNotices extension PeerInfoScreenNode { func performButtonAction(key: PeerInfoHeaderButtonKey, buttonNode: PeerInfoHeaderButtonNode?, gesture: ContextGesture?) { @@ -775,15 +776,46 @@ extension PeerInfoScreenNode { } if !copyProtectionEnabled { - let infoController = self.context.sharedContext.makePeerCopyProtectionInfoScreen(context: self.context, completion: { [weak self] in - guard let self else { - return + if !self.context.isPremium { + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let demoController = context.sharedContext.makePremiumDemoController(context: context, subject: .copyProtection, forceDark: false, action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .copyProtection, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }, dismissed: nil) + replaceImpl = { [weak demoController] c in + demoController?.replace(with: c) } - let _ = self.context.engine.peers.toggleMessageCopyProtection(peerId: user.id, enabled: true).start() - - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, peerNearbyData: nil, completion: { _ in })) - }) - self.controller?.push(infoController) + self.controller?.push(demoController) + } else { + let action = { [weak self] in + guard let self else { + return + } + let _ = self.context.engine.peers.toggleMessageCopyProtection(peerId: user.id, enabled: true).start() + + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, peerNearbyData: nil, completion: { _ in })) + } + let _ = (ApplicationSpecificNotice.getCopyProtectionTips(accountManager: self.context.sharedContext.accountManager) + |> deliverOnMainQueue).start(next: { [weak self] count in + guard let self else { + return + } + if count > 3 { + action() + } else { + let infoController = self.context.sharedContext.makePeerCopyProtectionInfoScreen(context: self.context, completion: { [weak self] in + guard let self else { + return + } + action() + + let _ = ApplicationSpecificNotice.incrementCopyProtectionTips(accountManager: self.context.sharedContext.accountManager) + }) + self.controller?.push(infoController) + } + }) + } } else { let _ = self.context.engine.peers.toggleMessageCopyProtection(peerId: user.id, enabled: false).start() diff --git a/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift b/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift index 6b0355b5bd..86a9eb018f 100644 --- a/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift +++ b/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift @@ -885,6 +885,7 @@ private extension MediaEditorValues { cropOrientation: nil, gradientColors: nil, videoTrimRange: nil, + videoBounce: false, videoIsMuted: false, videoIsFullHd: true, videoIsMirrored: false, @@ -921,6 +922,7 @@ private extension MediaEditorValues { if legacyAdjustments.trimStartValue > 0.0 || !legacyAdjustments.trimEndValue.isZero { videoTrimRange = legacyAdjustments.trimStartValue ..< legacyAdjustments.trimEndValue } + let videoBounce = legacyAdjustments.bounce var entities: [CodableDrawingEntity] = [] var drawing: UIImage? @@ -1032,6 +1034,7 @@ private extension MediaEditorValues { cropOrientation: legacyAdjustments.cropOrientation.cropOrientation, gradientColors: nil, videoTrimRange: videoTrimRange, + videoBounce: videoBounce, videoIsMuted: legacyAdjustments.sendAsGif, videoIsFullHd: true, videoIsMirrored: false, diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index 23a942d872..4cc16219bc 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -1937,7 +1937,7 @@ public class VideoMessageCameraScreen: ViewController { guard let self else { return } - let values = MediaEditorValues(peerId: self.context.account.peerId, originalDimensions: dimensions, cropOffset: .zero, cropRect: CGRect(origin: .zero, size: dimensions.cgSize), cropScale: 1.0, cropRotation: 0.0, cropMirroring: false, cropOrientation: nil, gradientColors: nil, videoTrimRange: self.node.previewState?.trimRange, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, additionalVideoPositionChanges: [], additionalVideoTrimRange: nil, additionalVideoOffset: nil, additionalVideoVolume: nil, collage: [], nightTheme: false, drawing: nil, maskDrawing: nil, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, collageTrackSamples: nil, coverImageTimestamp: nil, coverDimensions: nil, qualityPreset: .videoMessage) + let values = MediaEditorValues(peerId: self.context.account.peerId, originalDimensions: dimensions, cropOffset: .zero, cropRect: CGRect(origin: .zero, size: dimensions.cgSize), cropScale: 1.0, cropRotation: 0.0, cropMirroring: false, cropOrientation: nil, gradientColors: nil, videoTrimRange: self.node.previewState?.trimRange, videoBounce: false, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, additionalVideoPositionChanges: [], additionalVideoTrimRange: nil, additionalVideoOffset: nil, additionalVideoVolume: nil, collage: [], nightTheme: false, drawing: nil, maskDrawing: nil, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, collageTrackSamples: nil, coverImageTimestamp: nil, coverDimensions: nil, qualityPreset: .videoMessage) var resourceAdjustments: VideoMediaResourceAdjustments? = nil if let valuesData = try? JSONEncoder().encode(values) { diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/LiveBounce.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveBounce.imageset/Contents.json new file mode 100644 index 0000000000..1f5d873122 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveBounce.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "bounce.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/LiveBounce.imageset/bounce.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveBounce.imageset/bounce.pdf new file mode 100644 index 0000000000000000000000000000000000000000..54e4cbc63f1e4675aa44de903602cc68778aec13 GIT binary patch literal 7420 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 wFz*5E%WDqtsDxE+vJKv3vUL=DJy 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 Kkx^D%?*9Pn$FI@= literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOn.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOn.imageset/Contents.json new file mode 100644 index 0000000000..d195276e89 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOn.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "live.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOn.imageset/live.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOn.imageset/live.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f3872355a5d4302575fde918a7a726c66578978d GIT binary patch literal 7144 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 JG08tu_&X`-;0sjyek7D4h!oH3EsxejmplY$B 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift index 9c98dd5c4e..4cd0437724 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift @@ -14,6 +14,7 @@ import ChatControllerInteraction import EventKit import EventKitUI import ChatScheduleTimeController +import TextFormat extension ChatControllerImpl: EKEventEditViewDelegate { func openDateContextMenu(date: Int32, params: ChatControllerInteraction.LongTapParams) -> Void { @@ -54,7 +55,8 @@ extension ChatControllerImpl: EKEventEditViewDelegate { return } - UIPasteboard.general.string = "\(date)" + let fullDate = stringForEntityFormattedDate(timestamp: date, format: .full(timeFormat: .short, dateFormat: .long, dayOfWeek: false), strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat) + UIPasteboard.general.string = fullDate self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: "Date copied to clipboard."), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) })) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift index 346f2e6b8f..5d489d7069 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift @@ -195,6 +195,7 @@ extension ChatControllerImpl { cropOrientation: .up, gradientColors: [.clear, .clear], videoTrimRange: nil, + videoBounce: false, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index eeef314da8..b99610b078 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -1127,7 +1127,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let isSecret = self.chatPresentationInterfaceState.copyProtectionEnabled || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat || self.chatLocation.peerId?.isVerificationCodes == true if self.historyNodeContainer.isSecret != isSecret { + #if DEBUG + self.historyNodeContainer.isSecret = false + #else self.historyNodeContainer.isSecret = isSecret + #endif setLayerDisableScreenshots(self.titleAccessoryPanelContainer.layer, isSecret) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index bb8fbe149d..bf858b81ae 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1273,6 +1273,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState UIPasteboard.general.string = diceEmoji } else { let copyTextWithEntities = { + var messageText = message.text var messageEntities: [MessageTextEntity]? var restrictedText: String? for attribute in message.attributes { @@ -1282,6 +1283,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState if let attribute = attribute as? RestrictedContentMessageAttribute { restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? "" } + if let attribute = attribute as? AudioTranscriptionMessageAttribute { + messageText = attribute.text + } } if let restrictedText = restrictedText { @@ -1296,7 +1300,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } else if let translateToLang, let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translateToLang }) as? TranslationMessageAttribute, !translation.text.isEmpty { storeMessageTextInPasteboard(translation.text, entities: translation.entities) } else { - storeMessageTextInPasteboard(message.text, entities: messageEntities) + storeMessageTextInPasteboard(messageText, entities: messageEntities) } } @@ -1309,8 +1313,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState for media in message.media { if let image = media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { let _ = (context.account.postbox.mediaBox.resourceData(largest.resource, option: .incremental(waitUntilFetchStatus: false)) - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { data in + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { data in if data.complete, let imageData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { if let image = UIImage(data: imageData) { if !messageText.isEmpty { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index c32e601a2f..8b252c3cb8 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2941,6 +2941,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .animatedEmoji case .todo: mappedSource = .todo + case .copyProtection: + mappedSource = .copyProtection case let .auth(price): mappedSource = .auth(price) case let .premiumGift(file): @@ -3021,6 +3023,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSubject = .messageEffects case .todo: mappedSubject = .todo + case .copyProtection: + mappedSubject = .copyProtection case .business: mappedSubject = .business buttonText = presentationData.strings.Chat_EmptyStateIntroFooterPremiumActionButton