mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Permission and search UI improvements
This commit is contained in:
parent
e67f7316ef
commit
6d8c7243e5
@ -8696,3 +8696,6 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"Conversation.ViewInChannel" = "View in Channel";
|
"Conversation.ViewInChannel" = "View in Channel";
|
||||||
|
|
||||||
|
|
||||||
|
"Conversation.HoldForAudioOnly" = "Hold to record audio.";
|
||||||
|
"Conversation.HoldForVideoOnly" = "Hold to record video.";
|
||||||
|
@ -477,7 +477,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
controller?.inProgress = true
|
controller?.inProgress = true
|
||||||
strongSelf.actionDisposable.set((resendAuthorizationCode(account: strongSelf.account)
|
strongSelf.actionDisposable.set((resendAuthorizationCode(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, apiId: strongSelf.apiId, apiHash: strongSelf.apiHash, firebaseSecretStream: strongSelf.sharedContext.firebaseSecretStream)
|
||||||
|> deliverOnMainQueue).start(next: { result in
|
|> deliverOnMainQueue).start(next: { result in
|
||||||
controller?.inProgress = false
|
controller?.inProgress = false
|
||||||
}, error: { error in
|
}, error: { error in
|
||||||
|
@ -621,11 +621,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
var canFullscreen = false
|
var canFullscreen = false
|
||||||
|
|
||||||
var canEdit = false
|
var canEdit = false
|
||||||
|
var isImage = false
|
||||||
|
var isVideo = false
|
||||||
for media in message.media {
|
for media in message.media {
|
||||||
if media is TelegramMediaImage {
|
if media is TelegramMediaImage {
|
||||||
canEdit = true
|
canEdit = true
|
||||||
|
isImage = true
|
||||||
} else if let media = media as? TelegramMediaFile, !media.isAnimated {
|
} else if let media = media as? TelegramMediaFile, !media.isAnimated {
|
||||||
var isVideo = false
|
|
||||||
for attribute in media.attributes {
|
for attribute in media.attributes {
|
||||||
switch attribute {
|
switch attribute {
|
||||||
case let .Video(_, dimensions, _):
|
case let .Video(_, dimensions, _):
|
||||||
@ -666,7 +668,19 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
} else if let channel = peer as? TelegramChannel {
|
} else if let channel = peer as? TelegramChannel {
|
||||||
if message.flags.contains(.Incoming) {
|
if message.flags.contains(.Incoming) {
|
||||||
canDelete = channel.hasPermission(.deleteAllMessages)
|
canDelete = channel.hasPermission(.deleteAllMessages)
|
||||||
canEdit = canEdit && channel.hasPermission(.sendMessages)
|
if canEdit {
|
||||||
|
if isImage {
|
||||||
|
if !channel.hasPermission(.sendPhoto) {
|
||||||
|
canEdit = false
|
||||||
|
}
|
||||||
|
} else if isVideo {
|
||||||
|
if !channel.hasPermission(.sendVideo) {
|
||||||
|
canEdit = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
canEdit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
canDelete = true
|
canDelete = true
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
@property (nonatomic, copy) void (^pressed)(void);
|
@property (nonatomic, copy) void (^pressed)(void);
|
||||||
@property (nonatomic, strong) TGMenuSheetPallete *pallete;
|
@property (nonatomic, strong) TGMenuSheetPallete *pallete;
|
||||||
|
|
||||||
- (instancetype)initForSelfPortrait:(bool)selfPortrait;
|
- (instancetype)initForSelfPortrait:(bool)selfPortrait videoModeByDefault:(bool)videoModeByDefault;
|
||||||
|
|
||||||
@property (nonatomic, readonly) bool previewViewAttached;
|
@property (nonatomic, readonly) bool previewViewAttached;
|
||||||
- (void)detachPreviewView;
|
- (void)detachPreviewView;
|
||||||
|
@ -19,7 +19,8 @@ typedef enum {
|
|||||||
TGCameraControllerPassportMultipleIntent,
|
TGCameraControllerPassportMultipleIntent,
|
||||||
TGCameraControllerAvatarIntent,
|
TGCameraControllerAvatarIntent,
|
||||||
TGCameraControllerSignupAvatarIntent,
|
TGCameraControllerSignupAvatarIntent,
|
||||||
TGCameraControllerGenericPhotoOnlyIntent
|
TGCameraControllerGenericPhotoOnlyIntent,
|
||||||
|
TGCameraControllerGenericVideoOnlyIntent
|
||||||
} TGCameraControllerIntent;
|
} TGCameraControllerIntent;
|
||||||
|
|
||||||
@interface TGCameraControllerWindow : TGOverlayControllerWindow
|
@interface TGCameraControllerWindow : TGOverlayControllerWindow
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
@property (nonatomic, assign) CGRect previewViewFrame;
|
@property (nonatomic, assign) CGRect previewViewFrame;
|
||||||
|
|
||||||
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera;
|
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar videoModeByDefault:(bool)videoModeByDefault hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera;
|
||||||
|
|
||||||
- (void)setDocumentFrameHidden:(bool)hidden;
|
- (void)setDocumentFrameHidden:(bool)hidden;
|
||||||
- (void)setCameraMode:(PGCameraMode)mode;
|
- (void)setCameraMode:(PGCameraMode)mode;
|
||||||
|
@ -10,6 +10,6 @@
|
|||||||
|
|
||||||
- (void)setHidden:(bool)hidden animated:(bool)animated;
|
- (void)setHidden:(bool)hidden animated:(bool)animated;
|
||||||
|
|
||||||
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar;
|
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar videoModeByDefault:(bool)videoModeByDefault;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
@property (nonatomic, readonly) bool allowGrouping;
|
@property (nonatomic, readonly) bool allowGrouping;
|
||||||
@property (nonatomic, readonly) int selectionLimit;
|
@property (nonatomic, readonly) int selectionLimit;
|
||||||
@property (nonatomic, copy) void (^selectionLimitExceeded)(void);
|
@property (nonatomic, copy) void (^selectionLimitExceeded)(void);
|
||||||
|
@property (nonatomic, copy) bool (^attemptSelectingItem)(id<TGMediaSelectableItem>);
|
||||||
|
|
||||||
@property (nonatomic, assign) bool grouping;
|
@property (nonatomic, assign) bool grouping;
|
||||||
- (SSignal *)groupingChangedSignal;
|
- (SSignal *)groupingChangedSignal;
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
@implementation TGAttachmentCameraView
|
@implementation TGAttachmentCameraView
|
||||||
|
|
||||||
- (instancetype)initForSelfPortrait:(bool)selfPortrait
|
- (instancetype)initForSelfPortrait:(bool)selfPortrait videoModeByDefault:(bool)videoModeByDefault
|
||||||
{
|
{
|
||||||
self = [super initWithFrame:CGRectZero];
|
self = [super initWithFrame:CGRectZero];
|
||||||
if (self != nil)
|
if (self != nil)
|
||||||
@ -46,7 +46,7 @@
|
|||||||
PGCamera *camera = nil;
|
PGCamera *camera = nil;
|
||||||
if ([PGCamera cameraAvailable])
|
if ([PGCamera cameraAvailable])
|
||||||
{
|
{
|
||||||
camera = [[PGCamera alloc] initWithMode:PGCameraModePhoto position:selfPortrait ? PGCameraPositionFront : PGCameraPositionUndefined];
|
camera = [[PGCamera alloc] initWithMode:videoModeByDefault ? PGCameraModeVideo : PGCameraModePhoto position:selfPortrait ? PGCameraPositionFront : PGCameraPositionUndefined];
|
||||||
}
|
}
|
||||||
_camera = camera;
|
_camera = camera;
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
|
|||||||
|
|
||||||
if (hasCamera)
|
if (hasCamera)
|
||||||
{
|
{
|
||||||
_cameraView = [[TGAttachmentCameraView alloc] initForSelfPortrait:selfPortrait];
|
_cameraView = [[TGAttachmentCameraView alloc] initForSelfPortrait:selfPortrait videoModeByDefault:false];
|
||||||
_cameraView.frame = CGRectMake(_smallLayout.minimumLineSpacing, 0, TGAttachmentCellSize.width, TGAttachmentCellSize.height);
|
_cameraView.frame = CGRectMake(_smallLayout.minimumLineSpacing, 0, TGAttachmentCellSize.width, TGAttachmentCellSize.height);
|
||||||
[_cameraView startPreview];
|
[_cameraView startPreview];
|
||||||
|
|
||||||
|
@ -170,8 +170,12 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
|||||||
|
|
||||||
_items = [[NSMutableArray alloc] init];
|
_items = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
if (_intent != TGCameraControllerGenericIntent)
|
if (_intent != TGCameraControllerGenericIntent) {
|
||||||
_allowCaptions = false;
|
_allowCaptions = false;
|
||||||
|
}
|
||||||
|
if (_intent == TGCameraControllerGenericVideoOnlyIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent) {
|
||||||
|
_allowCaptions = false;
|
||||||
|
}
|
||||||
_saveEditedPhotos = saveEditedPhotos;
|
_saveEditedPhotos = saveEditedPhotos;
|
||||||
_saveCapturedMedia = saveCapturedMedia;
|
_saveCapturedMedia = saveCapturedMedia;
|
||||||
|
|
||||||
@ -303,12 +307,12 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
|||||||
|
|
||||||
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
|
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
|
||||||
{
|
{
|
||||||
_interfaceView = [[TGCameraMainPhoneView alloc] initWithFrame:screenBounds avatar:_intent == TGCameraControllerAvatarIntent hasUltrawideCamera:_camera.hasUltrawideCamera hasTelephotoCamera:_camera.hasTelephotoCamera];
|
_interfaceView = [[TGCameraMainPhoneView alloc] initWithFrame:screenBounds avatar:_intent == TGCameraControllerAvatarIntent videoModeByDefault:_intent == TGCameraControllerGenericVideoOnlyIntent hasUltrawideCamera:_camera.hasUltrawideCamera hasTelephotoCamera:_camera.hasTelephotoCamera];
|
||||||
[_interfaceView setInterfaceOrientation:interfaceOrientation animated:false];
|
[_interfaceView setInterfaceOrientation:interfaceOrientation animated:false];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_interfaceView = [[TGCameraMainTabletView alloc] initWithFrame:screenBounds avatar:_intent == TGCameraControllerAvatarIntent hasUltrawideCamera:_camera.hasUltrawideCamera hasTelephotoCamera:_camera.hasTelephotoCamera];
|
_interfaceView = [[TGCameraMainTabletView alloc] initWithFrame:screenBounds avatar:_intent == TGCameraControllerAvatarIntent videoModeByDefault:_intent == TGCameraControllerGenericVideoOnlyIntent hasUltrawideCamera:_camera.hasUltrawideCamera hasTelephotoCamera:_camera.hasTelephotoCamera];
|
||||||
[_interfaceView setInterfaceOrientation:interfaceOrientation animated:false];
|
[_interfaceView setInterfaceOrientation:interfaceOrientation animated:false];
|
||||||
|
|
||||||
CGSize referenceSize = [self referenceViewSizeForOrientation:interfaceOrientation];
|
CGSize referenceSize = [self referenceViewSizeForOrientation:interfaceOrientation];
|
||||||
@ -451,8 +455,12 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_intent != TGCameraControllerGenericIntent && _intent != TGCameraControllerAvatarIntent)
|
if (_intent != TGCameraControllerGenericIntent && _intent != TGCameraControllerAvatarIntent) {
|
||||||
[_interfaceView setHasModeControl:false];
|
[_interfaceView setHasModeControl:false];
|
||||||
|
}
|
||||||
|
if (_intent == TGCameraControllerGenericVideoOnlyIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent) {
|
||||||
|
[_interfaceView setHasModeControl:false];
|
||||||
|
}
|
||||||
|
|
||||||
if (@available(iOS 11.0, *)) {
|
if (@available(iOS 11.0, *)) {
|
||||||
_backgroundView.accessibilityIgnoresInvertColors = true;
|
_backgroundView.accessibilityIgnoresInvertColors = true;
|
||||||
@ -1527,7 +1535,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
|||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
bool hasCamera = !self.inhibitMultipleCapture && (((_intent == TGCameraControllerGenericIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent) && !_shortcut) || (_intent == TGCameraControllerPassportMultipleIntent));
|
bool hasCamera = !self.inhibitMultipleCapture && (((_intent == TGCameraControllerGenericIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent || _intent == TGCameraControllerGenericVideoOnlyIntent) && !_shortcut) || (_intent == TGCameraControllerPassportMultipleIntent));
|
||||||
TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:galleryItems focusItem:focusItem selectionContext:_items.count > 1 ? selectionContext : nil editingContext:editingContext hasCaptions:self.allowCaptions allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:_intent == TGCameraControllerPassportIntent || _intent == TGCameraControllerPassportIdIntent || _intent == TGCameraControllerPassportMultipleIntent inhibitDocumentCaptions:self.inhibitDocumentCaptions hasSelectionPanel:true hasCamera:hasCamera recipientName:self.recipientName];
|
TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:galleryItems focusItem:focusItem selectionContext:_items.count > 1 ? selectionContext : nil editingContext:editingContext hasCaptions:self.allowCaptions allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:_intent == TGCameraControllerPassportIntent || _intent == TGCameraControllerPassportIdIntent || _intent == TGCameraControllerPassportMultipleIntent inhibitDocumentCaptions:self.inhibitDocumentCaptions hasSelectionPanel:true hasCamera:hasCamera recipientName:self.recipientName];
|
||||||
model.inhibitMute = self.inhibitMute;
|
model.inhibitMute = self.inhibitMute;
|
||||||
model.controller = galleryController;
|
model.controller = galleryController;
|
||||||
|
@ -101,7 +101,7 @@
|
|||||||
@synthesize cancelPressed;
|
@synthesize cancelPressed;
|
||||||
@synthesize actionHandle = _actionHandle;
|
@synthesize actionHandle = _actionHandle;
|
||||||
|
|
||||||
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera
|
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar videoModeByDefault:(bool)videoModeByDefault hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera
|
||||||
{
|
{
|
||||||
self = [super initWithFrame:frame];
|
self = [super initWithFrame:frame];
|
||||||
if (self != nil)
|
if (self != nil)
|
||||||
@ -314,7 +314,7 @@
|
|||||||
[_shutterButton addGestureRecognizer:shutterPanGestureRecognizer];
|
[_shutterButton addGestureRecognizer:shutterPanGestureRecognizer];
|
||||||
[_bottomPanelView addSubview:_shutterButton];
|
[_bottomPanelView addSubview:_shutterButton];
|
||||||
|
|
||||||
_modeControl = [[TGCameraModeControl alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, _modeControlHeight) avatar:avatar];
|
_modeControl = [[TGCameraModeControl alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, _modeControlHeight) avatar:avatar videoModeByDefault:videoModeByDefault];
|
||||||
[_bottomPanelView addSubview:_modeControl];
|
[_bottomPanelView addSubview:_modeControl];
|
||||||
|
|
||||||
_flipButton = [[TGCameraFlipButton alloc] initWithFrame:CGRectMake(0, 0, 48, 48)];
|
_flipButton = [[TGCameraFlipButton alloc] initWithFrame:CGRectMake(0, 0, 48, 48)];
|
||||||
@ -414,6 +414,12 @@
|
|||||||
[_photoCounterButton addTarget:self action:@selector(photoCounterButtonPressed) forControlEvents:UIControlEventTouchUpInside];
|
[_photoCounterButton addTarget:self action:@selector(photoCounterButtonPressed) forControlEvents:UIControlEventTouchUpInside];
|
||||||
_photoCounterButton.userInteractionEnabled = false;
|
_photoCounterButton.userInteractionEnabled = false;
|
||||||
[_bottomPanelView addSubview:_photoCounterButton];
|
[_bottomPanelView addSubview:_photoCounterButton];
|
||||||
|
|
||||||
|
if (videoModeByDefault) {
|
||||||
|
[UIView performWithoutAnimation:^{
|
||||||
|
[self updateForCameraModeChangeWithPreviousMode:PGCameraModePhoto];
|
||||||
|
}];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ const CGFloat TGCameraTabletPanelViewWidth = 102.0f;
|
|||||||
@synthesize shutterReleased;
|
@synthesize shutterReleased;
|
||||||
@synthesize cancelPressed;
|
@synthesize cancelPressed;
|
||||||
|
|
||||||
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera
|
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar videoModeByDefault:(bool)videoModeByDefault hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera
|
||||||
{
|
{
|
||||||
self = [super initWithFrame:frame];
|
self = [super initWithFrame:frame];
|
||||||
if (self != nil)
|
if (self != nil)
|
||||||
@ -83,7 +83,7 @@ const CGFloat TGCameraTabletPanelViewWidth = 102.0f;
|
|||||||
[_shutterButton addTarget:self action:@selector(shutterButtonReleased) forControlEvents:UIControlEventTouchUpInside];
|
[_shutterButton addTarget:self action:@selector(shutterButtonReleased) forControlEvents:UIControlEventTouchUpInside];
|
||||||
[_panelView addSubview:_shutterButton];
|
[_panelView addSubview:_shutterButton];
|
||||||
|
|
||||||
_modeControl = [[TGCameraModeControl alloc] initWithFrame:CGRectMake(0, 0, _panelView.frame.size.width, 260) avatar:avatar];
|
_modeControl = [[TGCameraModeControl alloc] initWithFrame:CGRectMake(0, 0, _panelView.frame.size.width, 260) avatar:avatar videoModeByDefault:videoModeByDefault];
|
||||||
[_panelView addSubview:_modeControl];
|
[_panelView addSubview:_modeControl];
|
||||||
|
|
||||||
__weak TGCameraMainTabletView *weakSelf = self;
|
__weak TGCameraMainTabletView *weakSelf = self;
|
||||||
|
@ -21,7 +21,7 @@ const CGFloat TGCameraModeControlVerticalInteritemSpace = 29.0f;
|
|||||||
|
|
||||||
@implementation TGCameraModeControl
|
@implementation TGCameraModeControl
|
||||||
|
|
||||||
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar
|
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar videoModeByDefault:(bool)videoModeByDefault
|
||||||
{
|
{
|
||||||
self = [super initWithFrame:frame];
|
self = [super initWithFrame:frame];
|
||||||
if (self != nil)
|
if (self != nil)
|
||||||
@ -87,7 +87,11 @@ const CGFloat TGCameraModeControlVerticalInteritemSpace = 29.0f;
|
|||||||
_wrapperView.frame = CGRectMake(33, 0, self.frame.size.width, topOffset - TGCameraModeControlVerticalInteritemSpace);
|
_wrapperView.frame = CGRectMake(33, 0, self.frame.size.width, topOffset - TGCameraModeControlVerticalInteritemSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cameraMode = PGCameraModePhoto;
|
if (videoModeByDefault) {
|
||||||
|
self.cameraMode = PGCameraModeVideo;
|
||||||
|
} else {
|
||||||
|
self.cameraMode = PGCameraModePhoto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,12 @@
|
|||||||
{
|
{
|
||||||
if (![(id)item conformsToProtocol:@protocol(TGMediaSelectableItem)])
|
if (![(id)item conformsToProtocol:@protocol(TGMediaSelectableItem)])
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (_attemptSelectingItem) {
|
||||||
|
if (!_attemptSelectingItem(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NSString *identifier = item.uniqueIdentifier;
|
NSString *identifier = item.uniqueIdentifier;
|
||||||
if (selected)
|
if (selected)
|
||||||
|
@ -160,7 +160,9 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||||||
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay))
|
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay))
|
||||||
checkNode.valueChanged = { [weak self] value in
|
checkNode.valueChanged = { [weak self] value in
|
||||||
if let strongSelf = self, let interaction = strongSelf.interaction, let selectableItem = strongSelf.selectableItem {
|
if let strongSelf = self, let interaction = strongSelf.interaction, let selectableItem = strongSelf.selectableItem {
|
||||||
interaction.toggleSelection(selectableItem, value, false)
|
if !interaction.toggleSelection(selectableItem, value, false) {
|
||||||
|
strongSelf.checkNode?.setSelected(false, animated: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.addSubnode(checkNode)
|
self.addSubnode(checkNode)
|
||||||
|
@ -25,7 +25,7 @@ import MoreButtonNode
|
|||||||
final class MediaPickerInteraction {
|
final class MediaPickerInteraction {
|
||||||
let openMedia: (PHFetchResult<PHAsset>, Int, UIImage?) -> Void
|
let openMedia: (PHFetchResult<PHAsset>, Int, UIImage?) -> Void
|
||||||
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
|
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
|
||||||
let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Void
|
let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Bool
|
||||||
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void
|
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void
|
||||||
let schedule: () -> Void
|
let schedule: () -> Void
|
||||||
let dismissInput: () -> Void
|
let dismissInput: () -> Void
|
||||||
@ -33,7 +33,7 @@ final class MediaPickerInteraction {
|
|||||||
let editingState: TGMediaEditingContext
|
let editingState: TGMediaEditingContext
|
||||||
var hiddenMediaId: String?
|
var hiddenMediaId: String?
|
||||||
|
|
||||||
init(openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Void, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
init(openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||||
self.openMedia = openMedia
|
self.openMedia = openMedia
|
||||||
self.openSelectedMedia = openSelectedMedia
|
self.openSelectedMedia = openSelectedMedia
|
||||||
self.toggleSelection = toggleSelection
|
self.toggleSelection = toggleSelection
|
||||||
@ -403,7 +403,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let controller = self.controller, case .assets(nil) = controller.subject {
|
if let controller = self.controller, case .assets(nil) = controller.subject {
|
||||||
let cameraView = TGAttachmentCameraView(forSelfPortrait: false)!
|
let cameraView = TGAttachmentCameraView(forSelfPortrait: false, videoModeByDefault: controller.bannedSendPhotos != nil && controller.bannedSendVideos == nil)!
|
||||||
cameraView.clipsToBounds = true
|
cameraView.clipsToBounds = true
|
||||||
cameraView.removeCorners()
|
cameraView.removeCorners()
|
||||||
cameraView.pressed = { [weak self] in
|
cameraView.pressed = { [weak self] in
|
||||||
@ -946,7 +946,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
if cameraAccess == nil {
|
if cameraAccess == nil {
|
||||||
cameraRect = nil
|
cameraRect = nil
|
||||||
}
|
}
|
||||||
/*if let (untilDate, personal) = self.controller?.bannedSendMedia {
|
|
||||||
|
var bannedSendMedia: (Int32, Bool)?
|
||||||
|
if let bannedSendPhotos = self.controller?.bannedSendPhotos, let bannedSendVideos = self.controller?.bannedSendVideos {
|
||||||
|
bannedSendMedia = (max(bannedSendPhotos.0, bannedSendVideos.0), bannedSendPhotos.1 || bannedSendVideos.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (untilDate, personal) = bannedSendMedia {
|
||||||
self.gridNode.isHidden = true
|
self.gridNode.isHidden = true
|
||||||
|
|
||||||
let banDescription: String
|
let banDescription: String
|
||||||
@ -973,7 +979,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
placeholderTransition.updateFrame(node: placeholderNode, frame: innerBounds)
|
placeholderTransition.updateFrame(node: placeholderNode, frame: innerBounds)
|
||||||
|
|
||||||
self.updateNavigation(transition: .immediate)
|
self.updateNavigation(transition: .immediate)
|
||||||
} else */if case .notDetermined = mediaAccess {
|
} else if case .notDetermined = mediaAccess {
|
||||||
} else {
|
} else {
|
||||||
if case .limited = mediaAccess {
|
if case .limited = mediaAccess {
|
||||||
let manageNode: MediaPickerManageNode
|
let manageNode: MediaPickerManageNode
|
||||||
@ -1073,6 +1079,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bannedSendMedia: (Int32, Bool)?
|
||||||
|
if let bannedSendPhotos = self.controller?.bannedSendPhotos, let bannedSendVideos = self.controller?.bannedSendVideos {
|
||||||
|
bannedSendMedia = (max(bannedSendPhotos.0, bannedSendVideos.0), bannedSendPhotos.1 || bannedSendVideos.1)
|
||||||
|
}
|
||||||
|
|
||||||
if case let .noAccess(cameraAccess) = self.state {
|
if case let .noAccess(cameraAccess) = self.state {
|
||||||
var placeholderTransition = transition
|
var placeholderTransition = transition
|
||||||
let placeholderNode: MediaPickerPlaceholderNode
|
let placeholderNode: MediaPickerPlaceholderNode
|
||||||
@ -1099,7 +1110,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
placeholderNode.update(layout: layout, theme: self.presentationData.theme, strings: self.presentationData.strings, hasCamera: cameraAccess == .authorized, transition: placeholderTransition)
|
placeholderNode.update(layout: layout, theme: self.presentationData.theme, strings: self.presentationData.strings, hasCamera: cameraAccess == .authorized, transition: placeholderTransition)
|
||||||
placeholderTransition.updateFrame(node: placeholderNode, frame: innerBounds)
|
placeholderTransition.updateFrame(node: placeholderNode, frame: innerBounds)
|
||||||
} else if let placeholderNode = self.placeholderNode {//, self.controller?.bannedSendMedia == nil {
|
} else if let placeholderNode = self.placeholderNode, bannedSendMedia == nil {
|
||||||
self.placeholderNode = nil
|
self.placeholderNode = nil
|
||||||
placeholderNode.removeFromSupernode()
|
placeholderNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
@ -1161,6 +1172,45 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
|
|
||||||
self.statusBar.statusBarStyle = .Ignore
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
|
|
||||||
|
selectionContext.attemptSelectingItem = { [weak self] item in
|
||||||
|
guard let self else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let _ = item as? TGMediaPickerGalleryPhotoItem {
|
||||||
|
if self.bannedSendPhotos != nil {
|
||||||
|
//TODO:localize
|
||||||
|
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: "Sending photos is not allowed", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if let _ = item as? TGMediaPickerGalleryVideoItem {
|
||||||
|
if self.bannedSendVideos != nil {
|
||||||
|
//TODO:localize
|
||||||
|
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: "Sending videos is not allowed", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if let asset = item as? TGMediaAsset {
|
||||||
|
if asset.isVideo {
|
||||||
|
if self.bannedSendVideos != nil {
|
||||||
|
//TODO:localize
|
||||||
|
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: "Sending videos is not allowed", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.bannedSendPhotos != nil {
|
||||||
|
//TODO:localize
|
||||||
|
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: "Sending photos is not allowed", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -1223,7 +1273,39 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
|
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
|
||||||
self?.controllerNode.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
|
self?.controllerNode.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
|
||||||
}, toggleSelection: { [weak self] item, value, suggestUndo in
|
}, toggleSelection: { [weak self] item, value, suggestUndo in
|
||||||
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
|
if let self = self, let selectionState = self.interaction?.selectionState {
|
||||||
|
if let _ = item as? TGMediaPickerGalleryPhotoItem {
|
||||||
|
if self.bannedSendPhotos != nil {
|
||||||
|
//TODO:localize
|
||||||
|
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: "Sending photos is not allowed", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if let _ = item as? TGMediaPickerGalleryVideoItem {
|
||||||
|
if self.bannedSendVideos != nil {
|
||||||
|
//TODO:localize
|
||||||
|
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: "Sending videos is not allowed", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if let asset = item as? TGMediaAsset {
|
||||||
|
if asset.isVideo {
|
||||||
|
if self.bannedSendVideos != nil {
|
||||||
|
//TODO:localize
|
||||||
|
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: "Sending videos is not allowed", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.bannedSendPhotos != nil {
|
||||||
|
//TODO:localize
|
||||||
|
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: "Sending photos is not allowed", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var showUndo = false
|
var showUndo = false
|
||||||
if suggestUndo {
|
if suggestUndo {
|
||||||
if !value {
|
if !value {
|
||||||
@ -1237,8 +1319,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
selectionState.setItem(item, selected: value)
|
selectionState.setItem(item, selected: value)
|
||||||
|
|
||||||
if showUndo {
|
if showUndo {
|
||||||
strongSelf.showSelectionUndo(item: item)
|
self.showSelectionUndo(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, completion in
|
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, completion in
|
||||||
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState, !strongSelf.isDismissing {
|
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState, !strongSelf.isDismissing {
|
||||||
|
@ -254,7 +254,9 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
|||||||
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay))
|
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay))
|
||||||
checkNode.valueChanged = { [weak self] value in
|
checkNode.valueChanged = { [weak self] value in
|
||||||
if let strongSelf = self, let interaction = strongSelf.interaction, let selectableItem = strongSelf.asset as? TGMediaSelectableItem {
|
if let strongSelf = self, let interaction = strongSelf.interaction, let selectableItem = strongSelf.asset as? TGMediaSelectableItem {
|
||||||
interaction.toggleSelection(selectableItem, value, true)
|
if !interaction.toggleSelection(selectableItem, value, true) {
|
||||||
|
strongSelf.checkNode?.setSelected(false, animated: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.addSubnode(checkNode)
|
self.addSubnode(checkNode)
|
||||||
|
@ -378,7 +378,7 @@ private struct ChannelPermissionsControllerState: Equatable {
|
|||||||
|
|
||||||
func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatBannedRightsFlags, isForum: Bool) -> String {
|
func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatBannedRightsFlags, isForum: Bool) -> String {
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
if right.contains(.banSendMessages) {
|
if right.contains(.banSendText) {
|
||||||
return strings.Channel_BanUser_PermissionSendMessages
|
return strings.Channel_BanUser_PermissionSendMessages
|
||||||
} else if right.contains(.banSendMedia) {
|
} else if right.contains(.banSendMedia) {
|
||||||
return strings.Channel_BanUser_PermissionSendMedia
|
return strings.Channel_BanUser_PermissionSendMedia
|
||||||
@ -416,7 +416,7 @@ func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatB
|
|||||||
}
|
}
|
||||||
|
|
||||||
func compactStringForGroupPermission(strings: PresentationStrings, right: TelegramChatBannedRightsFlags) -> String {
|
func compactStringForGroupPermission(strings: PresentationStrings, right: TelegramChatBannedRightsFlags) -> String {
|
||||||
if right.contains(.banSendMessages) {
|
if right.contains(.banSendText) {
|
||||||
return strings.GroupPermission_NoSendMessages
|
return strings.GroupPermission_NoSendMessages
|
||||||
} else if right.contains(.banSendMedia) {
|
} else if right.contains(.banSendMedia) {
|
||||||
return strings.GroupPermission_NoSendMedia
|
return strings.GroupPermission_NoSendMedia
|
||||||
@ -440,7 +440,7 @@ func compactStringForGroupPermission(strings: PresentationStrings, right: Telegr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let internal_allPossibleGroupPermissionList: [(TelegramChatBannedRightsFlags, TelegramChannelPermission)] = [
|
private let internal_allPossibleGroupPermissionList: [(TelegramChatBannedRightsFlags, TelegramChannelPermission)] = [
|
||||||
(.banSendMessages, .banMembers),
|
(.banSendText, .banMembers),
|
||||||
(.banSendMedia, .banMembers),
|
(.banSendMedia, .banMembers),
|
||||||
(.banSendPhotos, .banMembers),
|
(.banSendPhotos, .banMembers),
|
||||||
(.banSendVideos, .banMembers),
|
(.banSendVideos, .banMembers),
|
||||||
@ -460,9 +460,8 @@ private let internal_allPossibleGroupPermissionList: [(TelegramChatBannedRightsF
|
|||||||
public func allGroupPermissionList(peer: EnginePeer) -> [(TelegramChatBannedRightsFlags, TelegramChannelPermission)] {
|
public func allGroupPermissionList(peer: EnginePeer) -> [(TelegramChatBannedRightsFlags, TelegramChannelPermission)] {
|
||||||
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
||||||
return [
|
return [
|
||||||
(.banSendMessages, .banMembers),
|
(.banSendText, .banMembers),
|
||||||
(.banSendMedia, .banMembers),
|
(.banSendMedia, .banMembers),
|
||||||
(.banSendPolls, .banMembers),
|
|
||||||
(.banAddMembers, .banMembers),
|
(.banAddMembers, .banMembers),
|
||||||
(.banPinMessages, .pinMessages),
|
(.banPinMessages, .pinMessages),
|
||||||
(.banManageTopics, .manageTopics),
|
(.banManageTopics, .manageTopics),
|
||||||
@ -470,9 +469,8 @@ public func allGroupPermissionList(peer: EnginePeer) -> [(TelegramChatBannedRigh
|
|||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
(.banSendMessages, .banMembers),
|
(.banSendText, .banMembers),
|
||||||
(.banSendMedia, .banMembers),
|
(.banSendMedia, .banMembers),
|
||||||
(.banSendPolls, .banMembers),
|
|
||||||
(.banAddMembers, .banMembers),
|
(.banAddMembers, .banMembers),
|
||||||
(.banPinMessages, .pinMessages),
|
(.banPinMessages, .pinMessages),
|
||||||
(.banChangeInfo, .changeInfo)
|
(.banChangeInfo, .changeInfo)
|
||||||
@ -490,6 +488,7 @@ public func banSendMediaSubList() -> [(TelegramChatBannedRightsFlags, TelegramCh
|
|||||||
(.banSendVoice, .banMembers),
|
(.banSendVoice, .banMembers),
|
||||||
(.banSendInstantVideos, .banMembers),
|
(.banSendInstantVideos, .banMembers),
|
||||||
(.banEmbedLinks, .banMembers),
|
(.banEmbedLinks, .banMembers),
|
||||||
|
(.banSendPolls, .banMembers),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,13 +499,13 @@ let publicGroupRestrictedPermissions: TelegramChatBannedRightsFlags = [
|
|||||||
|
|
||||||
func groupPermissionDependencies(_ right: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags {
|
func groupPermissionDependencies(_ right: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags {
|
||||||
if right.contains(.banSendMedia) || banSendMediaSubList().contains(where: { $0.0 == right }) {
|
if right.contains(.banSendMedia) || banSendMediaSubList().contains(where: { $0.0 == right }) {
|
||||||
return [.banSendMessages]
|
return []
|
||||||
} else if right.contains(.banSendGifs) {
|
} else if right.contains(.banSendGifs) {
|
||||||
return [.banSendMessages]
|
return []
|
||||||
} else if right.contains(.banEmbedLinks) {
|
} else if right.contains(.banEmbedLinks) {
|
||||||
return [.banSendMessages]
|
return []
|
||||||
} else if right.contains(.banSendPolls) {
|
} else if right.contains(.banSendPolls) {
|
||||||
return [.banSendMessages]
|
return []
|
||||||
} else if right.contains(.banChangeInfo) {
|
} else if right.contains(.banChangeInfo) {
|
||||||
return []
|
return []
|
||||||
} else if right.contains(.banAddMembers) {
|
} else if right.contains(.banAddMembers) {
|
||||||
|
@ -460,7 +460,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
} else {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
strongSelf.stableEmptyResultEmoji = nil
|
||||||
}
|
}
|
||||||
emojiContent = emojiContent.withUpdatedItemGroups(itemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
|
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
strongSelf.stableEmptyResultEmoji = nil
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,10 @@ import Postbox
|
|||||||
|
|
||||||
|
|
||||||
public enum TelegramChannelPermission {
|
public enum TelegramChannelPermission {
|
||||||
case sendMessages
|
case sendText
|
||||||
|
case sendPhoto
|
||||||
|
case sendVideo
|
||||||
|
case sendSomething
|
||||||
case pinMessages
|
case pinMessages
|
||||||
case manageTopics
|
case manageTopics
|
||||||
case createTopics
|
case createTopics
|
||||||
@ -30,7 +33,7 @@ public extension TelegramChannel {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
switch permission {
|
switch permission {
|
||||||
case .sendMessages:
|
case .sendText:
|
||||||
if case .broadcast = self.info {
|
if case .broadcast = self.info {
|
||||||
if let adminRights = self.adminRights {
|
if let adminRights = self.adminRights {
|
||||||
return adminRights.rights.contains(.canPostMessages)
|
return adminRights.rights.contains(.canPostMessages)
|
||||||
@ -41,10 +44,80 @@ public extension TelegramChannel {
|
|||||||
if let _ = self.adminRights {
|
if let _ = self.adminRights {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendMessages) {
|
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendText) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendMessages) {
|
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendText) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case .sendPhoto:
|
||||||
|
if case .broadcast = self.info {
|
||||||
|
if let adminRights = self.adminRights {
|
||||||
|
return adminRights.rights.contains(.canPostMessages)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let _ = self.adminRights {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendPhotos) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendPhotos) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case .sendVideo:
|
||||||
|
if case .broadcast = self.info {
|
||||||
|
if let adminRights = self.adminRights {
|
||||||
|
return adminRights.rights.contains(.canPostMessages)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let _ = self.adminRights {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendVideos) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendVideos) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case .sendSomething:
|
||||||
|
if case .broadcast = self.info {
|
||||||
|
if let adminRights = self.adminRights {
|
||||||
|
return adminRights.rights.contains(.canPostMessages)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let _ = self.adminRights {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let flags: TelegramChatBannedRightsFlags = [
|
||||||
|
.banSendText,
|
||||||
|
.banSendInstantVideos,
|
||||||
|
.banSendVoice,
|
||||||
|
.banSendPhotos,
|
||||||
|
.banSendVideos,
|
||||||
|
.banSendStickers,
|
||||||
|
.banSendPolls,
|
||||||
|
.banSendFiles,
|
||||||
|
.banSendInline
|
||||||
|
]
|
||||||
|
|
||||||
|
if let bannedRights = self.bannedRights, bannedRights.flags.intersection(flags) == flags {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.intersection(flags) == flags {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -15,6 +15,7 @@ extension TelegramChatBannedRights {
|
|||||||
var apiBannedRights: Api.ChatBannedRights {
|
var apiBannedRights: Api.ChatBannedRights {
|
||||||
var effectiveFlags = self.flags
|
var effectiveFlags = self.flags
|
||||||
effectiveFlags.remove(.banSendMedia)
|
effectiveFlags.remove(.banSendMedia)
|
||||||
|
effectiveFlags.remove(TelegramChatBannedRightsFlags(rawValue: 1 << 1))
|
||||||
|
|
||||||
return .chatBannedRights(flags: effectiveFlags.rawValue, untilDate: self.untilDate)
|
return .chatBannedRights(flags: effectiveFlags.rawValue, untilDate: self.untilDate)
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,10 @@ public enum SendAuthorizationCodeResult {
|
|||||||
func storeFutureLoginToken(accountManager: AccountManager<TelegramAccountManagerTypes>, token: Data) {
|
func storeFutureLoginToken(accountManager: AccountManager<TelegramAccountManagerTypes>, token: Data) {
|
||||||
let _ = (accountManager.transaction { transaction -> Void in
|
let _ = (accountManager.transaction { transaction -> Void in
|
||||||
var tokens = transaction.getStoredLoginTokens()
|
var tokens = transaction.getStoredLoginTokens()
|
||||||
tokens.insert(token, at: 0)
|
|
||||||
|
#if DEBUG
|
||||||
|
tokens.removeAll()
|
||||||
|
#endif
|
||||||
|
|
||||||
var cloudValue: [Data] = []
|
var cloudValue: [Data] = []
|
||||||
if let list = NSUbiquitousKeyValueStore.default.object(forKey: "T_SLTokens") as? [String] {
|
if let list = NSUbiquitousKeyValueStore.default.object(forKey: "T_SLTokens") as? [String] {
|
||||||
@ -95,6 +98,7 @@ func storeFutureLoginToken(accountManager: AccountManager<TelegramAccountManager
|
|||||||
tokens.insert(data, at: 0)
|
tokens.insert(data, at: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tokens.insert(token, at: 0)
|
||||||
if tokens.count > 20 {
|
if tokens.count > 20 {
|
||||||
tokens.removeLast(tokens.count - 20)
|
tokens.removeLast(tokens.count - 20)
|
||||||
}
|
}
|
||||||
@ -143,12 +147,20 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
|||||||
return Data(base64Encoded: stringData)
|
return Data(base64Encoded: stringData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if DEBUG
|
||||||
|
cloudValue.removeAll()
|
||||||
|
#endif
|
||||||
return accountManager.transaction { transaction -> [Data] in
|
return accountManager.transaction { transaction -> [Data] in
|
||||||
return transaction.getStoredLoginTokens()
|
return transaction.getStoredLoginTokens()
|
||||||
}
|
}
|
||||||
|> castError(AuthorizationCodeRequestError.self)
|
|> castError(AuthorizationCodeRequestError.self)
|
||||||
|> mapToSignal { localAuthTokens -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
|> mapToSignal { localAuthTokens -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||||
var authTokens = localAuthTokens
|
var authTokens = localAuthTokens
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
authTokens.removeAll()
|
||||||
|
#endif
|
||||||
|
|
||||||
for data in cloudValue {
|
for data in cloudValue {
|
||||||
if !authTokens.contains(data) {
|
if !authTokens.contains(data) {
|
||||||
authTokens.insert(data, at: 0)
|
authTokens.insert(data, at: 0)
|
||||||
@ -274,7 +286,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
|||||||
|> castError(AuthorizationCodeRequestError.self)
|
|> castError(AuthorizationCodeRequestError.self)
|
||||||
|> mapToSignal { firebaseSecret -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
|> mapToSignal { firebaseSecret -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||||
guard let firebaseSecret = firebaseSecret else {
|
guard let firebaseSecret = firebaseSecret else {
|
||||||
return internalResendAuthorizationCode(account: account, number: phoneNumber, hash: phoneCodeHash, syncContacts: syncContacts)
|
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: phoneNumber, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sendFirebaseAuthorizationCode(accountManager: accountManager, account: account, phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret, syncContacts: syncContacts)
|
return sendFirebaseAuthorizationCode(accountManager: accountManager, account: account, phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret, syncContacts: syncContacts)
|
||||||
@ -293,7 +305,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
|||||||
}
|
}
|
||||||
|> castError(AuthorizationCodeRequestError.self)
|
|> castError(AuthorizationCodeRequestError.self)
|
||||||
} else {
|
} else {
|
||||||
return internalResendAuthorizationCode(account: account, number: phoneNumber, hash: phoneCodeHash, syncContacts: syncContacts)
|
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: phoneNumber, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,7 +345,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func internalResendAuthorizationCode(account: UnauthorizedAccount, number: String, hash: String, syncContacts: Bool) -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> {
|
private func internalResendAuthorizationCode(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, number: String, apiId: Int32, apiHash: String, hash: String, syncContacts: Bool, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> {
|
||||||
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|
||||||
|> mapError { error -> AuthorizationCodeRequestError in
|
|> mapError { error -> AuthorizationCodeRequestError in
|
||||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||||
@ -349,60 +361,155 @@ private func internalResendAuthorizationCode(account: UnauthorizedAccount, numbe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> mapToSignal { sentCode -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
|> mapToSignal { sentCode -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||||
return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in
|
return account.postbox.transaction { transaction -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||||
switch sentCode {
|
switch sentCode {
|
||||||
case let .sentCode(_, type, phoneCodeHash, nextType, timeout):
|
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
|
||||||
var parsedNextType: AuthorizationCodeNextType?
|
var parsedNextType: AuthorizationCodeNextType?
|
||||||
if let nextType = nextType {
|
if let nextType = nextType {
|
||||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
|
||||||
|
return firebaseSecretStream
|
||||||
|
|> map { mapping -> String? in
|
||||||
|
guard let receipt = receipt else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if let value = mapping[receipt] {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if receipt == "" && mapping.count == 1 {
|
||||||
|
return mapping.first?.value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|> filter { $0 != nil }
|
||||||
|
|> take(1)
|
||||||
|
|> timeout(Double(pushTimeout ?? 15), queue: .mainQueue(), alternate: .single(nil))
|
||||||
|
|> castError(AuthorizationCodeRequestError.self)
|
||||||
|
|> mapToSignal { firebaseSecret -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||||
|
guard let firebaseSecret = firebaseSecret else {
|
||||||
|
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendFirebaseAuthorizationCode(accountManager: accountManager, account: account, phoneNumber: number, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret, syncContacts: syncContacts)
|
||||||
|
|> `catch` { _ -> Signal<Bool, SendFirebaseAuthorizationCodeError> in
|
||||||
|
return .single(false)
|
||||||
|
}
|
||||||
|
|> mapError { _ -> AuthorizationCodeRequestError in
|
||||||
|
return .generic(info: nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { success -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||||
|
if success {
|
||||||
|
return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in
|
||||||
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||||
|
|
||||||
|
return .sentCode(account)
|
||||||
|
}
|
||||||
|
|> castError(AuthorizationCodeRequestError.self)
|
||||||
|
} else {
|
||||||
|
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return .sentCode(account)
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||||
|
|
||||||
|
return .single(.sentCode(account))
|
||||||
case .sentCodeSuccess:
|
case .sentCodeSuccess:
|
||||||
return .loggedIn
|
return .single(.loggedIn)
|
||||||
}
|
}
|
||||||
} |> mapError { _ -> AuthorizationCodeRequestError in }
|
}
|
||||||
|
|> mapError { _ -> AuthorizationCodeRequestError in }
|
||||||
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func resendAuthorizationCode(account: UnauthorizedAccount) -> Signal<Void, AuthorizationCodeRequestError> {
|
public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, apiId: Int32, apiHash: String, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal<Void, AuthorizationCodeRequestError> {
|
||||||
return account.postbox.transaction { transaction -> Signal<Void, AuthorizationCodeRequestError> in
|
return account.postbox.transaction { transaction -> Signal<Void, AuthorizationCodeRequestError> in
|
||||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||||
switch state.contents {
|
switch state.contents {
|
||||||
case let .confirmationCodeEntry(number, _, hash, _, nextType, syncContacts):
|
case let .confirmationCodeEntry(number, _, hash, _, nextType, syncContacts):
|
||||||
if nextType != nil {
|
if nextType != nil {
|
||||||
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|
||||||
|> mapError { error -> AuthorizationCodeRequestError in
|
|> mapError { error -> AuthorizationCodeRequestError in
|
||||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||||
return .limitExceeded
|
return .limitExceeded
|
||||||
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
|
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
|
||||||
return .invalidPhoneNumber
|
return .invalidPhoneNumber
|
||||||
} else if error.errorDescription == "PHONE_NUMBER_FLOOD" {
|
} else if error.errorDescription == "PHONE_NUMBER_FLOOD" {
|
||||||
return .phoneLimitExceeded
|
return .phoneLimitExceeded
|
||||||
} else if error.errorDescription == "PHONE_NUMBER_BANNED" {
|
} else if error.errorDescription == "PHONE_NUMBER_BANNED" {
|
||||||
return .phoneBanned
|
return .phoneBanned
|
||||||
} else {
|
} else {
|
||||||
return .generic(info: (Int(error.errorCode), error.errorDescription))
|
return .generic(info: (Int(error.errorCode), error.errorDescription))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|> mapToSignal { sentCode -> Signal<Void, AuthorizationCodeRequestError> in
|
}
|
||||||
return account.postbox.transaction { transaction -> Void in
|
|> mapToSignal { sentCode -> Signal<Void, AuthorizationCodeRequestError> in
|
||||||
switch sentCode {
|
return account.postbox.transaction { transaction -> Signal<Void, AuthorizationCodeRequestError> in
|
||||||
case let .sentCode(_, type, phoneCodeHash, nextType, timeout):
|
switch sentCode {
|
||||||
|
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
|
||||||
var parsedNextType: AuthorizationCodeNextType?
|
var parsedNextType: AuthorizationCodeNextType?
|
||||||
if let nextType = nextType {
|
if let nextType = nextType {
|
||||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||||
}
|
|
||||||
|
|
||||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
|
||||||
case .sentCodeSuccess:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
} |> mapError { _ -> AuthorizationCodeRequestError in }
|
|
||||||
|
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
|
||||||
|
return firebaseSecretStream
|
||||||
|
|> map { mapping -> String? in
|
||||||
|
guard let receipt = receipt else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if let value = mapping[receipt] {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if receipt == "" && mapping.count == 1 {
|
||||||
|
return mapping.first?.value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|> filter { $0 != nil }
|
||||||
|
|> take(1)
|
||||||
|
|> timeout(Double(pushTimeout ?? 15), queue: .mainQueue(), alternate: .single(nil))
|
||||||
|
|> castError(AuthorizationCodeRequestError.self)
|
||||||
|
|> mapToSignal { firebaseSecret -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||||
|
guard let firebaseSecret = firebaseSecret else {
|
||||||
|
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendFirebaseAuthorizationCode(accountManager: accountManager, account: account, phoneNumber: number, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret, syncContacts: syncContacts)
|
||||||
|
|> `catch` { _ -> Signal<Bool, SendFirebaseAuthorizationCodeError> in
|
||||||
|
return .single(false)
|
||||||
|
}
|
||||||
|
|> mapError { _ -> AuthorizationCodeRequestError in
|
||||||
|
return .generic(info: nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { success -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||||
|
if success {
|
||||||
|
return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in
|
||||||
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||||
|
|
||||||
|
return .sentCode(account)
|
||||||
|
}
|
||||||
|
|> castError(AuthorizationCodeRequestError.self)
|
||||||
|
} else {
|
||||||
|
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> map { _ -> Void in return Void() }
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||||
|
case .sentCodeSuccess:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return .single(Void())
|
||||||
}
|
}
|
||||||
|
|> mapError { _ -> AuthorizationCodeRequestError in }
|
||||||
|
|> switchToLatest
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return .fail(.generic(info: nil))
|
return .fail(.generic(info: nil))
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ public enum ServerProvidedSuggestion: String {
|
|||||||
case newcomerTicks = "NEWCOMER_TICKS"
|
case newcomerTicks = "NEWCOMER_TICKS"
|
||||||
case validatePhoneNumber = "VALIDATE_PHONE_NUMBER"
|
case validatePhoneNumber = "VALIDATE_PHONE_NUMBER"
|
||||||
case validatePassword = "VALIDATE_PASSWORD"
|
case validatePassword = "VALIDATE_PASSWORD"
|
||||||
case setupPassword = "SETUP_2FA"
|
case setupPassword = "SETUP_PASSWORD"
|
||||||
}
|
}
|
||||||
|
|
||||||
private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:])
|
private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:])
|
||||||
@ -33,12 +33,7 @@ public func getServerProvidedSuggestions(account: Account) -> Signal<[ServerProv
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
var list = listItems
|
|
||||||
list.append(ServerProvidedSuggestion.setupPassword.rawValue)
|
|
||||||
#else
|
|
||||||
let list = listItems
|
let list = listItems
|
||||||
#endif
|
|
||||||
|
|
||||||
return list.compactMap { item -> ServerProvidedSuggestion? in
|
return list.compactMap { item -> ServerProvidedSuggestion? in
|
||||||
return ServerProvidedSuggestion(rawValue: item)
|
return ServerProvidedSuggestion(rawValue: item)
|
||||||
|
@ -12,7 +12,6 @@ public struct TelegramChatBannedRightsFlags: OptionSet, Hashable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static let banReadMessages = TelegramChatBannedRightsFlags(rawValue: 1 << 0)
|
public static let banReadMessages = TelegramChatBannedRightsFlags(rawValue: 1 << 0)
|
||||||
public static let banSendMessages = TelegramChatBannedRightsFlags(rawValue: 1 << 1)
|
|
||||||
public static let banSendMedia = TelegramChatBannedRightsFlags(rawValue: 1 << 2)
|
public static let banSendMedia = TelegramChatBannedRightsFlags(rawValue: 1 << 2)
|
||||||
public static let banSendStickers = TelegramChatBannedRightsFlags(rawValue: 1 << 3)
|
public static let banSendStickers = TelegramChatBannedRightsFlags(rawValue: 1 << 3)
|
||||||
public static let banSendGifs = TelegramChatBannedRightsFlags(rawValue: 1 << 4)
|
public static let banSendGifs = TelegramChatBannedRightsFlags(rawValue: 1 << 4)
|
||||||
@ -30,6 +29,7 @@ public struct TelegramChatBannedRightsFlags: OptionSet, Hashable {
|
|||||||
public static let banSendMusic = TelegramChatBannedRightsFlags(rawValue: 1 << 22)
|
public static let banSendMusic = TelegramChatBannedRightsFlags(rawValue: 1 << 22)
|
||||||
public static let banSendVoice = TelegramChatBannedRightsFlags(rawValue: 1 << 23)
|
public static let banSendVoice = TelegramChatBannedRightsFlags(rawValue: 1 << 23)
|
||||||
public static let banSendFiles = TelegramChatBannedRightsFlags(rawValue: 1 << 24)
|
public static let banSendFiles = TelegramChatBannedRightsFlags(rawValue: 1 << 24)
|
||||||
|
public static let banSendText = TelegramChatBannedRightsFlags(rawValue: 1 << 25)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct TelegramChatBannedRights: PostboxCoding, Equatable {
|
public struct TelegramChatBannedRights: PostboxCoding, Equatable {
|
||||||
|
@ -14,7 +14,7 @@ public func canSendMessagesToPeer(_ peer: Peer) -> Bool {
|
|||||||
} else if let peer = peer as? TelegramSecretChat {
|
} else if let peer = peer as? TelegramSecretChat {
|
||||||
return peer.embeddedState == .active
|
return peer.embeddedState == .active
|
||||||
} else if let peer = peer as? TelegramChannel {
|
} else if let peer = peer as? TelegramChannel {
|
||||||
return peer.hasPermission(.sendMessages)
|
return peer.hasPermission(.sendSomething)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
private func updateData(_ data: KeyboardInputData) {
|
private func updateData(_ data: KeyboardInputData) {
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
self.state?.selectedItem = data.emoji.itemGroups.first?.items.first
|
self.state?.selectedItem = data.emoji.panelItemGroups.first?.items.first
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
let updateSearchQuery: (String, String) -> Void = { [weak self] rawQuery, languageCode in
|
let updateSearchQuery: (String, String) -> Void = { [weak self] rawQuery, languageCode in
|
||||||
@ -735,9 +735,9 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if state?.keyboardContentId == AnyHashable("emoji") {
|
if state?.keyboardContentId == AnyHashable("emoji") {
|
||||||
data.emoji = data.emoji.withUpdatedItemGroups(itemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults)
|
data.emoji = data.emoji.withUpdatedItemGroups(panelItemGroups: data.emoji.panelItemGroups, contentItemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults)
|
||||||
} else {
|
} else {
|
||||||
data.stickers = data.stickers?.withUpdatedItemGroups(itemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults)
|
data.stickers = data.stickers?.withUpdatedItemGroups(panelItemGroups: data.stickers?.panelItemGroups ?? searchResult.groups, contentItemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1196,7 +1196,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
iconFile: nil
|
iconFile: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
inputData.emoji = inputData.emoji.withUpdatedItemGroups(itemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
|
inputData.emoji = inputData.emoji.withUpdatedItemGroups(panelItemGroups: inputData.emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
var transition: Transition = .immediate
|
var transition: Transition = .immediate
|
||||||
@ -1586,9 +1586,9 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
|
|
||||||
private func processInputData(inputData: InputData) -> InputData {
|
private func processInputData(inputData: InputData) -> InputData {
|
||||||
return InputData(
|
return InputData(
|
||||||
emoji: inputData.emoji.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: inputData.emoji.itemGroups), itemContentUniqueId: inputData.emoji.itemContentUniqueId, emptySearchResults: inputData.emoji.emptySearchResults),
|
emoji: inputData.emoji.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: inputData.emoji.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: inputData.emoji.contentItemGroups), itemContentUniqueId: inputData.emoji.itemContentUniqueId, emptySearchResults: inputData.emoji.emptySearchResults),
|
||||||
stickers: inputData.stickers.flatMap { stickers in
|
stickers: inputData.stickers.flatMap { stickers in
|
||||||
return stickers.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.itemGroups), itemContentUniqueId: nil, emptySearchResults: nil)
|
return stickers.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.contentItemGroups), itemContentUniqueId: nil, emptySearchResults: nil)
|
||||||
},
|
},
|
||||||
gifs: inputData.gifs,
|
gifs: inputData.gifs,
|
||||||
availableGifSearchEmojies: inputData.availableGifSearchEmojies
|
availableGifSearchEmojies: inputData.availableGifSearchEmojies
|
||||||
|
@ -371,7 +371,7 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
} else {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
strongSelf.stableEmptyResultEmoji = nil
|
||||||
}
|
}
|
||||||
emojiContent = emojiContent.withUpdatedItemGroups(itemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
|
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
strongSelf.stableEmptyResultEmoji = nil
|
||||||
}
|
}
|
||||||
|
@ -1520,8 +1520,10 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
var text: String
|
var text: String
|
||||||
var useOpaqueTheme: Bool
|
var useOpaqueTheme: Bool
|
||||||
var isActive: Bool
|
var isActive: Bool
|
||||||
|
var hasPresetSearch: Bool
|
||||||
var size: CGSize
|
var size: CGSize
|
||||||
var canFocus: Bool
|
var canFocus: Bool
|
||||||
|
var hasSearchItems: Bool
|
||||||
|
|
||||||
static func ==(lhs: Params, rhs: Params) -> Bool {
|
static func ==(lhs: Params, rhs: Params) -> Bool {
|
||||||
if lhs.theme !== rhs.theme {
|
if lhs.theme !== rhs.theme {
|
||||||
@ -1539,12 +1541,18 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
if lhs.isActive != rhs.isActive {
|
if lhs.isActive != rhs.isActive {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.hasPresetSearch != rhs.hasPresetSearch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.size != rhs.size {
|
if lhs.size != rhs.size {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.canFocus != rhs.canFocus {
|
if lhs.canFocus != rhs.canFocus {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.hasSearchItems != rhs.hasSearchItems {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1565,6 +1573,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
private let searchIconView: UIImageView
|
private let searchIconView: UIImageView
|
||||||
private let searchIconTintView: UIImageView
|
private let searchIconTintView: UIImageView
|
||||||
|
|
||||||
|
private let backIconView: UIImageView
|
||||||
|
private let backIconTintView: UIImageView
|
||||||
|
|
||||||
private let clearIconView: UIImageView
|
private let clearIconView: UIImageView
|
||||||
private let clearIconTintView: UIImageView
|
private let clearIconTintView: UIImageView
|
||||||
private let clearIconButton: HighlightTrackingButton
|
private let clearIconButton: HighlightTrackingButton
|
||||||
@ -1575,9 +1586,12 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
private let cancelButtonTitle: ComponentView<Empty>
|
private let cancelButtonTitle: ComponentView<Empty>
|
||||||
private let cancelButton: HighlightTrackingButton
|
private let cancelButton: HighlightTrackingButton
|
||||||
|
|
||||||
|
private var suggestedItemsView: ComponentView<Empty>?
|
||||||
|
|
||||||
private var textField: EmojiSearchTextField?
|
private var textField: EmojiSearchTextField?
|
||||||
|
|
||||||
private var tapRecognizer: UITapGestureRecognizer?
|
private var tapRecognizer: UITapGestureRecognizer?
|
||||||
|
private(set) var currentPresetSearchTerm: String?
|
||||||
|
|
||||||
private var params: Params?
|
private var params: Params?
|
||||||
|
|
||||||
@ -1598,6 +1612,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
self.searchIconView = UIImageView()
|
self.searchIconView = UIImageView()
|
||||||
self.searchIconTintView = UIImageView()
|
self.searchIconTintView = UIImageView()
|
||||||
|
|
||||||
|
self.backIconView = UIImageView()
|
||||||
|
self.backIconTintView = UIImageView()
|
||||||
|
|
||||||
self.clearIconView = UIImageView()
|
self.clearIconView = UIImageView()
|
||||||
self.clearIconTintView = UIImageView()
|
self.clearIconTintView = UIImageView()
|
||||||
self.clearIconButton = HighlightableButton()
|
self.clearIconButton = HighlightableButton()
|
||||||
@ -1619,6 +1636,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
self.addSubview(self.searchIconView)
|
self.addSubview(self.searchIconView)
|
||||||
self.tintContainerView.addSubview(self.searchIconTintView)
|
self.tintContainerView.addSubview(self.searchIconTintView)
|
||||||
|
|
||||||
|
self.addSubview(self.backIconView)
|
||||||
|
self.tintContainerView.addSubview(self.backIconTintView)
|
||||||
|
|
||||||
self.addSubview(self.clearIconView)
|
self.addSubview(self.clearIconView)
|
||||||
self.tintContainerView.addSubview(self.clearIconTintView)
|
self.tintContainerView.addSubview(self.clearIconTintView)
|
||||||
self.addSubview(self.clearIconButton)
|
self.addSubview(self.clearIconButton)
|
||||||
@ -1681,25 +1701,38 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
|
|
||||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
if case .ended = recognizer.state {
|
if case .ended = recognizer.state {
|
||||||
if self.textField == nil, let textComponentView = self.textView.view, self.params?.canFocus == true {
|
let location = recognizer.location(in: self)
|
||||||
let backgroundFrame = self.backgroundLayer.frame
|
if self.backIconView.frame.contains(location) {
|
||||||
let textFieldFrame = CGRect(origin: CGPoint(x: textComponentView.frame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textComponentView.frame.minX, height: backgroundFrame.height))
|
if let suggestedItemsView = self.suggestedItemsView?.view as? EmojiSearchSearchBarComponent.View {
|
||||||
|
suggestedItemsView.clearSelection(dispatchEvent : true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.textField == nil, let textComponentView = self.textView.view, self.params?.canFocus == true {
|
||||||
|
let backgroundFrame = self.backgroundLayer.frame
|
||||||
|
let textFieldFrame = CGRect(origin: CGPoint(x: textComponentView.frame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textComponentView.frame.minX, height: backgroundFrame.height))
|
||||||
|
|
||||||
|
let textField = EmojiSearchTextField(frame: textFieldFrame)
|
||||||
|
textField.autocorrectionType = .no
|
||||||
|
self.textField = textField
|
||||||
|
self.insertSubview(textField, belowSubview: self.clearIconView)
|
||||||
|
textField.delegate = self
|
||||||
|
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
||||||
|
}
|
||||||
|
|
||||||
let textField = EmojiSearchTextField(frame: textFieldFrame)
|
self.currentPresetSearchTerm = nil
|
||||||
textField.autocorrectionType = .no
|
if let suggestedItemsView = self.suggestedItemsView?.view as? EmojiSearchSearchBarComponent.View {
|
||||||
self.textField = textField
|
suggestedItemsView.clearSelection(dispatchEvent: false)
|
||||||
self.insertSubview(textField, belowSubview: self.clearIconView)
|
}
|
||||||
textField.delegate = self
|
|
||||||
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
self.activated()
|
||||||
|
|
||||||
|
self.textField?.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.activated()
|
|
||||||
|
|
||||||
self.textField?.becomeFirstResponder()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func cancelPressed() {
|
@objc private func cancelPressed() {
|
||||||
|
self.currentPresetSearchTerm = nil
|
||||||
self.updateQuery("", "en")
|
self.updateQuery("", "en")
|
||||||
|
|
||||||
self.clearIconView.isHidden = true
|
self.clearIconView.isHidden = true
|
||||||
@ -1720,6 +1753,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func clearPressed() {
|
@objc private func clearPressed() {
|
||||||
|
self.currentPresetSearchTerm = nil
|
||||||
self.updateQuery("", "en")
|
self.updateQuery("", "en")
|
||||||
self.textField?.text = ""
|
self.textField?.text = ""
|
||||||
|
|
||||||
@ -1767,6 +1801,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
self.clearIconTintView.isHidden = text.isEmpty
|
self.clearIconTintView.isHidden = text.isEmpty
|
||||||
self.clearIconButton.isHidden = text.isEmpty
|
self.clearIconButton.isHidden = text.isEmpty
|
||||||
|
|
||||||
|
self.currentPresetSearchTerm = nil
|
||||||
self.updateQuery(text, inputLanguage)
|
self.updateQuery(text, inputLanguage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1775,28 +1810,37 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.params = nil
|
self.params = nil
|
||||||
self.update(theme: params.theme, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, transition: transition)
|
self.update(theme: params.theme, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, hasSearchItems: params.hasSearchItems, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(theme: PresentationTheme, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, transition: Transition) {
|
public func update(theme: PresentationTheme, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, hasSearchItems: Bool, transition: Transition) {
|
||||||
let params = Params(
|
let params = Params(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
strings: strings,
|
strings: strings,
|
||||||
text: text,
|
text: text,
|
||||||
useOpaqueTheme: useOpaqueTheme,
|
useOpaqueTheme: useOpaqueTheme,
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
|
hasPresetSearch: self.currentPresetSearchTerm == nil,
|
||||||
size: size,
|
size: size,
|
||||||
canFocus: canFocus
|
canFocus: canFocus,
|
||||||
|
hasSearchItems: hasSearchItems
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.params == params {
|
if self.params == params {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isActiveWithText = isActive && self.currentPresetSearchTerm == nil
|
||||||
|
|
||||||
|
let isLeftAligned = isActiveWithText || hasSearchItems
|
||||||
|
|
||||||
if self.params?.theme !== theme {
|
if self.params?.theme !== theme {
|
||||||
self.searchIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: theme.chat.inputMediaPanel.panelContentVibrantOverlayColor)
|
self.searchIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: theme.chat.inputMediaPanel.panelContentVibrantOverlayColor)
|
||||||
self.searchIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)
|
self.searchIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)
|
||||||
|
|
||||||
|
self.backIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.chat.inputMediaPanel.panelContentVibrantOverlayColor)
|
||||||
|
self.backIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)
|
||||||
|
|
||||||
self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.chat.inputMediaPanel.panelContentVibrantOverlayColor)
|
self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.chat.inputMediaPanel.panelContentVibrantOverlayColor)
|
||||||
self.clearIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)
|
self.clearIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)
|
||||||
}
|
}
|
||||||
@ -1864,7 +1908,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
let cancelButtonSpacing: CGFloat = 8.0
|
let cancelButtonSpacing: CGFloat = 8.0
|
||||||
|
|
||||||
var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight))
|
var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight))
|
||||||
if isActive {
|
if isActiveWithText {
|
||||||
backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing
|
backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing
|
||||||
}
|
}
|
||||||
transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame)
|
transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame)
|
||||||
@ -1873,14 +1917,96 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height)))
|
transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height)))
|
||||||
|
|
||||||
var textFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width - textSize.width) / 2.0), y: backgroundFrame.minY + floor((backgroundFrame.height - textSize.height) / 2.0)), size: textSize)
|
var textFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width - textSize.width) / 2.0), y: backgroundFrame.minY + floor((backgroundFrame.height - textSize.height) / 2.0)), size: textSize)
|
||||||
if isActive {
|
if isLeftAligned {
|
||||||
textFrame.origin.x = backgroundFrame.minX + sideTextInset
|
textFrame.origin.x = backgroundFrame.minX + sideTextInset
|
||||||
}
|
}
|
||||||
|
|
||||||
if let image = self.searchIconView.image {
|
if let image = self.searchIconView.image {
|
||||||
let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
|
let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
|
||||||
transition.setFrame(view: self.searchIconView, frame: iconFrame)
|
transition.setBounds(view: self.searchIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||||
transition.setFrame(view: self.searchIconTintView, frame: iconFrame)
|
transition.setPosition(view: self.searchIconView, position: iconFrame.center)
|
||||||
|
transition.setBounds(view: self.searchIconTintView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||||
|
transition.setPosition(view: self.searchIconTintView, position: iconFrame.center)
|
||||||
|
transition.setScale(view: self.searchIconView, scale: self.currentPresetSearchTerm == nil ? 1.0 : 0.001)
|
||||||
|
transition.setAlpha(view: self.searchIconView, alpha: self.currentPresetSearchTerm == nil ? 1.0 : 0.0)
|
||||||
|
transition.setScale(view: self.searchIconTintView, scale: self.currentPresetSearchTerm == nil ? 1.0 : 0.001)
|
||||||
|
transition.setAlpha(view: self.searchIconTintView, alpha: self.currentPresetSearchTerm == nil ? 1.0 : 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let image = self.backIconView.image {
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
|
||||||
|
transition.setBounds(view: self.backIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||||
|
transition.setPosition(view: self.backIconView, position: iconFrame.center)
|
||||||
|
transition.setBounds(view: self.backIconTintView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||||
|
transition.setPosition(view: self.backIconTintView, position: iconFrame.center)
|
||||||
|
transition.setScale(view: self.backIconView, scale: self.currentPresetSearchTerm != nil ? 1.0 : 0.001)
|
||||||
|
transition.setAlpha(view: self.backIconView, alpha: self.currentPresetSearchTerm != nil ? 1.0 : 0.0)
|
||||||
|
transition.setScale(view: self.backIconTintView, scale: self.currentPresetSearchTerm != nil ? 1.0 : 0.001)
|
||||||
|
transition.setAlpha(view: self.backIconTintView, alpha: self.currentPresetSearchTerm != nil ? 1.0 : 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasSearchItems {
|
||||||
|
let suggestedItemsView: ComponentView<Empty>
|
||||||
|
var suggestedItemsTransition = transition
|
||||||
|
if let current = self.suggestedItemsView {
|
||||||
|
suggestedItemsView = current
|
||||||
|
} else {
|
||||||
|
suggestedItemsTransition = .immediate
|
||||||
|
suggestedItemsView = ComponentView()
|
||||||
|
self.suggestedItemsView = suggestedItemsView
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemsX: CGFloat = textFrame.maxX + 8.0
|
||||||
|
let suggestedItemsFrame = CGRect(origin: CGPoint(x: itemsX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - itemsX, height: backgroundFrame.height))
|
||||||
|
let _ = suggestedItemsView.update(
|
||||||
|
transition: suggestedItemsTransition,
|
||||||
|
component: AnyComponent(EmojiSearchSearchBarComponent(
|
||||||
|
theme: theme,
|
||||||
|
strings: strings,
|
||||||
|
searchTermUpdated: { [weak self] term in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var shouldChangeActivation = false
|
||||||
|
if (self.currentPresetSearchTerm == nil) != (term == nil) {
|
||||||
|
shouldChangeActivation = true
|
||||||
|
}
|
||||||
|
self.currentPresetSearchTerm = term
|
||||||
|
|
||||||
|
if shouldChangeActivation {
|
||||||
|
self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
|
|
||||||
|
if term == nil {
|
||||||
|
self.deactivated(self.textField?.isFirstResponder ?? false)
|
||||||
|
self.updateQuery(term ?? "", "en")
|
||||||
|
} else {
|
||||||
|
self.updateQuery(term ?? "", "en")
|
||||||
|
self.activated()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.updateQuery(term ?? "", "en")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: suggestedItemsFrame.size
|
||||||
|
)
|
||||||
|
if let suggestedItemsComponentView = suggestedItemsView.view {
|
||||||
|
if suggestedItemsComponentView.superview == nil {
|
||||||
|
self.addSubview(suggestedItemsComponentView)
|
||||||
|
}
|
||||||
|
suggestedItemsTransition.setFrame(view: suggestedItemsComponentView, frame: suggestedItemsFrame)
|
||||||
|
suggestedItemsTransition.setAlpha(view: suggestedItemsComponentView, alpha: isActiveWithText ? 0.0 : 1.0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let suggestedItemsView = self.suggestedItemsView {
|
||||||
|
self.suggestedItemsView = nil
|
||||||
|
if let suggestedItemsComponentView = suggestedItemsView.view {
|
||||||
|
transition.setAlpha(view: suggestedItemsComponentView, alpha: 0.0, completion: { [weak suggestedItemsComponentView] _ in
|
||||||
|
suggestedItemsComponentView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let image = self.clearIconView.image {
|
if let image = self.clearIconView.image {
|
||||||
@ -2389,7 +2515,8 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
public let animationCache: AnimationCache
|
public let animationCache: AnimationCache
|
||||||
public let animationRenderer: MultiAnimationRenderer
|
public let animationRenderer: MultiAnimationRenderer
|
||||||
public let inputInteractionHolder: InputInteractionHolder
|
public let inputInteractionHolder: InputInteractionHolder
|
||||||
public let itemGroups: [ItemGroup]
|
public let panelItemGroups: [ItemGroup]
|
||||||
|
public let contentItemGroups: [ItemGroup]
|
||||||
public let itemLayoutType: ItemLayoutType
|
public let itemLayoutType: ItemLayoutType
|
||||||
public let itemContentUniqueId: AnyHashable?
|
public let itemContentUniqueId: AnyHashable?
|
||||||
public let warpContentsOnEdges: Bool
|
public let warpContentsOnEdges: Bool
|
||||||
@ -2407,7 +2534,8 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
animationCache: AnimationCache,
|
animationCache: AnimationCache,
|
||||||
animationRenderer: MultiAnimationRenderer,
|
animationRenderer: MultiAnimationRenderer,
|
||||||
inputInteractionHolder: InputInteractionHolder,
|
inputInteractionHolder: InputInteractionHolder,
|
||||||
itemGroups: [ItemGroup],
|
panelItemGroups: [ItemGroup],
|
||||||
|
contentItemGroups: [ItemGroup],
|
||||||
itemLayoutType: ItemLayoutType,
|
itemLayoutType: ItemLayoutType,
|
||||||
itemContentUniqueId: AnyHashable?,
|
itemContentUniqueId: AnyHashable?,
|
||||||
warpContentsOnEdges: Bool,
|
warpContentsOnEdges: Bool,
|
||||||
@ -2424,7 +2552,8 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
self.animationCache = animationCache
|
self.animationCache = animationCache
|
||||||
self.animationRenderer = animationRenderer
|
self.animationRenderer = animationRenderer
|
||||||
self.inputInteractionHolder = inputInteractionHolder
|
self.inputInteractionHolder = inputInteractionHolder
|
||||||
self.itemGroups = itemGroups
|
self.panelItemGroups = panelItemGroups
|
||||||
|
self.contentItemGroups = contentItemGroups
|
||||||
self.itemLayoutType = itemLayoutType
|
self.itemLayoutType = itemLayoutType
|
||||||
self.itemContentUniqueId = itemContentUniqueId
|
self.itemContentUniqueId = itemContentUniqueId
|
||||||
self.warpContentsOnEdges = warpContentsOnEdges
|
self.warpContentsOnEdges = warpContentsOnEdges
|
||||||
@ -2436,7 +2565,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
self.selectedItems = selectedItems
|
self.selectedItems = selectedItems
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedItemGroups(itemGroups: [ItemGroup], itemContentUniqueId: AnyHashable?, emptySearchResults: EmptySearchResults?) -> EmojiPagerContentComponent {
|
public func withUpdatedItemGroups(panelItemGroups: [ItemGroup], contentItemGroups: [ItemGroup], itemContentUniqueId: AnyHashable?, emptySearchResults: EmptySearchResults?) -> EmojiPagerContentComponent {
|
||||||
return EmojiPagerContentComponent(
|
return EmojiPagerContentComponent(
|
||||||
id: self.id,
|
id: self.id,
|
||||||
context: self.context,
|
context: self.context,
|
||||||
@ -2444,7 +2573,8 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
animationCache: self.animationCache,
|
animationCache: self.animationCache,
|
||||||
animationRenderer: self.animationRenderer,
|
animationRenderer: self.animationRenderer,
|
||||||
inputInteractionHolder: self.inputInteractionHolder,
|
inputInteractionHolder: self.inputInteractionHolder,
|
||||||
itemGroups: itemGroups,
|
panelItemGroups: panelItemGroups,
|
||||||
|
contentItemGroups: contentItemGroups,
|
||||||
itemLayoutType: self.itemLayoutType,
|
itemLayoutType: self.itemLayoutType,
|
||||||
itemContentUniqueId: itemContentUniqueId,
|
itemContentUniqueId: itemContentUniqueId,
|
||||||
warpContentsOnEdges: self.warpContentsOnEdges,
|
warpContentsOnEdges: self.warpContentsOnEdges,
|
||||||
@ -2479,7 +2609,10 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
if lhs.inputInteractionHolder !== rhs.inputInteractionHolder {
|
if lhs.inputInteractionHolder !== rhs.inputInteractionHolder {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.itemGroups != rhs.itemGroups {
|
if lhs.panelItemGroups != rhs.panelItemGroups {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.contentItemGroups != rhs.contentItemGroups {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.itemLayoutType != rhs.itemLayoutType {
|
if lhs.itemLayoutType != rhs.itemLayoutType {
|
||||||
@ -4148,7 +4281,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
var subgroupItemIndex: Int?
|
var subgroupItemIndex: Int?
|
||||||
if group.supergroupId == supergroupId {
|
if group.supergroupId == supergroupId {
|
||||||
if let subgroupId = subgroupId {
|
if let subgroupId = subgroupId {
|
||||||
inner: for itemGroup in component.itemGroups {
|
inner: for itemGroup in component.contentItemGroups {
|
||||||
if itemGroup.supergroupId == supergroupId {
|
if itemGroup.supergroupId == supergroupId {
|
||||||
for i in 0 ..< itemGroup.items.count {
|
for i in 0 ..< itemGroup.items.count {
|
||||||
if itemGroup.items[i].subgroupId == subgroupId {
|
if itemGroup.items[i].subgroupId == subgroupId {
|
||||||
@ -4858,7 +4991,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
|
|
||||||
self.updateScrollingOffset(isReset: false, transition: .immediate)
|
self.updateScrollingOffset(isReset: false, transition: .immediate)
|
||||||
|
|
||||||
if self.isSearchActivated {
|
if self.isSearchActivated, let visibleSearchHeader = self.visibleSearchHeader, visibleSearchHeader.currentPresetSearchTerm == nil {
|
||||||
self.visibleSearchHeader?.deactivate()
|
self.visibleSearchHeader?.deactivate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4884,9 +5017,9 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
|
let isInteracting = self.scrollView.isDragging || self.scrollView.isDecelerating
|
||||||
if let previousScrollingOffsetValue = self.previousScrollingOffset, !self.keepTopPanelVisibleUntilScrollingInput {
|
if let previousScrollingOffsetValue = self.previousScrollingOffset, !self.keepTopPanelVisibleUntilScrollingInput, !self.isSearchActivated {
|
||||||
let currentBounds = scrollView.bounds
|
let currentBounds = self.scrollView.bounds
|
||||||
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
|
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
|
||||||
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY)
|
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY)
|
||||||
|
|
||||||
@ -4968,7 +5101,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for groupItems in itemLayout.visibleItems(for: effectiveVisibleBounds) {
|
for groupItems in itemLayout.visibleItems(for: effectiveVisibleBounds) {
|
||||||
let itemGroup = component.itemGroups[groupItems.groupIndex]
|
let itemGroup = component.contentItemGroups[groupItems.groupIndex]
|
||||||
let itemGroupLayout = itemLayout.itemGroupLayouts[groupItems.groupIndex]
|
let itemGroupLayout = itemLayout.itemGroupLayouts[groupItems.groupIndex]
|
||||||
|
|
||||||
var assignTopVisibleSubgroupId = false
|
var assignTopVisibleSubgroupId = false
|
||||||
@ -5875,11 +6008,11 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
var previousAbsoluteItemPositions: [VisualItemKey: CGPoint] = [:]
|
var previousAbsoluteItemPositions: [VisualItemKey: CGPoint] = [:]
|
||||||
|
|
||||||
var anchorItems: [ItemLayer.Key: CGRect] = [:]
|
var anchorItems: [ItemLayer.Key: CGRect] = [:]
|
||||||
if let previousComponent = previousComponent, let previousItemLayout = self.itemLayout, previousComponent.itemGroups != component.itemGroups {
|
if let previousComponent = previousComponent, let previousItemLayout = self.itemLayout, previousComponent.contentItemGroups != component.contentItemGroups, previousComponent.itemContentUniqueId == component.itemContentUniqueId {
|
||||||
if !transition.animation.isImmediate {
|
if !transition.animation.isImmediate {
|
||||||
var previousItemPositionsValue: [VisualItemKey: CGPoint] = [:]
|
var previousItemPositionsValue: [VisualItemKey: CGPoint] = [:]
|
||||||
for groupIndex in 0 ..< previousComponent.itemGroups.count {
|
for groupIndex in 0 ..< previousComponent.contentItemGroups.count {
|
||||||
let itemGroup = previousComponent.itemGroups[groupIndex]
|
let itemGroup = previousComponent.contentItemGroups[groupIndex]
|
||||||
for itemIndex in 0 ..< itemGroup.items.count {
|
for itemIndex in 0 ..< itemGroup.items.count {
|
||||||
let item = itemGroup.items[itemIndex]
|
let item = itemGroup.items[itemIndex]
|
||||||
let itemKey: ItemLayer.Key
|
let itemKey: ItemLayer.Key
|
||||||
@ -5976,7 +6109,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var itemGroups: [ItemGroupDescription] = []
|
var itemGroups: [ItemGroupDescription] = []
|
||||||
for itemGroup in component.itemGroups {
|
for itemGroup in component.contentItemGroups {
|
||||||
itemGroups.append(ItemGroupDescription(
|
itemGroups.append(ItemGroupDescription(
|
||||||
supergroupId: itemGroup.supergroupId,
|
supergroupId: itemGroup.supergroupId,
|
||||||
groupId: itemGroup.groupId,
|
groupId: itemGroup.groupId,
|
||||||
@ -6021,15 +6154,14 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
|
|
||||||
let scrollOriginY: CGFloat = 0.0
|
let scrollOriginY: CGFloat = 0.0
|
||||||
|
|
||||||
|
|
||||||
let scrollSize = CGSize(width: availableSize.width, height: availableSize.height)
|
let scrollSize = CGSize(width: availableSize.width, height: availableSize.height)
|
||||||
transition.setPosition(view: self.scrollView, position: CGPoint(x: 0.0, y: scrollOriginY))
|
transition.setPosition(view: self.scrollView, position: CGPoint(x: 0.0, y: scrollOriginY))
|
||||||
|
|
||||||
transition.setFrame(view: self.scrollViewClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.searchHeight : 0.0), size: availableSize))
|
transition.setFrame(view: self.scrollViewClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.itemInsets.top : 0.0), size: availableSize))
|
||||||
transition.setBounds(view: self.scrollViewClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.searchHeight : 0.0), size: availableSize))
|
transition.setBounds(view: self.scrollViewClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.itemInsets.top : 0.0), size: availableSize))
|
||||||
|
|
||||||
transition.setFrame(view: self.vibrancyClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.searchHeight : 0.0), size: availableSize))
|
transition.setFrame(view: self.vibrancyClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.itemInsets.top : 0.0), size: availableSize))
|
||||||
transition.setBounds(view: self.vibrancyClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.searchHeight : 0.0), size: availableSize))
|
transition.setBounds(view: self.vibrancyClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.itemInsets.top : 0.0), size: availableSize))
|
||||||
|
|
||||||
let previousSize = self.scrollView.bounds.size
|
let previousSize = self.scrollView.bounds.size
|
||||||
var resetScrolling = false
|
var resetScrolling = false
|
||||||
@ -6041,6 +6173,10 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
self.scrollView.bounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollSize)
|
self.scrollView.bounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollSize)
|
||||||
|
|
||||||
|
if resetScrolling {
|
||||||
|
itemTransition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
let warpHeight: CGFloat = 50.0
|
let warpHeight: CGFloat = 50.0
|
||||||
var topWarpInset = pagerEnvironment.containerInsets.top
|
var topWarpInset = pagerEnvironment.containerInsets.top
|
||||||
if self.isSearchActivated {
|
if self.isSearchActivated {
|
||||||
@ -6100,16 +6236,16 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
outer: for i in 0 ..< component.itemGroups.count {
|
outer: for i in 0 ..< component.contentItemGroups.count {
|
||||||
for anchorItem in sortedAnchorItems {
|
for anchorItem in sortedAnchorItems {
|
||||||
if component.itemGroups[i].groupId != anchorItem.0.groupId {
|
if component.contentItemGroups[i].groupId != anchorItem.0.groupId {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for j in 0 ..< component.itemGroups[i].items.count {
|
for j in 0 ..< component.contentItemGroups[i].items.count {
|
||||||
let itemKey: ItemLayer.Key
|
let itemKey: ItemLayer.Key
|
||||||
itemKey = ItemLayer.Key(
|
itemKey = ItemLayer.Key(
|
||||||
groupId: component.itemGroups[i].groupId,
|
groupId: component.contentItemGroups[i].groupId,
|
||||||
itemId: component.itemGroups[i].items[j].content.id
|
itemId: component.contentItemGroups[i].items[j].content.id
|
||||||
)
|
)
|
||||||
|
|
||||||
if itemKey == anchorItem.0 {
|
if itemKey == anchorItem.0 {
|
||||||
@ -6137,19 +6273,15 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if resetScrolling {
|
if resetScrolling {
|
||||||
if component.displaySearchWithPlaceholder != nil && !self.isSearchActivated && component.searchInitiallyHidden {
|
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollSize)
|
||||||
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 50.0), size: scrollSize)
|
|
||||||
} else {
|
|
||||||
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollSize)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ignoreScrolling = false
|
self.ignoreScrolling = false
|
||||||
|
|
||||||
if calculateUpdatedItemPositions {
|
if calculateUpdatedItemPositions {
|
||||||
var updatedItemPositionsValue: [VisualItemKey: CGPoint] = [:]
|
var updatedItemPositionsValue: [VisualItemKey: CGPoint] = [:]
|
||||||
for groupIndex in 0 ..< component.itemGroups.count {
|
for groupIndex in 0 ..< component.contentItemGroups.count {
|
||||||
let itemGroup = component.itemGroups[groupIndex]
|
let itemGroup = component.contentItemGroups[groupIndex]
|
||||||
let itemGroupLayout = itemLayout.itemGroupLayouts[groupIndex]
|
let itemGroupLayout = itemLayout.itemGroupLayouts[groupIndex]
|
||||||
for itemIndex in 0 ..< itemGroup.items.count {
|
for itemIndex in 0 ..< itemGroup.items.count {
|
||||||
let item = itemGroup.items[itemIndex]
|
let item = itemGroup.items[itemIndex]
|
||||||
@ -6204,14 +6336,16 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/*if visibleSearchHeader.superview != self.scrollView {
|
/*if component.inputInteractionHolder.inputInteraction?.externalBackground == nil {
|
||||||
self.scrollView.addSubview(visibleSearchHeader)
|
if visibleSearchHeader.superview != self.scrollView {
|
||||||
self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
self.scrollView.addSubview(visibleSearchHeader)
|
||||||
|
self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
||||||
|
}
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
visibleSearchHeader = EmojiSearchHeaderView(activated: { [weak self] in
|
visibleSearchHeader = EmojiSearchHeaderView(activated: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6219,7 +6353,9 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
component.inputInteractionHolder.inputInteraction?.openSearch()
|
component.inputInteractionHolder.inputInteraction?.openSearch()
|
||||||
} else {
|
} else {
|
||||||
strongSelf.isSearchActivated = true
|
strongSelf.isSearchActivated = true
|
||||||
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(true)
|
if visibleSearchHeader.currentPresetSearchTerm == nil {
|
||||||
|
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(true)
|
||||||
|
}
|
||||||
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
|
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
|
||||||
}
|
}
|
||||||
}, deactivated: { [weak self] isFirstResponder in
|
}, deactivated: { [weak self] isFirstResponder in
|
||||||
@ -6255,8 +6391,8 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
||||||
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, transition: transition)
|
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, hasSearchItems: component.displaySearchWithPlaceholder == keyboardChildEnvironment.strings.EmojiSearch_SearchEmojiPlaceholder, transition: transition)
|
||||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
|
transition.attachAnimation(view: visibleSearchHeader, completion: { [weak self] completed in
|
||||||
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -6266,6 +6402,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame)
|
||||||
} else {
|
} else {
|
||||||
if let visibleSearchHeader = self.visibleSearchHeader {
|
if let visibleSearchHeader = self.visibleSearchHeader {
|
||||||
self.visibleSearchHeader = nil
|
self.visibleSearchHeader = nil
|
||||||
@ -6307,8 +6444,30 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var animateContentCrossfade = false
|
||||||
|
if let previousComponent, previousComponent.itemContentUniqueId != component.itemContentUniqueId, itemTransition.animation.isImmediate, !transition.animation.isImmediate {
|
||||||
|
animateContentCrossfade = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if animateContentCrossfade {
|
||||||
|
for (_, itemLayer) in self.visibleItemLayers {
|
||||||
|
if let snapshotLayer = itemLayer.snapshotContentTree() {
|
||||||
|
itemLayer.superlayer?.insertSublayer(snapshotLayer, above: itemLayer)
|
||||||
|
snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
|
||||||
|
snapshotLayer?.removeFromSuperlayer()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: attemptSynchronousLoads, previousItemPositions: previousItemPositions, previousAbsoluteItemPositions: previousAbsoluteItemPositions, updatedItemPositions: updatedItemPositions, hintDisappearingGroupFrame: hintDisappearingGroupFrame)
|
self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: attemptSynchronousLoads, previousItemPositions: previousItemPositions, previousAbsoluteItemPositions: previousAbsoluteItemPositions, updatedItemPositions: updatedItemPositions, hintDisappearingGroupFrame: hintDisappearingGroupFrame)
|
||||||
|
|
||||||
|
if animateContentCrossfade {
|
||||||
|
for (_, itemLayer) in self.visibleItemLayers {
|
||||||
|
itemLayer.animateAlpha(from: 0.0, to: CGFloat(itemLayer.opacity), duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7127,6 +7286,41 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let allItemGroups = itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
|
||||||
|
var headerItem = group.headerItem
|
||||||
|
|
||||||
|
if let groupId = group.id.base as? ItemCollectionId {
|
||||||
|
outer: for (id, info, _) in view.collectionInfos {
|
||||||
|
if id == groupId, let info = info as? StickerPackCollectionInfo {
|
||||||
|
if let thumbnailFileId = info.thumbnailFileId {
|
||||||
|
for item in group.items {
|
||||||
|
if let itemFile = item.itemFile, itemFile.fileId.id == thumbnailFileId {
|
||||||
|
headerItem = EntityKeyboardAnimationData(file: itemFile)
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EmojiPagerContentComponent.ItemGroup(
|
||||||
|
supergroupId: group.supergroupId,
|
||||||
|
groupId: group.id,
|
||||||
|
title: group.title,
|
||||||
|
subtitle: group.subtitle,
|
||||||
|
actionButtonTitle: nil,
|
||||||
|
isFeatured: group.isFeatured,
|
||||||
|
isPremiumLocked: group.isPremiumLocked,
|
||||||
|
isEmbedded: false,
|
||||||
|
hasClear: group.isClearable,
|
||||||
|
collapsedLineCount: group.collapsedLineCount,
|
||||||
|
displayPremiumBadges: false,
|
||||||
|
headerItem: headerItem,
|
||||||
|
items: group.items
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return EmojiPagerContentComponent(
|
return EmojiPagerContentComponent(
|
||||||
id: "emoji",
|
id: "emoji",
|
||||||
context: context,
|
context: context,
|
||||||
@ -7134,40 +7328,8 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
animationCache: animationCache,
|
animationCache: animationCache,
|
||||||
animationRenderer: animationRenderer,
|
animationRenderer: animationRenderer,
|
||||||
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
|
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
|
||||||
itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
|
panelItemGroups: allItemGroups,
|
||||||
var headerItem = group.headerItem
|
contentItemGroups: allItemGroups,
|
||||||
|
|
||||||
if let groupId = group.id.base as? ItemCollectionId {
|
|
||||||
outer: for (id, info, _) in view.collectionInfos {
|
|
||||||
if id == groupId, let info = info as? StickerPackCollectionInfo {
|
|
||||||
if let thumbnailFileId = info.thumbnailFileId {
|
|
||||||
for item in group.items {
|
|
||||||
if let itemFile = item.itemFile, itemFile.fileId.id == thumbnailFileId {
|
|
||||||
headerItem = EntityKeyboardAnimationData(file: itemFile)
|
|
||||||
break outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return EmojiPagerContentComponent.ItemGroup(
|
|
||||||
supergroupId: group.supergroupId,
|
|
||||||
groupId: group.id,
|
|
||||||
title: group.title,
|
|
||||||
subtitle: group.subtitle,
|
|
||||||
actionButtonTitle: nil,
|
|
||||||
isFeatured: group.isFeatured,
|
|
||||||
isPremiumLocked: group.isPremiumLocked,
|
|
||||||
isEmbedded: false,
|
|
||||||
hasClear: group.isClearable,
|
|
||||||
collapsedLineCount: group.collapsedLineCount,
|
|
||||||
displayPremiumBadges: false,
|
|
||||||
headerItem: headerItem,
|
|
||||||
items: group.items
|
|
||||||
)
|
|
||||||
},
|
|
||||||
itemLayoutType: .compact,
|
itemLayoutType: .compact,
|
||||||
itemContentUniqueId: nil,
|
itemContentUniqueId: nil,
|
||||||
warpContentsOnEdges: isReactionSelection || isStatusSelection,
|
warpContentsOnEdges: isReactionSelection || isStatusSelection,
|
||||||
@ -7644,6 +7806,33 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
|
|
||||||
let isMasks = stickerNamespaces.contains(Namespaces.ItemCollection.CloudMaskPacks)
|
let isMasks = stickerNamespaces.contains(Namespaces.ItemCollection.CloudMaskPacks)
|
||||||
|
|
||||||
|
let allItemGroups = itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
|
||||||
|
var hasClear = false
|
||||||
|
var isEmbedded = false
|
||||||
|
if group.id == AnyHashable("recent") {
|
||||||
|
hasClear = true
|
||||||
|
} else if group.id == AnyHashable("featuredTop") {
|
||||||
|
hasClear = true
|
||||||
|
isEmbedded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return EmojiPagerContentComponent.ItemGroup(
|
||||||
|
supergroupId: group.supergroupId,
|
||||||
|
groupId: group.id,
|
||||||
|
title: group.title,
|
||||||
|
subtitle: group.subtitle,
|
||||||
|
actionButtonTitle: group.actionButtonTitle,
|
||||||
|
isFeatured: group.isFeatured,
|
||||||
|
isPremiumLocked: group.isPremiumLocked,
|
||||||
|
isEmbedded: isEmbedded,
|
||||||
|
hasClear: hasClear,
|
||||||
|
collapsedLineCount: nil,
|
||||||
|
displayPremiumBadges: group.displayPremiumBadges,
|
||||||
|
headerItem: group.headerItem,
|
||||||
|
items: group.items
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return EmojiPagerContentComponent(
|
return EmojiPagerContentComponent(
|
||||||
id: isMasks ? "masks" : "stickers",
|
id: isMasks ? "masks" : "stickers",
|
||||||
context: context,
|
context: context,
|
||||||
@ -7651,32 +7840,8 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
animationCache: animationCache,
|
animationCache: animationCache,
|
||||||
animationRenderer: animationRenderer,
|
animationRenderer: animationRenderer,
|
||||||
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
|
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
|
||||||
itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
|
panelItemGroups: allItemGroups,
|
||||||
var hasClear = false
|
contentItemGroups: allItemGroups,
|
||||||
var isEmbedded = false
|
|
||||||
if group.id == AnyHashable("recent") {
|
|
||||||
hasClear = true
|
|
||||||
} else if group.id == AnyHashable("featuredTop") {
|
|
||||||
hasClear = true
|
|
||||||
isEmbedded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return EmojiPagerContentComponent.ItemGroup(
|
|
||||||
supergroupId: group.supergroupId,
|
|
||||||
groupId: group.id,
|
|
||||||
title: group.title,
|
|
||||||
subtitle: group.subtitle,
|
|
||||||
actionButtonTitle: group.actionButtonTitle,
|
|
||||||
isFeatured: group.isFeatured,
|
|
||||||
isPremiumLocked: group.isPremiumLocked,
|
|
||||||
isEmbedded: isEmbedded,
|
|
||||||
hasClear: hasClear,
|
|
||||||
collapsedLineCount: nil,
|
|
||||||
displayPremiumBadges: group.displayPremiumBadges,
|
|
||||||
headerItem: group.headerItem,
|
|
||||||
items: group.items
|
|
||||||
)
|
|
||||||
},
|
|
||||||
itemLayoutType: .detailed,
|
itemLayoutType: .detailed,
|
||||||
itemContentUniqueId: nil,
|
itemContentUniqueId: nil,
|
||||||
warpContentsOnEdges: false,
|
warpContentsOnEdges: false,
|
||||||
|
@ -0,0 +1,328 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import PagerComponent
|
||||||
|
import TelegramPresentationData
|
||||||
|
import TelegramCore
|
||||||
|
import Postbox
|
||||||
|
import AnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
import AccountContext
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import ComponentDisplayAdapters
|
||||||
|
import LottieAnimationComponent
|
||||||
|
|
||||||
|
final class EmojiSearchSearchBarComponent: Component {
|
||||||
|
let theme: PresentationTheme
|
||||||
|
let strings: PresentationStrings
|
||||||
|
let searchTermUpdated: (String?) -> Void
|
||||||
|
|
||||||
|
init(
|
||||||
|
theme: PresentationTheme,
|
||||||
|
strings: PresentationStrings,
|
||||||
|
searchTermUpdated: @escaping (String?) -> Void
|
||||||
|
) {
|
||||||
|
self.theme = theme
|
||||||
|
self.strings = strings
|
||||||
|
self.searchTermUpdated = searchTermUpdated
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: EmojiSearchSearchBarComponent, rhs: EmojiSearchSearchBarComponent) -> Bool {
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ItemLayout {
|
||||||
|
let containerSize: CGSize
|
||||||
|
let itemCount: Int
|
||||||
|
let itemSize: CGSize
|
||||||
|
let itemSpacing: CGFloat
|
||||||
|
let contentSize: CGSize
|
||||||
|
let sideInset: CGFloat
|
||||||
|
|
||||||
|
init(containerSize: CGSize, itemCount: Int) {
|
||||||
|
self.containerSize = containerSize
|
||||||
|
self.itemCount = itemCount
|
||||||
|
self.itemSpacing = 8.0
|
||||||
|
self.sideInset = 8.0
|
||||||
|
self.itemSize = CGSize(width: 24.0, height: 24.0)
|
||||||
|
|
||||||
|
self.contentSize = CGSize(width: self.sideInset * 2.0 + self.itemSize.width * CGFloat(self.itemCount) + self.itemSpacing * CGFloat(max(0, self.itemCount - 1)), height: containerSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func visibleItems(for rect: CGRect) -> Range<Int>? {
|
||||||
|
let offsetRect = rect.offsetBy(dx: -self.sideInset, dy: 0.0)
|
||||||
|
var minVisibleIndex = Int(floor((offsetRect.minX - self.itemSpacing) / (self.itemSize.width + self.itemSpacing)))
|
||||||
|
minVisibleIndex = max(0, minVisibleIndex)
|
||||||
|
var maxVisibleIndex = Int(ceil((offsetRect.maxX - self.itemSpacing) / (self.itemSize.height + self.itemSpacing)))
|
||||||
|
maxVisibleIndex = min(maxVisibleIndex, self.itemCount - 1)
|
||||||
|
|
||||||
|
if minVisibleIndex <= maxVisibleIndex {
|
||||||
|
return minVisibleIndex ..< (maxVisibleIndex + 1)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func frame(at index: Int) -> CGRect {
|
||||||
|
return CGRect(origin: CGPoint(x: self.sideInset + CGFloat(index) * (self.itemSize.width + self.itemSpacing), y: floor((self.containerSize.height - self.itemSize.height) * 0.5)), size: self.itemSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class View: UIView, UIScrollViewDelegate {
|
||||||
|
private let scrollView: UIScrollView
|
||||||
|
|
||||||
|
private var visibleItemViews: [AnyHashable: ComponentView<Empty>] = [:]
|
||||||
|
private let selectedItemBackground: SimpleLayer
|
||||||
|
|
||||||
|
private var items: [String] = []
|
||||||
|
|
||||||
|
private var component: EmojiSearchSearchBarComponent?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
private var itemLayout: ItemLayout?
|
||||||
|
private var ignoreScrolling: Bool = false
|
||||||
|
|
||||||
|
private let maskLayer: SimpleLayer
|
||||||
|
|
||||||
|
private var selectedItem: String?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.scrollView = UIScrollView()
|
||||||
|
self.maskLayer = SimpleLayer()
|
||||||
|
|
||||||
|
self.selectedItemBackground = SimpleLayer()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.scrollView.delaysContentTouches = false
|
||||||
|
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||||
|
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||||
|
}
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||||
|
}
|
||||||
|
self.scrollView.showsVerticalScrollIndicator = true
|
||||||
|
self.scrollView.showsHorizontalScrollIndicator = false
|
||||||
|
self.scrollView.delegate = self
|
||||||
|
self.scrollView.clipsToBounds = false
|
||||||
|
self.scrollView.scrollsToTop = false
|
||||||
|
|
||||||
|
self.addSubview(self.scrollView)
|
||||||
|
|
||||||
|
//self.layer.mask = self.maskLayer
|
||||||
|
self.layer.addSublayer(self.maskLayer)
|
||||||
|
self.layer.masksToBounds = true
|
||||||
|
|
||||||
|
self.scrollView.layer.addSublayer(self.selectedItemBackground)
|
||||||
|
|
||||||
|
self.scrollView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||||
|
|
||||||
|
self.items = ["Smile", "🤔", "😝", "😡", "😐", "🏌️♀️", "🎉", "😨", "❤️", "😄", "👍", "☹️", "👎", "⛔", "💤", "💼", "🍔", "🏠", "🛁", "🏖", "⚽️", "🕔"]
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
if case .ended = recognizer.state {
|
||||||
|
let location = recognizer.location(in: self.scrollView)
|
||||||
|
for (id, itemView) in self.visibleItemViews {
|
||||||
|
if let itemComponentView = itemView.view, itemComponentView.frame.contains(location), let item = id.base as? String {
|
||||||
|
if self.selectedItem == item {
|
||||||
|
self.selectedItem = nil
|
||||||
|
} else {
|
||||||
|
self.selectedItem = item
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
self.component?.searchTermUpdated(self.selectedItem)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearSelection(dispatchEvent: Bool) {
|
||||||
|
if self.selectedItem != nil {
|
||||||
|
self.selectedItem = nil
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
if dispatchEvent {
|
||||||
|
self.component?.searchTermUpdated(self.selectedItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
if !self.ignoreScrolling {
|
||||||
|
self.updateScrolling(transition: .immediate, fromScrolling: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateScrolling(transition: Transition, fromScrolling: Bool) {
|
||||||
|
guard let component = self.component, let itemLayout = self.itemLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var validItemIds = Set<AnyHashable>()
|
||||||
|
let visibleBounds = self.scrollView.bounds
|
||||||
|
|
||||||
|
var animateAppearingItems = false
|
||||||
|
if fromScrolling {
|
||||||
|
animateAppearingItems = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let items = self.items
|
||||||
|
|
||||||
|
for i in 0 ..< items.count {
|
||||||
|
let itemFrame = itemLayout.frame(at: i)
|
||||||
|
if visibleBounds.intersects(itemFrame) {
|
||||||
|
let item = items[i]
|
||||||
|
validItemIds.insert(AnyHashable(item))
|
||||||
|
|
||||||
|
var animateItem = false
|
||||||
|
var itemTransition = transition
|
||||||
|
let itemView: ComponentView<Empty>
|
||||||
|
if let current = self.visibleItemViews[item] {
|
||||||
|
itemView = current
|
||||||
|
} else {
|
||||||
|
animateItem = animateAppearingItems
|
||||||
|
itemTransition = .immediate
|
||||||
|
itemView = ComponentView<Empty>()
|
||||||
|
self.visibleItemViews[item] = itemView
|
||||||
|
}
|
||||||
|
|
||||||
|
let animationName: String
|
||||||
|
|
||||||
|
switch EmojiPagerContentComponent.StaticEmojiSegment.allCases[i % EmojiPagerContentComponent.StaticEmojiSegment.allCases.count] {
|
||||||
|
case .people:
|
||||||
|
animationName = "emojicat_smiles"
|
||||||
|
case .animalsAndNature:
|
||||||
|
animationName = "emojicat_animals"
|
||||||
|
case .foodAndDrink:
|
||||||
|
animationName = "emojicat_food"
|
||||||
|
case .activityAndSport:
|
||||||
|
animationName = "emojicat_activity"
|
||||||
|
case .travelAndPlaces:
|
||||||
|
animationName = "emojicat_places"
|
||||||
|
case .objects:
|
||||||
|
animationName = "emojicat_objects"
|
||||||
|
case .symbols:
|
||||||
|
animationName = "emojicat_symbols"
|
||||||
|
case .flags:
|
||||||
|
animationName = "emojicat_flags"
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseColor: UIColor
|
||||||
|
baseColor = component.theme.chat.inputMediaPanel.panelIconColor
|
||||||
|
|
||||||
|
let baseHighlightedColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.blitOver(component.theme.chat.inputPanel.panelBackgroundColor, alpha: 1.0)
|
||||||
|
let color = baseColor.blitOver(baseHighlightedColor, alpha: 1.0)
|
||||||
|
|
||||||
|
let _ = itemTransition
|
||||||
|
let _ = itemView.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(LottieAnimationComponent(
|
||||||
|
animation: LottieAnimationComponent.AnimationItem(
|
||||||
|
name: animationName,
|
||||||
|
mode: .still(position: .end)
|
||||||
|
),
|
||||||
|
colors: ["__allcolors__": color],
|
||||||
|
size: itemLayout.itemSize
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: itemLayout.itemSize
|
||||||
|
)
|
||||||
|
if let view = itemView.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
self.scrollView.addSubview(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
itemTransition.setPosition(view: view, position: CGPoint(x: itemFrame.midX, y: itemFrame.midY))
|
||||||
|
itemTransition.setBounds(view: view, bounds: CGRect(origin: CGPoint(), size: CGSize(width: itemLayout.itemSize.width, height: itemLayout.itemSize.height)))
|
||||||
|
let scaleFactor = itemFrame.width / itemLayout.itemSize.width
|
||||||
|
itemTransition.setSublayerTransform(view: view, transform: CATransform3DMakeScale(scaleFactor, scaleFactor, 1.0))
|
||||||
|
|
||||||
|
let isHidden = !visibleBounds.intersects(itemFrame)
|
||||||
|
if isHidden != view.isHidden {
|
||||||
|
view.isHidden = isHidden
|
||||||
|
|
||||||
|
if !isHidden {
|
||||||
|
if let view = view as? LottieAnimationComponent.View {
|
||||||
|
view.playOnce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if animateItem {
|
||||||
|
if let view = view as? LottieAnimationComponent.View {
|
||||||
|
view.playOnce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var removedItemIds: [AnyHashable] = []
|
||||||
|
for (id, itemView) in self.visibleItemViews {
|
||||||
|
if !validItemIds.contains(id) {
|
||||||
|
removedItemIds.append(id)
|
||||||
|
itemView.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in removedItemIds {
|
||||||
|
self.visibleItemViews.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let selectedItem = self.selectedItem, let index = self.items.firstIndex(of: selectedItem) {
|
||||||
|
self.selectedItemBackground.isHidden = false
|
||||||
|
|
||||||
|
let selectedItemCenter = itemLayout.frame(at: index).center
|
||||||
|
let selectionSize = CGSize(width: 28.0, height: 28.0)
|
||||||
|
|
||||||
|
self.selectedItemBackground.backgroundColor = component.theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor
|
||||||
|
self.selectedItemBackground.cornerRadius = selectionSize.height * 0.5
|
||||||
|
|
||||||
|
self.selectedItemBackground.frame = CGRect(origin: CGPoint(x: floor(selectedItemCenter.x - selectionSize.width * 0.5), y: floor(selectedItemCenter.y - selectionSize.height * 0.5)), size: selectionSize)
|
||||||
|
} else {
|
||||||
|
self.selectedItemBackground.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: EmojiSearchSearchBarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
transition.setCornerRadius(layer: self.layer, cornerRadius: availableSize.height * 0.5)
|
||||||
|
|
||||||
|
let itemLayout = ItemLayout(containerSize: availableSize, itemCount: self.items.count)
|
||||||
|
self.itemLayout = itemLayout
|
||||||
|
|
||||||
|
self.ignoreScrolling = true
|
||||||
|
if self.scrollView.bounds.size != availableSize {
|
||||||
|
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
|
}
|
||||||
|
if self.scrollView.contentSize != itemLayout.contentSize {
|
||||||
|
self.scrollView.contentSize = itemLayout.contentSize
|
||||||
|
}
|
||||||
|
self.ignoreScrolling = false
|
||||||
|
|
||||||
|
self.updateScrolling(transition: transition, fromScrolling: false)
|
||||||
|
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
@ -302,7 +302,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
if let maskContent = component.maskContent {
|
if let maskContent = component.maskContent {
|
||||||
var topMaskItems: [EntityKeyboardTopPanelComponent.Item] = []
|
var topMaskItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||||
|
|
||||||
for itemGroup in maskContent.itemGroups {
|
for itemGroup in maskContent.panelItemGroups {
|
||||||
if let id = itemGroup.supergroupId.base as? String {
|
if let id = itemGroup.supergroupId.base as? String {
|
||||||
let iconMapping: [String: EntityKeyboardIconTopPanelComponent.Icon] = [
|
let iconMapping: [String: EntityKeyboardIconTopPanelComponent.Icon] = [
|
||||||
"saved": .saved,
|
"saved": .saved,
|
||||||
@ -359,7 +359,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
items: topMaskItems,
|
items: topMaskItems,
|
||||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||||
defaultActiveItemId: maskContent.itemGroups.first?.groupId,
|
defaultActiveItemId: maskContent.panelItemGroups.first?.groupId,
|
||||||
activeContentItemIdUpdated: masksContentItemIdUpdated,
|
activeContentItemIdUpdated: masksContentItemIdUpdated,
|
||||||
reorderItems: { [weak self] items in
|
reorderItems: { [weak self] items in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -476,7 +476,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
for itemGroup in stickerContent.itemGroups {
|
for itemGroup in stickerContent.panelItemGroups {
|
||||||
if let id = itemGroup.supergroupId.base as? String {
|
if let id = itemGroup.supergroupId.base as? String {
|
||||||
if id == "peerSpecific" {
|
if id == "peerSpecific" {
|
||||||
if let avatarPeer = stickerContent.avatarPeer {
|
if let avatarPeer = stickerContent.avatarPeer {
|
||||||
@ -551,7 +551,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
items: topStickerItems,
|
items: topStickerItems,
|
||||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||||
defaultActiveItemId: stickerContent.itemGroups.first?.groupId,
|
defaultActiveItemId: stickerContent.panelItemGroups.first?.groupId,
|
||||||
activeContentItemIdUpdated: stickersContentItemIdUpdated,
|
activeContentItemIdUpdated: stickersContentItemIdUpdated,
|
||||||
reorderItems: { [weak self] items in
|
reorderItems: { [weak self] items in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -581,7 +581,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
if let emojiContent = component.emojiContent {
|
if let emojiContent = component.emojiContent {
|
||||||
contents.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(emojiContent)))
|
contents.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(emojiContent)))
|
||||||
var topEmojiItems: [EntityKeyboardTopPanelComponent.Item] = []
|
var topEmojiItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||||
for itemGroup in emojiContent.itemGroups {
|
for itemGroup in emojiContent.panelItemGroups {
|
||||||
if !itemGroup.items.isEmpty {
|
if !itemGroup.items.isEmpty {
|
||||||
if let id = itemGroup.groupId.base as? String {
|
if let id = itemGroup.groupId.base as? String {
|
||||||
if id == "recent" {
|
if id == "recent" {
|
||||||
|
@ -979,7 +979,7 @@ public final class GifPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
||||||
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: false, isActive: false, size: searchHeaderFrame.size, canFocus: false, transition: transition)
|
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: false, isActive: false, size: searchHeaderFrame.size, canFocus: false, hasSearchItems: true, transition: transition)
|
||||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
|
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
|
||||||
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
||||||
return
|
return
|
||||||
|
@ -1394,6 +1394,10 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
})
|
})
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
if #available(iOS 12.0, *) {
|
||||||
|
UIApplication.shared.registerForRemoteNotifications()
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3855,6 +3855,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}, displayUndo: { [weak self] content in
|
}, displayUndo: { [weak self] content in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
strongSelf.window?.forEachController({ controller in
|
||||||
|
if let controller = controller as? UndoOverlayController {
|
||||||
|
controller.dismiss()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
strongSelf.forEachController({ controller in
|
||||||
|
if let controller = controller as? UndoOverlayController {
|
||||||
|
controller.dismiss()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in
|
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in
|
||||||
return true
|
return true
|
||||||
}), in: .current)
|
}), in: .current)
|
||||||
@ -4440,7 +4453,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
hasScheduledMessages = peerView.get()
|
hasScheduledMessages = peerView.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { view -> Signal<Bool, NoError> in
|
|> mapToSignal { view -> Signal<Bool, NoError> in
|
||||||
if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendMessages) {
|
if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendSomething) {
|
||||||
return .single(false)
|
return .single(false)
|
||||||
} else {
|
} else {
|
||||||
return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder))
|
return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder))
|
||||||
@ -4864,6 +4877,37 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
.updatedAutoremoveTimeout(autoremoveTimeout)
|
.updatedAutoremoveTimeout(autoremoveTimeout)
|
||||||
.updatedCurrentSendAsPeerId(currentSendAsPeerId)
|
.updatedCurrentSendAsPeerId(currentSendAsPeerId)
|
||||||
.updatedCopyProtectionEnabled(copyProtectionEnabled)
|
.updatedCopyProtectionEnabled(copyProtectionEnabled)
|
||||||
|
.updatedInterfaceState { interfaceState in
|
||||||
|
var interfaceState = interfaceState
|
||||||
|
|
||||||
|
if let channel = renderedPeer?.peer as? TelegramChannel {
|
||||||
|
if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
} else if channel.hasBannedPermission(.banSendVoice) != nil {
|
||||||
|
if channel.hasBannedPermission(.banSendInstantVideos) == nil {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
|
||||||
|
}
|
||||||
|
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
if channel.hasBannedPermission(.banSendVoice) == nil {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let group = renderedPeer?.peer as? TelegramGroup {
|
||||||
|
if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
} else if group.hasBannedPermission(.banSendVoice) {
|
||||||
|
if !group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
|
||||||
|
}
|
||||||
|
} else if group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
if !group.hasBannedPermission(.banSendVoice) {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return interfaceState
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if case .standard(previewing: false) = mode, let channel = renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info {
|
if case .standard(previewing: false) = mode, let channel = renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info {
|
||||||
@ -4995,7 +5039,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
hasScheduledMessages = peerView
|
hasScheduledMessages = peerView
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { view -> Signal<Bool, NoError> in
|
|> mapToSignal { view -> Signal<Bool, NoError> in
|
||||||
if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendMessages) {
|
if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendSomething) {
|
||||||
return .single(false)
|
return .single(false)
|
||||||
} else {
|
} else {
|
||||||
return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder))
|
return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder))
|
||||||
@ -5271,6 +5315,37 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return renderedPeer
|
return renderedPeer
|
||||||
}.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages).updatedCurrentSendAsPeerId(currentSendAsPeerId)
|
}.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages).updatedCurrentSendAsPeerId(currentSendAsPeerId)
|
||||||
.updatedCopyProtectionEnabled(copyProtectionEnabled)
|
.updatedCopyProtectionEnabled(copyProtectionEnabled)
|
||||||
|
.updatedInterfaceState { interfaceState in
|
||||||
|
var interfaceState = interfaceState
|
||||||
|
|
||||||
|
if let channel = renderedPeer?.peer as? TelegramChannel {
|
||||||
|
if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
} else if channel.hasBannedPermission(.banSendVoice) != nil {
|
||||||
|
if channel.hasBannedPermission(.banSendInstantVideos) == nil {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
|
||||||
|
}
|
||||||
|
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
if channel.hasBannedPermission(.banSendVoice) == nil {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let group = renderedPeer?.peer as? TelegramGroup {
|
||||||
|
if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
} else if group.hasBannedPermission(.banSendVoice) {
|
||||||
|
if !group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
|
||||||
|
}
|
||||||
|
} else if group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
if !group.hasBannedPermission(.banSendVoice) {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return interfaceState
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if !strongSelf.didSetChatLocationInfoReady {
|
if !strongSelf.didSetChatLocationInfoReady {
|
||||||
strongSelf.didSetChatLocationInfoReady = true
|
strongSelf.didSetChatLocationInfoReady = true
|
||||||
@ -6312,7 +6387,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let opaqueState = (combinedInitialData.initialData?.storedInterfaceState).flatMap(_internal_decodeStoredChatInterfaceState) {
|
if let opaqueState = (combinedInitialData.initialData?.storedInterfaceState).flatMap(_internal_decodeStoredChatInterfaceState) {
|
||||||
let interfaceState = ChatInterfaceState.parse(opaqueState)
|
var interfaceState = ChatInterfaceState.parse(opaqueState)
|
||||||
|
|
||||||
var pinnedMessageId: MessageId?
|
var pinnedMessageId: MessageId?
|
||||||
var peerIsBlocked: Bool = false
|
var peerIsBlocked: Bool = false
|
||||||
@ -6343,6 +6418,32 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
} else if let _ = combinedInitialData.cachedData as? CachedSecretChatData {
|
} else if let _ = combinedInitialData.cachedData as? CachedSecretChatData {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let channel = combinedInitialData.initialData?.peer as? TelegramChannel {
|
||||||
|
if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
} else if channel.hasBannedPermission(.banSendVoice) != nil {
|
||||||
|
if channel.hasBannedPermission(.banSendInstantVideos) == nil {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
|
||||||
|
}
|
||||||
|
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
if channel.hasBannedPermission(.banSendVoice) == nil {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let group = combinedInitialData.initialData?.peer as? TelegramGroup {
|
||||||
|
if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
} else if group.hasBannedPermission(.banSendVoice) {
|
||||||
|
if !group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
|
||||||
|
}
|
||||||
|
} else if group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
if !group.hasBannedPermission(.banSendVoice) {
|
||||||
|
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if case let .replyThread(replyThreadMessageId) = strongSelf.chatLocation {
|
if case let .replyThread(replyThreadMessageId) = strongSelf.chatLocation {
|
||||||
if let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
if let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||||
pinnedMessageId = nil
|
pinnedMessageId = nil
|
||||||
@ -8344,10 +8445,55 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.mediaRecordingModeTooltipController?.dismiss()
|
strongSelf.mediaRecordingModeTooltipController?.dismiss()
|
||||||
strongSelf.interfaceInteraction?.updateShowWebView { _ in
|
strongSelf.interfaceInteraction?.updateShowWebView { _ in
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bannedMediaInput = false
|
||||||
|
if let channel = peer as? TelegramChannel {
|
||||||
|
if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
bannedMediaInput = true
|
||||||
|
} else if channel.hasBannedPermission(.banSendVoice) != nil {
|
||||||
|
if !isVideo {
|
||||||
|
//TODO:localize
|
||||||
|
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: "The admins of this group do not allow to send voice messages."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
if isVideo {
|
||||||
|
//TODO:localize
|
||||||
|
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: "The admins of this group do not allow to send video messages."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let group = peer as? TelegramGroup {
|
||||||
|
if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
bannedMediaInput = true
|
||||||
|
} else if group.hasBannedPermission(.banSendVoice) {
|
||||||
|
if !isVideo {
|
||||||
|
//TODO:localize
|
||||||
|
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: "The admins of this group do not allow to send voice messages."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
if isVideo {
|
||||||
|
//TODO:localize
|
||||||
|
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: "The admins of this group do not allow to send video messages."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bannedMediaInput {
|
||||||
|
//TODO:localize
|
||||||
|
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: "The admins of this group do not allow to send video and voice messages."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let requestId = strongSelf.beginMediaRecordingRequestId
|
let requestId = strongSelf.beginMediaRecordingRequestId
|
||||||
let begin: () -> Void = {
|
let begin: () -> Void = {
|
||||||
@ -8607,35 +8753,77 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
tooltipController.dismissImmediately()
|
tooltipController.dismissImmediately()
|
||||||
}
|
}
|
||||||
}, switchMediaRecordingMode: { [weak self] in
|
}, switchMediaRecordingMode: { [weak self] in
|
||||||
if let strongSelf = self {
|
guard let strongSelf = self else {
|
||||||
if strongSelf.recordingModeFeedback == nil {
|
return
|
||||||
strongSelf.recordingModeFeedback = HapticFeedback()
|
|
||||||
strongSelf.recordingModeFeedback?.prepareImpact()
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.recordingModeFeedback?.impact()
|
|
||||||
var updatedMode: ChatTextInputMediaRecordingButtonMode?
|
|
||||||
|
|
||||||
strongSelf.updateChatPresentationInterfaceState(interactive: true, {
|
|
||||||
return $0.updatedInterfaceState({ current in
|
|
||||||
let mode: ChatTextInputMediaRecordingButtonMode
|
|
||||||
switch current.mediaRecordingMode {
|
|
||||||
case .audio:
|
|
||||||
mode = .video
|
|
||||||
case .video:
|
|
||||||
mode = .audio
|
|
||||||
}
|
|
||||||
updatedMode = mode
|
|
||||||
return current.withUpdatedMediaRecordingMode(mode)
|
|
||||||
}).updatedShowWebView(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
if let updatedMode = updatedMode, updatedMode == .video {
|
|
||||||
let _ = ApplicationSpecificNotice.incrementChatMediaMediaRecordingTips(accountManager: strongSelf.context.sharedContext.accountManager, count: 3).start()
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.displayMediaRecordingTooltip()
|
|
||||||
}
|
}
|
||||||
|
guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var bannedMediaInput = false
|
||||||
|
if let channel = peer as? TelegramChannel {
|
||||||
|
if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
bannedMediaInput = true
|
||||||
|
} else if channel.hasBannedPermission(.banSendVoice) != nil {
|
||||||
|
if channel.hasBannedPermission(.banSendInstantVideos) == nil {
|
||||||
|
strongSelf.displayMediaRecordingTooltip()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
if channel.hasBannedPermission(.banSendVoice) == nil {
|
||||||
|
strongSelf.displayMediaRecordingTooltip()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let group = peer as? TelegramGroup {
|
||||||
|
if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
bannedMediaInput = true
|
||||||
|
} else if group.hasBannedPermission(.banSendVoice) {
|
||||||
|
if !group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
strongSelf.displayMediaRecordingTooltip()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
if !group.hasBannedPermission(.banSendVoice) {
|
||||||
|
strongSelf.displayMediaRecordingTooltip()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bannedMediaInput {
|
||||||
|
//TODO:localize
|
||||||
|
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: "The admins of this group do not allow to send video and voice messages."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strongSelf.recordingModeFeedback == nil {
|
||||||
|
strongSelf.recordingModeFeedback = HapticFeedback()
|
||||||
|
strongSelf.recordingModeFeedback?.prepareImpact()
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.recordingModeFeedback?.impact()
|
||||||
|
var updatedMode: ChatTextInputMediaRecordingButtonMode?
|
||||||
|
|
||||||
|
strongSelf.updateChatPresentationInterfaceState(interactive: true, {
|
||||||
|
return $0.updatedInterfaceState({ current in
|
||||||
|
let mode: ChatTextInputMediaRecordingButtonMode
|
||||||
|
switch current.mediaRecordingMode {
|
||||||
|
case .audio:
|
||||||
|
mode = .video
|
||||||
|
case .video:
|
||||||
|
mode = .audio
|
||||||
|
}
|
||||||
|
updatedMode = mode
|
||||||
|
return current.withUpdatedMediaRecordingMode(mode)
|
||||||
|
}).updatedShowWebView(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
if let updatedMode = updatedMode, updatedMode == .video {
|
||||||
|
let _ = ApplicationSpecificNotice.incrementChatMediaMediaRecordingTips(accountManager: strongSelf.context.sharedContext.accountManager, count: 3).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.displayMediaRecordingTooltip()
|
||||||
}, setupMessageAutoremoveTimeout: { [weak self] in
|
}, setupMessageAutoremoveTimeout: { [weak self] in
|
||||||
guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else {
|
guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else {
|
||||||
return
|
return
|
||||||
@ -11948,25 +12136,54 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let _ = peer
|
||||||
|
|
||||||
let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in
|
let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in
|
||||||
let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self)
|
let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self)
|
||||||
return entry ?? GeneratedMediaStoreSettings.defaultSettings
|
return entry ?? GeneratedMediaStoreSettings.defaultSettings
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] settings in
|
|> deliverOnMainQueue).start(next: { [weak self] settings in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var photoOnly = false
|
var enablePhoto = true
|
||||||
|
var enableVideo = true
|
||||||
|
|
||||||
if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall {
|
if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall {
|
||||||
photoOnly = true
|
enableVideo = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var bannedSendPhotos: (Int32, Bool)?
|
||||||
|
var bannedSendVideos: (Int32, Bool)?
|
||||||
|
|
||||||
|
if let channel = peer as? TelegramChannel {
|
||||||
|
if let value = channel.hasBannedPermission(.banSendPhotos) {
|
||||||
|
bannedSendPhotos = value
|
||||||
|
}
|
||||||
|
if let value = channel.hasBannedPermission(.banSendVideos) {
|
||||||
|
bannedSendVideos = value
|
||||||
|
}
|
||||||
|
} else if let group = peer as? TelegramGroup {
|
||||||
|
if group.hasBannedPermission(.banSendPhotos) {
|
||||||
|
bannedSendPhotos = (Int32.max, false)
|
||||||
|
}
|
||||||
|
if group.hasBannedPermission(.banSendVideos) {
|
||||||
|
bannedSendVideos = (Int32.max, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bannedSendPhotos != nil {
|
||||||
|
enablePhoto = false
|
||||||
|
}
|
||||||
|
if bannedSendVideos != nil {
|
||||||
|
enableVideo = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let storeCapturedMedia = peer.id.namespace != Namespaces.Peer.SecretChat
|
let storeCapturedMedia = peer.id.namespace != Namespaces.Peer.SecretChat
|
||||||
let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
||||||
|
|
||||||
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
|
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil)
|
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil)
|
||||||
if !inputText.string.isEmpty {
|
if !inputText.string.isEmpty {
|
||||||
@ -12603,12 +12820,40 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
})
|
})
|
||||||
}, openCamera: { [weak self] cameraView, menuController in
|
}, openCamera: { [weak self] cameraView, menuController in
|
||||||
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
||||||
var photoOnly = false
|
var enablePhoto = true
|
||||||
|
var enableVideo = true
|
||||||
|
|
||||||
if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall {
|
if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall {
|
||||||
photoOnly = true
|
enableVideo = false
|
||||||
}
|
}
|
||||||
|
|
||||||
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: peer.id.namespace != Namespaces.Peer.SecretChat, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
|
var bannedSendPhotos: (Int32, Bool)?
|
||||||
|
var bannedSendVideos: (Int32, Bool)?
|
||||||
|
|
||||||
|
if let channel = peer as? TelegramChannel {
|
||||||
|
if let value = channel.hasBannedPermission(.banSendPhotos) {
|
||||||
|
bannedSendPhotos = value
|
||||||
|
}
|
||||||
|
if let value = channel.hasBannedPermission(.banSendVideos) {
|
||||||
|
bannedSendVideos = value
|
||||||
|
}
|
||||||
|
} else if let group = peer as? TelegramGroup {
|
||||||
|
if group.hasBannedPermission(.banSendPhotos) {
|
||||||
|
bannedSendPhotos = (Int32.max, false)
|
||||||
|
}
|
||||||
|
if group.hasBannedPermission(.banSendVideos) {
|
||||||
|
bannedSendVideos = (Int32.max, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bannedSendPhotos != nil {
|
||||||
|
enablePhoto = false
|
||||||
|
}
|
||||||
|
if bannedSendVideos != nil {
|
||||||
|
enableVideo = false
|
||||||
|
}
|
||||||
|
|
||||||
|
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: peer.id.namespace != Namespaces.Peer.SecretChat, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if editMediaOptions != nil {
|
if editMediaOptions != nil {
|
||||||
strongSelf.editMessageMediaWithLegacySignals(signals!)
|
strongSelf.editMessageMediaWithLegacySignals(signals!)
|
||||||
@ -13096,8 +13341,93 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
controller.getCaptionPanelView = { [weak self] in
|
controller.attemptItemSelection = { [weak strongSelf] item in
|
||||||
return self?.getCaptionPanelView()
|
guard let strongSelf, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ItemType {
|
||||||
|
case gif
|
||||||
|
case image
|
||||||
|
case video
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemType: ItemType?
|
||||||
|
switch item {
|
||||||
|
case let .internalReference(reference):
|
||||||
|
if reference.type == "gif" {
|
||||||
|
itemType = .gif
|
||||||
|
} else if reference.type == "photo" {
|
||||||
|
itemType = .image
|
||||||
|
} else if reference.type == "video" {
|
||||||
|
itemType = .video
|
||||||
|
}
|
||||||
|
case let .externalReference(reference):
|
||||||
|
if reference.type == "gif" {
|
||||||
|
itemType = .gif
|
||||||
|
} else if reference.type == "photo" {
|
||||||
|
itemType = .image
|
||||||
|
} else if reference.type == "video" {
|
||||||
|
itemType = .video
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bannedSendPhotos: (Int32, Bool)?
|
||||||
|
var bannedSendVideos: (Int32, Bool)?
|
||||||
|
var bannedSendGifs: (Int32, Bool)?
|
||||||
|
|
||||||
|
if let channel = peer as? TelegramChannel {
|
||||||
|
if let value = channel.hasBannedPermission(.banSendPhotos) {
|
||||||
|
bannedSendPhotos = value
|
||||||
|
}
|
||||||
|
if let value = channel.hasBannedPermission(.banSendVideos) {
|
||||||
|
bannedSendVideos = value
|
||||||
|
}
|
||||||
|
if let value = channel.hasBannedPermission(.banSendGifs) {
|
||||||
|
bannedSendGifs = value
|
||||||
|
}
|
||||||
|
} else if let group = peer as? TelegramGroup {
|
||||||
|
if group.hasBannedPermission(.banSendPhotos) {
|
||||||
|
bannedSendPhotos = (Int32.max, false)
|
||||||
|
}
|
||||||
|
if group.hasBannedPermission(.banSendVideos) {
|
||||||
|
bannedSendVideos = (Int32.max, false)
|
||||||
|
}
|
||||||
|
if group.hasBannedPermission(.banSendGifs) {
|
||||||
|
bannedSendGifs = (Int32.max, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let itemType {
|
||||||
|
switch itemType {
|
||||||
|
case .image:
|
||||||
|
if bannedSendPhotos != nil {
|
||||||
|
//TODO:localize
|
||||||
|
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: "Sending photos is not allowed", actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .video:
|
||||||
|
if bannedSendVideos != nil {
|
||||||
|
//TODO:localize
|
||||||
|
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: "Sending videos is not allowed", actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .gif:
|
||||||
|
if bannedSendGifs != nil {
|
||||||
|
//TODO:localize
|
||||||
|
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: "Sending gifs is not allowed", actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
controller.getCaptionPanelView = { [weak strongSelf] in
|
||||||
|
return strongSelf?.getCaptionPanelView()
|
||||||
}
|
}
|
||||||
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}
|
}
|
||||||
@ -16597,15 +16927,55 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func displayMediaRecordingTooltip() {
|
private func displayMediaRecordingTooltip() {
|
||||||
|
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let rect: CGRect? = self.chatDisplayNode.frameForInputActionButton()
|
let rect: CGRect? = self.chatDisplayNode.frameForInputActionButton()
|
||||||
|
|
||||||
let updatedMode: ChatTextInputMediaRecordingButtonMode = self.presentationInterfaceState.interfaceState.mediaRecordingMode
|
let updatedMode: ChatTextInputMediaRecordingButtonMode = self.presentationInterfaceState.interfaceState.mediaRecordingMode
|
||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
|
|
||||||
|
var canSwitch = true
|
||||||
|
if let channel = peer as? TelegramChannel {
|
||||||
|
if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
canSwitch = false
|
||||||
|
} else if channel.hasBannedPermission(.banSendVoice) != nil {
|
||||||
|
if channel.hasBannedPermission(.banSendInstantVideos) == nil {
|
||||||
|
canSwitch = false
|
||||||
|
}
|
||||||
|
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
|
if channel.hasBannedPermission(.banSendVoice) == nil {
|
||||||
|
canSwitch = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let group = peer as? TelegramGroup {
|
||||||
|
if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
canSwitch = false
|
||||||
|
} else if group.hasBannedPermission(.banSendVoice) {
|
||||||
|
if !group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
canSwitch = false
|
||||||
|
}
|
||||||
|
} else if group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
|
if !group.hasBannedPermission(.banSendVoice) {
|
||||||
|
canSwitch = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if updatedMode == .audio {
|
if updatedMode == .audio {
|
||||||
text = self.presentationData.strings.Conversation_HoldForAudio
|
if canSwitch {
|
||||||
|
text = self.presentationData.strings.Conversation_HoldForAudio
|
||||||
|
} else {
|
||||||
|
text = self.presentationData.strings.Conversation_HoldForAudioOnly
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
text = self.presentationData.strings.Conversation_HoldForVideo
|
if canSwitch {
|
||||||
|
text = self.presentationData.strings.Conversation_HoldForVideo
|
||||||
|
} else {
|
||||||
|
text = self.presentationData.strings.Conversation_HoldForVideoOnly
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.silentPostTooltipController?.dismiss()
|
self.silentPostTooltipController?.dismiss()
|
||||||
|
@ -280,6 +280,8 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
|||||||
var currentAutoremoveTimeout: Int32? = chatPresentationInterfaceState.autoremoveTimeout
|
var currentAutoremoveTimeout: Int32? = chatPresentationInterfaceState.autoremoveTimeout
|
||||||
var canSetupAutoremoveTimeout = false
|
var canSetupAutoremoveTimeout = false
|
||||||
|
|
||||||
|
var canSendTextMessages = true
|
||||||
|
|
||||||
var accessoryItems: [ChatTextInputAccessoryItem] = []
|
var accessoryItems: [ChatTextInputAccessoryItem] = []
|
||||||
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat {
|
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat {
|
||||||
var extendedSearchLayout = false
|
var extendedSearchLayout = false
|
||||||
@ -298,6 +300,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
|||||||
if !group.hasBannedPermission(.banChangeInfo) {
|
if !group.hasBannedPermission(.banChangeInfo) {
|
||||||
canSetupAutoremoveTimeout = true
|
canSetupAutoremoveTimeout = true
|
||||||
}
|
}
|
||||||
|
canSendTextMessages = !group.hasBannedPermission(.banSendText)
|
||||||
} else if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser {
|
} else if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser {
|
||||||
if user.botInfo == nil {
|
if user.botInfo == nil {
|
||||||
canSetupAutoremoveTimeout = true
|
canSetupAutoremoveTimeout = true
|
||||||
@ -306,6 +309,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
|||||||
if channel.hasPermission(.changeInfo) {
|
if channel.hasPermission(.changeInfo) {
|
||||||
canSetupAutoremoveTimeout = true
|
canSetupAutoremoveTimeout = true
|
||||||
}
|
}
|
||||||
|
canSendTextMessages = channel.hasBannedPermission(.banSendText) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if canSetupAutoremoveTimeout {
|
if canSetupAutoremoveTimeout {
|
||||||
@ -375,10 +379,16 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
|||||||
accessoryItems.append(.commands)
|
accessoryItems.append(.commands)
|
||||||
}
|
}
|
||||||
|
|
||||||
if stickersEnabled {
|
if !canSendTextMessages {
|
||||||
accessoryItems.append(.input(isEnabled: true, inputMode: stickersAreEmoji ? .emoji : .stickers))
|
if stickersEnabled && !stickersAreEmoji {
|
||||||
|
accessoryItems.append(.input(isEnabled: true, inputMode: .stickers))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
accessoryItems.append(.input(isEnabled: true, inputMode: .emoji))
|
if stickersEnabled {
|
||||||
|
accessoryItems.append(.input(isEnabled: true, inputMode: stickersAreEmoji ? .emoji : .stickers))
|
||||||
|
} else {
|
||||||
|
accessoryItems.append(.input(isEnabled: true, inputMode: .emoji))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isTextEmpty, let message = chatPresentationInterfaceState.keyboardButtonsMessage, let _ = message.visibleButtonKeyboardMarkup, chatPresentationInterfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId != message.id {
|
if isTextEmpty, let message = chatPresentationInterfaceState.keyboardButtonsMessage, let _ = message.visibleButtonKeyboardMarkup, chatPresentationInterfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId != message.id {
|
||||||
|
@ -283,7 +283,7 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS
|
|||||||
case .peer:
|
case .peer:
|
||||||
if let channel = peer as? TelegramChannel {
|
if let channel = peer as? TelegramChannel {
|
||||||
if case .member = channel.participationStatus {
|
if case .member = channel.participationStatus {
|
||||||
canReply = channel.hasPermission(.sendMessages)
|
canReply = channel.hasPermission(.sendSomething)
|
||||||
}
|
}
|
||||||
} else if let group = peer as? TelegramGroup {
|
} else if let group = peer as? TelegramGroup {
|
||||||
if case .Member = group.membership {
|
if case .Member = group.membership {
|
||||||
|
@ -196,22 +196,22 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isMember && channel.hasBannedPermission(.banSendMessages) != nil && !channel.flags.contains(.isGigagroup) {
|
if isMember && channel.hasBannedPermission(.banSendText) != nil && !channel.flags.contains(.isGigagroup) {
|
||||||
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
/*if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRestrictedInputPanelNode()
|
let panel = ChatRestrictedInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
switch channel.info {
|
switch channel.info {
|
||||||
case .broadcast:
|
case .broadcast:
|
||||||
if chatPresentationInterfaceState.interfaceState.editMessage != nil, channel.hasPermission(.editAllMessages) {
|
if chatPresentationInterfaceState.interfaceState.editMessage != nil, channel.hasPermission(.editAllMessages) {
|
||||||
displayInputTextPanel = true
|
displayInputTextPanel = true
|
||||||
} else if !channel.hasPermission(.sendMessages) || !isMember {
|
} else if !channel.hasPermission(.sendSomething) || !isMember {
|
||||||
if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) {
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
@ -235,7 +235,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .member:
|
case .member:
|
||||||
if channel.flags.contains(.isGigagroup) && !channel.hasPermission(.sendMessages) {
|
if channel.flags.contains(.isGigagroup) && !channel.hasPermission(.sendSomething) {
|
||||||
if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) {
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
@ -280,15 +280,15 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.hasBannedPermission(.banSendMessages) {
|
if group.hasBannedPermission(.banSendText) {
|
||||||
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
/*if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatRestrictedInputPanelNode()
|
let panel = ChatRestrictedInputPanelNode()
|
||||||
panel.context = context
|
panel.context = context
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -660,7 +660,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
let order: [(TelegramChatBannedRightsFlags, String)] = [
|
let order: [(TelegramChatBannedRightsFlags, String)] = [
|
||||||
(.banReadMessages, self.presentationData.strings.Channel_AdminLog_BanReadMessages),
|
(.banReadMessages, self.presentationData.strings.Channel_AdminLog_BanReadMessages),
|
||||||
(.banSendMessages, self.presentationData.strings.Channel_AdminLog_BanSendMessages),
|
(.banSendText, self.presentationData.strings.Channel_AdminLog_BanSendMessages),
|
||||||
(.banSendMedia, self.presentationData.strings.Channel_AdminLog_BanSendMedia),
|
(.banSendMedia, self.presentationData.strings.Channel_AdminLog_BanSendMedia),
|
||||||
(.banSendStickers, self.presentationData.strings.Channel_AdminLog_BanSendStickersAndGifs),
|
(.banSendStickers, self.presentationData.strings.Channel_AdminLog_BanSendStickersAndGifs),
|
||||||
(.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks),
|
(.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks),
|
||||||
@ -1015,7 +1015,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
let order: [(TelegramChatBannedRightsFlags, String)] = [
|
let order: [(TelegramChatBannedRightsFlags, String)] = [
|
||||||
(.banReadMessages, self.presentationData.strings.Channel_AdminLog_BanReadMessages),
|
(.banReadMessages, self.presentationData.strings.Channel_AdminLog_BanReadMessages),
|
||||||
(.banSendMessages, self.presentationData.strings.Channel_AdminLog_BanSendMessages),
|
(.banSendText, self.presentationData.strings.Channel_AdminLog_BanSendMessages),
|
||||||
(.banSendMedia, self.presentationData.strings.Channel_AdminLog_BanSendMedia),
|
(.banSendMedia, self.presentationData.strings.Channel_AdminLog_BanSendMedia),
|
||||||
(.banSendStickers, self.presentationData.strings.Channel_AdminLog_BanSendStickersAndGifs),
|
(.banSendStickers, self.presentationData.strings.Channel_AdminLog_BanSendStickersAndGifs),
|
||||||
(.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks),
|
(.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks),
|
||||||
|
@ -32,9 +32,9 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
|
|||||||
|
|
||||||
let bannedPermission: (Int32, Bool)?
|
let bannedPermission: (Int32, Bool)?
|
||||||
if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel {
|
if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||||
bannedPermission = channel.hasBannedPermission(.banSendMessages)
|
bannedPermission = channel.hasBannedPermission(.banSendText)
|
||||||
} else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup {
|
} else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup {
|
||||||
if group.hasBannedPermission(.banSendMessages) {
|
if group.hasBannedPermission(.banSendText) {
|
||||||
bannedPermission = (Int32.max, false)
|
bannedPermission = (Int32.max, false)
|
||||||
} else {
|
} else {
|
||||||
bannedPermission = nil
|
bannedPermission = nil
|
||||||
|
@ -462,6 +462,7 @@ final class ChatTextViewForOverlayContent: UIView, ChatInputPanelViewForOverlayC
|
|||||||
class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||||
let clippingNode: ASDisplayNode
|
let clippingNode: ASDisplayNode
|
||||||
var textPlaceholderNode: ImmediateTextNode
|
var textPlaceholderNode: ImmediateTextNode
|
||||||
|
var textLockIconNode: ASImageNode?
|
||||||
var contextPlaceholderNode: TextNode?
|
var contextPlaceholderNode: TextNode?
|
||||||
var slowmodePlaceholderNode: ChatTextInputSlowmodePlaceholderNode?
|
var slowmodePlaceholderNode: ChatTextInputSlowmodePlaceholderNode?
|
||||||
let textInputContainerBackgroundNode: ASImageNode
|
let textInputContainerBackgroundNode: ASImageNode
|
||||||
@ -516,6 +517,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
private var updatingInputState = false
|
private var updatingInputState = false
|
||||||
|
|
||||||
private var currentPlaceholder: String?
|
private var currentPlaceholder: String?
|
||||||
|
private var sendingTextDisabled: Bool = false
|
||||||
|
|
||||||
private var presentationInterfaceState: ChatPresentationInterfaceState?
|
private var presentationInterfaceState: ChatPresentationInterfaceState?
|
||||||
private var initializedPlaceholder = false
|
private var initializedPlaceholder = false
|
||||||
@ -923,7 +925,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
|
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
|
||||||
recognizer.touchDown = { [weak self] in
|
recognizer.touchDown = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.ensureFocused()
|
if strongSelf.sendingTextDisabled {
|
||||||
|
guard let controller = strongSelf.interfaceInteraction?.chatController() as? ChatControllerImpl else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//TODO:localize
|
||||||
|
controller.controllerInteraction?.displayUndo(.info(title: nil, text: "The admins of this group do not allow to send text messages."))
|
||||||
|
} else {
|
||||||
|
strongSelf.ensureFocused()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recognizer.waitForTouchUp = { [weak self] in
|
recognizer.waitForTouchUp = { [weak self] in
|
||||||
@ -997,6 +1007,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
textInputNode.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: -13.0)
|
textInputNode.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: -13.0)
|
||||||
self.textInputContainer.addSubnode(textInputNode)
|
self.textInputContainer.addSubnode(textInputNode)
|
||||||
textInputNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
textInputNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
|
textInputNode.isUserInteractionEnabled = !self.sendingTextDisabled
|
||||||
self.textInputNode = textInputNode
|
self.textInputNode = textInputNode
|
||||||
|
|
||||||
var accessoryButtonsWidth: CGFloat = 0.0
|
var accessoryButtonsWidth: CGFloat = 0.0
|
||||||
@ -1024,8 +1035,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self.updateSpoiler()
|
self.updateSpoiler()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.textInputBackgroundNode.isUserInteractionEnabled = false
|
self.textInputBackgroundNode.isUserInteractionEnabled = !textInputNode.isUserInteractionEnabled
|
||||||
self.textInputBackgroundNode.view.removeGestureRecognizer(self.textInputBackgroundNode.view.gestureRecognizers![0])
|
//self.textInputBackgroundNode.view.removeGestureRecognizer(self.textInputBackgroundNode.view.gestureRecognizers![0])
|
||||||
|
|
||||||
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
|
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
|
||||||
recognizer.touchDown = { [weak self] in
|
recognizer.touchDown = { [weak self] in
|
||||||
@ -1198,6 +1209,18 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self.attachmentButton.accessibilityTraits = (!isSlowmodeActive || isMediaEnabled) ? [.button] : [.button, .notEnabled]
|
self.attachmentButton.accessibilityTraits = (!isSlowmodeActive || isMediaEnabled) ? [.button] : [.button, .notEnabled]
|
||||||
self.attachmentButtonDisabledNode.isHidden = !isSlowmodeActive || isMediaEnabled
|
self.attachmentButtonDisabledNode.isHidden = !isSlowmodeActive || isMediaEnabled
|
||||||
|
|
||||||
|
var sendingTextDisabled = false
|
||||||
|
if let peer = interfaceState.renderedPeer?.peer {
|
||||||
|
if let channel = peer as? TelegramChannel, channel.hasBannedPermission(.banSendText) != nil {
|
||||||
|
sendingTextDisabled = true
|
||||||
|
} else if let group = peer as? TelegramGroup, group.hasBannedPermission(.banSendText) {
|
||||||
|
sendingTextDisabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.sendingTextDisabled = sendingTextDisabled
|
||||||
|
|
||||||
|
self.textInputNode?.isUserInteractionEnabled = !sendingTextDisabled
|
||||||
|
|
||||||
var buttonTitleUpdated = false
|
var buttonTitleUpdated = false
|
||||||
var menuTextSize = self.menuButtonTextNode.frame.size
|
var menuTextSize = self.menuButtonTextNode.frame.size
|
||||||
if self.presentationInterfaceState != interfaceState {
|
if self.presentationInterfaceState != interfaceState {
|
||||||
@ -1347,22 +1370,30 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self.initializedPlaceholder = true
|
self.initializedPlaceholder = true
|
||||||
|
|
||||||
var placeholder: String
|
var placeholder: String
|
||||||
|
|
||||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||||
if interfaceState.interfaceState.silentPosting {
|
if interfaceState.interfaceState.silentPosting {
|
||||||
placeholder = interfaceState.strings.Conversation_InputTextSilentBroadcastPlaceholder
|
placeholder = interfaceState.strings.Conversation_InputTextSilentBroadcastPlaceholder
|
||||||
} else {
|
} else {
|
||||||
placeholder = interfaceState.strings.Conversation_InputTextBroadcastPlaceholder
|
placeholder = interfaceState.strings.Conversation_InputTextBroadcastPlaceholder
|
||||||
}
|
}
|
||||||
} else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.canBeAnonymous) {
|
|
||||||
placeholder = interfaceState.strings.Conversation_InputTextAnonymousPlaceholder
|
|
||||||
} else if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation, !replyThreadMessage.isForumPost {
|
|
||||||
if replyThreadMessage.isChannelPost {
|
|
||||||
placeholder = interfaceState.strings.Conversation_InputTextPlaceholderComment
|
|
||||||
} else {
|
|
||||||
placeholder = interfaceState.strings.Conversation_InputTextPlaceholderReply
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
placeholder = interfaceState.strings.Conversation_InputTextPlaceholder
|
if sendingTextDisabled {
|
||||||
|
//TODO:localize
|
||||||
|
placeholder = "Text not allowed"
|
||||||
|
} else {
|
||||||
|
if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.canBeAnonymous) {
|
||||||
|
placeholder = interfaceState.strings.Conversation_InputTextAnonymousPlaceholder
|
||||||
|
} else if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation, !replyThreadMessage.isForumPost {
|
||||||
|
if replyThreadMessage.isChannelPost {
|
||||||
|
placeholder = interfaceState.strings.Conversation_InputTextPlaceholderComment
|
||||||
|
} else {
|
||||||
|
placeholder = interfaceState.strings.Conversation_InputTextPlaceholderReply
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
placeholder = interfaceState.strings.Conversation_InputTextPlaceholder
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let keyboardButtonsMessage = interfaceState.keyboardButtonsMessage, interfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId != keyboardButtonsMessage.id {
|
if let keyboardButtonsMessage = interfaceState.keyboardButtonsMessage, interfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId != keyboardButtonsMessage.id {
|
||||||
@ -2037,7 +2068,35 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: textInputBackgroundFrame)
|
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: textInputBackgroundFrame)
|
||||||
transition.updateAlpha(node: self.textInputBackgroundNode, alpha: audioRecordingItemsAlpha)
|
transition.updateAlpha(node: self.textInputBackgroundNode, alpha: audioRecordingItemsAlpha)
|
||||||
|
|
||||||
transition.updateFrame(node: self.textPlaceholderNode, frame: CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: self.textPlaceholderNode.frame.size))
|
let textPlaceholderFrame: CGRect
|
||||||
|
if sendingTextDisabled {
|
||||||
|
textPlaceholderFrame = CGRect(origin: CGPoint(x: textInputBackgroundFrame.minX + floor((textInputBackgroundFrame.width - self.textPlaceholderNode.bounds.width) / 2.0), y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: self.textPlaceholderNode.frame.size)
|
||||||
|
|
||||||
|
let textLockIconNode: ASImageNode
|
||||||
|
var textLockIconTransition = transition
|
||||||
|
if let current = self.textLockIconNode {
|
||||||
|
textLockIconNode = current
|
||||||
|
} else {
|
||||||
|
textLockIconTransition = .immediate
|
||||||
|
textLockIconNode = ASImageNode()
|
||||||
|
self.textLockIconNode = textLockIconNode
|
||||||
|
self.textPlaceholderNode.addSubnode(textLockIconNode)
|
||||||
|
|
||||||
|
textLockIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: interfaceState.theme.chat.inputPanel.inputPlaceholderColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let image = textLockIconNode.image {
|
||||||
|
textLockIconTransition.updateFrame(node: textLockIconNode, frame: CGRect(origin: CGPoint(x: -image.size.width - 4.0, y: floor((textPlaceholderFrame.height - image.size.height) / 2.0)), size: image.size))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
textPlaceholderFrame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: self.textPlaceholderNode.frame.size)
|
||||||
|
|
||||||
|
if let textLockIconNode = self.textLockIconNode {
|
||||||
|
self.textLockIconNode = nil
|
||||||
|
textLockIconNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.textPlaceholderNode, frame: textPlaceholderFrame)
|
||||||
|
|
||||||
var textPlaceholderAlpha: CGFloat = audioRecordingItemsAlpha
|
var textPlaceholderAlpha: CGFloat = audioRecordingItemsAlpha
|
||||||
if self.textPlaceholderNode.frame.width > (nextButtonTopRight.x - textInputBackgroundFrame.minX) - 32.0 {
|
if self.textPlaceholderNode.frame.width > (nextButtonTopRight.x - textInputBackgroundFrame.minX) - 32.0 {
|
||||||
@ -3352,6 +3411,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ensureFocused() {
|
func ensureFocused() {
|
||||||
|
if self.sendingTextDisabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if self.textInputNode == nil {
|
if self.textInputNode == nil {
|
||||||
self.loadTextInputNode()
|
self.loadTextInputNode()
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import ShareController
|
|||||||
import LegacyUI
|
import LegacyUI
|
||||||
import LegacyMediaPickerUI
|
import LegacyMediaPickerUI
|
||||||
|
|
||||||
func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, attachmentController: ViewController? = nil, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: NSAttributedString, hasSchedule: Bool, photoOnly: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, dismissedWithResult: @escaping () -> Void = {}, finishedTransitionIn: @escaping () -> Void = {}) {
|
func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, attachmentController: ViewController? = nil, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: NSAttributedString, hasSchedule: Bool, enablePhoto: Bool, enableVideo: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, dismissedWithResult: @escaping () -> Void = {}, finishedTransitionIn: @escaping () -> Void = {}) {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||||
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
|
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
|
||||||
@ -22,7 +22,16 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: Ch
|
|||||||
|
|
||||||
let controller: TGCameraController
|
let controller: TGCameraController
|
||||||
if let cameraView = cameraView, let previewView = cameraView.previewView() {
|
if let cameraView = cameraView, let previewView = cameraView.previewView() {
|
||||||
controller = TGCameraController(context: legacyController.context, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, saveCapturedMedia: saveCapturedPhotos && !isSecretChat, camera: previewView.camera, previewView: previewView, intent: photoOnly ? TGCameraControllerGenericPhotoOnlyIntent : TGCameraControllerGenericIntent)
|
let intent: TGCameraControllerIntent
|
||||||
|
if !enableVideo {
|
||||||
|
intent = TGCameraControllerGenericPhotoOnlyIntent
|
||||||
|
} else if !enablePhoto {
|
||||||
|
intent = TGCameraControllerGenericVideoOnlyIntent
|
||||||
|
} else {
|
||||||
|
intent = TGCameraControllerGenericIntent
|
||||||
|
}
|
||||||
|
|
||||||
|
controller = TGCameraController(context: legacyController.context, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, saveCapturedMedia: saveCapturedPhotos && !isSecretChat, camera: previewView.camera, previewView: previewView, intent: intent)
|
||||||
} else {
|
} else {
|
||||||
controller = TGCameraController(context: legacyController.context, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, saveCapturedMedia: saveCapturedPhotos && !isSecretChat)
|
controller = TGCameraController(context: legacyController.context, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, saveCapturedMedia: saveCapturedPhotos && !isSecretChat)
|
||||||
}
|
}
|
||||||
|
@ -1516,7 +1516,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
if isCreator || (channel.adminRights != nil && channel.hasPermission(.sendMessages)) {
|
if isCreator || (channel.adminRights != nil && channel.hasPermission(.sendSomething)) {
|
||||||
let messagesShouldHaveSignatures: Bool
|
let messagesShouldHaveSignatures: Bool
|
||||||
switch channel.info {
|
switch channel.info {
|
||||||
case let .broadcast(info):
|
case let .broadcast(info):
|
||||||
|
@ -33,7 +33,7 @@ final class WebSearchControllerInteraction {
|
|||||||
let openResult: (ChatContextResult) -> Void
|
let openResult: (ChatContextResult) -> Void
|
||||||
let setSearchQuery: (String) -> Void
|
let setSearchQuery: (String) -> Void
|
||||||
let deleteRecentQuery: (String) -> Void
|
let deleteRecentQuery: (String) -> Void
|
||||||
let toggleSelection: (ChatContextResult, Bool) -> Void
|
let toggleSelection: (ChatContextResult, Bool) -> Bool
|
||||||
let sendSelected: (ChatContextResult?, Bool, Int32?) -> Void
|
let sendSelected: (ChatContextResult?, Bool, Int32?) -> Void
|
||||||
let schedule: () -> Void
|
let schedule: () -> Void
|
||||||
let avatarCompleted: (UIImage) -> Void
|
let avatarCompleted: (UIImage) -> Void
|
||||||
@ -41,7 +41,7 @@ final class WebSearchControllerInteraction {
|
|||||||
let editingState: TGMediaEditingContext
|
let editingState: TGMediaEditingContext
|
||||||
var hiddenMediaId: String?
|
var hiddenMediaId: String?
|
||||||
|
|
||||||
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Void, sendSelected: @escaping (ChatContextResult?, Bool, Int32?) -> Void, schedule: @escaping () -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?) -> Void, schedule: @escaping () -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||||
self.openResult = openResult
|
self.openResult = openResult
|
||||||
self.setSearchQuery = setSearchQuery
|
self.setSearchQuery = setSearchQuery
|
||||||
self.deleteRecentQuery = deleteRecentQuery
|
self.deleteRecentQuery = deleteRecentQuery
|
||||||
@ -119,6 +119,8 @@ public final class WebSearchController: ViewController {
|
|||||||
|
|
||||||
public var searchingUpdated: (Bool) -> Void = { _ in }
|
public var searchingUpdated: (Bool) -> Void = { _ in }
|
||||||
|
|
||||||
|
public var attemptItemSelection: (ChatContextResult) -> Bool = { _ in return true }
|
||||||
|
|
||||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, configuration: EngineConfiguration.SearchBots, mode: WebSearchControllerMode) {
|
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, configuration: EngineConfiguration.SearchBots, mode: WebSearchControllerMode) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
@ -233,8 +235,14 @@ public final class WebSearchController: ViewController {
|
|||||||
}
|
}
|
||||||
}, toggleSelection: { [weak self] result, value in
|
}, toggleSelection: { [weak self] result, value in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
if !strongSelf.attemptItemSelection(result) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
let item = LegacyWebSearchItem(result: result)
|
let item = LegacyWebSearchItem(result: result)
|
||||||
strongSelf.controllerInteraction?.selectionState?.setItem(item, selected: value)
|
strongSelf.controllerInteraction?.selectionState?.setItem(item, selected: value)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}, sendSelected: { [weak self] current, silently, scheduleTime in
|
}, sendSelected: { [weak self] current, silently, scheduleTime in
|
||||||
if let selectionState = selectionState, let results = self?.controllerNode.currentExternalResults {
|
if let selectionState = selectionState, let results = self?.controllerNode.currentExternalResults {
|
||||||
@ -258,6 +266,18 @@ public final class WebSearchController: ViewController {
|
|||||||
}
|
}
|
||||||
}, selectionState: selectionState, editingState: editingState)
|
}, selectionState: selectionState, editingState: editingState)
|
||||||
|
|
||||||
|
selectionState?.attemptSelectingItem = { [weak self] item in
|
||||||
|
guard let self else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if let item = item as? LegacyWebSearchItem {
|
||||||
|
return self.attemptItemSelection(item.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if let selectionState = selectionState {
|
if let selectionState = selectionState {
|
||||||
self.selectionDisposable = (selectionChangedSignal(selectionState: selectionState)
|
self.selectionDisposable = (selectionChangedSignal(selectionState: selectionState)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
|
@ -213,8 +213,13 @@ final class WebSearchItemNode: GridItemNode {
|
|||||||
func updateSelectionState(animated: Bool) {
|
func updateSelectionState(animated: Bool) {
|
||||||
if self.checkNode == nil, let item = self.item, let _ = item.controllerInteraction.selectionState {
|
if self.checkNode == nil, let item = self.item, let _ = item.controllerInteraction.selectionState {
|
||||||
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: item.theme, style: .overlay))
|
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: item.theme, style: .overlay))
|
||||||
checkNode.valueChanged = { value in
|
checkNode.valueChanged = { [weak self] value in
|
||||||
item.controllerInteraction.toggleSelection(item.result, value)
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !item.controllerInteraction.toggleSelection(item.result, value) {
|
||||||
|
self.checkNode?.setSelected(false, animated: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.addSubnode(checkNode)
|
self.addSubnode(checkNode)
|
||||||
self.checkNode = checkNode
|
self.checkNode = checkNode
|
||||||
|
Loading…
x
Reference in New Issue
Block a user