diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h index 0b8fd17750..cff37cb96a 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h @@ -2,7 +2,7 @@ @interface TGPhotoVideoEditor : NSObject -+ (void)presentWithContext:(id)context parentController:(TGViewController *)parentController screenImage:(UIImage *)screenImage image:(UIImage *)image video:(NSURL *)video didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed; ++ (void)presentWithContext:(id)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed; + (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id)item recipientName:(NSString *)recipientName stickersContext:(id)stickersContext completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed; diff --git a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m index bf48081b5e..98be115651 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m +++ b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m @@ -8,9 +8,11 @@ #import "TGMediaPickerGalleryVideoItemView.h" +#import "LegacyComponentsInternal.h" + @implementation TGPhotoVideoEditor -+ (void)presentWithContext:(id)context parentController:(TGViewController *)parentController screenImage:(UIImage *)screenImage image:(UIImage *)image video:(NSURL *)video didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed ++ (void)presentWithContext:(id)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed { id windowManager = [context makeOverlayWindowManager]; @@ -18,52 +20,85 @@ if (image != nil) { editableItem = image; } else if (video != nil) { + if (![video.path.lowercaseString hasSuffix:@".mp4"]) { + NSString *tmpPath = NSTemporaryDirectory(); + int64_t fileId = 0; + arc4random_buf(&fileId, sizeof(fileId)); + NSString *videoMp4FilePath = [tmpPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%" PRId64 ".mp4", fileId]]; + [[NSFileManager defaultManager] removeItemAtPath:videoMp4FilePath error:nil]; + [[NSFileManager defaultManager] createSymbolicLinkAtPath:videoMp4FilePath withDestinationPath:video.path error:nil]; + video = [NSURL fileURLWithPath:videoMp4FilePath]; + } + editableItem = [[TGCameraCapturedVideo alloc] initWithURL:video]; } - TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:editableItem intent:TGPhotoEditorControllerAvatarIntent adjustments:nil caption:nil screenImage:screenImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent] selectedTab:TGPhotoEditorCropTab]; -// controller.stickersContext = _stickersContext; - controller.skipInitialTransition = true; - controller.dontHideStatusBar = true; - controller.didFinishEditing = ^(__unused id adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges) - { - if (didFinishWithImage != nil) - didFinishWithImage(resultImage); - }; - controller.didFinishEditingVideo = ^(NSURL *url, id adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) { - if (didFinishWithVideo != nil) - didFinishWithVideo(resultImage, url, adjustments); - }; - controller.requestThumbnailImage = ^(id editableItem) - { - return [editableItem thumbnailImageSignal]; - }; - controller.requestOriginalScreenSizeImage = ^(id editableItem, NSTimeInterval position) - { - return [editableItem screenImageSignal:position]; - }; - controller.requestOriginalFullSizeImage = ^(id editableItem, NSTimeInterval position) - { - if (editableItem.isVideo) { - if ([editableItem isKindOfClass:[TGMediaAsset class]]) { - return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem allowNetworkAccess:true]; - } else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) { - return ((TGCameraCapturedVideo *)editableItem).avAsset; + + void (^present)(UIImage *) = ^(UIImage *screenImage) { + TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:editableItem intent:TGPhotoEditorControllerAvatarIntent adjustments:nil caption:nil screenImage:screenImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent] selectedTab:TGPhotoEditorCropTab]; + // controller.stickersContext = _stickersContext; + controller.skipInitialTransition = true; + controller.dontHideStatusBar = true; + controller.didFinishEditing = ^(__unused id adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges) + { + if (didFinishWithImage != nil) + didFinishWithImage(resultImage); + }; + controller.didFinishEditingVideo = ^(NSURL *url, id adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) { + if (didFinishWithVideo != nil) + didFinishWithVideo(resultImage, url, adjustments); + }; + controller.requestThumbnailImage = ^(id editableItem) + { + return [editableItem thumbnailImageSignal]; + }; + + controller.requestOriginalScreenSizeImage = ^(id editableItem, NSTimeInterval position) + { + return [editableItem screenImageSignal:position]; + }; + controller.requestOriginalFullSizeImage = ^(id editableItem, NSTimeInterval position) + { + if (editableItem.isVideo) { + if ([editableItem isKindOfClass:[TGMediaAsset class]]) { + return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem allowNetworkAccess:true]; + } else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) { + return ((TGCameraCapturedVideo *)editableItem).avAsset; + } else { + return [editableItem originalImageSignal:position]; + } } else { return [editableItem originalImageSignal:position]; } - } else { - return [editableItem originalImageSignal:position]; - } - }; - controller.onDismiss = ^{ - dismissed(); + }; + controller.onDismiss = ^{ + dismissed(); + }; + + TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:controller]; + controllerWindow.hidden = false; + controller.view.clipsToBounds = true; }; - TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:controller]; - controllerWindow.hidden = false; - controller.view.clipsToBounds = true; + if (image != nil) { + present(image); + } else if (video != nil) { + AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:[AVURLAsset assetWithURL:video]]; + imageGenerator.appliesPreferredTrackTransform = true; + imageGenerator.maximumSize = CGSizeMake(1280, 1280); + imageGenerator.requestedTimeToleranceBefore = kCMTimeZero; + imageGenerator.requestedTimeToleranceAfter = kCMTimeZero; + + [imageGenerator generateCGImagesAsynchronouslyForTimes:@[ [NSValue valueWithCMTime:kCMTimeZero] ] completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) { + if (result == AVAssetImageGeneratorSucceeded) { + UIImage *screenImage = [UIImage imageWithCGImage:image]; + TGDispatchOnMainThread(^{ + present(screenImage); + }); + } + }]; + } } + (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id)item recipientName:(NSString *)recipientName stickersContext:(id)stickersContext completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift index 3a9e02ab9b..4e49a364b2 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift @@ -6,7 +6,7 @@ import LegacyComponents import TelegramPresentationData import LegacyUI -public func presentLegacyAvatarEditor(theme: PresentationTheme, screenImage: UIImage?, image: UIImage?, video: URL?, present: (ViewController, Any?) -> Void, imageCompletion: @escaping (UIImage) -> Void, videoCompletion: @escaping (UIImage, URL, TGVideoEditAdjustments?) -> Void) { +public func presentLegacyAvatarEditor(theme: PresentationTheme, image: UIImage?, video: URL?, present: (ViewController, Any?) -> Void, imageCompletion: @escaping (UIImage) -> Void, videoCompletion: @escaping (UIImage, URL, TGVideoEditAdjustments?) -> Void) { let legacyController = LegacyController(presentation: .custom, theme: theme) legacyController.statusBar.statusBarStyle = .Ignore @@ -19,7 +19,7 @@ public func presentLegacyAvatarEditor(theme: PresentationTheme, screenImage: UII present(legacyController, nil) - TGPhotoVideoEditor.present(with: legacyController.context, parentController: emptyController, screenImage: screenImage, image: image, video: video, didFinishWithImage: { image in + TGPhotoVideoEditor.present(with: legacyController.context, parentController: emptyController, image: image, video: video, didFinishWithImage: { image in if let image = image { imageCompletion(image) } diff --git a/submodules/PeerAvatarGalleryUI/BUCK b/submodules/PeerAvatarGalleryUI/BUCK index b6d5a48e59..e0a9dd6fcb 100644 --- a/submodules/PeerAvatarGalleryUI/BUCK +++ b/submodules/PeerAvatarGalleryUI/BUCK @@ -21,6 +21,7 @@ static_library( "//submodules/RadialStatusNode:RadialStatusNode", "//submodules/ShareController:ShareController", "//submodules/AppBundle:AppBundle", + "//submodules/LegacyComponents:LegacyComponents", "//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI", "//submodules/SaveToCameraRoll:SaveToCameraRoll", ], diff --git a/submodules/PeerAvatarGalleryUI/BUILD b/submodules/PeerAvatarGalleryUI/BUILD index 8f52dff372..401cd954bb 100644 --- a/submodules/PeerAvatarGalleryUI/BUILD +++ b/submodules/PeerAvatarGalleryUI/BUILD @@ -22,6 +22,7 @@ swift_library( "//submodules/RadialStatusNode:RadialStatusNode", "//submodules/ShareController:ShareController", "//submodules/AppBundle:AppBundle", + "//submodules/LegacyComponents:LegacyComponents", "//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI", "//submodules/SaveToCameraRoll:SaveToCameraRoll", ], diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index a84805e33f..681a9626f0 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -10,6 +10,7 @@ import SyncCore import TelegramPresentationData import AccountContext import GalleryUI +import LegacyComponents import LegacyMediaPickerUI import SaveToCameraRoll @@ -187,6 +188,9 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>() private let centralItemAttributesDisposable = DisposableSet(); + public var avatarPhotoEditCompletion: ((UIImage) -> Void)? + public var avatarVideoEditCompletion: ((UIImage, URL, TGVideoEditAdjustments?) -> Void)? + private let _hiddenMedia = Promise(nil) public var hiddenMedia: Signal { return self._hiddenMedia.get() @@ -635,7 +639,6 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr case let .progress(value): break case let .data(data): - let screenImage: UIImage? let image: UIImage? let video: URL? if isImage { @@ -644,22 +647,28 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } else { image = nil } - screenImage = image video = nil } else { image = nil video = URL(fileURLWithPath: data.path) - screenImage = nil } - presentLegacyAvatarEditor(theme: strongSelf.presentationData.theme, screenImage: screenImage, image: image, video: video, present: { [weak self] c, a in + + let avatarPhotoEditCompletion = strongSelf.avatarPhotoEditCompletion + let avatarVideoEditCompletion = strongSelf.avatarVideoEditCompletion + + presentLegacyAvatarEditor(theme: strongSelf.presentationData.theme, image: image, video: video, present: { [weak self] c, a in if let strongSelf = self { strongSelf.present(c, in: .window(.root), with: a, blockInteraction: true) } - }, imageCompletion: { [weak self] image in - - }, videoCompletion: { [weak self] image, url, adjustments in - + }, imageCompletion: { image in + avatarPhotoEditCompletion?(image) + }, videoCompletion: { image, url, adjustments in + avatarVideoEditCompletion?(image, url, adjustments) }) + + Queue.mainQueue().after(0.4) { + strongSelf.dismiss(forceAway: true) + } } })) } diff --git a/submodules/SettingsUI/Sources/EditSettingsController.swift b/submodules/SettingsUI/Sources/EditSettingsController.swift index 3b98300ed9..4e03724257 100644 --- a/submodules/SettingsUI/Sources/EditSettingsController.swift +++ b/submodules/SettingsUI/Sources/EditSettingsController.swift @@ -549,7 +549,7 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar hasPhotos = true } - let completedPhotoImpl: (UIImage) -> Void = { image in + let completedProfilePhotoImpl: (UIImage) -> Void = { image in if let data = image.jpegData(compressionQuality: 0.6) { let resource = LocalFileMediaResource(fileId: arc4random64()) context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) @@ -580,7 +580,7 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar } } - let completedVideoImpl: (UIImage, URL, TGVideoEditAdjustments?) -> Void = { image, url, adjustments in + let completedProfileVideoImpl: (UIImage, URL, TGVideoEditAdjustments?) -> Void = { image, url, adjustments in if let data = image.jpegData(compressionQuality: 0.6) { let photoResource = LocalFileMediaResource(fileId: arc4random64()) context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) @@ -675,18 +675,18 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar mixin.requestSearchController = { assetsController in let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in assetsController?.dismiss() - completedPhotoImpl(result) + completedProfilePhotoImpl(result) })) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } mixin.didFinishWithImage = { image in if let image = image { - completedPhotoImpl(image) + completedProfilePhotoImpl(image) } } mixin.didFinishWithVideo = { image, url, adjustments in if let image = image, let url = url { - completedVideoImpl(image, url, adjustments) + completedProfileVideoImpl(image, url, adjustments) } } mixin.didFinishWithDelete = { @@ -727,6 +727,12 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar if peer.smallProfileImage != nil { let galleryController = AvatarGalleryController(context: context, peer: peer, remoteEntries: cachedAvatarEntries.with { $0 }, replaceRootController: { controller, ready in }) + galleryController.avatarPhotoEditCompletion = { image in + completedProfilePhotoImpl(image) + } + galleryController.avatarVideoEditCompletion = { image, url, adjustments in + completedProfileVideoImpl(image, url, adjustments) + } presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in return nil })) diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index 3fadfd78b6..c1d2221fac 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -994,6 +994,119 @@ public func settingsController(context: AccountContext, accountManager: AccountM let blockedPeers = Promise(nil) let hasTwoStepAuthPromise = Promise(nil) + let completedProfilePhotoImpl: (UIImage) -> Void = { image in + if let data = image.jpegData(compressionQuality: 0.6) { + let resource = LocalFileMediaResource(fileId: arc4random64()) + context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource) + updateState { state in + var state = state + state.updatingAvatar = .image(representation, true) + return state + } + updateAvatarDisposable.set((updateAccountPhoto(account: context.account, resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + }) |> deliverOnMainQueue).start(next: { result in + switch result { + case .complete: + updateState { state in + var state = state + state.updatingAvatar = nil + return state + } + case .progress: + break + } + })) + } + } + + let completedProfileVideoImpl: (UIImage, URL, TGVideoEditAdjustments?) -> Void = { image, url, adjustments in + if let data = image.jpegData(compressionQuality: 0.6) { + let photoResource = LocalFileMediaResource(fileId: arc4random64()) + context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) + let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource) + updateState { state in + var state = state + state.updatingAvatar = .image(representation, true) + return state + } + + var videoStartTimestamp: Double? = nil + if let adjustments = adjustments, adjustments.videoStartValue > 0.0 { + videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue + } + + let signal = Signal { subscriber in + var filteredPath = url.path + if filteredPath.hasPrefix("file://") { + filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)]) + } + + let avAsset = AVURLAsset(url: URL(fileURLWithPath: filteredPath)) + let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in + if let paintingData = adjustments.paintingData, paintingData.hasAnimation { + return LegacyPaintEntityRenderer(account: context.account, adjustments: adjustments) + } else { + return nil + } + } + let uploadInterface = LegacyLiveUploadInterface(account: context.account) + let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)! + + let signalDisposable = signal.start(next: { next in + if let result = next as? TGMediaVideoConversionResult { + if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) { + context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) + } + + var value = stat() + if stat(result.fileURL.path, &value) == 0 { + if let data = try? Data(contentsOf: result.fileURL) { + let resource: TelegramMediaResource + if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult { + resource = LocalFileMediaResource(fileId: liveUploadData.id) + } else { + resource = LocalFileMediaResource(fileId: arc4random64()) + } + context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + subscriber.putNext(resource) + } + } + subscriber.putCompletion() + } + }, error: { _ in + }, completed: nil) + + let disposable = ActionDisposable { + signalDisposable?.dispose() + } + + return ActionDisposable { + disposable.dispose() + } + } + + updateAvatarDisposable.set((signal + |> mapToSignal { videoResource in + return updateAccountPhoto(account: context.account, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + }) + } |> deliverOnMainQueue).start(next: { result in + switch result { + case .complete: + updateState { state in + var state = state + state.updatingAvatar = nil + return state + } + case .progress: + break + } + })) + } + } + let arguments = SettingsItemArguments(sharedContext: context.sharedContext, avatarAndNameInfoContext: avatarAndNameInfoContext, avatarTapAction: { var updating = false updateState { @@ -1013,6 +1126,12 @@ public func settingsController(context: AccountContext, accountManager: AccountM let galleryController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { controller, ready in }) + galleryController.avatarPhotoEditCompletion = { image in + completedProfilePhotoImpl(image) + } + galleryController.avatarVideoEditCompletion = { image, url, adjustments in + completedProfileVideoImpl(image, url, adjustments) + } hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.last?.representation updateHiddenAvatarImpl?() @@ -1284,137 +1403,24 @@ public func settingsController(context: AccountContext, accountManager: AccountM if let peer = peer, !peer.profileImageRepresentations.isEmpty { hasPhotos = true } - - let completedImpl: (UIImage) -> Void = { image in - if let data = image.jpegData(compressionQuality: 0.6) { - let resource = LocalFileMediaResource(fileId: arc4random64()) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) - let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource) - updateState { state in - var state = state - state.updatingAvatar = .image(representation, true) - return state - } - updateAvatarDisposable.set((updateAccountPhoto(account: context.account, resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) - }) |> deliverOnMainQueue).start(next: { result in - switch result { - case .complete: - updateState { state in - var state = state - state.updatingAvatar = nil - return state - } - case .progress: - break - } - })) - } - } - - let completedVideoImpl: (UIImage, URL, TGVideoEditAdjustments?) -> Void = { image, url, adjustments in - if let data = image.jpegData(compressionQuality: 0.6) { - let photoResource = LocalFileMediaResource(fileId: arc4random64()) - context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) - let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource) - updateState { state in - var state = state - state.updatingAvatar = .image(representation, true) - return state - } - - var videoStartTimestamp: Double? = nil - if let adjustments = adjustments, adjustments.videoStartValue > 0.0 { - videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue - } - - let signal = Signal { subscriber in - var filteredPath = url.path - if filteredPath.hasPrefix("file://") { - filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)]) - } - - let avAsset = AVURLAsset(url: URL(fileURLWithPath: filteredPath)) - let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in - if let paintingData = adjustments.paintingData, paintingData.hasAnimation { - return LegacyPaintEntityRenderer(account: context.account, adjustments: adjustments) - } else { - return nil - } - } - let uploadInterface = LegacyLiveUploadInterface(account: context.account) - let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)! - - let signalDisposable = signal.start(next: { next in - if let result = next as? TGMediaVideoConversionResult { - if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) { - context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) - } - - var value = stat() - if stat(result.fileURL.path, &value) == 0 { - if let data = try? Data(contentsOf: result.fileURL) { - let resource: TelegramMediaResource - if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult { - resource = LocalFileMediaResource(fileId: liveUploadData.id) - } else { - resource = LocalFileMediaResource(fileId: arc4random64()) - } - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - subscriber.putNext(resource) - } - } - subscriber.putCompletion() - } - }, error: { _ in - }, completed: nil) - - let disposable = ActionDisposable { - signalDisposable?.dispose() - } - - return ActionDisposable { - disposable.dispose() - } - } - - updateAvatarDisposable.set((signal - |> mapToSignal { videoResource in - return updateAccountPhoto(account: context.account, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) - }) - } |> deliverOnMainQueue).start(next: { result in - switch result { - case .complete: - updateState { state in - var state = state - state.updatingAvatar = nil - return state - } - case .progress: - break - } - })) - } - } - + let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: true, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)! let _ = currentAvatarMixin.swap(mixin) mixin.requestSearchController = { assetsController in let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in assetsController?.dismiss() - completedImpl(result) + completedProfilePhotoImpl(result) })) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } mixin.didFinishWithImage = { image in if let image = image { - completedImpl(image) + completedProfilePhotoImpl(image) } } mixin.didFinishWithVideo = { image, url, adjustments in if let image = image, let url = url { - completedVideoImpl(image, url, adjustments) + completedProfileVideoImpl(image, url, adjustments) } } mixin.didFinishWithDelete = {