diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 0ce626560f..5528836ad4 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8233,3 +8233,6 @@ Sorry for the inconvenience."; "Channel.AdminLog.TopicRenamedWithRemovedIcon" = "%1$@ renamed topic %2$@ to %3$@ and removed icon"; "Channel.AdminLog.TopicChangedIcon" = "%1$@ changed topic %2$@ icon to %3$@"; "Channel.AdminLog.TopicRemovedIcon" = "%1$@ removed topic %2$@ icon"; + +"Attachment.Pasteboard" = "Clipboard"; +"Attachment.DiscardPasteboardAlertText" = "Discard pasted items?"; diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index f4efd141cd..53123bb647 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -168,6 +168,7 @@ public class AttachmentController: ViewController { private let buttons: [AttachmentButtonType] private let initialButton: AttachmentButtonType private let fromMenu: Bool + private let hasTextInput: Bool private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? public var willDismiss: () -> Void = {} @@ -437,10 +438,10 @@ public class AttachmentController: ViewController { } } - private func updateSelectionCount(_ count: Int) { + fileprivate func updateSelectionCount(_ count: Int, animated: Bool = true) { self.selectionCount = count if let layout = self.validLayout { - self.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate) } } @@ -754,7 +755,7 @@ public class AttachmentController: ViewController { let previousHasButton = self.hasButton let hasButton = self.panel.isButtonVisible && !self.isDismissing self.hasButton = hasButton - if let controller = self.controller, controller.buttons.count > 1 { + if let controller = self.controller, controller.buttons.count > 1 || controller.hasTextInput { hasPanel = true } @@ -856,13 +857,14 @@ public class AttachmentController: ViewController { public var getSourceRect: (() -> CGRect?)? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatLocation: ChatLocation, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery, fromMenu: Bool = false, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatLocation: ChatLocation, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery, fromMenu: Bool = false, hasTextInput: Bool = true, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView? = { return nil}) { self.context = context self.updatedPresentationData = updatedPresentationData self.chatLocation = chatLocation self.buttons = buttons self.initialButton = initialButton self.fromMenu = fromMenu + self.hasTextInput = hasTextInput self.makeEntityInputView = makeEntityInputView super.init(navigationBarPresentationData: nil) @@ -886,6 +888,10 @@ public class AttachmentController: ViewController { return self.buttons.contains(.standalone) } + public func updateSelectionCount(_ count: Int) { + self.node.updateSelectionCount(count, animated: false) + } + private var node: Node { return self.displayNode as! Node } diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h index 15cebcd0c7..9ced702203 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h @@ -83,9 +83,6 @@ #import #import #import -#import -#import -#import #import #import #import diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGClipboardGalleryMixin.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGClipboardGalleryMixin.h deleted file mode 100644 index 3b712f0cf9..0000000000 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGClipboardGalleryMixin.h +++ /dev/null @@ -1,31 +0,0 @@ -#import -#import - -#import - -@class TGClipboardGalleryPhotoItem; - -@protocol TGPhotoPaintStickersContext; - -@interface TGClipboardGalleryMixin : NSObject - -@property (nonatomic, copy) void (^itemFocused)(TGClipboardGalleryPhotoItem *); - -@property (nonatomic, copy) void (^willTransitionIn)(); -@property (nonatomic, copy) void (^willTransitionOut)(); -@property (nonatomic, copy) void (^didTransitionOut)(); -@property (nonatomic, copy) UIView *(^referenceViewForItem)(TGClipboardGalleryPhotoItem *); - -@property (nonatomic, copy) void (^completeWithItem)(TGClipboardGalleryPhotoItem *item, bool silentPosting, int32_t scheduleTime); - -@property (nonatomic, copy) void (^editorOpened)(void); -@property (nonatomic, copy) void (^editorClosed)(void); - -@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t)); -@property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t)); - -- (instancetype)initWithContext:(id)context image:(UIImage *)image images:(NSArray *)images parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id)stickersContext hasCaptions:(bool)hasCaptions hasTimer:(bool)hasTimer hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder recipientName:(NSString *)recipientName; - -- (void)present; - -@end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGClipboardGalleryPhotoItem.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGClipboardGalleryPhotoItem.h deleted file mode 100644 index 97065641fd..0000000000 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGClipboardGalleryPhotoItem.h +++ /dev/null @@ -1,10 +0,0 @@ -#import -#import - -@interface TGClipboardGalleryPhotoItem : NSObject - -@property (nonatomic, strong) UIImage *image; - -- (instancetype)initWithImage:(UIImage *)image; - -@end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGClipboardMenu.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGClipboardMenu.h deleted file mode 100644 index d80b7bf955..0000000000 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGClipboardMenu.h +++ /dev/null @@ -1,20 +0,0 @@ -#import - -#import - -@class TGViewController; -@class TGMenuSheetController; - -@class TGMediaSelectionContext; -@class TGMediaEditingContext; - -@protocol TGMediaSelectableItem; -@protocol TGPhotoPaintStickersContext; - -@interface TGClipboardMenu : NSObject - -+ (TGMenuSheetController *)presentInParentController:(TGViewController *)parentController context:(id)context images:(NSArray *)images allowGrouping:(bool)allowGrouping hasCaption:(bool)hasCaption hasTimer:(bool)hasTimer hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder recipientName:(NSString *)recipientName stickersContext:(id)stickersContext presentScheduleController:(void (^)(void(^)(int32_t)))presentScheduleController presentTimerController:(void (^)(void(^)(int32_t)))presentTimerController completed:(void (^)(TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id currentItem, bool silentPosting, int32_t scheduleTime))completed dismissed:(void (^)(void))dismissed sourceView:(UIView *)sourceView sourceRect:(CGRect (^)(void))sourceRect; - -+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext currentItem:(id)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *))descriptionGenerator; - -@end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h index 5eac3189d4..f587ebab6f 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h @@ -109,4 +109,6 @@ typedef enum + (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext intent:(TGMediaAssetsControllerIntent)intent currentItem:(TGMediaAsset *)currentItem storeAssets:(bool)storeAssets convertToJpeg:(bool)convertToJpeg descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *, NSString *))descriptionGenerator saveEditedPhotos:(bool)saveEditedPhotos; ++ (NSArray *)pasteboardResultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext intent:(TGMediaAssetsControllerIntent)intent currentItem:(id)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *, NSString *))descriptionGenerator; + @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h index 8a19a1355f..8d2175553f 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h @@ -14,6 +14,7 @@ @class TGMediaSelectionContext; @protocol TGMediaSelectableItem; +@protocol TGModernGalleryEditableItem; @protocol TGPhotoPaintStickersContext; diff --git a/submodules/LegacyComponents/Sources/TGClipboardGalleryMixin.m b/submodules/LegacyComponents/Sources/TGClipboardGalleryMixin.m deleted file mode 100644 index 75c0c744eb..0000000000 --- a/submodules/LegacyComponents/Sources/TGClipboardGalleryMixin.m +++ /dev/null @@ -1,281 +0,0 @@ -#import "TGClipboardGalleryMixin.h" - -#import - -#import "LegacyComponentsInternal.h" - -#import -#import "TGClipboardGalleryPhotoItem.h" -#import "TGClipboardGalleryModel.h" - -#import -#import - -#import -#import -#import -#import - -#import "TGMediaPickerSendActionSheetController.h" - -@interface TGClipboardGalleryMixin () -{ - TGMediaEditingContext *_editingContext; - bool _asFile; - - __weak TGViewController *_parentController; - __weak TGModernGalleryController *_galleryController; - TGModernGalleryController *_strongGalleryController; - - NSUInteger _itemsLimit; - - id _context; -} - -@property (nonatomic, weak, readonly) TGClipboardGalleryModel *galleryModel; - -@end - -@implementation TGClipboardGalleryMixin - -- (instancetype)initWithContext:(id)context image:(UIImage *)image images:(NSArray *)images parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id)stickersContext hasCaptions:(bool)hasCaptions hasTimer:(bool)hasTimer hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder recipientName:(NSString *)recipientName -{ - self = [super init]; - if (self != nil) - { - _context = context; - _parentController = parentController; - _editingContext = editingContext; - - __weak TGClipboardGalleryMixin *weakSelf = self; - - TGModernGalleryController *modernGallery = [[TGModernGalleryController alloc] initWithContext:_context]; - _galleryController = modernGallery; - _strongGalleryController = modernGallery; - modernGallery.isImportant = true; - - __block NSUInteger focusIndex = 0; - [images enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * _Nonnull stop) - { - if (obj == image) - { - focusIndex = idx; - *stop = true; - } - }]; - - TGClipboardGalleryModel *model = [[TGClipboardGalleryModel alloc] initWithContext:_context images:images focusIndex:focusIndex selectionContext:selectionContext editingContext:editingContext stickersContext:stickersContext hasCaptions:hasCaptions hasTimer:hasTimer hasSelectionPanel:false recipientName:recipientName]; - _galleryModel = model; - model.controller = modernGallery; - model.willFinishEditingItem = ^(id editableItem, id adjustments, id representation, bool hasChanges) - { - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (hasChanges) - { - [editingContext setAdjustments:adjustments forItem:editableItem]; - [editingContext setTemporaryRep:representation forItem:editableItem]; - } - - if (selectionContext != nil && adjustments != nil && [editableItem conformsToProtocol:@protocol(TGMediaSelectableItem)]) - [selectionContext setItem:(id)editableItem selected:true]; - }; - - model.didFinishEditingItem = ^(id editableItem, __unused id adjustments, UIImage *resultImage, UIImage *thumbnailImage) - { - [editingContext setImage:resultImage thumbnailImage:thumbnailImage forItem:editableItem synchronous:false]; - }; - - model.didFinishRenderingFullSizeImage = ^(id editableItem, UIImage *resultImage) - { - [editingContext setFullSizeImage:resultImage forItem:editableItem]; - }; - - model.saveItemCaption = ^(id editableItem, NSAttributedString *caption) - { - [editingContext setCaption:caption forItem:editableItem]; - - if (selectionContext != nil && caption.length > 0 && [editableItem conformsToProtocol:@protocol(TGMediaSelectableItem)]) - [selectionContext setItem:(id)editableItem selected:true]; - }; - - [model.interfaceView updateSelectionInterface:selectionContext.count counterVisible:(selectionContext.count > 0) animated:false]; - model.interfaceView.donePressed = ^(id item) - { - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_galleryModel.dismiss(true, false); - - if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem((TGClipboardGalleryPhotoItem *)item, false, 0); - }; - - model.interfaceView.doneLongPressed = ^(id item) { - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil || !(hasSilentPosting || hasSchedule)) - return; - - if (iosMajorVersion() >= 10) { - UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium]; - [generator impactOccurred]; - } - - bool effectiveHasSchedule = hasSchedule; - for (id item in strongSelf->_galleryModel.selectionContext.selectedItems) - { - if ([item isKindOfClass:[TGMediaAsset class]]) - { - if ([[strongSelf->_editingContext timerForItem:item] integerValue] > 0) - { - effectiveHasSchedule = false; - break; - } - } - } - - TGMediaPickerSendActionSheetController *controller = [[TGMediaPickerSendActionSheetController alloc] initWithContext:strongSelf->_context isDark:true sendButtonFrame:strongSelf.galleryModel.interfaceView.doneButtonFrame canSendSilently:hasSilentPosting canSchedule:effectiveHasSchedule reminder:reminder hasTimer:hasTimer]; - controller.send = ^{ - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_galleryModel.dismiss(true, false); - - if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem((TGClipboardGalleryPhotoItem *)item, false, 0); - }; - controller.sendSilently = ^{ - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_galleryModel.dismiss(true, false); - - if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem((TGClipboardGalleryPhotoItem *)item, true, 0); - }; - controller.schedule = ^{ - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf.presentScheduleController(^(int32_t time) { - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_galleryModel.dismiss(true, false); - - if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem((TGClipboardGalleryPhotoItem *)item, false, time); - }); - }; - controller.sendWithTimer = ^{ - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf.presentTimerController(^(int32_t time) { - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_galleryModel.dismiss(true, false); - - TGMediaEditingContext *editingContext = strongSelf->_editingContext; - NSMutableArray *items = [strongSelf->_galleryModel.selectionContext.selectedItems mutableCopy]; - [items addObject:((TGClipboardGalleryPhotoItem *)item).image]; - - for (id editableItem in items) { - [editingContext setTimer:@(time) forItem:editableItem]; - } - - if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem((TGClipboardGalleryPhotoItem *)item, false, 0); - }); - }; - - TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:[strongSelf->_context makeOverlayWindowManager] parentController:strongSelf->_parentController contentController:controller]; - controllerWindow.hidden = false; - }; - - modernGallery.model = model; - modernGallery.itemFocused = ^(id item) - { - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf != nil && strongSelf.itemFocused != nil) - strongSelf.itemFocused((TGClipboardGalleryPhotoItem *)item); - }; - - modernGallery.beginTransitionIn = ^UIView *(id item, TGModernGalleryItemView *itemView) - { - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return nil; - - if (strongSelf.willTransitionIn != nil) - strongSelf.willTransitionIn(); - - if (strongSelf.referenceViewForItem != nil) - return strongSelf.referenceViewForItem((TGClipboardGalleryPhotoItem *)item); - - return nil; - }; - - modernGallery.finishedTransitionIn = ^(__unused id item, __unused TGModernGalleryItemView *itemView) - { - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf->_galleryModel.interfaceView setSelectedItemsModel:strongSelf->_galleryModel.selectedItemsModel]; - }; - - modernGallery.beginTransitionOut = ^UIView *(id item, TGModernGalleryItemView *itemView) - { - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf != nil) - { - if (strongSelf.willTransitionOut != nil) - strongSelf.willTransitionOut(); - - if (strongSelf.referenceViewForItem != nil) - return strongSelf.referenceViewForItem((TGClipboardGalleryPhotoItem *)item); - } - return nil; - }; - - modernGallery.completedTransitionOut = ^ - { - __strong TGClipboardGalleryMixin *strongSelf = weakSelf; - if (strongSelf != nil && strongSelf.didTransitionOut != nil) - strongSelf.didTransitionOut(); - }; - } - return self; -} - -- (void)present -{ - _galleryModel.editorOpened = self.editorOpened; - _galleryModel.editorClosed = self.editorClosed; - - [_galleryController setPreviewMode:false]; - - TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:[_context makeOverlayWindowManager] parentController:_parentController contentController:_galleryController]; - controllerWindow.hidden = false; - _galleryController.view.clipsToBounds = true; - - _strongGalleryController = nil; -} - -- (UIViewController *)galleryController -{ - return _galleryController; -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGClipboardGalleryModel.h b/submodules/LegacyComponents/Sources/TGClipboardGalleryModel.h deleted file mode 100644 index c4af3c90d7..0000000000 --- a/submodules/LegacyComponents/Sources/TGClipboardGalleryModel.h +++ /dev/null @@ -1,31 +0,0 @@ -#import - -#import -#import - -#import - -#import - -@interface TGClipboardGalleryModel : TGModernGalleryModel - -@property (nonatomic, copy) void (^willFinishEditingItem)(id item, id adjustments, id temporaryRep, bool hasChanges); -@property (nonatomic, copy) void (^didFinishEditingItem)(iditem, id adjustments, UIImage *resultImage, UIImage *thumbnailImage); -@property (nonatomic, copy) void (^didFinishRenderingFullSizeImage)(id item, UIImage *fullSizeImage); - -@property (nonatomic, copy) void (^saveItemCaption)(id item, NSAttributedString *caption); - -@property (nonatomic, copy) void (^editorOpened)(void); -@property (nonatomic, copy) void (^editorClosed)(void); - -@property (nonatomic, weak) TGModernGalleryController *controller; - -@property (nonatomic, readonly, strong) TGMediaPickerGalleryInterfaceView *interfaceView; -@property (nonatomic, readonly, strong) TGMediaPickerGallerySelectedItemsModel *selectedItemsModel; - -@property (nonatomic, readonly) TGMediaSelectionContext *selectionContext; -@property (nonatomic, strong) id stickersContext; - -- (instancetype)initWithContext:(id)context images:(NSArray *)images focusIndex:(NSUInteger)focusIndex selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id)stickersContext hasCaptions:(bool)hasCaptions hasTimer:(bool)hasTimer hasSelectionPanel:(bool)hasSelectionPanel recipientName:(NSString *)recipientName; - -@end diff --git a/submodules/LegacyComponents/Sources/TGClipboardGalleryModel.m b/submodules/LegacyComponents/Sources/TGClipboardGalleryModel.m deleted file mode 100644 index d6140737e4..0000000000 --- a/submodules/LegacyComponents/Sources/TGClipboardGalleryModel.m +++ /dev/null @@ -1,500 +0,0 @@ -#import "TGClipboardGalleryModel.h" - -#import "TGMediaPickerGallerySelectedItemsModel.h" - -#import "LegacyComponentsInternal.h" - -#import -#import -#import "TGModernGallerySelectableItem.h" -#import "TGModernGalleryEditableItem.h" -#import "TGModernGalleryEditableItemView.h" -#import - -#import "TGClipboardGalleryPhotoItem.h" - -#import "TGModernMediaListItem.h" -#import "TGModernMediaListSelectableItem.h" - -#import - -#import - -@interface TGClipboardGalleryModel () -{ - id _itemBeingEdited; - TGMediaEditingContext *_editingContext; - - id _context; -} - -@property (nonatomic, weak) TGPhotoEditorController *editorController; - -@end - -@implementation TGClipboardGalleryModel - -- (instancetype)initWithContext:(id)context images:(NSArray *)images focusIndex:(NSUInteger)focusIndex selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id)stickersContext hasCaptions:(bool)hasCaptions hasTimer:(bool)hasTimer hasSelectionPanel:(bool)hasSelectionPanel recipientName:(NSString *)recipientName -{ - self = [super init]; - if (self != nil) - { - _context = context; - - NSMutableArray *items = [[NSMutableArray alloc] init]; - TGClipboardGalleryPhotoItem *focusItem = nil; - NSUInteger i = 0; - for (UIImage *image in images) - { - TGClipboardGalleryPhotoItem *item = [[TGClipboardGalleryPhotoItem alloc] initWithImage:image]; - item.selectionContext = selectionContext; - item.editingContext = editingContext; - item.stickersContext = stickersContext; - [items addObject:item]; - - if (i == focusIndex) - focusItem = item; - - i++; - } - - [self _replaceItems:items focusingOnItem:focusItem]; - - _editingContext = editingContext; - _selectionContext = selectionContext; - _stickersContext = stickersContext; - - __weak TGClipboardGalleryModel *weakSelf = self; - if (selectionContext != nil) - { - _selectedItemsModel = [[TGMediaPickerGallerySelectedItemsModel alloc] initWithSelectionContext:selectionContext]; - _selectedItemsModel.selectionUpdated = ^(bool reload, bool incremental, bool add, NSInteger index) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf.interfaceView updateSelectionInterface:[strongSelf selectionCount] counterVisible:([strongSelf selectionCount] > 0) animated:incremental]; - [strongSelf.interfaceView updateSelectedPhotosView:reload incremental:incremental add:add index:index]; - }; - } - - _interfaceView = [[TGMediaPickerGalleryInterfaceView alloc] initWithContext:_context focusItem:focusItem selectionContext:selectionContext editingContext:editingContext stickersContext:stickersContext hasSelectionPanel:hasSelectionPanel hasCameraButton:false recipientName:recipientName]; - _interfaceView.hasCaptions = hasCaptions; - _interfaceView.hasTimer = hasTimer; - [_interfaceView setEditorTabPressed:^(TGPhotoEditorTab tab) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - __strong TGModernGalleryController *controller = strongSelf.controller; - if ([controller.currentItem conformsToProtocol:@protocol(TGModernGalleryEditableItem)]) - [strongSelf presentPhotoEditorForItem:(id)controller.currentItem tab:tab]; - }]; - _interfaceView.photoStripItemSelected = ^(NSInteger index) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf setCurrentItemWithIndex:index]; - }; - _interfaceView.captionSet = ^(id item, NSAttributedString *caption) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil || strongSelf.saveItemCaption == nil) - return; - - __strong TGModernGalleryController *controller = strongSelf.controller; - if ([controller.currentItem conformsToProtocol:@protocol(TGModernGalleryEditableItem)]) - strongSelf.saveItemCaption(((id)item).editableMediaItem, caption); - }; - _interfaceView.timerRequested = ^ - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - __strong TGModernGalleryController *controller = strongSelf.controller; - id editableMediaItem = ((id)controller.currentItem).editableMediaItem; - - NSString *description = editableMediaItem.isVideo ? TGLocalized(@"SecretTimer.VideoDescription") : TGLocalized(@"SecretTimer.ImageDescription"); - - NSString *lastValueKey = @"mediaPickerLastTimerValue_v0"; - NSNumber *value = [strongSelf->_editingContext timerForItem:editableMediaItem]; - if (value == nil) - value = [[NSUserDefaults standardUserDefaults] objectForKey:lastValueKey]; - - [TGSecretTimerMenu presentInParentController:controller context:strongSelf->_context dark:true description:description values:[TGSecretTimerMenu secretMediaTimerValues] value:value completed:^(NSNumber *value) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (value == nil) - [[NSUserDefaults standardUserDefaults] removeObjectForKey:lastValueKey]; - else - [[NSUserDefaults standardUserDefaults] setObject:value forKey:lastValueKey]; - - [strongSelf->_editingContext setTimer:value forItem:editableMediaItem]; - - if (value.integerValue != 0) - { - __strong TGModernGalleryController *controller = strongSelf.controller; - id selectableItem = nil; - if ([controller.currentItem conformsToProtocol:@protocol(TGModernGallerySelectableItem)]) - { - selectableItem = ((id)controller.currentItem).selectableMediaItem; - - if (selectableItem != nil) { - [strongSelf->_selectionContext setItem:selectableItem selected:true animated:false sender:nil]; - } - } - } - } dismissed:^ - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf->_interfaceView setAllInterfaceHidden:false delay:0.0f animated:true]; - } sourceView:controller.view sourceRect:^CGRect - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return CGRectZero; - - __strong TGModernGalleryController *controller = strongSelf.controller; - return [strongSelf->_interfaceView.timerButton convertRect:strongSelf->_interfaceView.timerButton.bounds toView:controller.view]; - }]; - }; - } - return self; -} - -- (NSInteger)selectionCount -{ - return _selectedItemsModel.selectedCount; -} - -- (void)setCurrentItem:(id)item direction:(TGModernGalleryScrollAnimationDirection)direction -{ - if (![(id)item conformsToProtocol:@protocol(TGMediaSelectableItem)]) - return; - - id targetSelectableItem = (id)item; - - __block NSUInteger newIndex = NSNotFound; - [self.items enumerateObjectsUsingBlock:^(id galleryItem, NSUInteger idx, BOOL *stop) - { - if ([galleryItem conformsToProtocol:@protocol(TGModernGallerySelectableItem)]) - { - id selectableItem = ((id)galleryItem).selectableMediaItem; - - if ([selectableItem.uniqueIdentifier isEqual:targetSelectableItem.uniqueIdentifier]) - { - newIndex = idx; - *stop = true; - } - } - }]; - - TGModernGalleryController *galleryController = self.controller; - [galleryController setCurrentItemIndex:newIndex direction:direction animated:true]; -} - -- (void)setCurrentItemWithIndex:(NSUInteger)index -{ - if (_selectedItemsModel == nil) - return; - - TGModernGalleryController *galleryController = self.controller; - - if (![galleryController.currentItem conformsToProtocol:@protocol(TGModernGallerySelectableItem)]) - return; - - id currentGalleryItem = (id)galleryController.currentItem; - - __block NSUInteger currentSelectedItemIndex = NSNotFound; - [_selectedItemsModel.items enumerateObjectsUsingBlock:^(id item, NSUInteger index, BOOL *stop) - { - if ([item.uniqueIdentifier isEqualToString:currentGalleryItem.selectableMediaItem.uniqueIdentifier]) - { - currentSelectedItemIndex = index; - *stop = true; - } - }]; - - id item = _selectedItemsModel.items[index]; - - TGModernGalleryScrollAnimationDirection direction = TGModernGalleryScrollAnimationDirectionLeft; - if (currentSelectedItemIndex < index) - direction = TGModernGalleryScrollAnimationDirectionRight; - - [self setCurrentItem:item direction:direction]; -} - -- (UIView *)createInterfaceView -{ - return _interfaceView; -} - -- (UIView *)referenceViewForItem:(id)item frame:(CGRect *)frame -{ - TGModernGalleryController *galleryController = self.controller; - TGModernGalleryItemView *galleryItemView = [galleryController itemViewForItem:item]; - - if ([galleryItemView isKindOfClass:[TGModernGalleryZoomableItemView class]]) - { - TGModernGalleryZoomableItemView *zoomableItemView = (TGModernGalleryZoomableItemView *)galleryItemView; - - if (zoomableItemView.contentView != nil) - { - if (frame != NULL) - *frame = [zoomableItemView transitionViewContentRect]; - - return (UIImageView *)zoomableItemView.transitionContentView; - } - } - - return nil; -} - -- (void)updateHiddenItem -{ - TGModernGalleryController *galleryController = self.controller; - - for (TGModernGalleryItemView *itemView in galleryController.visibleItemViews) - { - if ([itemView conformsToProtocol:@protocol(TGModernGalleryEditableItemView)]) - [(TGModernGalleryItemView *)itemView setHiddenAsBeingEdited:[itemView.item isEqual:_itemBeingEdited]]; - } -} - -- (void)updateEditedItemView -{ - TGModernGalleryController *galleryController = self.controller; - - for (TGModernGalleryItemView *itemView in galleryController.visibleItemViews) - { - if ([itemView conformsToProtocol:@protocol(TGModernGalleryEditableItemView)]) - { - if ([itemView.item isEqual:_itemBeingEdited]) - { - [(TGModernGalleryItemView *)itemView setItem:_itemBeingEdited synchronously:true]; - if (self.itemsUpdated != nil) - self.itemsUpdated(_itemBeingEdited); - } - } - } -} - -- (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab -{ - __weak TGClipboardGalleryModel *weakSelf = self; - - if (_itemBeingEdited != nil) - return; - - _itemBeingEdited = item; - - PGPhotoEditorValues *editorValues = (PGPhotoEditorValues *)[item.editingContext adjustmentsForItem:item.editableMediaItem]; - - NSAttributedString *caption = [item.editingContext captionForItem:item.editableMediaItem]; - - CGRect refFrame = CGRectZero; - UIView *editorReferenceView = [self referenceViewForItem:item frame:&refFrame]; - UIView *referenceView = nil; - UIImage *screenImage = nil; - UIView *referenceParentView = nil; - UIImage *image = nil; - - bool isVideo = false; - if ([editorReferenceView isKindOfClass:[UIImageView class]]) - { - screenImage = [(UIImageView *)editorReferenceView image]; - referenceView = editorReferenceView; - } - - TGPhotoEditorControllerIntent intent = isVideo ? TGPhotoEditorControllerVideoIntent : TGPhotoEditorControllerGenericIntent; - TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:_context item:item.editableMediaItem intent:intent adjustments:editorValues caption:caption screenImage:screenImage availableTabs:_interfaceView.currentTabs selectedTab:tab]; - controller.editingContext = _editingContext; - controller.stickersContext = _stickersContext; - self.editorController = controller; - controller.willFinishEditing = ^(id adjustments, id temporaryRep, bool hasChanges) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_itemBeingEdited = nil; - - if (strongSelf.willFinishEditingItem != nil) - strongSelf.willFinishEditingItem(item.editableMediaItem, adjustments, temporaryRep, hasChanges); - }; - - void (^didFinishEditingItem)(iditem, id adjustments, UIImage *resultImage, UIImage *thumbnailImage) = self.didFinishEditingItem; - controller.didFinishEditing = ^(id adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) { - TGLegacyLog(@"controller.didFinishEditing strongSelf == nil"); - } - -#ifdef DEBUG - if (adjustments != nil && hasChanges && !isVideo) - NSAssert(resultImage != nil, @"resultImage should not be nil"); -#endif - - if (hasChanges) - { - if (didFinishEditingItem != nil) { - didFinishEditingItem(item.editableMediaItem, adjustments, resultImage, thumbnailImage); - } - } - }; - - controller.didFinishRenderingFullSizeImage = ^(UIImage *image) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (strongSelf.didFinishRenderingFullSizeImage != nil) - strongSelf.didFinishRenderingFullSizeImage(item.editableMediaItem, image); - }; - - controller.captionSet = ^(NSAttributedString *caption) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (strongSelf.saveItemCaption != nil) - strongSelf.saveItemCaption(item.editableMediaItem, caption); - }; - - controller.requestToolbarsHidden = ^(bool hidden, bool animated) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf.interfaceView setToolbarsHidden:hidden animated:animated]; - }; - - controller.beginTransitionIn = ^UIView *(CGRect *referenceFrame, __unused UIView **parentView) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return nil; - - if (strongSelf.editorOpened != nil) - strongSelf.editorOpened(); - - [strongSelf updateHiddenItem]; - [strongSelf.interfaceView editorTransitionIn]; - - *referenceFrame = refFrame; - - if (referenceView.superview == nil) - *parentView = referenceParentView; - - if (iosMajorVersion() >= 7) - [strongSelf.controller setNeedsStatusBarAppearanceUpdate]; - else - [_context setStatusBarHidden:true withAnimation:UIStatusBarAnimationNone]; - - return referenceView; - }; - - controller.finishedTransitionIn = ^ - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - TGModernGalleryController *galleryController = strongSelf.controller; - TGModernGalleryItemView *galleryItemView = [galleryController itemViewForItem:strongSelf->_itemBeingEdited]; - if (![galleryItemView isKindOfClass:[TGModernGalleryZoomableItemView class]]) - return; - - TGModernGalleryZoomableItemView *zoomableItemView = (TGModernGalleryZoomableItemView *)galleryItemView; - [zoomableItemView reset]; - }; - - controller.beginTransitionOut = ^UIView *(CGRect *referenceFrame, __unused UIView **parentView) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return nil; - - [strongSelf.interfaceView editorTransitionOut]; - - CGRect refFrame; - UIView *referenceView = [strongSelf referenceViewForItem:item frame:&refFrame]; - - *referenceFrame = refFrame; - - return referenceView; - }; - - controller.finishedTransitionOut = ^(__unused bool saved) - { - __strong TGClipboardGalleryModel *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (strongSelf.editorClosed != nil) - strongSelf.editorClosed(); - - [strongSelf updateHiddenItem]; - - if (iosMajorVersion() >= 7) - [strongSelf.controller setNeedsStatusBarAppearanceUpdate]; - else { - [_context setStatusBarHidden:false withAnimation:UIStatusBarAnimationNone]; - } - }; - - controller.requestThumbnailImage = ^SSignal *(id editableItem) - { - return [editableItem thumbnailImageSignal]; - }; - - controller.requestOriginalScreenSizeImage = ^SSignal *(id editableItem, NSTimeInterval position) - { - return [editableItem screenImageSignal:position]; - }; - - controller.requestOriginalFullSizeImage = ^SSignal *(id editableItem, NSTimeInterval position) - { - return [editableItem originalImageSignal:position]; - }; - - controller.requestImage = ^ - { - return image; - }; - - [self.controller addChildViewController:controller]; - [self.controller.view addSubview:controller.view]; -} - -- (void)_replaceItems:(NSArray *)items focusingOnItem:(id)item -{ - [super _replaceItems:items focusingOnItem:item]; - - TGModernGalleryController *controller = self.controller; - - NSArray *itemViews = [controller.visibleItemViews copy]; - for (TGModernGalleryItemView *itemView in itemViews) - [itemView setItem:itemView.item synchronously:false]; -} - -- (bool)_shouldAutorotate -{ - TGPhotoEditorController *editorController = self.editorController; - return (!editorController || [editorController shouldAutorotate]); -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGClipboardGalleryPhotoItem.m b/submodules/LegacyComponents/Sources/TGClipboardGalleryPhotoItem.m deleted file mode 100644 index 4a711e3464..0000000000 --- a/submodules/LegacyComponents/Sources/TGClipboardGalleryPhotoItem.m +++ /dev/null @@ -1,56 +0,0 @@ -#import "TGClipboardGalleryPhotoItem.h" -#import "TGClipboardGalleryPhotoItemView.h" - -#import "LegacyComponentsInternal.h" - -#import "UIImage+TGMediaEditableItem.h" - -@implementation TGClipboardGalleryPhotoItem - -@synthesize selectionContext; -@synthesize editingContext; -@synthesize stickersContext; - -- (instancetype)initWithImage:(UIImage *)image -{ - self = [super init]; - if (self != nil) - { - _image = image; - } - return self; -} - -- (NSString *)uniqueId -{ - return self.image.uniqueIdentifier; -} - -- (id)selectableMediaItem -{ - return self.image; -} - -- (id)editableMediaItem -{ - return self.image; -} - -- (TGPhotoEditorTab)toolbarTabs -{ - return TGPhotoEditorCropTab | TGPhotoEditorToolsTab | TGPhotoEditorPaintTab; -} - - -- (Class)viewClass -{ - return [TGClipboardGalleryPhotoItemView class]; -} - -- (BOOL)isEqual:(id)object -{ - return [object isKindOfClass:[TGClipboardGalleryPhotoItem class]] && TGObjectCompare(_image, ((TGClipboardGalleryPhotoItem *)object)->_image); -} - - -@end diff --git a/submodules/LegacyComponents/Sources/TGClipboardGalleryPhotoItemView.h b/submodules/LegacyComponents/Sources/TGClipboardGalleryPhotoItemView.h deleted file mode 100644 index 9646d8f683..0000000000 --- a/submodules/LegacyComponents/Sources/TGClipboardGalleryPhotoItemView.h +++ /dev/null @@ -1,11 +0,0 @@ -#import -#import "TGModernGalleryEditableItemView.h" -#import "TGModernGalleryImageItemImageView.h" - -@interface TGClipboardGalleryPhotoItemView : TGModernGalleryZoomableItemView - -@property (nonatomic) CGSize imageSize; - -@property (nonatomic, strong) TGModernGalleryImageItemImageView *imageView; - -@end diff --git a/submodules/LegacyComponents/Sources/TGClipboardGalleryPhotoItemView.m b/submodules/LegacyComponents/Sources/TGClipboardGalleryPhotoItemView.m deleted file mode 100644 index 8f24b1348e..0000000000 --- a/submodules/LegacyComponents/Sources/TGClipboardGalleryPhotoItemView.m +++ /dev/null @@ -1,208 +0,0 @@ -#import "TGClipboardGalleryPhotoItemView.h" - -#import "LegacyComponentsInternal.h" -#import "TGFont.h" -#import "TGStringUtils.h" - -#import - -#import - -#import -#import -#import - -#import - -#import "TGClipboardGalleryPhotoItem.h" - -@interface TGClipboardGalleryPhotoItemView () -{ - UIView *_temporaryRepView; - - SMetaDisposable *_attributesDisposable; -} -@end - -@implementation TGClipboardGalleryPhotoItemView - -- (instancetype)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self != nil) - { - _imageView = [[TGModernGalleryImageItemImageView alloc] init]; - [self.scrollView addSubview:_imageView]; - } - return self; -} - -- (void)dealloc -{ - [_attributesDisposable dispose]; -} - -- (void)setHiddenAsBeingEdited:(bool)hidden -{ - self.imageView.hidden = hidden; - _temporaryRepView.hidden = hidden; -} - -- (void)prepareForRecycle -{ - _imageView.hidden = false; - [_imageView reset]; -} - -- (void)setItem:(TGClipboardGalleryPhotoItem *)item synchronously:(bool)synchronously -{ - [super setItem:item synchronously:synchronously]; - - _imageSize = item.image.size; - [self reset]; - - if (item.image == nil) - { - [self.imageView reset]; - } - else - { - __weak TGClipboardGalleryPhotoItemView *weakSelf = self; - void (^fadeOutRepView)(void) = ^ - { - __strong TGClipboardGalleryPhotoItemView *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (strongSelf->_temporaryRepView == nil) - return; - - UIView *repView = strongSelf->_temporaryRepView; - strongSelf->_temporaryRepView = nil; - [UIView animateWithDuration:0.2f animations:^ - { - repView.alpha = 0.0f; - } completion:^(__unused BOOL finished) - { - [repView removeFromSuperview]; - }]; - }; - - SSignal *assetSignal = [SSignal single:item.image]; - - SSignal *imageSignal = assetSignal; - if (item.editingContext != nil) - { - imageSignal = [[[item.editingContext imageSignalForItem:item.editableMediaItem] deliverOn:[SQueue mainQueue]] mapToSignal:^SSignal *(id result) - { - __strong TGClipboardGalleryPhotoItemView *strongSelf = weakSelf; - if (strongSelf == nil) - return [SSignal complete]; - - if (result == nil) - { - return [[assetSignal deliverOn:[SQueue mainQueue]] afterNext:^(__unused id next) - { - fadeOutRepView(); - }]; - } - else if ([result isKindOfClass:[UIView class]]) - { - [strongSelf _setTemporaryRepView:result]; - return [[SSignal single:nil] deliverOn:[SQueue mainQueue]]; - } - else - { - return [[[SSignal single:result] deliverOn:[SQueue mainQueue]] afterNext:^(__unused id next) - { - fadeOutRepView(); - }]; - } - }]; - } - - - [self.imageView setSignal:[[imageSignal deliverOn:[SQueue mainQueue]] afterNext:^(id next) - { - __strong TGClipboardGalleryPhotoItemView *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if ([next isKindOfClass:[UIImage class]]) - strongSelf->_imageSize = ((UIImage *)next).size; - - [strongSelf reset]; - }]]; - } -} - -- (void)_setTemporaryRepView:(UIView *)view -{ - [_temporaryRepView removeFromSuperview]; - _temporaryRepView = view; - - _imageSize = TGScaleToSize(view.frame.size, self.containerView.frame.size); - - view.hidden = self.imageView.hidden; - view.frame = CGRectMake((self.containerView.frame.size.width - _imageSize.width) / 2.0f, (self.containerView.frame.size.height - _imageSize.height) / 2.0f, _imageSize.width, _imageSize.height); - - [self.containerView addSubview:view]; -} - -- (void)singleTap -{ - if ([self.item conformsToProtocol:@protocol(TGModernGallerySelectableItem)]) - { - TGMediaSelectionContext *selectionContext = ((id)self.item).selectionContext; - id item = ((id)self.item).selectableMediaItem; - - [selectionContext toggleItemSelection:item animated:true sender:nil success:nil]; - } - else - { - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(itemViewDidRequestInterfaceShowHide:)]) - [delegate itemViewDidRequestInterfaceShowHide:self]; - } -} - -- (UIView *)footerView -{ - return nil; -} - -- (SSignal *)contentAvailabilityStateSignal -{ - return [SSignal single:@true]; -} - -- (CGSize)contentSize -{ - return _imageSize; -} - -- (UIView *)contentView -{ - return _imageView; -} - -- (UIView *)transitionContentView -{ - if (_temporaryRepView != nil) - return _temporaryRepView; - - return [self contentView]; -} - -- (UIView *)transitionView -{ - return self.containerView; -} - -- (CGRect)transitionViewContentRect -{ - UIView *contentView = [self transitionContentView]; - return [contentView convertRect:contentView.bounds toView:[self transitionView]]; -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGClipboardMenu.m b/submodules/LegacyComponents/Sources/TGClipboardMenu.m deleted file mode 100644 index c84ae2b79f..0000000000 --- a/submodules/LegacyComponents/Sources/TGClipboardMenu.m +++ /dev/null @@ -1,226 +0,0 @@ -#import "TGClipboardMenu.h" - -#import "LegacyComponentsInternal.h" - -#import -#import - -#import "TGClipboardPreviewItemView.h" - -@implementation TGClipboardMenu - -+ (TGMenuSheetController *)presentInParentController:(TGViewController *)parentController context:(id)context images:(NSArray *)images allowGrouping:(bool)allowGrouping hasCaption:(bool)hasCaption hasTimer:(bool)hasTimer hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder recipientName:(NSString *)recipientName stickersContext:(id)stickersContext presentScheduleController:(void (^)(void(^)(int32_t)))presentScheduleController presentTimerController:(void (^)(void(^)(int32_t)))presentTimerController completed:(void (^)(TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id currentItem, bool silentPosting, int32_t scheduleTime))completed dismissed:(void (^)(void))dismissed sourceView:(UIView *)sourceView sourceRect:(CGRect (^)(void))sourceRect -{ - bool centered = false; - if (sourceRect == nil) - { - centered = true; - sourceRect = ^CGRect - { - return CGRectMake(CGRectGetMidX(sourceView.frame), CGRectGetMidY(sourceView.frame), 0, 0); - }; - } - - TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:context dark:false]; - __weak TGMenuSheetController *weakController = controller; - controller.dismissesByOutsideTap = true; - controller.forceFullScreen = true; - controller.hasSwipeGesture = true; - controller.narrowInLandscape = true; - controller.sourceRect = sourceRect; - controller.permittedArrowDirections = centered ? 0 : (UIPopoverArrowDirectionUp | UIPopoverArrowDirectionDown); - controller.willDismiss = ^(__unused bool manual) - { - }; - controller.didDismiss = ^(__unused bool manual) { - if (dismissed != nil) - dismissed(); - }; - - NSMutableArray *itemViews = [[NSMutableArray alloc] init]; - - TGClipboardPreviewItemView *previewItem = [[TGClipboardPreviewItemView alloc] initWithContext:context images:images allowGrouping:allowGrouping]; - __weak TGClipboardPreviewItemView *weakPreviewItem = previewItem; - previewItem.stickersContext = stickersContext; - previewItem.parentController = parentController; - previewItem.allowCaptions = hasCaption; - previewItem.hasTimer = hasTimer; - previewItem.hasSilentPosting = hasSilentPosting; - previewItem.hasSchedule = hasSchedule; - previewItem.reminder = reminder; - previewItem.recipientName = recipientName; - previewItem.presentScheduleController = presentScheduleController; - previewItem.presentTimerController = presentTimerController; - previewItem.sendPressed = ^(UIImage *currentItem, bool silentPosting, int32_t scheduleTime) - { - __strong TGClipboardPreviewItemView *strongPreviewItem = weakPreviewItem; - completed(strongPreviewItem.selectionContext, strongPreviewItem.editingContext, currentItem, silentPosting, scheduleTime); - - __strong TGMenuSheetController *strongController = weakController; - [strongController dismissAnimated:true]; - }; - [itemViews addObject:previewItem]; - - NSString *sendTitle = TGLocalized(@"Clipboard.SendPhoto"); - NSUInteger photosCount = images.count; - if (photosCount > 1) - { - NSString *format = TGLocalized([TGStringUtils integerValueFormat:@"AttachmentMenu.SendPhoto_" value:photosCount]); - sendTitle = [NSString stringWithFormat:format, [NSString stringWithFormat:@"%ld", photosCount]]; - } - - TGMenuSheetButtonItemView *sendItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:sendTitle type:TGMenuSheetButtonTypeSend fontSize:20.0 action:^ - { - __strong TGClipboardPreviewItemView *strongPreviewItem = weakPreviewItem; - completed(strongPreviewItem.selectionContext, strongPreviewItem.editingContext, nil, false, 0); - - __strong TGMenuSheetController *strongController = weakController; - [strongController dismissAnimated:true]; - }]; - [itemViews addObject:sendItem]; - - __weak TGMenuSheetButtonItemView *weakSendItem = sendItem; - previewItem.selectionChanged = ^(NSUInteger count) - { - __strong TGMenuSheetButtonItemView *strongSendItem = weakSendItem; - __strong TGClipboardPreviewItemView *strongPreviewItem = weakPreviewItem; - if (count > 0) - { - NSString *format = TGLocalized([TGStringUtils integerValueFormat:@"AttachmentMenu.SendPhoto_" value:count]); - NSString *sendTitle = [NSString stringWithFormat:format, [NSString stringWithFormat:@"%ld", count]]; - [strongSendItem setTitle:sendTitle]; - } - - strongSendItem.userInteractionEnabled = count > 0; - [strongPreviewItem setCollapsed:count == 0 animated:true]; - }; - - TGMenuSheetButtonItemView *cancelItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^ - { - __strong TGMenuSheetController *strongController = weakController; - [strongController dismissAnimated:true]; - }]; - [itemViews addObject:cancelItem]; - - [controller setItemViews:itemViews animated:false]; - - return controller; - -} - -+ (int64_t)generateGroupedId -{ - int64_t value; - arc4random_buf(&value, sizeof(int64_t)); - return value; -} - -+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext currentItem:(id)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *))descriptionGenerator -{ - NSMutableArray *signals = [[NSMutableArray alloc] init]; - NSMutableArray *selectedItems = [selectionContext.selectedItems mutableCopy]; - if (selectedItems.count == 0 && currentItem != nil) - [selectedItems addObject:currentItem]; - - NSNumber *groupedId; - NSInteger i = 0; - bool grouping = selectionContext.grouping; - - bool hasAnyTimers = false; - if (editingContext != nil || grouping) - { - for (UIImage *asset in selectedItems) - { - if ([editingContext timerForItem:asset] != nil) { - hasAnyTimers = true; - } - } - } - - if (grouping && selectedItems.count > 1) - groupedId = @([self generateGroupedId]); - - for (UIImage *asset in selectedItems) - { - NSAttributedString *caption = [editingContext captionForItem:asset]; - id adjustments = [editingContext adjustmentsForItem:asset]; - NSNumber *timer = [editingContext timerForItem:asset]; - - SSignal *inlineSignal = [[SSignal single:asset] map:^id(UIImage *image) - { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - dict[@"type"] = @"editedPhoto"; - dict[@"image"] = image; - - if (timer != nil) - dict[@"timer"] = timer; - - if (groupedId != nil) - dict[@"groupedId"] = groupedId; - - id generatedItem = descriptionGenerator(dict, caption, nil); - return generatedItem; - }]; - - SSignal *assetSignal = inlineSignal; - SSignal *imageSignal = assetSignal; - 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]; - dict[@"type"] = @"editedPhoto"; - dict[@"image"] = image; - - if (adjustments.paintingData.stickers.count > 0) - dict[@"stickers"] = adjustments.paintingData.stickers; - - if (timer != nil) - dict[@"timer"] = timer; - - if (groupedId != nil) - dict[@"groupedId"] = groupedId; - - id generatedItem = descriptionGenerator(dict, caption, nil); - return generatedItem; - }] catch:^SSignal *(__unused id error) - { - return inlineSignal; - }]]; - - i++; - - if (groupedId != nil && i == 10) - { - i = 0; - groupedId = @([self generateGroupedId]); - } - } - return signals; -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGClipboardPreviewCell.h b/submodules/LegacyComponents/Sources/TGClipboardPreviewCell.h deleted file mode 100644 index d25f041b2b..0000000000 --- a/submodules/LegacyComponents/Sources/TGClipboardPreviewCell.h +++ /dev/null @@ -1,23 +0,0 @@ -#import -#import -#import - -@class TGMediaSelectionContext; -@class TGMediaEditingContext; - -@interface TGClipboardPreviewCell : UICollectionViewCell - -@property (nonatomic, readonly) UIImage *image; -@property (nonatomic, readonly) TGImageView *imageView; - -@property (nonatomic, strong) TGMediaSelectionContext *selectionContext; -@property (nonatomic, strong) TGMediaEditingContext *editingContext; - -- (void)setCornersImage:(UIImage *)cornersImage; - -- (void)setImage:(UIImage *)image signal:(SSignal *)signal hasCheck:(bool)hasCheck; -- (void)setHidden:(bool)hidden animated:(bool)animated; - -@end - -extern NSString *const TGClipboardPreviewCellIdentifier; diff --git a/submodules/LegacyComponents/Sources/TGClipboardPreviewCell.m b/submodules/LegacyComponents/Sources/TGClipboardPreviewCell.m deleted file mode 100644 index 11f21576ce..0000000000 --- a/submodules/LegacyComponents/Sources/TGClipboardPreviewCell.m +++ /dev/null @@ -1,228 +0,0 @@ -#import "TGClipboardPreviewCell.h" - -#import -#import - -#import - -#import - -NSString *const TGClipboardPreviewCellIdentifier = @"TGClipboardPreviewCell"; -const CGFloat TGClipboardCellCornerRadius = 5.5f; - -@interface TGClipboardPreviewCell () -{ - TGCheckButtonView *_checkButton; - UIImageView *_cornersView; - - SMetaDisposable *_itemSelectedDisposable; -} -@end - -@implementation TGClipboardPreviewCell - -- (instancetype)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self != nil) - { - self.backgroundColor = [UIColor whiteColor]; - self.clipsToBounds = true; - - _imageView = [[TGImageView alloc] initWithFrame:self.bounds]; - _imageView.contentMode = UIViewContentModeScaleAspectFill; - [self addSubview:_imageView]; - - _cornersView = [[UIImageView alloc] init]; - _cornersView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _cornersView.frame = self.bounds; - [self addSubview:_cornersView]; - } - return self; -} - -- (void)setCornersImage:(UIImage *)cornersImage -{ - _cornersView.image = cornersImage; -} - -- (void)setImage:(UIImage *)image signal:(SSignal *)signal hasCheck:(bool)hasCheck -{ - _image = image; - - if (self.selectionContext != nil) - { - if (_checkButton == nil && hasCheck) - { - _checkButton = [[TGCheckButtonView alloc] initWithStyle:TGCheckButtonStyleMedia]; - [_checkButton addTarget:self action:@selector(checkButtonPressed) forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:_checkButton]; - } - - if (_itemSelectedDisposable == nil) - _itemSelectedDisposable = [[SMetaDisposable alloc] init]; - - [self setChecked:[self.selectionContext isItemSelected:(id)_image] animated:false]; - __weak TGClipboardPreviewCell *weakSelf = self; - [_itemSelectedDisposable setDisposable:[[self.selectionContext itemInformativeSelectedSignal:(id)_image] startWithNext:^(TGMediaSelectionChange *next) - { - __strong TGClipboardPreviewCell *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (next.sender != strongSelf->_checkButton) - [strongSelf setChecked:next.selected animated:next.animated]; - }]]; - - _checkButton.hidden = !hasCheck; - } - - if (_image == nil) - { - [_imageView reset]; - return; - } - - [self setSignal:signal]; -} - - -- (void)setSignal:(SSignal *)signal -{ - if (signal != nil) - [_imageView setSignal:signal]; - else - [_imageView reset]; -} - -- (void)checkButtonPressed -{ - [_checkButton setSelected:!_checkButton.selected animated:true]; - - [self.selectionContext setItem:(id)_image selected:_checkButton.selected animated:false sender:_checkButton]; -} - -- (void)setChecked:(bool)checked animated:(bool)animated -{ - [_checkButton setSelected:checked animated:animated]; -} - -- (void)prepareForReuse -{ - [super prepareForReuse]; - [_imageView reset]; -} - -- (void)setHidden:(bool)hidden animated:(bool)animated -{ - if (hidden != _imageView.hidden) - { - _imageView.hidden = hidden; - - if (animated) - { - if (!hidden) - { - for (UIView *view in self.subviews) - { - if (view != _imageView && view != _cornersView) - view.alpha = 0.0f; - } - } - - [UIView animateWithDuration:0.2 animations:^ - { - if (!hidden) - { - for (UIView *view in self.subviews) - { - if (view != _imageView && view != _cornersView) - view.alpha = 1.0f; - } - } - }]; - } - else - { - for (UIView *view in self.subviews) - { - if (view != _imageView && view != _cornersView) - view.alpha = hidden ? 0.0f : 1.0f; - } - } - } -} - -- (void)layoutSubviews -{ - _imageView.frame = self.bounds; - - if (_checkButton != nil) - { - CGFloat offset = 0.0f; - if (self.superview != nil) - { - CGRect rect = [self.superview convertRect:self.frame toView:self.superview.superview]; - if (rect.origin.x < 0) - offset = rect.origin.x * -1; - else if (CGRectGetMaxX(rect) > self.superview.frame.size.width) - offset = self.superview.frame.size.width - CGRectGetMaxX(rect); - } - - CGFloat x = MAX(0, MIN(self.bounds.size.width - _checkButton.frame.size.width, self.bounds.size.width - _checkButton.frame.size.width + offset)); - _checkButton.frame = CGRectMake(x, 0, _checkButton.frame.size.width, _checkButton.frame.size.height); - } -} - -- (UIImage *)transitionImage -{ - CGFloat scale = 1.0f; - CGSize scaledBoundsSize = CGSizeZero; - CGSize scaledImageSize = CGSizeZero; - - if (self.imageView.image.size.width > self.imageView.image.size.height) - { - scale = self.frame.size.height / self.imageView.image.size.height; - scaledBoundsSize = CGSizeMake(self.frame.size.width / scale, self.imageView.image.size.height); - - scaledImageSize = CGSizeMake(self.imageView.image.size.width * scale, self.imageView.image.size.height * scale); - - if (scaledImageSize.width < self.frame.size.width) - { - scale = self.frame.size.width / self.imageView.image.size.width; - scaledBoundsSize = CGSizeMake(self.imageView.image.size.width, self.frame.size.height / scale); - } - } - else - { - scale = self.frame.size.width / self.imageView.image.size.width; - scaledBoundsSize = CGSizeMake(self.imageView.image.size.width, self.frame.size.height / scale); - - scaledImageSize = CGSizeMake(self.imageView.image.size.width * scale, self.imageView.image.size.height * scale); - - if (scaledImageSize.width < self.frame.size.width) - { - scale = self.frame.size.height / self.imageView.image.size.height; - scaledBoundsSize = CGSizeMake(self.frame.size.width / scale, self.imageView.image.size.height); - } - } - - CGRect rect = self.bounds; - UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0f); - - CGContextRef context = UIGraphicsGetCurrentContext(); - [[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) cornerRadius:TGClipboardCellCornerRadius] addClip]; - - CGContextScaleCTM(context, scale, scale); - [self.imageView.image drawInRect:CGRectMake((scaledBoundsSize.width - self.imageView.image.size.width) / 2, - (scaledBoundsSize.height - self.imageView.image.size.height) / 2, - self.imageView.image.size.width, - self.imageView.image.size.height)]; - - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return image; -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGClipboardPreviewItemView.h b/submodules/LegacyComponents/Sources/TGClipboardPreviewItemView.h deleted file mode 100644 index 02cd811ad6..0000000000 --- a/submodules/LegacyComponents/Sources/TGClipboardPreviewItemView.h +++ /dev/null @@ -1,34 +0,0 @@ -#import - -@class TGMediaSelectionContext; -@class TGMediaEditingContext; - -@protocol TGPhotoPaintStickersContext; - -@interface TGClipboardPreviewItemView : TGMenuSheetItemView - -@property (nonatomic, weak) TGViewController *parentController; - -@property (nonatomic, assign) bool allowCaptions; -@property (nonatomic, assign) bool hasTimer; -@property (nonatomic, strong) NSString *recipientName; - -@property (nonatomic, assign) bool hasSchedule; -@property (nonatomic, assign) bool hasSilentPosting; -@property (nonatomic, assign) bool reminder; - -@property (nonatomic, readonly) TGMediaSelectionContext *selectionContext; -@property (nonatomic, readonly) TGMediaEditingContext *editingContext; -@property (nonatomic, strong) id stickersContext; - -@property (nonatomic, copy) void (^selectionChanged)(NSUInteger); -@property (nonatomic, copy) void (^sendPressed)(UIImage *currentItem, bool silentPosting, int32_t scheduleTime); - -@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t)); -@property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t)); - -- (instancetype)initWithContext:(id)context images:(NSArray *)images allowGrouping:(bool)allowGrouping; - -- (void)setCollapsed:(bool)collapsed animated:(bool)animated; - -@end diff --git a/submodules/LegacyComponents/Sources/TGClipboardPreviewItemView.m b/submodules/LegacyComponents/Sources/TGClipboardPreviewItemView.m deleted file mode 100644 index 4a5d4741e9..0000000000 --- a/submodules/LegacyComponents/Sources/TGClipboardPreviewItemView.m +++ /dev/null @@ -1,299 +0,0 @@ -#import "TGClipboardPreviewItemView.h" - -#import "LegacyComponentsInternal.h" - -#import -#import - -#import - -#import "TGClipboardPreviewCell.h" - -const CGFloat TGClipboardPreviewMaxWidth = 250.0f; -const CGFloat TGClipboardPreviewCellHeight = 198.0f; -const CGFloat TGClipboardPreviewEdgeInset = 8.0f; - -@interface TGClipboardPreviewItemView () -{ - id _context; - - NSArray *_images; - - UICollectionView *_collectionView; - UICollectionViewFlowLayout *_collectionLayout; - - TGClipboardGalleryMixin *_galleryMixin; - UIImage *_hiddenItem; - - SMetaDisposable *_selectionChangedDisposable; - - bool _collapsed; - - UIImage *_cornersImage; -} -@end - -@implementation TGClipboardPreviewItemView - -- (instancetype)initWithContext:(id)context images:(NSArray *)images allowGrouping:(bool)allowGrouping -{ - self = [super initWithType:TGMenuSheetItemTypeDefault]; - if (self != nil) - { - _context = context; - _images = images; - - self.backgroundColor = [UIColor whiteColor]; - - _collectionLayout = [[UICollectionViewFlowLayout alloc] init]; - _collectionLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; - _collectionLayout.minimumLineSpacing = 8.0f; - - _collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, TGClipboardPreviewCellHeight + TGClipboardPreviewEdgeInset * 2) collectionViewLayout:_collectionLayout]; - if (@available(iOS 11.0, *)) { - _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - } - _collectionView.backgroundColor = [UIColor whiteColor]; - _collectionView.dataSource = self; - _collectionView.delegate = self; - _collectionView.showsHorizontalScrollIndicator = false; - _collectionView.showsVerticalScrollIndicator = false; - [_collectionView registerClass:[TGClipboardPreviewCell class] forCellWithReuseIdentifier:TGClipboardPreviewCellIdentifier]; - [self addSubview:_collectionView]; - - _selectionContext = [[TGMediaSelectionContext alloc] initWithGroupingAllowed:allowGrouping selectionLimit:100]; - if (allowGrouping) - _selectionContext.grouping = true; - - for (UIImage *image in _images) - { - [_selectionContext setItem:(id)image selected:true]; - } - - __weak TGClipboardPreviewItemView *weakSelf = self; - _selectionChangedDisposable = [[SMetaDisposable alloc] init]; - [_selectionChangedDisposable setDisposable:[[_selectionContext selectionChangedSignal] startWithNext:^(__unused TGMediaSelectionChange *change) - { - __strong TGClipboardPreviewItemView *strongSelf = weakSelf; - if (strongSelf != nil && strongSelf.selectionChanged != nil) - strongSelf.selectionChanged(strongSelf->_selectionContext.count); - }]]; - - _editingContext = [[TGMediaEditingContext alloc] init]; - } - return self; -} - -- (void)setPallete:(TGMenuSheetPallete *)pallete -{ - [super setPallete:pallete]; - - self.backgroundColor = pallete.backgroundColor; - _collectionView.backgroundColor = pallete.backgroundColor; - - CGFloat radius = 5.5f; - CGRect rect = CGRectMake(0, 0, 12.0f, 12.0f); - UIGraphicsBeginImageContextWithOptions(rect.size, false, 0); - CGContextRef context = UIGraphicsGetCurrentContext(); - - CGContextSetFillColorWithColor(context, pallete.backgroundColor.CGColor); - CGContextFillRect(context, rect); - - CGContextSetBlendMode(context, kCGBlendModeClear); - - CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor); - CGContextFillEllipseInRect(context, rect); - - _cornersImage = [UIGraphicsGetImageFromCurrentImageContext() resizableImageWithCapInsets:UIEdgeInsetsMake(radius, radius, radius, radius)]; - - UIGraphicsEndImageContext(); -} - -- (void)setCollapsed:(bool)collapsed animated:(bool)animated -{ - _collapsed = collapsed; - - [self _updateHeightAnimated:animated]; -} - -- (CGFloat)contentHeightCorrection -{ - return _collapsed ? -TGMenuSheetButtonItemViewHeight : 0.0f; -} - -- (SSignal *)_signalForImage:(UIImage *)image -{ - SSignal *originalSignal = [SSignal single:image]; - if (_editingContext == nil) - return originalSignal; - - SSignal *editedSignal = [_editingContext fastImageSignalForItem:image withUpdates:true]; - return [editedSignal mapToSignal:^SSignal *(id result) - { - if (result != nil) - return [SSignal single:result]; - else - return originalSignal; - }]; -} - -- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath -{ - TGClipboardPreviewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:TGClipboardPreviewCellIdentifier forIndexPath:indexPath]; - cell.selectionContext = _selectionContext; - cell.editingContext = _editingContext; - [cell setCornersImage:_cornersImage]; - - UIImage *image = _images[indexPath.row]; - [cell setImage:image signal:[self _signalForImage:image] hasCheck:_images.count > 1]; - - return cell; -} - -- (void)collectionView:(UICollectionView *)__unused collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath -{ - _galleryMixin = [self galleryMixinForIndexPath:indexPath]; - [_galleryMixin present]; -} - -- (NSInteger)collectionView:(UICollectionView *)__unused collectionView numberOfItemsInSection:(NSInteger)__unused section -{ - return _images.count; -} - -- (UIEdgeInsets)collectionView:(UICollectionView *)__unused collectionView layout:(UICollectionViewLayout *)__unused collectionViewLayout insetForSectionAtIndex:(NSInteger)__unused section -{ - return UIEdgeInsetsMake(TGClipboardPreviewEdgeInset, TGClipboardPreviewEdgeInset, TGClipboardPreviewEdgeInset, TGClipboardPreviewEdgeInset); -} - -- (CGSize)collectionView:(UICollectionView *)__unused collectionView layout:(UICollectionViewLayout *)__unused collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - CGSize dimensions = [(UIImage *)_images[indexPath.row] size]; - - CGSize maxPhotoSize = CGSizeMake(TGClipboardPreviewMaxWidth, TGClipboardPreviewCellHeight); - CGFloat width = MIN(maxPhotoSize.width, ceil(dimensions.width * maxPhotoSize.height / dimensions.height)); - return CGSizeMake(width, maxPhotoSize.height); -} - -- (void)scrollViewDidScroll:(UIScrollView *)__unused scrollView -{ - for (UICollectionViewCell *cell in _collectionView.visibleCells) - { - if ([cell isKindOfClass:[TGClipboardPreviewCell class]]) - [(TGClipboardPreviewCell *)cell setNeedsLayout]; - } -} - -- (bool)requiresDivider -{ - return false; -} - -- (CGFloat)preferredHeightForWidth:(CGFloat)__unused width screenHeight:(CGFloat)__unused screenHeight -{ - return TGClipboardPreviewCellHeight + TGClipboardPreviewEdgeInset * 2.0f; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - _collectionView.frame = self.bounds; -} - -#pragma mark - - -- (void)_setupGalleryMixin:(TGClipboardGalleryMixin *)mixin -{ - __weak TGClipboardPreviewItemView *weakSelf = self; - mixin.referenceViewForItem = ^UIView *(TGClipboardGalleryPhotoItem *item) - { - __strong TGClipboardPreviewItemView *strongSelf = weakSelf; - if (strongSelf != nil) - return [strongSelf referenceViewForAsset:item.image]; - - return nil; - }; - - mixin.itemFocused = ^(TGClipboardGalleryPhotoItem *item) - { - __strong TGClipboardPreviewItemView *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_hiddenItem = item.image; - [strongSelf updateHiddenCellAnimated:false]; - }; - - mixin.willTransitionIn = ^ - { - __strong TGClipboardPreviewItemView *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf.superview bringSubviewToFront:strongSelf]; - }; - - mixin.willTransitionOut = ^ - { - __strong TGClipboardPreviewItemView *strongSelf = weakSelf; - if (strongSelf == nil) - return; - }; - - mixin.didTransitionOut = ^ - { - __strong TGClipboardPreviewItemView *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_hiddenItem = nil; - [strongSelf updateHiddenCellAnimated:true]; - - strongSelf->_galleryMixin = nil; - }; - - - mixin.completeWithItem = ^(TGClipboardGalleryPhotoItem *item, bool silentPosting, int32_t scheduleTime) - { - __strong TGClipboardPreviewItemView *strongSelf = weakSelf; - if (strongSelf != nil && strongSelf.sendPressed != nil) - strongSelf.sendPressed(item.image, silentPosting, scheduleTime); - }; -} - -- (TGClipboardGalleryMixin *)galleryMixinForIndexPath:(NSIndexPath *)indexPath -{ - UIImage *image = _images[indexPath.row]; - UIImage *thumbnailImage = nil; - - TGClipboardPreviewCell *cell = (TGClipboardPreviewCell *)[_collectionView cellForItemAtIndexPath:indexPath]; - if ([cell isKindOfClass:[TGClipboardPreviewCell class]]) - thumbnailImage = cell.imageView.image; - - TGClipboardGalleryMixin *mixin = [[TGClipboardGalleryMixin alloc] initWithContext:_context image:image images:_images parentController:self.parentController thumbnailImage:thumbnailImage selectionContext:_selectionContext editingContext:_editingContext stickersContext:self.stickersContext hasCaptions:self.allowCaptions hasTimer:self.hasTimer hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule reminder:self.reminder recipientName:self.recipientName]; - mixin.presentScheduleController = self.presentScheduleController; - mixin.presentTimerController = self.presentTimerController; - - [self _setupGalleryMixin:mixin]; - - return mixin; -} - -- (UIView *)referenceViewForAsset:(UIImage *)image -{ - for (TGClipboardPreviewCell *cell in [_collectionView visibleCells]) - { - if ([cell.image isEqual:image]) - return cell; - } - - return nil; -} - -- (void)updateHiddenCellAnimated:(bool)animated -{ - for (TGClipboardPreviewCell *cell in [_collectionView visibleCells]) - [cell setHidden:([cell.image isEqual:_hiddenItem]) animated:animated]; -} - -@end diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 909b4328ca..93f4a69c92 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -674,8 +674,10 @@ if (strongSelf == nil) return; - [strongController dismissAnimated:true manual:false completion:nil]; - [[[LegacyComponentsGlobals provider] applicationInstance] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; + [strongController dismissAnimated:true manual:false completion:nil]; + [[[LegacyComponentsGlobals provider] applicationInstance] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:^(BOOL success) { + + }]; }], [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^ { @@ -1394,6 +1396,159 @@ return signals; } ++ (NSArray *)pasteboardResultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext intent:(TGMediaAssetsControllerIntent)intent currentItem:(id)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *, NSString *))descriptionGenerator +{ + NSMutableArray *signals = [[NSMutableArray alloc] init]; + NSMutableArray *selectedItems = [selectionContext.selectedItems mutableCopy]; + if (selectedItems.count == 0 && currentItem != nil) + [selectedItems addObject:currentItem]; + + NSNumber *groupedId; + NSInteger i = 0; + NSInteger num = 0; + bool grouping = selectionContext.grouping; + + bool hasAnyTimers = false; + if (editingContext != nil || grouping) + { + for (UIImage *asset in selectedItems) + { + if ([editingContext timerForItem:asset] != nil) { + hasAnyTimers = true; + } + id adjustments = [editingContext adjustmentsForItem:asset]; + if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]]) { + TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments; + if (videoAdjustments.sendAsGif) { + grouping = false; + } + } + for (TGPhotoPaintEntity *entity in adjustments.paintingData.entities) { + if (entity.animated) { + grouping = true; + } + } + } + } + + if (grouping && selectedItems.count > 1) + groupedId = @([self generateGroupedId]); + + for (UIImage *asset in selectedItems) + { + NSAttributedString *caption = [editingContext captionForItem:asset]; + if (editingContext.isForcedCaption) { + if (grouping && num > 0) { + caption = nil; + } else if (!grouping && num < selectedItems.count - 1) { + caption = nil; + } + } + + if (intent == TGMediaAssetsControllerSendFileIntent) + { + NSString *tempFileName = TGTemporaryFileName(nil); + NSData *imageData = UIImageJPEGRepresentation(asset, 1.0); + [imageData writeToURL:[NSURL fileURLWithPath:tempFileName] atomically:true]; + + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + dict[@"type"] = @"file"; + dict[@"tempFileUrl"] = [NSURL fileURLWithPath:tempFileName]; + dict[@"fileName"] = [NSString stringWithFormat:@"IMG%03ld.jpg", i]; + dict[@"mimeType"] = TGMimeTypeForFileUTI(@"image/jpeg"); + dict[@"previewImage"] = asset; + + if (groupedId != nil) + dict[@"groupedId"] = groupedId; + + id generatedItem = descriptionGenerator(dict, caption, nil, nil); + [signals addObject:[SSignal single:generatedItem]]; + + i++; + num++; + } else { + id adjustments = [editingContext adjustmentsForItem:asset]; + NSNumber *timer = [editingContext timerForItem:asset]; + + SSignal *inlineSignal = [[SSignal single:asset] map:^id(UIImage *image) + { + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + dict[@"type"] = @"editedPhoto"; + dict[@"image"] = image; + + if (timer != nil) + dict[@"timer"] = timer; + + if (groupedId != nil && !hasAnyTimers) + dict[@"groupedId"] = groupedId; + + id generatedItem = descriptionGenerator(dict, caption, nil, nil); + return generatedItem; + }]; + + SSignal *assetSignal = inlineSignal; + SSignal *imageSignal = assetSignal; + 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]; + dict[@"type"] = @"editedPhoto"; + dict[@"image"] = image; + + if (adjustments.paintingData.stickers.count > 0) + dict[@"stickers"] = adjustments.paintingData.stickers; + + if (timer != nil) + dict[@"timer"] = timer; + + if (groupedId != nil && !hasAnyTimers) + dict[@"groupedId"] = groupedId; + + id generatedItem = descriptionGenerator(dict, caption, nil, nil); + return generatedItem; + }] catch:^SSignal *(__unused id error) + { + return inlineSignal; + }]]; + + i++; + num++; + } + + if (groupedId != nil && i == 10) + { + i = 0; + groupedId = @([self generateGroupedId]); + } + } + return signals; +} + #pragma mark - - (UIBarButtonItem *)leftBarButtonItem diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index 0b0d2120d9..a61eb9db26 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -445,77 +445,3 @@ public func legacyMenuPaletteFromTheme(_ theme: PresentationTheme, forceDark: Bo } return TGMenuSheetPallete(dark: forceDark || theme.overallDarkAppearance, backgroundColor: sheetTheme.opaqueItemBackgroundColor, selectionColor: sheetTheme.opaqueItemHighlightedBackgroundColor, separatorColor: sheetTheme.opaqueItemSeparatorColor, accentColor: sheetTheme.controlAccentColor, destructiveColor: sheetTheme.destructiveActionTextColor, textColor: sheetTheme.primaryTextColor, secondaryTextColor: sheetTheme.secondaryTextColor, spinnerColor: sheetTheme.secondaryTextColor, badgeTextColor: sheetTheme.controlAccentColor, badgeImage: nil, cornersImage: generateStretchableFilledCircleImage(diameter: 11.0, color: nil, strokeColor: nil, strokeWidth: nil, backgroundColor: sheetTheme.opaqueItemBackgroundColor)) } - -public func presentLegacyPasteMenu(context: AccountContext, peer: Peer, chatLocation: ChatLocation, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal), images: [UIImage], presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: (ViewController, Any?) -> Void, initialLayout: ContainerViewLayout? = nil) -> ViewController { - let defaultVideoPreset = defaultVideoPresetForContext(context) - UserDefaults.standard.set(defaultVideoPreset.rawValue as NSNumber, forKey: "TG_preferredVideoPreset_v0") - - let presentationData = updatedPresentationData.initial - let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: initialLayout) - legacyController.statusBar.statusBarStyle = .Ignore - legacyController.controllerLoaded = { [weak legacyController] in - legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true - } - - let emptyController = LegacyEmptyController(context: legacyController.context)! - let navigationController = makeLegacyNavigationController(rootController: emptyController) - navigationController.setNavigationBarHidden(true, animated: false) - legacyController.bind(controller: navigationController) - - var hasTimer = false - var hasSilentPosting = false - if peer.id != context.account.peerId { - if peer is TelegramUser { - hasTimer = true - } - hasSilentPosting = true - } - let recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - - legacyController.enableSizeClassSignal = true - - let paintStickersContext = LegacyPaintStickersContext(context: context) - paintStickersContext.captionPanelView = { - return getCaptionPanelView() - } - paintStickersContext.presentStickersController = { completion in - return presentStickers({ file, animated, view, rect in - let coder = PostboxEncoder() - coder.encodeRootObject(file) - completion?(coder.makeData(), animated, view, rect) - }) - } - - let controller = TGClipboardMenu.present(inParentController: emptyController, context: legacyController.context, images: images, allowGrouping: allowGrouping, hasCaption: true, hasTimer: hasTimer, hasSilentPosting: hasSilentPosting, hasSchedule: hasSchedule, reminder: peer.id == context.account.peerId, recipientName: recipientName, stickersContext: paintStickersContext, presentScheduleController: { done in - presentSchedulePicker { time in - done?(time) - } - }, presentTimerController: { done in - presentTimerPicker { time in - done?(time) - } - }, completed: { selectionContext, editingContext, currentItem, silentPosting, scheduleTime in - let nativeGenerator = legacyAssetPickerItemGenerator() - let signals = TGClipboardMenu.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, descriptionGenerator: { _1, _2, _3 in - nativeGenerator(_1, _2, _3, nil) - }) - sendMessagesWithSignals(signals, silentPosting, scheduleTime) - }, dismissed: { [weak legacyController] in - legacyController?.dismiss() - }, sourceView: emptyController.view, sourceRect: nil)! - controller.customRemoveFromParentViewController = { [weak legacyController] in - legacyController?.dismiss() - } - - let presentationDisposable = updatedPresentationData.signal.start(next: { [weak legacyController] presentationData in - if let legacyController = legacyController, let controller = legacyController.legacyController as? TGMenuSheetController { - controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme, forceDark: false) - } - }) - legacyController.disposables.add(presentationDisposable) - - present(legacyController, nil) - controller.present(in: emptyController, sourceView: nil, animated: true) - - return legacyController -} diff --git a/submodules/MediaPasteboardUI/BUILD b/submodules/MediaPasteboardUI/BUILD new file mode 100644 index 0000000000..df60617029 --- /dev/null +++ b/submodules/MediaPasteboardUI/BUILD @@ -0,0 +1,26 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "MediaPasteboardUI", + module_name = "MediaPasteboardUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + "//submodules/AttachmentUI:AttachmentUI", + "//submodules/MediaPickerUI:MediaPickerUI", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/MediaPasteboardUI/Sources/MediaPasteboardScreen.swift b/submodules/MediaPasteboardUI/Sources/MediaPasteboardScreen.swift new file mode 100644 index 0000000000..c9d7099d61 --- /dev/null +++ b/submodules/MediaPasteboardUI/Sources/MediaPasteboardScreen.swift @@ -0,0 +1,29 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import TelegramPresentationData +import TelegramCore +import AttachmentUI +import MediaPickerUI +import AccountContext +import LegacyComponents + +public func mediaPasteboardScreen( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + peer: EnginePeer, + subjects: [UIImage], + presentMediaPicker: @escaping (_ subject: MediaPickerScreen.Subject, _ saveEditedPhotos: Bool, _ bannedSendMedia: (Int32, Bool)?, _ present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void) -> Void, + getSourceRect: (() -> CGRect?)? = nil +) -> ViewController { + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: peer.id), buttons: [.standalone], initialButton: .standalone) + controller.requestController = { _, present in + presentMediaPicker(.media(subjects.map { .image($0) }), false, nil, { mediaPicker, mediaPickerContext in + present(mediaPicker, mediaPickerContext) + }) + } + controller.updateSelectionCount(subjects.count) + controller.getSourceRect = getSourceRect + return controller +} diff --git a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift index 21f9f73cbc..dd12846ee1 100644 --- a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift +++ b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift @@ -56,6 +56,32 @@ private func gallerySelectionItems(item: TGMediaSelectableItem, selectionContext galleryItem.stickersContext = stickersContext galleryItems.append(galleryItem) + if selectedItem.uniqueIdentifier == item.uniqueIdentifier { + if let galleryItem = galleryItem as? TGMediaPickerGalleryItem { + galleryItem.immediateThumbnailImage = immediateThumbnail + } + focusItem = galleryItem + } + } else if let asset = selectedItem as? UIImage { + let galleryItem: (TGModernGallerySelectableItem & TGModernGalleryEditableItem) = TGMediaPickerGalleryPhotoItem(asset: asset) + galleryItem.selectionContext = selectionContext + galleryItem.editingContext = editingContext + galleryItem.stickersContext = stickersContext + galleryItems.append(galleryItem) + + if selectedItem.uniqueIdentifier == item.uniqueIdentifier { + if let galleryItem = galleryItem as? TGMediaPickerGalleryItem { + galleryItem.immediateThumbnailImage = immediateThumbnail + } + focusItem = galleryItem + } + } else if let asset = selectedItem as? TGCameraCapturedVideo { + let galleryItem: (TGModernGallerySelectableItem & TGModernGalleryEditableItem) = TGMediaPickerGalleryVideoItem(asset: asset) + galleryItem.selectionContext = selectionContext + galleryItem.editingContext = editingContext + galleryItem.stickersContext = stickersContext + galleryItems.append(galleryItem) + if selectedItem.uniqueIdentifier == item.uniqueIdentifier { if let galleryItem = galleryItem as? TGMediaPickerGalleryItem { galleryItem.immediateThumbnailImage = immediateThumbnail diff --git a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift index ba947c120f..8d9d91e3db 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift @@ -15,6 +15,7 @@ import PhotoResources enum MediaPickerGridItemContent: Equatable { case asset(PHFetchResult, Int) + case media(MediaPickerScreen.Subject.Media, Int) } final class MediaPickerGridItem: GridItem { @@ -32,21 +33,31 @@ final class MediaPickerGridItem: GridItem { func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { switch self.content { - case let .asset(fetchResult, index): - let node = MediaPickerGridItemNode() - node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme) - return node + case let .asset(fetchResult, index): + let node = MediaPickerGridItemNode() + node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme) + return node + case let .media(media, index): + let node = MediaPickerGridItemNode() + node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme) + return node } } func update(node: GridItemNode) { switch self.content { - case let .asset(fetchResult, index): - guard let node = node as? MediaPickerGridItemNode else { - assertionFailure() - return - } - node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme) + case let .asset(fetchResult, index): + guard let node = node as? MediaPickerGridItemNode else { + assertionFailure() + return + } + node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme) + case let .media(media, index): + guard let node = node as? MediaPickerGridItemNode else { + assertionFailure() + return + } + node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme) } } } @@ -65,6 +76,7 @@ private let maskImage = generateImage(CGSize(width: 1.0, height: 24.0), opaque: }) final class MediaPickerGridItemNode: GridItemNode { + var currentMediaState: (TGMediaSelectableItem, Int)? var currentState: (PHFetchResult, Int)? private let imageNode: ImageNode private var checkNode: InteractiveCheckNode? @@ -103,12 +115,14 @@ final class MediaPickerGridItemNode: GridItemNode { } var identifier: String { - return self.asset?.localIdentifier ?? "" + return self.selectableItem?.uniqueIdentifier ?? "" } - var asset: PHAsset? { - if let (fetchResult, index) = self.currentState { - return fetchResult[index] + var selectableItem: TGMediaSelectableItem? { + if let (media, _) = self.currentMediaState { + return media + } else if let (fetchResult, index) = self.currentState { + return TGMediaAsset(phAsset: fetchResult[index]) } else { return nil } @@ -116,25 +130,23 @@ final class MediaPickerGridItemNode: GridItemNode { var _cachedTag: Int32? var tag: Int32? { - if let tag = self._cachedTag { - return tag - } else if let asset = self.asset, let localTimestamp = asset.creationDate?.timeIntervalSince1970 { - let tag = Month(localTimestamp: Int32(localTimestamp)).packedValue - self._cachedTag = tag - return tag - } else { +// if let tag = self._cachedTag { +// return tag +// } else if let asset = self.asset, let localTimestamp = asset.creationDate?.timeIntervalSince1970 { +// let tag = Month(localTimestamp: Int32(localTimestamp)).packedValue +// self._cachedTag = tag +// return tag +// } else { return nil - } +// } } func updateSelectionState(animated: Bool = false) { if self.checkNode == nil, let _ = self.interaction?.selectionState, let theme = self.theme { let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay)) checkNode.valueChanged = { [weak self] value in - if let strongSelf = self, let asset = strongSelf.asset, let interaction = strongSelf.interaction { - if let legacyAsset = TGMediaAsset(phAsset: asset) { - interaction.toggleSelection(legacyAsset, value, false) - } + if let strongSelf = self, let interaction = strongSelf.interaction, let selectableItem = strongSelf.selectableItem { + interaction.toggleSelection(selectableItem, value, false) } } self.addSubnode(checkNode) @@ -142,10 +154,10 @@ final class MediaPickerGridItemNode: GridItemNode { self.setNeedsLayout() } - if let asset = self.asset, let interaction = self.interaction, let selectionState = interaction.selectionState { - let selected = selectionState.isIdentifierSelected(asset.localIdentifier) - if let legacyAsset = TGMediaAsset(phAsset: asset) { - let index = selectionState.index(of: legacyAsset) + if let interaction = self.interaction, let selectionState = interaction.selectionState { + let selected = selectionState.isIdentifierSelected(self.identifier) + if let selectableItem = self.selectableItem { + let index = selectionState.index(of: selectableItem) if index != NSNotFound { self.checkNode?.content = .counter(Int(index)) } @@ -155,12 +167,10 @@ final class MediaPickerGridItemNode: GridItemNode { } func updateHiddenMedia() { - if let asset = self.asset { - let wasHidden = self.isHidden - self.isHidden = self.interaction?.hiddenMediaId == asset.localIdentifier - if !self.isHidden && wasHidden { - self.animateFadeIn(animateCheckNode: true) - } + let wasHidden = self.isHidden + self.isHidden = self.interaction?.hiddenMediaId == self.identifier + if !self.isHidden && wasHidden { + self.animateFadeIn(animateCheckNode: true) } } @@ -178,6 +188,73 @@ final class MediaPickerGridItemNode: GridItemNode { self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:)))) } + + func setup(interaction: MediaPickerInteraction, media: MediaPickerScreen.Subject.Media, index: Int, theme: PresentationTheme) { + self.interaction = interaction + self.theme = theme + + self.backgroundColor = theme.list.mediaPlaceholderColor + + if self.currentMediaState == nil || self.currentMediaState!.0.uniqueIdentifier != media.identifier || self.currentState!.1 != index { +// let editingContext = interaction.editingState +// let asset = media.asset as? TGMediaEditableItem +// +// let editedSignal = Signal { subscriber in +// if let signal = editingContext.thumbnailImageSignal(forIdentifier: media.identifier) { +// let disposable = signal.start(next: { next in +// if let image = next as? UIImage { +// subscriber.putNext(image) +// } else { +// subscriber.putNext(nil) +// } +// }, error: { _ in +// }, completed: nil)! +// +// return ActionDisposable { +// disposable.dispose() +// } +// } else { +// return EmptyDisposable +// } +// } +// +// let originalImageSignal = Signal { subscriber in +// if let signal = asset?.thumbnailImageSignal?() +// } +// +// let scale = min(2.0, UIScreenScale) +// let targetSize = CGSize(width: 128.0 * scale, height: 128.0 * scale) +// let originalSignal: Signal = assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false) +// let imageSignal: Signal = editedSignal +// |> mapToSignal { result in +// if let result = result { +// return .single(result) +// } else { +// return originalSignal +// } +// } +// self.imageNode.setSignal(imageSignal) +// +// if case .video = media, let asset = media.asset as? TGCameraCapturedVideo { +// self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaVideo") +// +// if self.typeIconNode.supernode == nil { +// self.durationNode.attributedText = NSAttributedString(string: stringForDuration(Int32(asset.videoDuration)), font: Font.semibold(12.0), textColor: .white) +// +// self.addSubnode(self.gradientNode) +// self.addSubnode(self.typeIconNode) +// self.addSubnode(self.durationNode) +// self.setNeedsLayout() +// } +// } +// + self.currentMediaState = (media.asset, index) + self.setNeedsLayout() + } + + self.updateSelectionState() + self.updateHiddenMedia() + } func setup(interaction: MediaPickerInteraction, fetchResult: PHFetchResult, index: Int, theme: PresentationTheme) { self.interaction = interaction diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 5a6fa6749e..51ba8da18a 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -103,6 +103,34 @@ struct Month: Equatable { } public final class MediaPickerScreen: ViewController, AttachmentContainable { + public enum Subject { + public enum Media: Equatable { + case image(UIImage) + case video(URL) + + var asset: TGMediaSelectableItem { + switch self { + case let .image(image): + return image + case let .video(url): + return TGCameraCapturedVideo(url: url) + } + } + + var identifier: String { + switch self { + case let .image(image): + return image.uniqueIdentifier + case let .video(url): + return url.absoluteString + } + } + } + + case assets(PHAssetCollection?) + case media([Media]) + } + private let context: AccountContext private var presentationData: PresentationData private var presentationDataDisposable: Disposable? @@ -113,7 +141,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private let peer: EnginePeer? private let chatLocation: ChatLocation? private let bannedSendMedia: (Int32, Bool)? - private let collection: PHAssetCollection? + private let subject: Subject private let saveEditedPhotos: Bool private let titleView: MediaPickerTitleView @@ -149,6 +177,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { enum State { case noAccess(cameraAccess: AVAuthorizationStatus?) case assets(fetchResult: PHFetchResult?, preload: Bool, mediaAccess: PHAuthorizationStatus, cameraAccess: AVAuthorizationStatus?) + case media([Subject.Media]) } private weak var controller: MediaPickerScreen? @@ -205,7 +234,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { super.init() - if controller.collection != nil { + if case .assets(nil) = controller.subject { + } else { self.preloadPromise.set(false) } @@ -214,27 +244,32 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.containerNode.addSubnode(self.gridNode) // self.containerNode.addSubnode(self.scrollingArea) - let collection = controller.collection let preloadPromise = self.preloadPromise - let updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess()) - |> mapToSignal { mediaAccess, cameraAccess -> Signal in - if case .notDetermined = mediaAccess { - return .single(.assets(fetchResult: nil, preload: false, mediaAccess: mediaAccess, cameraAccess: cameraAccess)) - } else if [.restricted, .denied].contains(mediaAccess) { - return .single(.noAccess(cameraAccess: cameraAccess)) - } else { - if let collection = collection { - return combineLatest(mediaAssetsContext.fetchAssets(collection), preloadPromise.get()) - |> map { fetchResult, preload in - return .assets(fetchResult: fetchResult, preload: preload, mediaAccess: mediaAccess, cameraAccess: cameraAccess) - } + let updatedState: Signal + switch controller.subject { + case let .assets(collection): + updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess()) + |> mapToSignal { mediaAccess, cameraAccess -> Signal in + if case .notDetermined = mediaAccess { + return .single(.assets(fetchResult: nil, preload: false, mediaAccess: mediaAccess, cameraAccess: cameraAccess)) + } else if [.restricted, .denied].contains(mediaAccess) { + return .single(.noAccess(cameraAccess: cameraAccess)) } else { - return combineLatest(mediaAssetsContext.recentAssets(), preloadPromise.get()) - |> map { fetchResult, preload in - return .assets(fetchResult: fetchResult, preload: preload, mediaAccess: mediaAccess, cameraAccess: cameraAccess) + if let collection = collection { + return combineLatest(mediaAssetsContext.fetchAssets(collection), preloadPromise.get()) + |> map { fetchResult, preload in + return .assets(fetchResult: fetchResult, preload: preload, mediaAccess: mediaAccess, cameraAccess: cameraAccess) + } + } else { + return combineLatest(mediaAssetsContext.recentAssets(), preloadPromise.get()) + |> map { fetchResult, preload in + return .assets(fetchResult: fetchResult, preload: preload, mediaAccess: mediaAccess, cameraAccess: cameraAccess) + } } } } + case let .media(media): + updatedState = .single(.media(media)) } self.itemsDisposable = (updatedState @@ -325,10 +360,30 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.gridNode.scrollView.alwaysBounceVertical = true self.gridNode.scrollView.showsVerticalScrollIndicator = false - if self.controller?.collection != nil { + self.selectionGesture = MediaPickerGridSelectionGesture(target: nil, action: nil, gridNode: self.gridNode) + self.selectionGesture?.delegate = self + self.selectionGesture?.began = { [weak self] in + self?.controller?.cancelPanGesture() + } + self.selectionGesture?.itemAt = { [weak self] point in + if let strongSelf = self, let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? MediaPickerGridItemNode, let selectableItem = itemNode.selectableItem { + return (selectableItem, strongSelf.controller?.interaction?.selectionState?.isIdentifierSelected(selectableItem.uniqueIdentifier) ?? false) + } else { + return nil + } + } + self.selectionGesture?.updateSelection = { [weak self] asset, selected in + if let strongSelf = self { + strongSelf.controller?.interaction?.selectionState?.setItem(asset, selected: selected, animated: true, sender: nil) + } + } + + if let controller = self.controller, case let .assets(collection) = controller.subject, collection != nil { self.gridNode.view.interactiveTransitionGestureRecognizerTest = { point -> Bool in return point.x > 44.0 } + + self.selectionGesture?.sideInset = 44.0 } self.scrollingArea.beginScrolling = { [weak self] in @@ -346,7 +401,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { // strongSelf.isFastScrolling = false } - if self.controller?.collection == nil { + if let controller = self.controller, case .assets(nil) = controller.subject { let cameraView = TGAttachmentCameraView(forSelfPortrait: false)! cameraView.clipsToBounds = true cameraView.removeCorners() @@ -363,27 +418,6 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } else { self.containerNode.clipsToBounds = true } - - self.selectionGesture = MediaPickerGridSelectionGesture(target: nil, action: nil, gridNode: self.gridNode) - self.selectionGesture?.delegate = self - self.selectionGesture?.began = { [weak self] in - self?.controller?.cancelPanGesture() - } - self.selectionGesture?.itemAt = { [weak self] point in - if let strongSelf = self, let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? MediaPickerGridItemNode, let asset = itemNode.asset.flatMap({ TGMediaAsset(phAsset: $0) }) { - return (asset, strongSelf.controller?.interaction?.selectionState?.isItemSelected(asset) ?? false) - } else { - return nil - } - } - self.selectionGesture?.updateSelection = { [weak self] asset, selected in - if let strongSelf = self { - strongSelf.controller?.interaction?.selectionState?.setItem(asset, selected: selected, animated: true, sender: nil) - } - } - if self.controller?.collection != nil { - self.selectionGesture?.sideInset = 44.0 - } } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { @@ -450,51 +484,57 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { var updateLayout = false switch state { - case let .noAccess(cameraAccess): - if case .assets = previousState { - updateLayout = true - } else if case let .noAccess(previousCameraAccess) = previousState, previousCameraAccess != cameraAccess { + case let .noAccess(cameraAccess): + if case .assets = previousState { + updateLayout = true + } else if case let .noAccess(previousCameraAccess) = previousState, previousCameraAccess != cameraAccess { + updateLayout = true + } + if case .notDetermined = cameraAccess, !self.requestedCameraAccess { + self.requestedCameraAccess = true + self.mediaAssetsContext.requestCameraAccess() + } + case let .assets(fetchResult, preload, mediaAccess, cameraAccess): + if let fetchResult = fetchResult { + let totalCount = fetchResult.count + let count = preload ? min(13, totalCount) : totalCount + + for i in 0 ..< count { + let index: Int + if case let .assets(collection) = controller.subject, let _ = collection { + index = i + } else { + index = totalCount - i - 1 + } + entries.append(MediaPickerGridEntry(stableId: stableId, content: .asset(fetchResult, index))) + stableId += 1 + } + + if case let .assets(previousFetchResult, _, _, previousCameraAccess) = previousState, previousFetchResult == nil || previousCameraAccess != cameraAccess { updateLayout = true } + if case .notDetermined = cameraAccess, !self.requestedCameraAccess { self.requestedCameraAccess = true self.mediaAssetsContext.requestCameraAccess() } - case let .assets(fetchResult, preload, mediaAccess, cameraAccess): - if let fetchResult = fetchResult { - let totalCount = fetchResult.count - let count = preload ? min(13, totalCount) : totalCount - - for i in 0 ..< count { - let index: Int - if self.controller?.collection != nil { - index = i - } else { - index = totalCount - i - 1 - } - entries.append(MediaPickerGridEntry(stableId: stableId, content: .asset(fetchResult, index))) - stableId += 1 - } - - if case let .assets(previousFetchResult, _, _, previousCameraAccess) = previousState, previousFetchResult == nil || previousCameraAccess != cameraAccess { - updateLayout = true - } - - if case .notDetermined = cameraAccess, !self.requestedCameraAccess { - self.requestedCameraAccess = true - self.mediaAssetsContext.requestCameraAccess() - } - } else if case .notDetermined = mediaAccess, !self.requestedMediaAccess { - self.requestedMediaAccess = true - self.mediaAssetsContext.requestMediaAccess() - } + } else if case .notDetermined = mediaAccess, !self.requestedMediaAccess { + self.requestedMediaAccess = true + self.mediaAssetsContext.requestMediaAccess() + } + case let .media(media): + let count = media.count + for i in 0 ..< count { + entries.append(MediaPickerGridEntry(stableId: stableId, content: .media(media[i], i))) + stableId += 1 + } } let previousEntries = self.currentEntries self.currentEntries = entries var scrollToItem: GridNodeScrollToItem? - if self.controller?.collection != nil && previousEntries.isEmpty && !entries.isEmpty { + if case let .assets(collection) = controller.subject, let _ = collection, previousEntries.isEmpty && !entries.isEmpty { scrollToItem = GridNodeScrollToItem(index: entries.count - 1, position: .bottom(0.0), transition: .immediate, directionHint: .down, adjustForSection: false) } @@ -531,7 +571,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } private var currentDisplayMode: DisplayMode = .all - func updateDisplayMode(_ displayMode: DisplayMode) { + func updateDisplayMode(_ displayMode: DisplayMode, animated: Bool = true) { let updated = self.currentDisplayMode != displayMode self.currentDisplayMode = displayMode @@ -539,8 +579,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.controller?.dismissAllTooltips() if case .selected = displayMode, self.selectionNode == nil, let controller = self.controller { - let selectionNode = MediaPickerSelectedListNode(context: controller.context) - selectionNode.alpha = 0.0 + var persistentItems = false + if case .media = controller.subject { + persistentItems = true + } + + let selectionNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems) + selectionNode.alpha = animated ? 0.0 : 1.0 selectionNode.layer.allowsGroupOpacity = true selectionNode.isUserInteractionEnabled = false selectionNode.interaction = self.controller?.interaction @@ -548,7 +593,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if let strongSelf = self { var node: MediaPickerGridItemNode? strongSelf.gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? MediaPickerGridItemNode, itemNode.asset?.localIdentifier == identifier { + if let itemNode = itemNode as? MediaPickerGridItemNode, itemNode.identifier == identifier { node = itemNode } } @@ -583,13 +628,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } if updated { - switch displayMode { + if animated { + switch displayMode { case .selected: self.selectionNode?.animateIn(initiated: { [weak self] in self?.updateNavigation(transition: .immediate) }, completion: completion) case .all: self.selectionNode?.animateOut(completion: completion) + } + } else { + self.updateNavigation(transition: .immediate) } } } @@ -612,7 +661,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.openingMedia = true - let reversed = controller.collection == nil + let reversed: Bool + if case .assets(nil) = controller.subject { + reversed = true + } else { + reversed = false + } let index = reversed ? fetchResult.count - index - 1 : index self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, chatLocation: controller.chatLocation, presentationData: self.presentationData, source: .fetchResult(fetchResult: fetchResult, index: index, reversed: reversed), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: true, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in self?.hiddenMediaId.set(.single(id)) @@ -689,7 +743,14 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } let proceed: (Bool) -> Void = { convertToJpeg in - guard let signals = TGMediaAssetsController.resultSignals(for: controller.interaction?.selectionState, editingContext: controller.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: convertToJpeg, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: controller.saveEditedPhotos) else { + let signals: [Any]! + switch controller.subject { + case .assets: + signals = TGMediaAssetsController.resultSignals(for: controller.interaction?.selectionState, editingContext: controller.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: convertToJpeg, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: controller.saveEditedPhotos) + case .media: + signals = TGMediaAssetsController.pasteboardResultSignals(for: controller.interaction?.selectionState, editingContext: controller.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, descriptionGenerator: legacyAssetPickerItemGenerator()) + } + guard let signals = signals else { return } controller.completed = true @@ -980,8 +1041,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } }) - if let selectionNode = self.selectionNode { - let selectedItems = self.controller?.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? [] + if let selectionNode = self.selectionNode, let controller = self.controller { + let selectedItems = controller.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? [] let updateSelectionNode = { selectionNode.updateLayout(size: bounds.size, insets: cleanGridInsets, items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: transition) } @@ -1068,20 +1129,27 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private var isDismissing = false - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, bannedSendMedia: (Int32, Bool)?, collection: PHAssetCollection? = nil, editingContext: TGMediaEditingContext? = nil, selectionContext: TGMediaSelectionContext? = nil, saveEditedPhotos: Bool = false) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, bannedSendMedia: (Int32, Bool)?, subject: Subject, editingContext: TGMediaEditingContext? = nil, selectionContext: TGMediaSelectionContext? = nil, saveEditedPhotos: Bool = false) { self.context = context - + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.presentationData = presentationData self.updatedPresentationData = updatedPresentationData self.peer = peer self.chatLocation = chatLocation self.bannedSendMedia = bannedSendMedia - self.collection = collection + self.subject = subject self.saveEditedPhotos = saveEditedPhotos + let selectionContext = selectionContext ?? TGMediaSelectionContext() + self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) - self.titleView.title = collection?.localizedTitle ?? presentationData.strings.Attachment_Gallery + + if case let .assets(collection) = subject, let collection = collection { + self.titleView.title = collection.localizedTitle ?? presentationData.strings.Attachment_Gallery + } else { + self.titleView.title = presentationData.strings.Attachment_Gallery + } self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme) @@ -1108,16 +1176,16 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { strongSelf.controllerNode.updateDisplayMode(index == 0 ? .all : .selected) } } - + self.navigationItem.titleView = self.titleView - if collection == nil { + if case let .assets(collection) = self.subject, collection != nil { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) + } else { self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed) self.navigationItem.rightBarButtonItem?.target = self - } else { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) } self.moreButtonNode.action = { [weak self] _, gesture in @@ -1186,10 +1254,16 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if let strongSelf = self { strongSelf.controllerNode.dismissInput() } - }, selectionState: selectionContext ?? TGMediaSelectionContext(), editingState: editingContext ?? TGMediaEditingContext()) + }, selectionState: selectionContext, editingState: editingContext ?? TGMediaEditingContext()) self.interaction?.selectionState?.grouping = true - self.updateSelectionState(count: Int32(selectionContext?.count() ?? 0)) + if case let .media(media) = self.subject { + for item in media { + selectionContext.setItem(item.asset, selected: true) + } + } + + self.updateSelectionState(count: Int32(selectionContext.count())) } required init(coder aDecoder: NSCoder) { @@ -1205,25 +1279,43 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self._ready.set(self.controllerNode.ready.get()) + if case .media = self.subject { + self.controllerNode.updateDisplayMode(.selected, animated: false) + } + super.displayNodeDidLoad() } private weak var undoOverlayController: UndoOverlayController? private func showSelectionUndo(item: TGMediaSelectableItem) { - var asset: PHAsset? - if let item = item as? TGMediaAsset { - asset = item.backingAsset - } else if let item = item as? TGCameraCapturedVideo { - asset = item.originalAsset.backingAsset - } - - guard let asset = asset else { - return - } - let scale = min(2.0, UIScreenScale) let targetSize = CGSize(width: 64.0 * scale, height: 64.0 * scale) - let _ = (assetImage(asset: asset, targetSize: targetSize, exact: false) + + let image: Signal + if let item = item as? TGMediaAsset { + image = assetImage(asset: item.backingAsset, targetSize: targetSize, exact: false) + } else if let item = item as? TGCameraCapturedVideo { + image = assetImage(asset: item.originalAsset.backingAsset, targetSize: targetSize, exact: false) + } else if let item = item as? TGMediaEditableItem { + image = Signal { subscriber in + let disposable = item.thumbnailImageSignal?().start(next: { next in + if let next = next as? UIImage { + subscriber.putNext(next) + } + }, error: { _ in + }, completed: { + subscriber.putCompletion() + }) + + return ActionDisposable { + disposable?.dispose() + } + } + } else { + return + } + + let _ = (image |> deliverOnMainQueue).start(next: { [weak self] image in guard let strongSelf = self else { return @@ -1240,6 +1332,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } } else if let _ = item as? TGCameraCapturedVideo { videosCount += 1 + } else if let _ = item as? UIImage { + photosCount += 1 } }) let totalCount = Int32(photosCount + videosCount) @@ -1288,17 +1382,24 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private var selectionCount: Int32 = 0 fileprivate func updateSelectionState(count: Int32) { self.selectionCount = count - if count > 0 { - self.titleView.segments = [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(count)] - self.titleView.segmentsHidden = false - self.moreButtonNode.iconNode.enqueueState(.more, animated: true) - } else { + + if case let .media(media) = self.subject { + self.titleView.title = media.count == 1 ? self.presentationData.strings.Attachment_Pasteboard : self.presentationData.strings.Attachment_SelectedMedia(count) self.titleView.segmentsHidden = true - self.moreButtonNode.iconNode.enqueueState(.search, animated: true) - - if self.titleView.index != 0 { - Queue.mainQueue().after(0.3) { - self.titleView.index = 0 + self.moreButtonNode.iconNode.enqueueState(.more, animated: false) + } else { + if count > 0 { + self.titleView.segments = [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(count)] + self.titleView.segmentsHidden = false + self.moreButtonNode.iconNode.enqueueState(.more, animated: true) + } else { + self.titleView.segmentsHidden = true + self.moreButtonNode.iconNode.enqueueState(.search, animated: true) + + if self.titleView.index != 0 { + Queue.mainQueue().after(0.3) { + self.titleView.index = 0 + } } } } @@ -1328,7 +1429,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public func requestDismiss(completion: @escaping () -> Void) { if let selectionState = self.interaction?.selectionState, selectionState.count() > 0 { self.isDismissing = true - let controller = textAlertController(context: self.context, title: nil, text: self.presentationData.strings.Attachment_CancelSelectionAlertText, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Attachment_CancelSelectionAlertNo, action: { + + let text: String + if case .media = self.subject { + text = self.presentationData.strings.Attachment_DiscardPasteboardAlertText + } else { + text = self.presentationData.strings.Attachment_CancelSelectionAlertText + } + + let controller = textAlertController(context: self.context, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Attachment_CancelSelectionAlertNo, action: { }), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Attachment_CancelSelectionAlertYes, action: { [weak self] in self?.dismissAllTooltips() @@ -1383,7 +1492,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.requestAttachmentMenuExpansion() self.presentWebSearch(MediaGroupsScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mediaAssetsContext: self.controllerNode.mediaAssetsContext, openGroup: { [weak self] collection in if let strongSelf = self { - let mediaPicker = MediaPickerScreen(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: strongSelf.peer, chatLocation: strongSelf.chatLocation, bannedSendMedia: strongSelf.bannedSendMedia, collection: collection, editingContext: strongSelf.interaction?.editingState, selectionContext: strongSelf.interaction?.selectionState) + let mediaPicker = MediaPickerScreen(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: strongSelf.peer, chatLocation: strongSelf.chatLocation, bannedSendMedia: strongSelf.bannedSendMedia, subject: .assets(collection), editingContext: strongSelf.interaction?.editingState, selectionContext: strongSelf.interaction?.selectionState) mediaPicker.presentStickers = strongSelf.presentStickers mediaPicker.presentSchedulePicker = strongSelf.presentSchedulePicker diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index 36e33fc96e..d4c0feb291 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -15,7 +15,7 @@ import AccountContext import ChatMessageBackground private class MediaPickerSelectedItemNode: ASDisplayNode { - let asset: TGMediaAsset + let asset: TGMediaEditableItem private let interaction: MediaPickerInteraction? private let imageNode: ImageNode @@ -53,7 +53,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { private var videoDuration: Double? - init(asset: TGMediaAsset, interaction: MediaPickerInteraction?) { + init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?) { self.imageNode = ImageNode() self.imageNode.contentMode = .scaleAspectFill self.imageNode.clipsToBounds = true @@ -91,7 +91,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { if let adjustments = adjustments as? TGVideoEditAdjustments, adjustments.trimApplied() { duration = adjustments.trimEndValue - adjustments.trimStartValue } else { - duration = asset.videoDuration + duration = asset.originalDuration ?? 0.0 } strongSelf.videoDuration = duration @@ -114,6 +114,9 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { } @objc private func tap() { + guard let asset = self.asset as? TGMediaSelectableItem else { + return + } self.interaction?.openSelectedMedia(asset, self.imageNode.image) } @@ -142,14 +145,33 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { if let adjustments = self.interaction?.editingState.adjustments(for: self.asset), adjustments.cropApplied(forAvatar: false) { dimensions = adjustments.cropRect.size } else { - dimensions = self.asset.dimensions + dimensions = self.asset.originalSize ?? CGSize() } let scale = min(2.0, UIScreenScale) let scaledDimensions = dimensions.aspectFilled(CGSize(width: 320.0, height: 320.0)) let targetSize = CGSize(width: scaledDimensions.width * scale, height: scaledDimensions.height * scale) - let originalSignal = assetImage(asset: self.asset.backingAsset, targetSize: targetSize, exact: false) + let originalSignal: Signal + if let asset = self.asset as? TGMediaAsset { + originalSignal = assetImage(asset: asset.backingAsset, targetSize: targetSize, exact: false) + } else { + let asset = self.asset + originalSignal = Signal { subscriber in + let disposable = asset.screenImageSignal?(0.0).start(next: { next in + if let next = next as? UIImage { + subscriber.putNext(next) + } + }, error: { _ in + }, completed: { + subscriber.putCompletion() + }) + + return ActionDisposable { + disposable?.dispose() + } + } + } let imageSignal: Signal = editedSignal |> mapToSignal { result in if let result = result { @@ -166,8 +188,8 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { if self.checkNode == nil, let _ = self.interaction?.selectionState, let theme = self.theme { let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay)) checkNode.valueChanged = { [weak self] value in - if let strongSelf = self, let interaction = strongSelf.interaction { - interaction.toggleSelection(strongSelf.asset, value, true) + if let strongSelf = self, let interaction = strongSelf.interaction, let selectableItem = strongSelf.asset as? TGMediaSelectableItem { + interaction.toggleSelection(selectableItem, value, true) } } self.addSubnode(checkNode) @@ -178,9 +200,9 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { } } - if let interaction = self.interaction, let selectionState = interaction.selectionState, let identifier = self.asset.uniqueIdentifier { - let selected = selectionState.isIdentifierSelected(identifier) - let index = selectionState.index(of: self.asset) + if let interaction = self.interaction, let selectionState = interaction.selectionState, let selectableItem = self.asset as? TGMediaSelectableItem { + let selected = selectionState.isIdentifierSelected(selectableItem.uniqueIdentifier) + let index = selectionState.index(of: selectableItem) if index != NSNotFound { self.checkNode?.content = .counter(Int(index)) } @@ -379,6 +401,7 @@ private class MessageBackgroundNode: ASDisplayNode { final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private let context: AccountContext + private let persistentItems: Bool fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode private let scrollNode: ASScrollNode @@ -398,8 +421,10 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI private var didSetReady = false private var ready = Promise() - init(context: AccountContext) { + init(context: AccountContext, persistentItems: Bool) { self.context = context + self.persistentItems = persistentItems + self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground) self.wallpaperBackgroundNode.backgroundColor = .black self.scrollNode = ASScrollNode() @@ -421,7 +446,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI self.scrollNode.view.panGestureRecognizer.cancelsTouchesInView = true self.scrollNode.view.showsVerticalScrollIndicator = false - self.view.addGestureRecognizer(ReorderingGestureRecognizer(shouldBegin: { [weak self] point in + self.view.addGestureRecognizer(ReorderingGestureRecognizer(animateOnTouch: !self.persistentItems, shouldBegin: { [weak self] point in if let strongSelf = self, !strongSelf.scrollNode.view.isDragging && strongSelf.itemNodes.count > 1 { let point = strongSelf.view.convert(point, to: strongSelf.scrollNode.view) for (_, itemNode) in strongSelf.itemNodes { @@ -611,8 +636,8 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI } } - if let targetNode = targetNode, let targetIndex = self.interaction?.selectionState?.index(of: targetNode.asset) { - self.interaction?.selectionState?.move(itemNode.asset, to: targetIndex) + if let targetNode = targetNode, let sourceItem = itemNode.asset as? TGMediaSelectableItem, let targetItem = targetNode.asset as? TGMediaSelectableItem, let targetIndex = self.interaction?.selectionState?.index(of: targetItem) { + self.interaction?.selectionState?.move(sourceItem, to: targetIndex) } reorderNode.animateCompletion(completion: { [weak reorderNode] in reorderNode?.removeFromSupernode() @@ -647,7 +672,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI var validIds: [String] = [] for item in items { - guard let asset = item as? TGMediaAsset, let identifier = asset.uniqueIdentifier else { + guard let asset = item as? TGMediaEditableItem, let identifier = asset.uniqueIdentifier else { continue } @@ -671,7 +696,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI if let adjustments = self.interaction?.editingState.adjustments(for: asset), adjustments.cropApplied(forAvatar: false) { itemSizes.append(adjustments.cropRect.size) } else { - itemSizes.append(asset.dimensions) + itemSizes.append(asset.originalSize ?? CGSize()) } } @@ -981,9 +1006,12 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer { private var initialLocation: CGPoint? private var longPressTimer: SwiftSignalKit.Timer? + var animateOnTouch = true + private var itemNode: MediaPickerSelectedItemNode? - public init(shouldBegin: @escaping (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, itemNode: MediaPickerSelectedItemNode?), willBegin: @escaping (CGPoint) -> Void, began: @escaping (MediaPickerSelectedItemNode) -> Void, ended: @escaping (CGPoint?) -> Void, moved: @escaping (CGPoint) -> Void) { + public init(animateOnTouch: Bool, shouldBegin: @escaping (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, itemNode: MediaPickerSelectedItemNode?), willBegin: @escaping (CGPoint) -> Void, began: @escaping (MediaPickerSelectedItemNode) -> Void, ended: @escaping (CGPoint?) -> Void, moved: @escaping (CGPoint) -> Void) { + self.animateOnTouch = animateOnTouch self.shouldBegin = shouldBegin self.willBegin = willBegin self.began = began @@ -1048,7 +1076,7 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer { if let location = touches.first?.location(in: self.view) { let (allowed, requiresLongPress, itemNode) = self.shouldBegin(location) if allowed { - if let itemNode = itemNode { + if let itemNode = itemNode, self.animateOnTouch { itemNode.layer.animateScale(from: 1.0, to: 0.98, duration: 0.2, delay: 0.1) } self.itemNode = itemNode diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 01fc3d2eed..f022709509 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -302,6 +302,7 @@ swift_library( "//submodules/TelegramUI/Components/ChatTitleView", "//submodules/InviteLinksUI:InviteLinksUI", "//submodules/TelegramUI/Components/NotificationPeerExceptionController", + "//submodules/MediaPasteboardUI:MediaPasteboardUI", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index b3b72a9774..cdb76f7a90 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -84,6 +84,7 @@ import EntityKeyboard import ChatTitleView import EmojiStatusComponent import ChatTimerScreen +import MediaPasteboardUI #if DEBUG import os.signpost @@ -12609,11 +12610,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(actionSheet, in: .window(.root)) } - private func presentMediaPicker(saveEditedPhotos: Bool, bannedSendMedia: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { + private func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil), saveEditedPhotos: Bool, bannedSendMedia: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return } - let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), chatLocation: self.chatLocation, bannedSendMedia: bannedSendMedia, saveEditedPhotos: saveEditedPhotos) + let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), chatLocation: self.chatLocation, bannedSendMedia: bannedSendMedia, subject: subject, saveEditedPhotos: saveEditedPhotos) let mediaPickerContext = controller.mediaPickerContext controller.openCamera = { [weak self] cameraView in self?.openCamera(cameraView: cameraView) @@ -13699,43 +13700,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G |> deliverOnMainQueue).start(next: { [weak self] settings in if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { strongSelf.chatDisplayNode.dismissInput() - let _ = presentLegacyPasteMenu(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, updatedPresentationData: strongSelf.updatedPresentationData, images: images, presentSchedulePicker: { [weak self] done in - if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in - if let strongSelf = self { - done(time) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { - strongSelf.openScheduledMessages() - } - } - }) - } - }, presentTimerPicker: { [weak self] done in - if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { time in - done(time) - }) - } - }, sendMessagesWithSignals: { signals, silentPosting, scheduleTime in - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) - }, presentStickers: { [weak self] completion in - if let strongSelf = self { - let controller = DrawingStickersScreen(context: strongSelf.context, selectSticker: { fileReference, view, rect in - completion(fileReference.media, fileReference.media.isAnimatedSticker || fileReference.media.isVideoSticker, view, rect) - return true - }) - strongSelf.present(controller, in: .window(.root)) - return controller - } else { - return nil - } - }, getCaptionPanelView: { [weak self] in - return self?.getCaptionPanelView() - }, present: { [weak self] controller, arguments in - if let strongSelf = self { - strongSelf.present(controller, in: .window(.root), with: arguments) - } - }, initialLayout: strongSelf.validLayout) + + let controller = mediaPasteboardScreen( + context: strongSelf.context, + updatedPresentationData: strongSelf.updatedPresentationData, + peer: EnginePeer(peer), + subjects: images, + presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendMedia, present in + if let strongSelf = self { + strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendMedia: bannedSendMedia, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + }) + } + }, + getSourceRect: nil + ) + controller.navigationPresentation = .flatModal + strongSelf.push(controller) } }) } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 94dc29f0a4..34801b396b 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -1286,7 +1286,7 @@ private final class WebAppContextReferenceContentSource: ContextReferenceContent } public func standaloneWebAppController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, openUrl: @escaping (String) -> Void, getInputContainerNode: @escaping () -> (CGFloat, ASDisplayNode, () -> AttachmentController.InputPanelTransition?)? = { return nil }, completion: @escaping () -> Void = {}, willDismiss: @escaping () -> Void = {}, didDismiss: @escaping () -> Void = {}, getNavigationController: @escaping () -> NavigationController? = { return nil }, getSourceRect: (() -> CGRect?)? = nil) -> ViewController { - let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.fromMenu, makeEntityInputView: { + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.fromMenu, hasTextInput: false, makeEntityInputView: { return nil }) controller.getInputContainerNode = getInputContainerNode