diff --git a/submodules/LegacyComponents/LegacyComponents/Resources/LegacyComponentsResources.bundle/MediaSchedule@2x.png b/submodules/LegacyComponents/LegacyComponents/Resources/LegacyComponentsResources.bundle/MediaSchedule@2x.png new file mode 100644 index 0000000000..2829293819 Binary files /dev/null and b/submodules/LegacyComponents/LegacyComponents/Resources/LegacyComponentsResources.bundle/MediaSchedule@2x.png differ diff --git a/submodules/LegacyComponents/LegacyComponents/Resources/LegacyComponentsResources.bundle/MediaSchedule@3x.png b/submodules/LegacyComponents/LegacyComponents/Resources/LegacyComponentsResources.bundle/MediaSchedule@3x.png new file mode 100644 index 0000000000..f4eb832ef8 Binary files /dev/null and b/submodules/LegacyComponents/LegacyComponents/Resources/LegacyComponentsResources.bundle/MediaSchedule@3x.png differ diff --git a/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.h b/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.h index 83a96e3f62..3f218c9302 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.h +++ b/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.h @@ -2,6 +2,8 @@ #import #import +#import + @class TGMediaSelectionContext; @class TGMediaEditingContext; @class TGSuggestionContext; @@ -29,12 +31,13 @@ @property (nonatomic) bool inhibitMute; @property (nonatomic) bool disableStickers; @property (nonatomic) bool hasSilentPosting; +@property (nonatomic) bool hasSchedule; @property (nonatomic, strong) NSArray *underlyingViews; @property (nonatomic, assign) bool openEditor; @property (nonatomic, copy) void (^cameraPressed)(TGAttachmentCameraView *cameraView); -@property (nonatomic, copy) void (^sendPressed)(TGMediaAsset *currentItem, bool asFiles, bool silentPosting); +@property (nonatomic, copy) void (^sendPressed)(TGMediaAsset *currentItem, bool asFiles, TGMediaPickerGalleryCompletionMode mode); @property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image); @property (nonatomic, copy) void (^editorOpened)(void); diff --git a/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.m b/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.m index 549af5be97..8f78600fb8 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.m +++ b/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.m @@ -774,14 +774,14 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; strongSelf->_galleryMixin = nil; }; - mixin.completeWithItem = ^(TGMediaPickerGalleryItem *item, bool silentPosting) + mixin.completeWithItem = ^(TGMediaPickerGalleryItem *item, TGMediaPickerGalleryCompletionMode mode) { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf != nil && strongSelf.sendPressed != nil) { if (strongSelf->_selectionContext.allowGrouping) [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"]; - strongSelf.sendPressed(item.asset, strongSelf.asFile, silentPosting); + strongSelf.sendPressed(item.asset, strongSelf.asFile, mode); } }; @@ -801,7 +801,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; if ([cell isKindOfClass:[TGAttachmentAssetCell class]]) thumbnailImage = cell.imageView.image; - TGMediaPickerModernGalleryMixin *mixin = [[TGMediaPickerModernGalleryMixin alloc] initWithContext:_context item:asset fetchResult:_fetchResult parentController:self.parentController thumbnailImage:thumbnailImage selectionContext:_selectionContext editingContext:_editingContext suggestionContext:self.suggestionContext hasCaptions:(_allowCaptions && !_forProfilePhoto) allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:_inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:self.asFile itemsLimit:TGAttachmentDisplayedAssetLimit recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting]; + TGMediaPickerModernGalleryMixin *mixin = [[TGMediaPickerModernGalleryMixin alloc] initWithContext:_context item:asset fetchResult:_fetchResult parentController:self.parentController thumbnailImage:thumbnailImage selectionContext:_selectionContext editingContext:_editingContext suggestionContext:self.suggestionContext hasCaptions:(_allowCaptions && !_forProfilePhoto) allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:_inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:self.asFile itemsLimit:TGAttachmentDisplayedAssetLimit recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule]; __weak TGAttachmentCarouselItemView *weakSelf = self; mixin.thumbnailSignalForItem = ^SSignal *(id item) diff --git a/submodules/LegacyComponents/LegacyComponents/TGCameraController.h b/submodules/LegacyComponents/LegacyComponents/TGCameraController.h index bc792c72fb..2169853fb3 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGCameraController.h +++ b/submodules/LegacyComponents/LegacyComponents/TGCameraController.h @@ -36,6 +36,7 @@ typedef enum { @property (nonatomic, assign) bool inhibitMute; @property (nonatomic, assign) bool hasTimer; @property (nonatomic, assign) bool hasSilentPosting; +@property (nonatomic, assign) bool hasSchedule; @property (nonatomic, strong) TGSuggestionContext *suggestionContext; @property (nonatomic, assign) bool shortcut; diff --git a/submodules/LegacyComponents/LegacyComponents/TGCameraController.m b/submodules/LegacyComponents/LegacyComponents/TGCameraController.m index f811b49866..6c104bdb28 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGCameraController.m +++ b/submodules/LegacyComponents/LegacyComponents/TGCameraController.m @@ -1621,6 +1621,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus controller.suggestionContext = self.suggestionContext; controller.hasTimer = self.hasTimer; controller.hasSilentPosting = self.hasSilentPosting; + controller.hasSchedule = self.hasSchedule; __weak TGCameraPhotoPreviewController *weakController = controller; controller.beginTransitionIn = ^CGRect diff --git a/submodules/LegacyComponents/LegacyComponents/TGCameraPhotoPreviewController.h b/submodules/LegacyComponents/LegacyComponents/TGCameraPhotoPreviewController.h index 2824bd59de..19ad139c2a 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGCameraPhotoPreviewController.h +++ b/submodules/LegacyComponents/LegacyComponents/TGCameraPhotoPreviewController.h @@ -24,6 +24,7 @@ @property (nonatomic, assign) bool shouldStoreAssets; @property (nonatomic, assign) bool hasTimer; @property (nonatomic, assign) bool hasSilentPosting; +@property (nonatomic, assign) bool hasSchedule; - (instancetype)initWithContext:(id)context image:(UIImage *)image metadata:(PGCameraShotMetadata *)metadata recipientName:(NSString *)recipientName saveCapturedMedia:(bool)saveCapturedMedia saveEditedPhotos:(bool)saveEditedPhotos; - (instancetype)initWithContext:(id)context image:(UIImage *)image metadata:(PGCameraShotMetadata *)metadata recipientName:(NSString *)recipientName backButtonTitle:(NSString *)backButtonTitle doneButtonTitle:(NSString *)doneButtonTitle saveCapturedMedia:(bool)saveCapturedMedia saveEditedPhotos:(bool)saveEditedPhotos; diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.h b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.h index 0abe8f975d..2264bdf0f9 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.h +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.h @@ -4,6 +4,8 @@ #import #import +#import + @class TGMediaAssetsPickerController; @class TGViewController; @@ -56,6 +58,7 @@ typedef enum @property (nonatomic, assign) bool onlyCrop; @property (nonatomic, assign) bool inhibitMute; @property (nonatomic, assign) bool hasSilentPosting; +@property (nonatomic, assign) bool hasSchedule; @property (nonatomic, assign) bool liveVideoUploadEnabled; @property (nonatomic, assign) bool shouldShowFileTipIfNeeded; @@ -64,7 +67,7 @@ typedef enum @property (nonatomic, copy) NSDictionary *(^descriptionGenerator)(id, NSString *, NSArray *, NSString *); @property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image); -@property (nonatomic, copy) void (^completionBlock)(NSArray *signals, bool silentPosting); +@property (nonatomic, copy) void (^completionBlock)(NSArray *signals, TGMediaPickerGalleryCompletionMode mode); @property (nonatomic, copy) void (^singleCompletionBlock)(id item, TGMediaEditingContext *editingContext); @property (nonatomic, copy) void (^dismissalBlock)(void); @property (nonatomic, copy) void (^selectionBlock)(TGMediaAsset *asset, UIImage *); @@ -82,7 +85,7 @@ typedef enum - (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *))descriptionGenerator; - (void)completeWithAvatarImage:(UIImage *)image; -- (void)completeWithCurrentItem:(TGMediaAsset *)currentItem silentPosting:(bool)silentPosting; +- (void)completeWithCurrentItem:(TGMediaAsset *)currentItem mode:(TGMediaPickerGalleryCompletionMode)mode; - (void)dismiss; diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.m b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.m index 89a4ed425f..36417330bc 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.m @@ -2,7 +2,6 @@ #import "LegacyComponentsInternal.h" -#import "TGMediaAssetsMomentsController.h" #import "TGMediaGroupsController.h" #import @@ -122,6 +121,7 @@ pickerController.hasTimer = strongController.hasTimer; pickerController.onlyCrop = strongController.onlyCrop; pickerController.hasSilentPosting = strongController.hasSilentPosting; + pickerController.hasSchedule = strongController.hasSchedule; [strongController pushViewController:pickerController animated:true]; }; [groupsController loadViewIfNeeded]; @@ -207,6 +207,12 @@ self.pickerController.hasSilentPosting = hasSilentPosting; } +- (void)setHasSchedule:(bool)hasSchedule +{ + _hasSchedule = hasSchedule; + self.pickerController.hasSchedule = hasSchedule; +} + - (void)setOnlyCrop:(bool)onlyCrop { _onlyCrop = onlyCrop; @@ -451,7 +457,7 @@ { __strong TGMediaAssetsController *strongSelf = weakSelf; if (strongSelf != nil) - [strongSelf completeWithCurrentItem:nil silentPosting:false]; + [strongSelf completeWithCurrentItem:nil mode:TGMediaPickerGalleryCompletionModeGeneric]; }; } @@ -532,12 +538,12 @@ self.avatarCompletionBlock(image); } -- (void)completeWithCurrentItem:(TGMediaAsset *)currentItem silentPosting:(bool)silentPosting +- (void)completeWithCurrentItem:(TGMediaAsset *)currentItem mode:(TGMediaPickerGalleryCompletionMode)mode { if (self.completionBlock != nil) { NSArray *signals = [self resultSignalsWithCurrentItem:currentItem descriptionGenerator:self.descriptionGenerator]; - self.completionBlock(signals, silentPosting); + self.completionBlock(signals, mode); } else if (self.singleCompletionBlock != nil) { diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsPickerController.m b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsPickerController.m index 77b0799cb5..acad64e531 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsPickerController.m +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsPickerController.m @@ -10,6 +10,8 @@ #import "TGMediaAssetsVideoCell.h" #import "TGMediaAssetsGifCell.h" +#import + #import #import #import @@ -309,19 +311,19 @@ strongSelf->_galleryMixin = nil; }; - mixin.completeWithItem = ^(TGMediaPickerGalleryItem *item, bool silentPosting) + mixin.completeWithItem = ^(TGMediaPickerGalleryItem *item, TGMediaPickerGalleryCompletionMode mode) { __strong TGMediaAssetsPickerController *strongSelf = weakSelf; if (strongSelf == nil) return; - [(TGMediaAssetsController *)strongSelf.navigationController completeWithCurrentItem:item.asset silentPosting:silentPosting]; + [(TGMediaAssetsController *)strongSelf.navigationController completeWithCurrentItem:item.asset mode:mode]; }; } - (TGMediaPickerModernGalleryMixin *)_galleryMixinForContext:(id)context item:(id)item thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities inhibitDocumentCaptions:(bool)inhibitDocumentCaptions asFile:(bool)asFile { - return [[TGMediaPickerModernGalleryMixin alloc] initWithContext:context item:item fetchResult:_fetchResult parentController:self thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:asFile itemsLimit:0 recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting]; + return [[TGMediaPickerModernGalleryMixin alloc] initWithContext:context item:item fetchResult:_fetchResult parentController:self thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:asFile itemsLimit:0 recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule]; } - (TGMediaPickerModernGalleryMixin *)galleryMixinForIndexPath:(NSIndexPath *)indexPath previewMode:(bool)previewMode outAsset:(TGMediaAsset **)outAsset diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsUtils.h b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsUtils.h index b4d5ed7c8e..993fedd8a3 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsUtils.h +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsUtils.h @@ -4,6 +4,12 @@ @class TGMediaAsset; @class TGMediaSelectionContext; +typedef NS_ENUM(NSUInteger, TGMediaPickerGalleryCompletionMode) { + TGMediaPickerGalleryCompletionModeGeneric, + TGMediaPickerGalleryCompletionModeSilent, + TGMediaPickerGalleryCompletionModeSchedule +}; + @interface TGMediaAssetsPreheatMixin : NSObject @property (nonatomic, copy) NSInteger (^assetCount)(void); diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaGroupsController.m b/submodules/LegacyComponents/LegacyComponents/TGMediaGroupsController.m index 6a74569f59..eeaac76abf 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaGroupsController.m +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaGroupsController.m @@ -6,7 +6,6 @@ #import #import "TGMediaAssetsPickerController.h" -#import "TGMediaAssetsMomentsController.h" #import diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerController.h b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerController.h index 98a355ba8e..cf734aa4f2 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerController.h +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerController.h @@ -28,6 +28,7 @@ @property (nonatomic, assign) bool inhibitMute; @property (nonatomic, strong) NSString *recipientName; @property (nonatomic, assign) bool hasSilentPosting; +@property (nonatomic, assign) bool hasSchedule; @property (nonatomic, strong) TGMediaAssetsPallete *pallete; diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerGalleryModel.h b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerGalleryModel.h index 0d590719df..40d8da8921 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerGalleryModel.h +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerGalleryModel.h @@ -5,6 +5,7 @@ #import +#import #import @class TGModernGalleryController; diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.h b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.h index 7a2dfbdf6d..8e4401c5c8 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.h +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.h @@ -22,14 +22,14 @@ @property (nonatomic, copy) void (^didTransitionOut)(); @property (nonatomic, copy) UIView *(^referenceViewForItem)(TGMediaPickerGalleryItem *); -@property (nonatomic, copy) void (^completeWithItem)(TGMediaPickerGalleryItem *item, bool silentPosting); +@property (nonatomic, copy) void (^completeWithItem)(TGMediaPickerGalleryItem *item, TGMediaPickerGalleryCompletionMode mode); @property (nonatomic, copy) void (^editorOpened)(void); @property (nonatomic, copy) void (^editorClosed)(void); -- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting; +- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule; -- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting; +- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule; - (void)present; - (void)updateWithFetchResult:(TGMediaAssetFetchResult *)fetchResult; diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.m b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.m index c14a6c4602..29960e53d3 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.m +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.m @@ -39,17 +39,17 @@ @implementation TGMediaPickerModernGalleryMixin -- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting +- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule { - return [self initWithContext:context item:item fetchResult:fetchResult momentList:nil parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:recipientName hasSilentPosting: hasSilentPosting]; + return [self initWithContext:context item:item fetchResult:fetchResult momentList:nil parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:recipientName hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule]; } -- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting +- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule { - return [self initWithContext:context item:item fetchResult:nil momentList:momentList parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:nil hasSilentPosting: hasSilentPosting]; + return [self initWithContext:context item:item fetchResult:nil momentList:momentList parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:nil hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule]; } -- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting +- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule { self = [super init]; if (self != nil) @@ -141,13 +141,13 @@ model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item) { __strong TGMediaPickerModernGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil || !hasSilentPosting) + if (strongSelf == nil || !(hasSilentPosting || hasSchedule)) return; UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium]; [generator impactOccurred]; - TGMediaPickerSendActionSheetController *controller = [[TGMediaPickerSendActionSheetController alloc] initWithContext:strongSelf->_context sendButtonFrame:strongSelf.galleryModel.interfaceView.doneButtonFrame]; + TGMediaPickerSendActionSheetController *controller = [[TGMediaPickerSendActionSheetController alloc] initWithContext:strongSelf->_context sendButtonFrame:strongSelf.galleryModel.interfaceView.doneButtonFrame canSendSilently:hasSilentPosting]; controller.send = ^{ __strong TGMediaPickerModernGalleryMixin *strongSelf = weakSelf; if (strongSelf == nil) @@ -156,17 +156,27 @@ strongSelf->_galleryModel.dismiss(true, false); if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem(item, false); + strongSelf.completeWithItem(item, TGMediaPickerGalleryCompletionModeGeneric); }; controller.sendSilently = ^{ __strong TGMediaPickerModernGalleryMixin *strongSelf = weakSelf; if (strongSelf == nil) return; + strongSelf->_galleryModel.dismiss(true, TGMediaPickerGalleryCompletionModeSilent); + + if (strongSelf.completeWithItem != nil) + strongSelf.completeWithItem(item, true); + }; + controller.schedule = ^{ + __strong TGMediaPickerModernGalleryMixin *strongSelf = weakSelf; + if (strongSelf == nil) + return; + strongSelf->_galleryModel.dismiss(true, false); if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem(item, true); + strongSelf.completeWithItem(item, TGMediaPickerGalleryCompletionModeSchedule); }; TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:[strongSelf->_context makeOverlayWindowManager] parentController:strongSelf->_parentController contentController:controller]; diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.h b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.h index 201947a0ba..3506eb0908 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.h +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.h @@ -6,8 +6,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, copy) void (^send)(void); @property (nonatomic, copy) void (^sendSilently)(void); +@property (nonatomic, copy) void (^schedule)(void); -- (instancetype)initWithContext:(id)context sendButtonFrame:(CGRect)sendButtonFrame; +- (instancetype)initWithContext:(id)context sendButtonFrame:(CGRect)sendButtonFrame canSendSilently:(bool)canSendSilently; @end diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.m b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.m index f4f21cb014..d27edeb0c4 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.m +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.m @@ -6,30 +6,96 @@ #import "TGModernButton.h" #import "TGMediaAssetsController.h" +@interface TGMediaPickerSendActionSheetItemView : UIView +{ + TGModernButton *_buttonView; + UILabel *_buttonLabel; + UIImageView *_buttonIcon; +} + +@property (nonatomic, readonly) UILabel *buttonLabel; +@property (nonatomic, copy) void (^pressed)(void); + +@end + +@implementation TGMediaPickerSendActionSheetItemView + +- (instancetype)initWithTitle:(NSString *)title icon:(UIImage *)icon { + self = [super init]; + if (self != nil) { + _buttonView = [[TGModernButton alloc] init]; + _buttonView.adjustsImageWhenHighlighted = false; + + __weak TGMediaPickerSendActionSheetItemView *weakSelf = self; + _buttonView.highlitedChanged = ^(bool highlighted) { + __strong TGMediaPickerSendActionSheetItemView *strongSelf = weakSelf; + if (strongSelf != nil) { + if (highlighted) { + strongSelf->_buttonView.backgroundColor = UIColorRGB(0x363636); + } else { + strongSelf->_buttonView.backgroundColor = [UIColor clearColor]; + } + } + }; + [_buttonView addTarget:self action:@selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:_buttonView]; + + _buttonLabel = [[UILabel alloc] init]; + _buttonLabel.font = TGSystemFontOfSize(17.0f); + _buttonLabel.text = title; + _buttonLabel.textColor = [UIColor whiteColor]; + [_buttonLabel sizeToFit]; + _buttonLabel.userInteractionEnabled = false; + [self addSubview:_buttonLabel]; + + _buttonIcon = [[UIImageView alloc] init]; + _buttonIcon.image = TGTintedImage(icon, [UIColor whiteColor]); + [_buttonIcon sizeToFit]; + [self addSubview:_buttonIcon]; + } + return self; +} + +- (void)buttonPressed { + if (self.pressed != nil) + self.pressed(); +} + +- (void)layoutSubviews { + _buttonLabel.frame = CGRectMake(16.0, 11.0, _buttonLabel.frame.size.width, _buttonLabel.frame.size.height); + _buttonView.frame = self.bounds; + _buttonIcon.frame = CGRectMake(self.bounds.size.width - _buttonIcon.frame.size.width - 12.0, 9.0, _buttonIcon.frame.size.width, _buttonIcon.frame.size.height); +} + +@end + @interface TGMediaPickerSendActionSheetController () { id _context; CGRect _sendButtonFrame; + bool _canSendSilently; bool _autorotationWasEnabled; + bool _dismissed; UIVisualEffectView *_effectView; TGModernButton *_sendButton; UIView *_containerView; - TGModernButton *_buttonView; - UILabel *_buttonLabel; - UIImageView *_buttonIcon; + UIView *_separatorView; + TGMediaPickerSendActionSheetItemView *_sendSilentlyButton; + TGMediaPickerSendActionSheetItemView *_scheduleButton; } @end @implementation TGMediaPickerSendActionSheetController -- (instancetype)initWithContext:(id)context sendButtonFrame:(CGRect)sendButtonFrame { +- (instancetype)initWithContext:(id)context sendButtonFrame:(CGRect)sendButtonFrame canSendSilently:(bool)canSendSilently { self = [super initWithContext:context]; if (self != nil) { _context = context; _sendButtonFrame = sendButtonFrame; + _canSendSilently = canSendSilently; } return self; } @@ -49,34 +115,13 @@ _containerView.layer.cornerRadius = 12.0; [self.view addSubview:_containerView]; - __weak TGMediaPickerSendActionSheetController *weakSelf = self; - _buttonView = [[TGModernButton alloc] init]; - _buttonView.adjustsImageWhenHighlighted = false; - _buttonView.highlitedChanged = ^(bool highlighted) { - __strong TGMediaPickerSendActionSheetController *strongSelf = weakSelf; - if (strongSelf != nil) { - if (highlighted) { - strongSelf->_buttonView.backgroundColor = UIColorRGB(0x363636); - } else { - strongSelf->_buttonView.backgroundColor = [UIColor clearColor]; - } - } - }; - [_buttonView addTarget:self action:@selector(sendSilentlyPressed) forControlEvents:UIControlEventTouchUpInside]; - [_containerView addSubview:_buttonView]; + if (_canSendSilently) { + _sendSilentlyButton = [[TGMediaPickerSendActionSheetItemView alloc] initWithTitle:TGLocalized(@"Conversation.SendMessage.SendSilently") icon:TGComponentsImageNamed(@"MediaMute")]; + [_containerView addSubview:_sendSilentlyButton]; + } - _buttonLabel = [[UILabel alloc] init]; - _buttonLabel.font = TGSystemFontOfSize(17.0f); - _buttonLabel.text = TGLocalized(@"Conversation.SendMessage.SendSilently"); - _buttonLabel.textColor = [UIColor whiteColor]; - [_buttonLabel sizeToFit]; - _buttonLabel.userInteractionEnabled = false; - [_containerView addSubview:_buttonLabel]; - - _buttonIcon = [[UIImageView alloc] init]; - _buttonIcon.image = TGTintedImage(TGComponentsImageNamed(@"MediaMute"), [UIColor whiteColor]); - [_buttonIcon sizeToFit]; - [_containerView addSubview:_buttonIcon]; + _scheduleButton = [[TGMediaPickerSendActionSheetItemView alloc] initWithTitle:TGLocalized(@"Conversation.SendMessage.ScheduleMessage") icon:TGComponentsImageNamed(@"MediaSchedule")]; + [_containerView addSubview:_scheduleButton]; TGMediaAssetsPallete *pallete = nil; if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(mediaAssetsPallete)]) @@ -136,18 +181,20 @@ CGPoint targetPosition = _containerView.center; _containerView.center = CGPointMake(targetPosition.x + 160.0, targetPosition.y + 44.0); - [UIView animateWithDuration:0.3 delay:0.0 options:7 << 16 animations:^{ + _containerView.transform = CGAffineTransformMakeScale(0.1, 0.1); + [UIView animateWithDuration:0.42 delay:0.0 usingSpringWithDamping:104.0 initialSpringVelocity:0.0 options:kNilOptions animations:^{ + _containerView.transform = CGAffineTransformIdentity; _containerView.center = targetPosition; } completion:nil]; _containerView.alpha = 0.0f; - [UIView animateWithDuration:0.3 animations:^{ + [UIView animateWithDuration:0.2 animations:^{ _containerView.alpha = 1.0f; }]; } - (void)animateOut:(bool)cancel { - [UIView animateWithDuration:0.3 animations:^{ + [UIView animateWithDuration:0.2 animations:^{ if (iosMajorVersion() >= 9) { _effectView.effect = nil; } else { @@ -162,8 +209,10 @@ }]; if (cancel) { + _dismissed = true; [UIView animateWithDuration:0.3 delay:0.0 options:7 << 16 animations:^{ _containerView.center = CGPointMake(_containerView.center.x + 160.0, _containerView.center.y + 44.0); + _containerView.transform = CGAffineTransformMakeScale(0.1, 0.1); } completion:^(BOOL finished) { [self dismiss]; }]; @@ -178,11 +227,16 @@ _effectView.frame = self.view.bounds; _sendButton.frame = _sendButtonFrame; - _buttonLabel.frame = CGRectMake(16.0, 11.0, _buttonLabel.frame.size.width, _buttonLabel.frame.size.height); - CGFloat containerWidth = MAX(240.0, _buttonLabel.frame.size.width + 84.0); - _containerView.frame = CGRectMake(CGRectGetMaxX(_sendButtonFrame) - containerWidth - 8.0, _sendButtonFrame.origin.y - 44.0 - 4.0, containerWidth, 44.0); - _buttonView.frame = _containerView.bounds; - _buttonIcon.frame = CGRectMake(_containerView.frame.size.width - _buttonIcon.frame.size.width - 12.0, 9.0, _buttonIcon.frame.size.width, _buttonIcon.frame.size.height); + CGFloat itemHeight = 44.0; + CGFloat containerWidth = 240.0; + CGFloat containerHeight = _canSendSilently ? itemHeight * 2.0 : itemHeight; + containerWidth = MAX(containerWidth, MAX(_sendSilentlyButton.buttonLabel.frame.size.width, _scheduleButton.buttonLabel.frame.size.width) + 84.0); + if (!_dismissed) { + _containerView.frame = CGRectMake(CGRectGetMaxX(_sendButtonFrame) - containerWidth - 8.0, _sendButtonFrame.origin.y - containerHeight - 4.0, containerWidth, containerHeight); + } + + _sendSilentlyButton.frame = CGRectMake(0.0, 0.0, containerWidth, itemHeight); + _scheduleButton.frame = CGRectMake(0.0, containerHeight - itemHeight, containerWidth, itemHeight); } - (void)sendPressed { @@ -199,4 +253,11 @@ self.sendSilently(); } +- (void)schedulePressed { + [self animateOut:false]; + + if (self.schedule != nil) + self.schedule(); +} + @end diff --git a/submodules/LegacyComponents/LegacyComponents/TGPassportAttachMenu.m b/submodules/LegacyComponents/LegacyComponents/TGPassportAttachMenu.m index 2372fe11d1..606e7184c4 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGPassportAttachMenu.m +++ b/submodules/LegacyComponents/LegacyComponents/TGPassportAttachMenu.m @@ -67,7 +67,7 @@ [TGPassportAttachMenu _displayCameraWithView:cameraView menuController:strongController parentController:strongParentController context:context intent:intent uploadAction:uploadAction]; }; - carouselItem.sendPressed = ^(TGMediaAsset *currentItem, __unused bool asFiles, __unused bool silentPosting) + carouselItem.sendPressed = ^(TGMediaAsset *currentItem, __unused bool asFiles, __unused TGMediaPickerGalleryCompletionMode mode) { __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index ef30d071f5..fce72c2ae6 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -76,7 +76,8 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO } carouselItem.hasSilentPosting = !isSecretChat } - carouselItem.sendPressed = { [weak controller, weak carouselItem] currentItem, asFiles, silentPosting in + carouselItem.hasSchedule = !isSecretChat + carouselItem.sendPressed = { [weak controller, weak carouselItem] currentItem, asFiles, mode in if let controller = controller, let carouselItem = carouselItem { let intent: TGMediaAssetsControllerIntent = asFiles ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent let signals = TGMediaAssetsController.resultSignals(for: carouselItem.selectionContext, editingContext: carouselItem.editingContext, intent: intent, currentItem: currentItem, storeAssets: true, useMediaCache: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: saveEditedPhotos) @@ -84,7 +85,7 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO presentCantSendMultipleFiles() } else { controller.dismiss(animated: true) - sendMessagesWithSignals(signals, silentPosting) + sendMessagesWithSignals(signals, false) } } }; diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index a5975fa6f0..dc772c8dca 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -29,6 +29,7 @@ public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, co } controller.hasSilentPosting = !isSecretChat } + controller.hasSchedule = !isSecretChat controller.dismissalBlock = { } controller.selectionLimitExceeded = { diff --git a/submodules/LocationUI/Sources/LegacyLocationPicker.swift b/submodules/LocationUI/Sources/LegacyLocationPicker.swift index fe5e92e4e6..7e277d6947 100644 --- a/submodules/LocationUI/Sources/LegacyLocationPicker.swift +++ b/submodules/LocationUI/Sources/LegacyLocationPicker.swift @@ -13,7 +13,7 @@ private func generateClearIcon(color: UIColor) -> UIImage? { return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color) } -public func legacyLocationPickerController(context: AccountContext, selfPeer: Peer, peer: Peer, sendLocation: @escaping (CLLocationCoordinate2D, MapVenue?, String?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, theme: PresentationTheme, customLocationPicker: Bool = false, presentationCompleted: @escaping () -> Void = {}) -> ViewController { +public func legacyLocationPickerController(context: AccountContext, selfPeer: Peer, peer: Peer, sendLocation: @escaping (CLLocationCoordinate2D, MapVenue?, String?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, theme: PresentationTheme, customLocationPicker: Bool = false, hasLiveLocation: Bool = true, presentationCompleted: @escaping () -> Void = {}) -> ViewController { let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: theme) legacyController.presentationCompleted = { presentationCompleted() @@ -27,7 +27,7 @@ public func legacyLocationPickerController(context: AccountContext, selfPeer: Pe Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudUser ]) - if namespacesWithEnabledLiveLocation.contains(peer.id.namespace) && !customLocationPicker { + if namespacesWithEnabledLiveLocation.contains(peer.id.namespace) && !customLocationPicker && hasLiveLocation { controller.allowLiveLocationSharing = true } let navigationController = TGNavigationController(controllers: [controller])!