New media pasteboard screen

This commit is contained in:
Ilya Laktyushin 2022-10-25 19:58:51 +03:00
parent f8c5bf2a6b
commit 1b2379fe8c
30 changed files with 661 additions and 2252 deletions

View File

@ -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?";

View File

@ -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<PresentationData, NoError>)? = nil, chatLocation: ChatLocation, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery, fromMenu: Bool = false, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = 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
}

View File

@ -83,9 +83,6 @@
#import <LegacyComponents/TGChannelAdminRights.h>
#import <LegacyComponents/TGChannelBannedRights.h>
#import <LegacyComponents/TGCheckButtonView.h>
#import <LegacyComponents/TGClipboardGalleryMixin.h>
#import <LegacyComponents/TGClipboardGalleryPhotoItem.h>
#import <LegacyComponents/TGClipboardMenu.h>
#import <LegacyComponents/TGContactMediaAttachment.h>
#import <LegacyComponents/TGConversation.h>
#import <LegacyComponents/TGDataResource.h>

View File

@ -1,31 +0,0 @@
#import <Foundation/Foundation.h>
#import <LegacyComponents/TGModernGalleryController.h>
#import <LegacyComponents/LegacyComponentsContext.h>
@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<LegacyComponentsContext>)context image:(UIImage *)image images:(NSArray *)images parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext hasCaptions:(bool)hasCaptions hasTimer:(bool)hasTimer hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder recipientName:(NSString *)recipientName;
- (void)present;
@end

View File

@ -1,10 +0,0 @@
#import <LegacyComponents/TGModernGallerySelectableItem.h>
#import <LegacyComponents/TGModernGalleryEditableItem.h>
@interface TGClipboardGalleryPhotoItem : NSObject <TGModernGallerySelectableItem, TGModernGalleryEditableItem>
@property (nonatomic, strong) UIImage *image;
- (instancetype)initWithImage:(UIImage *)image;
@end

View File

@ -1,20 +0,0 @@
#import <Foundation/Foundation.h>
#import <LegacyComponents/LegacyComponentsContext.h>
@class TGViewController;
@class TGMenuSheetController;
@class TGMediaSelectionContext;
@class TGMediaEditingContext;
@protocol TGMediaSelectableItem;
@protocol TGPhotoPaintStickersContext;
@interface TGClipboardMenu : NSObject
+ (TGMenuSheetController *)presentInParentController:(TGViewController *)parentController context:(id<LegacyComponentsContext>)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<TGPhotoPaintStickersContext>)stickersContext presentScheduleController:(void (^)(void(^)(int32_t)))presentScheduleController presentTimerController:(void (^)(void(^)(int32_t)))presentTimerController completed:(void (^)(TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> 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<TGMediaSelectableItem>)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *))descriptionGenerator;
@end

View File

@ -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<TGMediaSelectableItem>)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *, NSString *))descriptionGenerator;
@end

View File

@ -14,6 +14,7 @@
@class TGMediaSelectionContext;
@protocol TGMediaSelectableItem;
@protocol TGModernGalleryEditableItem;
@protocol TGPhotoPaintStickersContext;

View File

@ -1,281 +0,0 @@
#import "TGClipboardGalleryMixin.h"
#import <LegacyComponents/LegacyComponents.h>
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGModernGalleryController.h>
#import "TGClipboardGalleryPhotoItem.h"
#import "TGClipboardGalleryModel.h"
#import <LegacyComponents/TGMediaEditingContext.h>
#import <LegacyComponents/TGMediaSelectionContext.h>
#import <LegacyComponents/TGMediaAsset.h>
#import <LegacyComponents/TGMediaAssetFetchResult.h>
#import <LegacyComponents/TGMediaAssetMomentList.h>
#import <LegacyComponents/TGMediaAssetMoment.h>
#import "TGMediaPickerSendActionSheetController.h"
@interface TGClipboardGalleryMixin ()
{
TGMediaEditingContext *_editingContext;
bool _asFile;
__weak TGViewController *_parentController;
__weak TGModernGalleryController *_galleryController;
TGModernGalleryController *_strongGalleryController;
NSUInteger _itemsLimit;
id<LegacyComponentsContext> _context;
}
@property (nonatomic, weak, readonly) TGClipboardGalleryModel *galleryModel;
@end
@implementation TGClipboardGalleryMixin
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context image:(UIImage *)image images:(NSArray *)images parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id<TGPhotoPaintStickersContext>)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<TGMediaEditableItem> editableItem, id<TGMediaEditAdjustments> 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<TGMediaSelectableItem>)editableItem selected:true];
};
model.didFinishEditingItem = ^(id<TGMediaEditableItem> editableItem, __unused id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage)
{
[editingContext setImage:resultImage thumbnailImage:thumbnailImage forItem:editableItem synchronous:false];
};
model.didFinishRenderingFullSizeImage = ^(id<TGMediaEditableItem> editableItem, UIImage *resultImage)
{
[editingContext setFullSizeImage:resultImage forItem:editableItem];
};
model.saveItemCaption = ^(id<TGMediaEditableItem> editableItem, NSAttributedString *caption)
{
[editingContext setCaption:caption forItem:editableItem];
if (selectionContext != nil && caption.length > 0 && [editableItem conformsToProtocol:@protocol(TGMediaSelectableItem)])
[selectionContext setItem:(id<TGMediaSelectableItem>)editableItem selected:true];
};
[model.interfaceView updateSelectionInterface:selectionContext.count counterVisible:(selectionContext.count > 0) animated:false];
model.interfaceView.donePressed = ^(id<TGModernGalleryItem> 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<TGModernGalleryItem> 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<TGMediaEditableItem> 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<TGModernGalleryItem> item)
{
__strong TGClipboardGalleryMixin *strongSelf = weakSelf;
if (strongSelf != nil && strongSelf.itemFocused != nil)
strongSelf.itemFocused((TGClipboardGalleryPhotoItem *)item);
};
modernGallery.beginTransitionIn = ^UIView *(id<TGModernGalleryItem> 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<TGModernGalleryItem> item, __unused TGModernGalleryItemView *itemView)
{
__strong TGClipboardGalleryMixin *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf->_galleryModel.interfaceView setSelectedItemsModel:strongSelf->_galleryModel.selectedItemsModel];
};
modernGallery.beginTransitionOut = ^UIView *(id<TGModernGalleryItem> 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

View File

@ -1,31 +0,0 @@
#import <LegacyComponents/TGModernGalleryModel.h>
#import <LegacyComponents/TGMediaPickerGalleryInterfaceView.h>
#import <LegacyComponents/TGModernGalleryController.h>
#import <LegacyComponents/TGPhotoEditorController.h>
#import <LegacyComponents/LegacyComponentsContext.h>
@interface TGClipboardGalleryModel : TGModernGalleryModel
@property (nonatomic, copy) void (^willFinishEditingItem)(id<TGMediaEditableItem> item, id<TGMediaEditAdjustments> adjustments, id temporaryRep, bool hasChanges);
@property (nonatomic, copy) void (^didFinishEditingItem)(id<TGMediaEditableItem>item, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage);
@property (nonatomic, copy) void (^didFinishRenderingFullSizeImage)(id<TGMediaEditableItem> item, UIImage *fullSizeImage);
@property (nonatomic, copy) void (^saveItemCaption)(id<TGMediaEditableItem> 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<TGPhotoPaintStickersContext> stickersContext;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context images:(NSArray *)images focusIndex:(NSUInteger)focusIndex selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext hasCaptions:(bool)hasCaptions hasTimer:(bool)hasTimer hasSelectionPanel:(bool)hasSelectionPanel recipientName:(NSString *)recipientName;
@end

View File

@ -1,500 +0,0 @@
#import "TGClipboardGalleryModel.h"
#import "TGMediaPickerGallerySelectedItemsModel.h"
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGModernGalleryController.h>
#import <LegacyComponents/TGModernGalleryItem.h>
#import "TGModernGallerySelectableItem.h"
#import "TGModernGalleryEditableItem.h"
#import "TGModernGalleryEditableItemView.h"
#import <LegacyComponents/TGModernGalleryZoomableItemView.h>
#import "TGClipboardGalleryPhotoItem.h"
#import "TGModernMediaListItem.h"
#import "TGModernMediaListSelectableItem.h"
#import <LegacyComponents/PGPhotoEditorValues.h>
#import <LegacyComponents/TGSecretTimerMenu.h>
@interface TGClipboardGalleryModel ()
{
id<TGModernGalleryEditableItem> _itemBeingEdited;
TGMediaEditingContext *_editingContext;
id<LegacyComponentsContext> _context;
}
@property (nonatomic, weak) TGPhotoEditorController *editorController;
@end
@implementation TGClipboardGalleryModel
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context images:(NSArray *)images focusIndex:(NSUInteger)focusIndex selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id<TGPhotoPaintStickersContext>)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<TGModernGalleryEditableItem>)controller.currentItem tab:tab];
}];
_interfaceView.photoStripItemSelected = ^(NSInteger index)
{
__strong TGClipboardGalleryModel *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf setCurrentItemWithIndex:index];
};
_interfaceView.captionSet = ^(id<TGModernGalleryItem> 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<TGModernGalleryEditableItem>)item).editableMediaItem, caption);
};
_interfaceView.timerRequested = ^
{
__strong TGClipboardGalleryModel *strongSelf = weakSelf;
if (strongSelf == nil)
return;
__strong TGModernGalleryController *controller = strongSelf.controller;
id<TGMediaEditableItem> editableMediaItem = ((id<TGModernGalleryEditableItem>)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<TGMediaSelectableItem> selectableItem = nil;
if ([controller.currentItem conformsToProtocol:@protocol(TGModernGallerySelectableItem)])
{
selectableItem = ((id<TGModernGallerySelectableItem>)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<TGMediaSelectableItem>)item direction:(TGModernGalleryScrollAnimationDirection)direction
{
if (![(id)item conformsToProtocol:@protocol(TGMediaSelectableItem)])
return;
id<TGMediaSelectableItem> targetSelectableItem = (id<TGMediaSelectableItem>)item;
__block NSUInteger newIndex = NSNotFound;
[self.items enumerateObjectsUsingBlock:^(id<TGModernGalleryItem> galleryItem, NSUInteger idx, BOOL *stop)
{
if ([galleryItem conformsToProtocol:@protocol(TGModernGallerySelectableItem)])
{
id<TGMediaSelectableItem> selectableItem = ((id<TGModernGallerySelectableItem>)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<TGModernGallerySelectableItem> currentGalleryItem = (id<TGModernGallerySelectableItem>)galleryController.currentItem;
__block NSUInteger currentSelectedItemIndex = NSNotFound;
[_selectedItemsModel.items enumerateObjectsUsingBlock:^(id<TGMediaSelectableItem> item, NSUInteger index, BOOL *stop)
{
if ([item.uniqueIdentifier isEqualToString:currentGalleryItem.selectableMediaItem.uniqueIdentifier])
{
currentSelectedItemIndex = index;
*stop = true;
}
}];
id<TGMediaSelectableItem> item = _selectedItemsModel.items[index];
TGModernGalleryScrollAnimationDirection direction = TGModernGalleryScrollAnimationDirectionLeft;
if (currentSelectedItemIndex < index)
direction = TGModernGalleryScrollAnimationDirectionRight;
[self setCurrentItem:item direction:direction];
}
- (UIView <TGModernGalleryInterfaceView> *)createInterfaceView
{
return _interfaceView;
}
- (UIView *)referenceViewForItem:(id<TGModernGalleryItem>)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 <TGModernGalleryEditableItemView> *)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 <TGModernGalleryEditableItemView> *)itemView setItem:_itemBeingEdited synchronously:true];
if (self.itemsUpdated != nil)
self.itemsUpdated(_itemBeingEdited);
}
}
}
}
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)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<TGMediaEditAdjustments> 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)(id<TGMediaEditableItem>item, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage) = self.didFinishEditingItem;
controller.didFinishEditing = ^(id<TGMediaEditAdjustments> 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<TGMediaEditableItem> editableItem)
{
return [editableItem thumbnailImageSignal];
};
controller.requestOriginalScreenSizeImage = ^SSignal *(id<TGMediaEditableItem> editableItem, NSTimeInterval position)
{
return [editableItem screenImageSignal:position];
};
controller.requestOriginalFullSizeImage = ^SSignal *(id<TGMediaEditableItem> 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<TGModernGalleryItem>)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

View File

@ -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<TGMediaSelectableItem>)selectableMediaItem
{
return self.image;
}
- (id<TGMediaEditableItem>)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

View File

@ -1,11 +0,0 @@
#import <LegacyComponents/LegacyComponents.h>
#import "TGModernGalleryEditableItemView.h"
#import "TGModernGalleryImageItemImageView.h"
@interface TGClipboardGalleryPhotoItemView : TGModernGalleryZoomableItemView <TGModernGalleryEditableItemView>
@property (nonatomic) CGSize imageSize;
@property (nonatomic, strong) TGModernGalleryImageItemImageView *imageView;
@end

View File

@ -1,208 +0,0 @@
#import "TGClipboardGalleryPhotoItemView.h"
#import "LegacyComponentsInternal.h"
#import "TGFont.h"
#import "TGStringUtils.h"
#import <LegacyComponents/TGMediaAssetImageSignals.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import <LegacyComponents/TGModernGalleryZoomableScrollView.h>
#import <LegacyComponents/TGMessageImageViewOverlayView.h>
#import <LegacyComponents/TGImageView.h>
#import <LegacyComponents/TGMediaSelectionContext.h>
#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<TGModernGallerySelectableItem>)self.item).selectionContext;
id<TGMediaSelectableItem> item = ((id<TGModernGallerySelectableItem>)self.item).selectableMediaItem;
[selectionContext toggleItemSelection:item animated:true sender:nil success:nil];
}
else
{
id<TGModernGalleryItemViewDelegate> 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

View File

@ -1,226 +0,0 @@
#import "TGClipboardMenu.h"
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGStringUtils.h>
#import <LegacyComponents/TGMenuSheetController.h>
#import "TGClipboardPreviewItemView.h"
@implementation TGClipboardMenu
+ (TGMenuSheetController *)presentInParentController:(TGViewController *)parentController context:(id<LegacyComponentsContext>)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<TGPhotoPaintStickersContext>)stickersContext presentScheduleController:(void (^)(void(^)(int32_t)))presentScheduleController presentTimerController:(void (^)(void(^)(int32_t)))presentTimerController completed:(void (^)(TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> 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<TGMediaSelectableItem>)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<TGMediaEditAdjustments> 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

View File

@ -1,23 +0,0 @@
#import <UIKit/UIKit.h>
#import <SSignalKit/SSignalKit.h>
#import <LegacyComponents/TGImageView.h>
@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;

View File

@ -1,228 +0,0 @@
#import "TGClipboardPreviewCell.h"
#import <LegacyComponents/TGMediaEditingContext.h>
#import <LegacyComponents/TGMediaSelectionContext.h>
#import <LegacyComponents/TGCheckButtonView.h>
#import <LegacyComponents/TGModernGalleryTransitionView.h>
NSString *const TGClipboardPreviewCellIdentifier = @"TGClipboardPreviewCell";
const CGFloat TGClipboardCellCornerRadius = 5.5f;
@interface TGClipboardPreviewCell () <TGModernGalleryTransitionView>
{
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<TGMediaSelectableItem>)_image] animated:false];
__weak TGClipboardPreviewCell *weakSelf = self;
[_itemSelectedDisposable setDisposable:[[self.selectionContext itemInformativeSelectedSignal:(id<TGMediaSelectableItem>)_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<TGMediaSelectableItem>)_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

View File

@ -1,34 +0,0 @@
#import <LegacyComponents/LegacyComponents.h>
@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<TGPhotoPaintStickersContext> 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<LegacyComponentsContext>)context images:(NSArray *)images allowGrouping:(bool)allowGrouping;
- (void)setCollapsed:(bool)collapsed animated:(bool)animated;
@end

View File

@ -1,299 +0,0 @@
#import "TGClipboardPreviewItemView.h"
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGMediaEditingContext.h>
#import <LegacyComponents/TGMediaSelectionContext.h>
#import <LegacyComponents/TGClipboardGalleryMixin.h>
#import "TGClipboardPreviewCell.h"
const CGFloat TGClipboardPreviewMaxWidth = 250.0f;
const CGFloat TGClipboardPreviewCellHeight = 198.0f;
const CGFloat TGClipboardPreviewEdgeInset = 8.0f;
@interface TGClipboardPreviewItemView () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
{
id<LegacyComponentsContext> _context;
NSArray *_images;
UICollectionView *_collectionView;
UICollectionViewFlowLayout *_collectionLayout;
TGClipboardGalleryMixin *_galleryMixin;
UIImage *_hiddenItem;
SMetaDisposable *_selectionChangedDisposable;
bool _collapsed;
UIImage *_cornersImage;
}
@end
@implementation TGClipboardPreviewItemView
- (instancetype)initWithContext:(id<LegacyComponentsContext>)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<TGMediaSelectableItem>)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

View File

@ -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<TGMediaSelectableItem>)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<TGMediaEditAdjustments> 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<TGMediaEditAdjustments> 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

View File

@ -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<PresentationData, NoError>), 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
}

View File

@ -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",
],
)

View File

@ -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<PresentationData, NoError>)? = 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
}

View File

@ -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

View File

@ -15,6 +15,7 @@ import PhotoResources
enum MediaPickerGridItemContent: Equatable {
case asset(PHFetchResult<PHAsset>, 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<PHAsset>, 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<UIImage?, NoError> { 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<UIImage?, NoError> { 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<UIImage, NoError> = assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false)
// let imageSignal: Signal<UIImage?, NoError> = 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<PHAsset>, index: Int, theme: PresentationTheme) {
self.interaction = interaction

View File

@ -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<PHAsset>?, 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<State, NoError> 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<State, NoError>
switch controller.subject {
case let .assets(collection):
updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess())
|> mapToSignal { mediaAccess, cameraAccess -> Signal<State, NoError> 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<PresentationData, NoError>)? = 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<PresentationData, NoError>)? = 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<UIImage?, NoError>
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<UIImage?, NoError> { 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

View File

@ -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<UIImage?, NoError>
if let asset = self.asset as? TGMediaAsset {
originalSignal = assetImage(asset: asset.backingAsset, targetSize: targetSize, exact: false)
} else {
let asset = self.asset
originalSignal = Signal<UIImage?, NoError> { 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<UIImage?, NoError> = 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<Bool>()
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

View File

@ -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,

View File

@ -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)
}
})
}

View File

@ -1286,7 +1286,7 @@ private final class WebAppContextReferenceContentSource: ContextReferenceContent
}
public func standaloneWebAppController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = 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