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 0000000000..54e4cbc63f Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveBounce.imageset/bounce.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/LiveLoop.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveLoop.imageset/Contents.json new file mode 100644 index 0000000000..b326472577 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveLoop.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "loop.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/LiveLoop.imageset/loop.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveLoop.imageset/loop.pdf new file mode 100644 index 0000000000..601496ca9a Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveLoop.imageset/loop.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOff.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOff.imageset/Contents.json new file mode 100644 index 0000000000..508c1c7026 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOff.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "liveoff.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOff.imageset/liveoff.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOff.imageset/liveoff.pdf new file mode 100644 index 0000000000..8299fd9eb4 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOff.imageset/liveoff.pdf differ 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 0000000000..f3872355a5 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Media Editor/LiveOn.imageset/live.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Scan.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/Scan.imageset/Contents.json new file mode 100644 index 0000000000..a2d69fc76f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/Scan.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "scandoc.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Scan.imageset/scandoc.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/Scan.imageset/scandoc.pdf new file mode 100644 index 0000000000..9292f2a3a1 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Media Editor/Scan.imageset/scandoc.pdf differ 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