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.HoldForAudioOnly" = "Hold to record audio.";
|
||||
"Conversation.HoldForVideoOnly" = "Hold to record video.";
|
||||
|
@ -477,7 +477,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
||||
}
|
||||
} else {
|
||||
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
|
||||
controller?.inProgress = false
|
||||
}, error: { error in
|
||||
|
@ -621,11 +621,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
var canFullscreen = false
|
||||
|
||||
var canEdit = false
|
||||
var isImage = false
|
||||
var isVideo = false
|
||||
for media in message.media {
|
||||
if media is TelegramMediaImage {
|
||||
canEdit = true
|
||||
isImage = true
|
||||
} else if let media = media as? TelegramMediaFile, !media.isAnimated {
|
||||
var isVideo = false
|
||||
for attribute in media.attributes {
|
||||
switch attribute {
|
||||
case let .Video(_, dimensions, _):
|
||||
@ -666,7 +668,19 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if message.flags.contains(.Incoming) {
|
||||
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 {
|
||||
canDelete = true
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
@property (nonatomic, copy) void (^pressed)(void);
|
||||
@property (nonatomic, strong) TGMenuSheetPallete *pallete;
|
||||
|
||||
- (instancetype)initForSelfPortrait:(bool)selfPortrait;
|
||||
- (instancetype)initForSelfPortrait:(bool)selfPortrait videoModeByDefault:(bool)videoModeByDefault;
|
||||
|
||||
@property (nonatomic, readonly) bool previewViewAttached;
|
||||
- (void)detachPreviewView;
|
||||
|
@ -19,7 +19,8 @@ typedef enum {
|
||||
TGCameraControllerPassportMultipleIntent,
|
||||
TGCameraControllerAvatarIntent,
|
||||
TGCameraControllerSignupAvatarIntent,
|
||||
TGCameraControllerGenericPhotoOnlyIntent
|
||||
TGCameraControllerGenericPhotoOnlyIntent,
|
||||
TGCameraControllerGenericVideoOnlyIntent
|
||||
} TGCameraControllerIntent;
|
||||
|
||||
@interface TGCameraControllerWindow : TGOverlayControllerWindow
|
||||
|
@ -67,7 +67,7 @@
|
||||
|
||||
@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)setCameraMode:(PGCameraMode)mode;
|
||||
|
@ -10,6 +10,6 @@
|
||||
|
||||
- (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
|
||||
|
@ -13,6 +13,7 @@
|
||||
@property (nonatomic, readonly) bool allowGrouping;
|
||||
@property (nonatomic, readonly) int selectionLimit;
|
||||
@property (nonatomic, copy) void (^selectionLimitExceeded)(void);
|
||||
@property (nonatomic, copy) bool (^attemptSelectingItem)(id<TGMediaSelectableItem>);
|
||||
|
||||
@property (nonatomic, assign) bool grouping;
|
||||
- (SSignal *)groupingChangedSignal;
|
||||
|
@ -33,7 +33,7 @@
|
||||
|
||||
@implementation TGAttachmentCameraView
|
||||
|
||||
- (instancetype)initForSelfPortrait:(bool)selfPortrait
|
||||
- (instancetype)initForSelfPortrait:(bool)selfPortrait videoModeByDefault:(bool)videoModeByDefault
|
||||
{
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self != nil)
|
||||
@ -46,7 +46,7 @@
|
||||
PGCamera *camera = nil;
|
||||
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;
|
||||
|
||||
|
@ -230,7 +230,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
|
||||
|
||||
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 startPreview];
|
||||
|
||||
|
@ -170,8 +170,12 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
|
||||
_items = [[NSMutableArray alloc] init];
|
||||
|
||||
if (_intent != TGCameraControllerGenericIntent)
|
||||
if (_intent != TGCameraControllerGenericIntent) {
|
||||
_allowCaptions = false;
|
||||
}
|
||||
if (_intent == TGCameraControllerGenericVideoOnlyIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent) {
|
||||
_allowCaptions = false;
|
||||
}
|
||||
_saveEditedPhotos = saveEditedPhotos;
|
||||
_saveCapturedMedia = saveCapturedMedia;
|
||||
|
||||
@ -303,12 +307,12 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
|
||||
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];
|
||||
}
|
||||
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];
|
||||
|
||||
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];
|
||||
}
|
||||
if (_intent == TGCameraControllerGenericVideoOnlyIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent) {
|
||||
[_interfaceView setHasModeControl:false];
|
||||
}
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
_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];
|
||||
model.inhibitMute = self.inhibitMute;
|
||||
model.controller = galleryController;
|
||||
|
@ -101,7 +101,7 @@
|
||||
@synthesize cancelPressed;
|
||||
@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];
|
||||
if (self != nil)
|
||||
@ -314,7 +314,7 @@
|
||||
[_shutterButton addGestureRecognizer:shutterPanGestureRecognizer];
|
||||
[_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];
|
||||
|
||||
_flipButton = [[TGCameraFlipButton alloc] initWithFrame:CGRectMake(0, 0, 48, 48)];
|
||||
@ -414,6 +414,12 @@
|
||||
[_photoCounterButton addTarget:self action:@selector(photoCounterButtonPressed) forControlEvents:UIControlEventTouchUpInside];
|
||||
_photoCounterButton.userInteractionEnabled = false;
|
||||
[_bottomPanelView addSubview:_photoCounterButton];
|
||||
|
||||
if (videoModeByDefault) {
|
||||
[UIView performWithoutAnimation:^{
|
||||
[self updateForCameraModeChangeWithPreviousMode:PGCameraModePhoto];
|
||||
}];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ const CGFloat TGCameraTabletPanelViewWidth = 102.0f;
|
||||
@synthesize shutterReleased;
|
||||
@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];
|
||||
if (self != nil)
|
||||
@ -83,7 +83,7 @@ const CGFloat TGCameraTabletPanelViewWidth = 102.0f;
|
||||
[_shutterButton addTarget:self action:@selector(shutterButtonReleased) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_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];
|
||||
|
||||
__weak TGCameraMainTabletView *weakSelf = self;
|
||||
|
@ -21,7 +21,7 @@ const CGFloat TGCameraModeControlVerticalInteritemSpace = 29.0f;
|
||||
|
||||
@implementation TGCameraModeControl
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar
|
||||
- (instancetype)initWithFrame:(CGRect)frame avatar:(bool)avatar videoModeByDefault:(bool)videoModeByDefault
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil)
|
||||
@ -87,7 +87,11 @@ const CGFloat TGCameraModeControlVerticalInteritemSpace = 29.0f;
|
||||
_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;
|
||||
}
|
||||
|
@ -72,6 +72,12 @@
|
||||
{
|
||||
if (![(id)item conformsToProtocol:@protocol(TGMediaSelectableItem)])
|
||||
return false;
|
||||
|
||||
if (_attemptSelectingItem) {
|
||||
if (!_attemptSelectingItem(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *identifier = item.uniqueIdentifier;
|
||||
if (selected)
|
||||
|
@ -160,7 +160,9 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay))
|
||||
checkNode.valueChanged = { [weak self] value in
|
||||
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)
|
||||
|
@ -25,7 +25,7 @@ import MoreButtonNode
|
||||
final class MediaPickerInteraction {
|
||||
let openMedia: (PHFetchResult<PHAsset>, Int, 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 schedule: () -> Void
|
||||
let dismissInput: () -> Void
|
||||
@ -33,7 +33,7 @@ final class MediaPickerInteraction {
|
||||
let editingState: TGMediaEditingContext
|
||||
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.openSelectedMedia = openSelectedMedia
|
||||
self.toggleSelection = toggleSelection
|
||||
@ -403,7 +403,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
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.removeCorners()
|
||||
cameraView.pressed = { [weak self] in
|
||||
@ -946,7 +946,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
if cameraAccess == 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
|
||||
|
||||
let banDescription: String
|
||||
@ -973,7 +979,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
placeholderTransition.updateFrame(node: placeholderNode, frame: innerBounds)
|
||||
|
||||
self.updateNavigation(transition: .immediate)
|
||||
} else */if case .notDetermined = mediaAccess {
|
||||
} else if case .notDetermined = mediaAccess {
|
||||
} else {
|
||||
if case .limited = mediaAccess {
|
||||
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 {
|
||||
var placeholderTransition = transition
|
||||
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)
|
||||
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
|
||||
placeholderNode.removeFromSupernode()
|
||||
}
|
||||
@ -1161,6 +1172,45 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
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)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
@ -1223,7 +1273,39 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
|
||||
self?.controllerNode.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
|
||||
}, 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
|
||||
if suggestUndo {
|
||||
if !value {
|
||||
@ -1237,8 +1319,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
selectionState.setItem(item, selected: value)
|
||||
|
||||
if showUndo {
|
||||
strongSelf.showSelectionUndo(item: item)
|
||||
self.showSelectionUndo(item: item)
|
||||
}
|
||||
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, completion in
|
||||
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))
|
||||
checkNode.valueChanged = { [weak self] value in
|
||||
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)
|
||||
|
@ -378,7 +378,7 @@ private struct ChannelPermissionsControllerState: Equatable {
|
||||
|
||||
func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatBannedRightsFlags, isForum: Bool) -> String {
|
||||
//TODO:localize
|
||||
if right.contains(.banSendMessages) {
|
||||
if right.contains(.banSendText) {
|
||||
return strings.Channel_BanUser_PermissionSendMessages
|
||||
} else if right.contains(.banSendMedia) {
|
||||
return strings.Channel_BanUser_PermissionSendMedia
|
||||
@ -416,7 +416,7 @@ func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatB
|
||||
}
|
||||
|
||||
func compactStringForGroupPermission(strings: PresentationStrings, right: TelegramChatBannedRightsFlags) -> String {
|
||||
if right.contains(.banSendMessages) {
|
||||
if right.contains(.banSendText) {
|
||||
return strings.GroupPermission_NoSendMessages
|
||||
} else if right.contains(.banSendMedia) {
|
||||
return strings.GroupPermission_NoSendMedia
|
||||
@ -440,7 +440,7 @@ func compactStringForGroupPermission(strings: PresentationStrings, right: Telegr
|
||||
}
|
||||
|
||||
private let internal_allPossibleGroupPermissionList: [(TelegramChatBannedRightsFlags, TelegramChannelPermission)] = [
|
||||
(.banSendMessages, .banMembers),
|
||||
(.banSendText, .banMembers),
|
||||
(.banSendMedia, .banMembers),
|
||||
(.banSendPhotos, .banMembers),
|
||||
(.banSendVideos, .banMembers),
|
||||
@ -460,9 +460,8 @@ private let internal_allPossibleGroupPermissionList: [(TelegramChatBannedRightsF
|
||||
public func allGroupPermissionList(peer: EnginePeer) -> [(TelegramChatBannedRightsFlags, TelegramChannelPermission)] {
|
||||
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
||||
return [
|
||||
(.banSendMessages, .banMembers),
|
||||
(.banSendText, .banMembers),
|
||||
(.banSendMedia, .banMembers),
|
||||
(.banSendPolls, .banMembers),
|
||||
(.banAddMembers, .banMembers),
|
||||
(.banPinMessages, .pinMessages),
|
||||
(.banManageTopics, .manageTopics),
|
||||
@ -470,9 +469,8 @@ public func allGroupPermissionList(peer: EnginePeer) -> [(TelegramChatBannedRigh
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
(.banSendMessages, .banMembers),
|
||||
(.banSendText, .banMembers),
|
||||
(.banSendMedia, .banMembers),
|
||||
(.banSendPolls, .banMembers),
|
||||
(.banAddMembers, .banMembers),
|
||||
(.banPinMessages, .pinMessages),
|
||||
(.banChangeInfo, .changeInfo)
|
||||
@ -490,6 +488,7 @@ public func banSendMediaSubList() -> [(TelegramChatBannedRightsFlags, TelegramCh
|
||||
(.banSendVoice, .banMembers),
|
||||
(.banSendInstantVideos, .banMembers),
|
||||
(.banEmbedLinks, .banMembers),
|
||||
(.banSendPolls, .banMembers),
|
||||
]
|
||||
}
|
||||
|
||||
@ -500,13 +499,13 @@ let publicGroupRestrictedPermissions: TelegramChatBannedRightsFlags = [
|
||||
|
||||
func groupPermissionDependencies(_ right: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags {
|
||||
if right.contains(.banSendMedia) || banSendMediaSubList().contains(where: { $0.0 == right }) {
|
||||
return [.banSendMessages]
|
||||
return []
|
||||
} else if right.contains(.banSendGifs) {
|
||||
return [.banSendMessages]
|
||||
return []
|
||||
} else if right.contains(.banEmbedLinks) {
|
||||
return [.banSendMessages]
|
||||
return []
|
||||
} else if right.contains(.banSendPolls) {
|
||||
return [.banSendMessages]
|
||||
return []
|
||||
} else if right.contains(.banChangeInfo) {
|
||||
return []
|
||||
} else if right.contains(.banAddMembers) {
|
||||
|
@ -460,7 +460,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
} else {
|
||||
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 {
|
||||
strongSelf.stableEmptyResultEmoji = nil
|
||||
}
|
||||
|
@ -3,7 +3,10 @@ import Postbox
|
||||
|
||||
|
||||
public enum TelegramChannelPermission {
|
||||
case sendMessages
|
||||
case sendText
|
||||
case sendPhoto
|
||||
case sendVideo
|
||||
case sendSomething
|
||||
case pinMessages
|
||||
case manageTopics
|
||||
case createTopics
|
||||
@ -30,7 +33,7 @@ public extension TelegramChannel {
|
||||
return true
|
||||
}
|
||||
switch permission {
|
||||
case .sendMessages:
|
||||
case .sendText:
|
||||
if case .broadcast = self.info {
|
||||
if let adminRights = self.adminRights {
|
||||
return adminRights.rights.contains(.canPostMessages)
|
||||
@ -41,10 +44,80 @@ public extension TelegramChannel {
|
||||
if let _ = self.adminRights {
|
||||
return true
|
||||
}
|
||||
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendMessages) {
|
||||
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendText) {
|
||||
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 true
|
||||
|
@ -15,6 +15,7 @@ extension TelegramChatBannedRights {
|
||||
var apiBannedRights: Api.ChatBannedRights {
|
||||
var effectiveFlags = self.flags
|
||||
effectiveFlags.remove(.banSendMedia)
|
||||
effectiveFlags.remove(TelegramChatBannedRightsFlags(rawValue: 1 << 1))
|
||||
|
||||
return .chatBannedRights(flags: effectiveFlags.rawValue, untilDate: self.untilDate)
|
||||
}
|
||||
|
@ -79,7 +79,10 @@ public enum SendAuthorizationCodeResult {
|
||||
func storeFutureLoginToken(accountManager: AccountManager<TelegramAccountManagerTypes>, token: Data) {
|
||||
let _ = (accountManager.transaction { transaction -> Void in
|
||||
var tokens = transaction.getStoredLoginTokens()
|
||||
tokens.insert(token, at: 0)
|
||||
|
||||
#if DEBUG
|
||||
tokens.removeAll()
|
||||
#endif
|
||||
|
||||
var cloudValue: [Data] = []
|
||||
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(token, at: 0)
|
||||
if tokens.count > 20 {
|
||||
tokens.removeLast(tokens.count - 20)
|
||||
}
|
||||
@ -143,12 +147,20 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
||||
return Data(base64Encoded: stringData)
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
cloudValue.removeAll()
|
||||
#endif
|
||||
return accountManager.transaction { transaction -> [Data] in
|
||||
return transaction.getStoredLoginTokens()
|
||||
}
|
||||
|> castError(AuthorizationCodeRequestError.self)
|
||||
|> mapToSignal { localAuthTokens -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||
var authTokens = localAuthTokens
|
||||
|
||||
#if DEBUG
|
||||
authTokens.removeAll()
|
||||
#endif
|
||||
|
||||
for data in cloudValue {
|
||||
if !authTokens.contains(data) {
|
||||
authTokens.insert(data, at: 0)
|
||||
@ -274,7 +286,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
||||
|> castError(AuthorizationCodeRequestError.self)
|
||||
|> mapToSignal { firebaseSecret -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||
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)
|
||||
@ -293,7 +305,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
||||
}
|
||||
|> castError(AuthorizationCodeRequestError.self)
|
||||
} 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)
|
||||
|> mapError { error -> AuthorizationCodeRequestError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
@ -349,60 +361,155 @@ private func internalResendAuthorizationCode(account: UnauthorizedAccount, numbe
|
||||
}
|
||||
}
|
||||
|> mapToSignal { sentCode -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||
return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in
|
||||
return account.postbox.transaction { transaction -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||
switch sentCode {
|
||||
case let .sentCode(_, type, phoneCodeHash, nextType, timeout):
|
||||
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
|
||||
var parsedNextType: AuthorizationCodeNextType?
|
||||
if let nextType = 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:
|
||||
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
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||
switch state.contents {
|
||||
case let .confirmationCodeEntry(number, _, hash, _, nextType, syncContacts):
|
||||
if nextType != nil {
|
||||
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|
||||
|> mapError { error -> AuthorizationCodeRequestError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
|
||||
return .invalidPhoneNumber
|
||||
} else if error.errorDescription == "PHONE_NUMBER_FLOOD" {
|
||||
return .phoneLimitExceeded
|
||||
} else if error.errorDescription == "PHONE_NUMBER_BANNED" {
|
||||
return .phoneBanned
|
||||
} else {
|
||||
return .generic(info: (Int(error.errorCode), error.errorDescription))
|
||||
}
|
||||
|> mapError { error -> AuthorizationCodeRequestError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
|
||||
return .invalidPhoneNumber
|
||||
} else if error.errorDescription == "PHONE_NUMBER_FLOOD" {
|
||||
return .phoneLimitExceeded
|
||||
} else if error.errorDescription == "PHONE_NUMBER_BANNED" {
|
||||
return .phoneBanned
|
||||
} else {
|
||||
return .generic(info: (Int(error.errorCode), error.errorDescription))
|
||||
}
|
||||
|> mapToSignal { sentCode -> Signal<Void, AuthorizationCodeRequestError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
switch sentCode {
|
||||
case let .sentCode(_, type, phoneCodeHash, nextType, timeout):
|
||||
|
||||
var parsedNextType: AuthorizationCodeNextType?
|
||||
if let nextType = 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
|
||||
}
|
||||
|> mapToSignal { sentCode -> Signal<Void, AuthorizationCodeRequestError> in
|
||||
return account.postbox.transaction { transaction -> Signal<Void, AuthorizationCodeRequestError> in
|
||||
switch sentCode {
|
||||
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
|
||||
var parsedNextType: AuthorizationCodeNextType?
|
||||
if let nextType = nextType {
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
} |> 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 {
|
||||
return .fail(.generic(info: nil))
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ public enum ServerProvidedSuggestion: String {
|
||||
case newcomerTicks = "NEWCOMER_TICKS"
|
||||
case validatePhoneNumber = "VALIDATE_PHONE_NUMBER"
|
||||
case validatePassword = "VALIDATE_PASSWORD"
|
||||
case setupPassword = "SETUP_2FA"
|
||||
case setupPassword = "SETUP_PASSWORD"
|
||||
}
|
||||
|
||||
private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:])
|
||||
@ -33,12 +33,7 @@ public func getServerProvidedSuggestions(account: Account) -> Signal<[ServerProv
|
||||
return []
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
var list = listItems
|
||||
list.append(ServerProvidedSuggestion.setupPassword.rawValue)
|
||||
#else
|
||||
let list = listItems
|
||||
#endif
|
||||
|
||||
return list.compactMap { item -> ServerProvidedSuggestion? in
|
||||
return ServerProvidedSuggestion(rawValue: item)
|
||||
|
@ -12,7 +12,6 @@ public struct TelegramChatBannedRightsFlags: OptionSet, Hashable {
|
||||
}
|
||||
|
||||
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 banSendStickers = TelegramChatBannedRightsFlags(rawValue: 1 << 3)
|
||||
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 banSendVoice = TelegramChatBannedRightsFlags(rawValue: 1 << 23)
|
||||
public static let banSendFiles = TelegramChatBannedRightsFlags(rawValue: 1 << 24)
|
||||
public static let banSendText = TelegramChatBannedRightsFlags(rawValue: 1 << 25)
|
||||
}
|
||||
|
||||
public struct TelegramChatBannedRights: PostboxCoding, Equatable {
|
||||
|
@ -14,7 +14,7 @@ public func canSendMessagesToPeer(_ peer: Peer) -> Bool {
|
||||
} else if let peer = peer as? TelegramSecretChat {
|
||||
return peer.embeddedState == .active
|
||||
} else if let peer = peer as? TelegramChannel {
|
||||
return peer.hasPermission(.sendMessages)
|
||||
return peer.hasPermission(.sendSomething)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ final class AvatarEditorScreenComponent: Component {
|
||||
private func updateData(_ data: KeyboardInputData) {
|
||||
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)
|
||||
|
||||
let updateSearchQuery: (String, String) -> Void = { [weak self] rawQuery, languageCode in
|
||||
@ -735,9 +735,9 @@ final class AvatarEditorScreenComponent: Component {
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
)
|
||||
}
|
||||
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
|
||||
@ -1586,9 +1586,9 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
private func processInputData(inputData: InputData) -> 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
|
||||
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,
|
||||
availableGifSearchEmojies: inputData.availableGifSearchEmojies
|
||||
|
@ -371,7 +371,7 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
} else {
|
||||
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 {
|
||||
strongSelf.stableEmptyResultEmoji = nil
|
||||
}
|
||||
|
@ -1520,8 +1520,10 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
var text: String
|
||||
var useOpaqueTheme: Bool
|
||||
var isActive: Bool
|
||||
var hasPresetSearch: Bool
|
||||
var size: CGSize
|
||||
var canFocus: Bool
|
||||
var hasSearchItems: Bool
|
||||
|
||||
static func ==(lhs: Params, rhs: Params) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
@ -1539,12 +1541,18 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
if lhs.isActive != rhs.isActive {
|
||||
return false
|
||||
}
|
||||
if lhs.hasPresetSearch != rhs.hasPresetSearch {
|
||||
return false
|
||||
}
|
||||
if lhs.size != rhs.size {
|
||||
return false
|
||||
}
|
||||
if lhs.canFocus != rhs.canFocus {
|
||||
return false
|
||||
}
|
||||
if lhs.hasSearchItems != rhs.hasSearchItems {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1565,6 +1573,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
private let searchIconView: UIImageView
|
||||
private let searchIconTintView: UIImageView
|
||||
|
||||
private let backIconView: UIImageView
|
||||
private let backIconTintView: UIImageView
|
||||
|
||||
private let clearIconView: UIImageView
|
||||
private let clearIconTintView: UIImageView
|
||||
private let clearIconButton: HighlightTrackingButton
|
||||
@ -1575,9 +1586,12 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
private let cancelButtonTitle: ComponentView<Empty>
|
||||
private let cancelButton: HighlightTrackingButton
|
||||
|
||||
private var suggestedItemsView: ComponentView<Empty>?
|
||||
|
||||
private var textField: EmojiSearchTextField?
|
||||
|
||||
private var tapRecognizer: UITapGestureRecognizer?
|
||||
private(set) var currentPresetSearchTerm: String?
|
||||
|
||||
private var params: Params?
|
||||
|
||||
@ -1598,6 +1612,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
self.searchIconView = UIImageView()
|
||||
self.searchIconTintView = UIImageView()
|
||||
|
||||
self.backIconView = UIImageView()
|
||||
self.backIconTintView = UIImageView()
|
||||
|
||||
self.clearIconView = UIImageView()
|
||||
self.clearIconTintView = UIImageView()
|
||||
self.clearIconButton = HighlightableButton()
|
||||
@ -1619,6 +1636,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
self.addSubview(self.searchIconView)
|
||||
self.tintContainerView.addSubview(self.searchIconTintView)
|
||||
|
||||
self.addSubview(self.backIconView)
|
||||
self.tintContainerView.addSubview(self.backIconTintView)
|
||||
|
||||
self.addSubview(self.clearIconView)
|
||||
self.tintContainerView.addSubview(self.clearIconTintView)
|
||||
self.addSubview(self.clearIconButton)
|
||||
@ -1681,25 +1701,38 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
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 location = recognizer.location(in: self)
|
||||
if self.backIconView.frame.contains(location) {
|
||||
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)
|
||||
textField.autocorrectionType = .no
|
||||
self.textField = textField
|
||||
self.insertSubview(textField, belowSubview: self.clearIconView)
|
||||
textField.delegate = self
|
||||
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
||||
self.currentPresetSearchTerm = nil
|
||||
if let suggestedItemsView = self.suggestedItemsView?.view as? EmojiSearchSearchBarComponent.View {
|
||||
suggestedItemsView.clearSelection(dispatchEvent: false)
|
||||
}
|
||||
|
||||
self.activated()
|
||||
|
||||
self.textField?.becomeFirstResponder()
|
||||
}
|
||||
|
||||
self.activated()
|
||||
|
||||
self.textField?.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
self.currentPresetSearchTerm = nil
|
||||
self.updateQuery("", "en")
|
||||
|
||||
self.clearIconView.isHidden = true
|
||||
@ -1720,6 +1753,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
}
|
||||
|
||||
@objc private func clearPressed() {
|
||||
self.currentPresetSearchTerm = nil
|
||||
self.updateQuery("", "en")
|
||||
self.textField?.text = ""
|
||||
|
||||
@ -1767,6 +1801,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
self.clearIconTintView.isHidden = text.isEmpty
|
||||
self.clearIconButton.isHidden = text.isEmpty
|
||||
|
||||
self.currentPresetSearchTerm = nil
|
||||
self.updateQuery(text, inputLanguage)
|
||||
}
|
||||
|
||||
@ -1775,28 +1810,37 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
return
|
||||
}
|
||||
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(
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
text: text,
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
isActive: isActive,
|
||||
hasPresetSearch: self.currentPresetSearchTerm == nil,
|
||||
size: size,
|
||||
canFocus: canFocus
|
||||
canFocus: canFocus,
|
||||
hasSearchItems: hasSearchItems
|
||||
)
|
||||
|
||||
if self.params == params {
|
||||
return
|
||||
}
|
||||
|
||||
let isActiveWithText = isActive && self.currentPresetSearchTerm == nil
|
||||
|
||||
let isLeftAligned = isActiveWithText || hasSearchItems
|
||||
|
||||
if self.params?.theme !== theme {
|
||||
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.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.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
|
||||
|
||||
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
|
||||
}
|
||||
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)))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
transition.setFrame(view: self.searchIconView, frame: iconFrame)
|
||||
transition.setFrame(view: self.searchIconTintView, frame: iconFrame)
|
||||
transition.setBounds(view: self.searchIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||
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 {
|
||||
@ -2389,7 +2515,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
public let animationCache: AnimationCache
|
||||
public let animationRenderer: MultiAnimationRenderer
|
||||
public let inputInteractionHolder: InputInteractionHolder
|
||||
public let itemGroups: [ItemGroup]
|
||||
public let panelItemGroups: [ItemGroup]
|
||||
public let contentItemGroups: [ItemGroup]
|
||||
public let itemLayoutType: ItemLayoutType
|
||||
public let itemContentUniqueId: AnyHashable?
|
||||
public let warpContentsOnEdges: Bool
|
||||
@ -2407,7 +2534,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationCache: AnimationCache,
|
||||
animationRenderer: MultiAnimationRenderer,
|
||||
inputInteractionHolder: InputInteractionHolder,
|
||||
itemGroups: [ItemGroup],
|
||||
panelItemGroups: [ItemGroup],
|
||||
contentItemGroups: [ItemGroup],
|
||||
itemLayoutType: ItemLayoutType,
|
||||
itemContentUniqueId: AnyHashable?,
|
||||
warpContentsOnEdges: Bool,
|
||||
@ -2424,7 +2552,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
self.inputInteractionHolder = inputInteractionHolder
|
||||
self.itemGroups = itemGroups
|
||||
self.panelItemGroups = panelItemGroups
|
||||
self.contentItemGroups = contentItemGroups
|
||||
self.itemLayoutType = itemLayoutType
|
||||
self.itemContentUniqueId = itemContentUniqueId
|
||||
self.warpContentsOnEdges = warpContentsOnEdges
|
||||
@ -2436,7 +2565,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
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(
|
||||
id: self.id,
|
||||
context: self.context,
|
||||
@ -2444,7 +2573,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationCache: self.animationCache,
|
||||
animationRenderer: self.animationRenderer,
|
||||
inputInteractionHolder: self.inputInteractionHolder,
|
||||
itemGroups: itemGroups,
|
||||
panelItemGroups: panelItemGroups,
|
||||
contentItemGroups: contentItemGroups,
|
||||
itemLayoutType: self.itemLayoutType,
|
||||
itemContentUniqueId: itemContentUniqueId,
|
||||
warpContentsOnEdges: self.warpContentsOnEdges,
|
||||
@ -2479,7 +2609,10 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if lhs.inputInteractionHolder !== rhs.inputInteractionHolder {
|
||||
return false
|
||||
}
|
||||
if lhs.itemGroups != rhs.itemGroups {
|
||||
if lhs.panelItemGroups != rhs.panelItemGroups {
|
||||
return false
|
||||
}
|
||||
if lhs.contentItemGroups != rhs.contentItemGroups {
|
||||
return false
|
||||
}
|
||||
if lhs.itemLayoutType != rhs.itemLayoutType {
|
||||
@ -4148,7 +4281,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var subgroupItemIndex: Int?
|
||||
if group.supergroupId == supergroupId {
|
||||
if let subgroupId = subgroupId {
|
||||
inner: for itemGroup in component.itemGroups {
|
||||
inner: for itemGroup in component.contentItemGroups {
|
||||
if itemGroup.supergroupId == supergroupId {
|
||||
for i in 0 ..< itemGroup.items.count {
|
||||
if itemGroup.items[i].subgroupId == subgroupId {
|
||||
@ -4858,7 +4991,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
self.updateScrollingOffset(isReset: false, transition: .immediate)
|
||||
|
||||
if self.isSearchActivated {
|
||||
if self.isSearchActivated, let visibleSearchHeader = self.visibleSearchHeader, visibleSearchHeader.currentPresetSearchTerm == nil {
|
||||
self.visibleSearchHeader?.deactivate()
|
||||
}
|
||||
}
|
||||
@ -4884,9 +5017,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
|
||||
if let previousScrollingOffsetValue = self.previousScrollingOffset, !self.keepTopPanelVisibleUntilScrollingInput {
|
||||
let currentBounds = scrollView.bounds
|
||||
let isInteracting = self.scrollView.isDragging || self.scrollView.isDecelerating
|
||||
if let previousScrollingOffsetValue = self.previousScrollingOffset, !self.keepTopPanelVisibleUntilScrollingInput, !self.isSearchActivated {
|
||||
let currentBounds = self.scrollView.bounds
|
||||
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
|
||||
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) {
|
||||
let itemGroup = component.itemGroups[groupItems.groupIndex]
|
||||
let itemGroup = component.contentItemGroups[groupItems.groupIndex]
|
||||
let itemGroupLayout = itemLayout.itemGroupLayouts[groupItems.groupIndex]
|
||||
|
||||
var assignTopVisibleSubgroupId = false
|
||||
@ -5875,11 +6008,11 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var previousAbsoluteItemPositions: [VisualItemKey: CGPoint] = [:]
|
||||
|
||||
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 {
|
||||
var previousItemPositionsValue: [VisualItemKey: CGPoint] = [:]
|
||||
for groupIndex in 0 ..< previousComponent.itemGroups.count {
|
||||
let itemGroup = previousComponent.itemGroups[groupIndex]
|
||||
for groupIndex in 0 ..< previousComponent.contentItemGroups.count {
|
||||
let itemGroup = previousComponent.contentItemGroups[groupIndex]
|
||||
for itemIndex in 0 ..< itemGroup.items.count {
|
||||
let item = itemGroup.items[itemIndex]
|
||||
let itemKey: ItemLayer.Key
|
||||
@ -5976,7 +6109,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
var itemGroups: [ItemGroupDescription] = []
|
||||
for itemGroup in component.itemGroups {
|
||||
for itemGroup in component.contentItemGroups {
|
||||
itemGroups.append(ItemGroupDescription(
|
||||
supergroupId: itemGroup.supergroupId,
|
||||
groupId: itemGroup.groupId,
|
||||
@ -6021,15 +6154,14 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
let scrollOriginY: CGFloat = 0.0
|
||||
|
||||
|
||||
let scrollSize = CGSize(width: availableSize.width, height: availableSize.height)
|
||||
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.setBounds(view: self.scrollViewClippingView, bounds: 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.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.setBounds(view: self.vibrancyClippingView, bounds: 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.itemInsets.top : 0.0), size: availableSize))
|
||||
|
||||
let previousSize = self.scrollView.bounds.size
|
||||
var resetScrolling = false
|
||||
@ -6041,6 +6173,10 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
self.scrollView.bounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollSize)
|
||||
|
||||
if resetScrolling {
|
||||
itemTransition = .immediate
|
||||
}
|
||||
|
||||
let warpHeight: CGFloat = 50.0
|
||||
var topWarpInset = pagerEnvironment.containerInsets.top
|
||||
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 {
|
||||
if component.itemGroups[i].groupId != anchorItem.0.groupId {
|
||||
if component.contentItemGroups[i].groupId != anchorItem.0.groupId {
|
||||
continue
|
||||
}
|
||||
for j in 0 ..< component.itemGroups[i].items.count {
|
||||
for j in 0 ..< component.contentItemGroups[i].items.count {
|
||||
let itemKey: ItemLayer.Key
|
||||
itemKey = ItemLayer.Key(
|
||||
groupId: component.itemGroups[i].groupId,
|
||||
itemId: component.itemGroups[i].items[j].content.id
|
||||
groupId: component.contentItemGroups[i].groupId,
|
||||
itemId: component.contentItemGroups[i].items[j].content.id
|
||||
)
|
||||
|
||||
if itemKey == anchorItem.0 {
|
||||
@ -6137,19 +6273,15 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
if resetScrolling {
|
||||
if component.displaySearchWithPlaceholder != nil && !self.isSearchActivated && component.searchInitiallyHidden {
|
||||
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.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollSize)
|
||||
}
|
||||
|
||||
self.ignoreScrolling = false
|
||||
|
||||
if calculateUpdatedItemPositions {
|
||||
var updatedItemPositionsValue: [VisualItemKey: CGPoint] = [:]
|
||||
for groupIndex in 0 ..< component.itemGroups.count {
|
||||
let itemGroup = component.itemGroups[groupIndex]
|
||||
for groupIndex in 0 ..< component.contentItemGroups.count {
|
||||
let itemGroup = component.contentItemGroups[groupIndex]
|
||||
let itemGroupLayout = itemLayout.itemGroupLayouts[groupIndex]
|
||||
for itemIndex in 0 ..< itemGroup.items.count {
|
||||
let item = itemGroup.items[itemIndex]
|
||||
@ -6204,14 +6336,16 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*if visibleSearchHeader.superview != self.scrollView {
|
||||
self.scrollView.addSubview(visibleSearchHeader)
|
||||
self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
||||
/*if component.inputInteractionHolder.inputInteraction?.externalBackground == nil {
|
||||
if visibleSearchHeader.superview != self.scrollView {
|
||||
self.scrollView.addSubview(visibleSearchHeader)
|
||||
self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
} else {
|
||||
visibleSearchHeader = EmojiSearchHeaderView(activated: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
guard let strongSelf = self, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -6219,7 +6353,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
component.inputInteractionHolder.inputInteraction?.openSearch()
|
||||
} else {
|
||||
strongSelf.isSearchActivated = true
|
||||
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(true)
|
||||
if visibleSearchHeader.currentPresetSearchTerm == nil {
|
||||
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(true)
|
||||
}
|
||||
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
|
||||
}
|
||||
}, 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))
|
||||
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, transition: transition)
|
||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
|
||||
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.attachAnimation(view: visibleSearchHeader, completion: { [weak self] completed in
|
||||
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
||||
return
|
||||
}
|
||||
@ -6266,6 +6402,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
||||
}
|
||||
})
|
||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame)
|
||||
} else {
|
||||
if let visibleSearchHeader = self.visibleSearchHeader {
|
||||
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)
|
||||
|
||||
if animateContentCrossfade {
|
||||
for (_, itemLayer) in self.visibleItemLayers {
|
||||
itemLayer.animateAlpha(from: 0.0, to: CGFloat(itemLayer.opacity), duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
id: "emoji",
|
||||
context: context,
|
||||
@ -7134,40 +7328,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationCache: animationCache,
|
||||
animationRenderer: animationRenderer,
|
||||
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
|
||||
itemGroups: 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
|
||||
)
|
||||
},
|
||||
panelItemGroups: allItemGroups,
|
||||
contentItemGroups: allItemGroups,
|
||||
itemLayoutType: .compact,
|
||||
itemContentUniqueId: nil,
|
||||
warpContentsOnEdges: isReactionSelection || isStatusSelection,
|
||||
@ -7644,6 +7806,33 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
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(
|
||||
id: isMasks ? "masks" : "stickers",
|
||||
context: context,
|
||||
@ -7651,32 +7840,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationCache: animationCache,
|
||||
animationRenderer: animationRenderer,
|
||||
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
|
||||
itemGroups: 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
|
||||
)
|
||||
},
|
||||
panelItemGroups: allItemGroups,
|
||||
contentItemGroups: allItemGroups,
|
||||
itemLayoutType: .detailed,
|
||||
itemContentUniqueId: nil,
|
||||
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 {
|
||||
var topMaskItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||
|
||||
for itemGroup in maskContent.itemGroups {
|
||||
for itemGroup in maskContent.panelItemGroups {
|
||||
if let id = itemGroup.supergroupId.base as? String {
|
||||
let iconMapping: [String: EntityKeyboardIconTopPanelComponent.Icon] = [
|
||||
"saved": .saved,
|
||||
@ -359,7 +359,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
theme: component.theme,
|
||||
items: topMaskItems,
|
||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||
defaultActiveItemId: maskContent.itemGroups.first?.groupId,
|
||||
defaultActiveItemId: maskContent.panelItemGroups.first?.groupId,
|
||||
activeContentItemIdUpdated: masksContentItemIdUpdated,
|
||||
reorderItems: { [weak self] items in
|
||||
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 id == "peerSpecific" {
|
||||
if let avatarPeer = stickerContent.avatarPeer {
|
||||
@ -551,7 +551,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
theme: component.theme,
|
||||
items: topStickerItems,
|
||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||
defaultActiveItemId: stickerContent.itemGroups.first?.groupId,
|
||||
defaultActiveItemId: stickerContent.panelItemGroups.first?.groupId,
|
||||
activeContentItemIdUpdated: stickersContentItemIdUpdated,
|
||||
reorderItems: { [weak self] items in
|
||||
guard let strongSelf = self else {
|
||||
@ -581,7 +581,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
if let emojiContent = component.emojiContent {
|
||||
contents.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(emojiContent)))
|
||||
var topEmojiItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||
for itemGroup in emojiContent.itemGroups {
|
||||
for itemGroup in emojiContent.panelItemGroups {
|
||||
if !itemGroup.items.isEmpty {
|
||||
if let id = itemGroup.groupId.base as? String {
|
||||
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))
|
||||
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
|
||||
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
||||
return
|
||||
|
@ -1394,6 +1394,10 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
})
|
||||
}*/
|
||||
|
||||
if #available(iOS 12.0, *) {
|
||||
UIApplication.shared.registerForRemoteNotifications()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -3855,6 +3855,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}, displayUndo: { [weak self] content in
|
||||
if let strongSelf = self {
|
||||
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
|
||||
return true
|
||||
}), in: .current)
|
||||
@ -4440,7 +4453,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
hasScheduledMessages = peerView.get()
|
||||
|> take(1)
|
||||
|> 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)
|
||||
} else {
|
||||
return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder))
|
||||
@ -4864,6 +4877,37 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
.updatedAutoremoveTimeout(autoremoveTimeout)
|
||||
.updatedCurrentSendAsPeerId(currentSendAsPeerId)
|
||||
.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 {
|
||||
@ -4995,7 +5039,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
hasScheduledMessages = peerView
|
||||
|> take(1)
|
||||
|> 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)
|
||||
} else {
|
||||
return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder))
|
||||
@ -5271,6 +5315,37 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return renderedPeer
|
||||
}.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages).updatedCurrentSendAsPeerId(currentSendAsPeerId)
|
||||
.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 {
|
||||
strongSelf.didSetChatLocationInfoReady = true
|
||||
@ -6312,7 +6387,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if let opaqueState = (combinedInitialData.initialData?.storedInterfaceState).flatMap(_internal_decodeStoredChatInterfaceState) {
|
||||
let interfaceState = ChatInterfaceState.parse(opaqueState)
|
||||
var interfaceState = ChatInterfaceState.parse(opaqueState)
|
||||
|
||||
var pinnedMessageId: MessageId?
|
||||
var peerIsBlocked: Bool = false
|
||||
@ -6343,6 +6418,32 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
} 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 let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||
pinnedMessageId = nil
|
||||
@ -8344,10 +8445,55 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.mediaRecordingModeTooltipController?.dismiss()
|
||||
strongSelf.interfaceInteraction?.updateShowWebView { _ in
|
||||
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 begin: () -> Void = {
|
||||
@ -8607,35 +8753,77 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
tooltipController.dismissImmediately()
|
||||
}
|
||||
}, switchMediaRecordingMode: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
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()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
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
|
||||
guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else {
|
||||
return
|
||||
@ -11948,25 +12136,54 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
let _ = peer
|
||||
|
||||
let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in
|
||||
let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self)
|
||||
return entry ?? GeneratedMediaStoreSettings.defaultSettings
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] settings in
|
||||
guard let strongSelf = self else {
|
||||
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
var photoOnly = false
|
||||
var enablePhoto = true
|
||||
var enableVideo = true
|
||||
|
||||
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 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 {
|
||||
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil)
|
||||
if !inputText.string.isEmpty {
|
||||
@ -12603,12 +12820,40 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}, openCamera: { [weak self] cameraView, menuController in
|
||||
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 {
|
||||
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 editMediaOptions != nil {
|
||||
strongSelf.editMessageMediaWithLegacySignals(signals!)
|
||||
@ -13096,8 +13341,93 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
})
|
||||
}))
|
||||
controller.getCaptionPanelView = { [weak self] in
|
||||
return self?.getCaptionPanelView()
|
||||
controller.attemptItemSelection = { [weak strongSelf] item in
|
||||
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))
|
||||
}
|
||||
@ -16597,15 +16927,55 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
private func displayMediaRecordingTooltip() {
|
||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
let rect: CGRect? = self.chatDisplayNode.frameForInputActionButton()
|
||||
|
||||
let updatedMode: ChatTextInputMediaRecordingButtonMode = self.presentationInterfaceState.interfaceState.mediaRecordingMode
|
||||
|
||||
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 {
|
||||
text = self.presentationData.strings.Conversation_HoldForAudio
|
||||
if canSwitch {
|
||||
text = self.presentationData.strings.Conversation_HoldForAudio
|
||||
} else {
|
||||
text = self.presentationData.strings.Conversation_HoldForAudioOnly
|
||||
}
|
||||
} 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()
|
||||
|
@ -280,6 +280,8 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
||||
var currentAutoremoveTimeout: Int32? = chatPresentationInterfaceState.autoremoveTimeout
|
||||
var canSetupAutoremoveTimeout = false
|
||||
|
||||
var canSendTextMessages = true
|
||||
|
||||
var accessoryItems: [ChatTextInputAccessoryItem] = []
|
||||
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat {
|
||||
var extendedSearchLayout = false
|
||||
@ -298,6 +300,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
||||
if !group.hasBannedPermission(.banChangeInfo) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
canSendTextMessages = !group.hasBannedPermission(.banSendText)
|
||||
} else if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser {
|
||||
if user.botInfo == nil {
|
||||
canSetupAutoremoveTimeout = true
|
||||
@ -306,6 +309,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
||||
if channel.hasPermission(.changeInfo) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
canSendTextMessages = channel.hasBannedPermission(.banSendText) == nil
|
||||
}
|
||||
|
||||
if canSetupAutoremoveTimeout {
|
||||
@ -375,10 +379,16 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
||||
accessoryItems.append(.commands)
|
||||
}
|
||||
|
||||
if stickersEnabled {
|
||||
accessoryItems.append(.input(isEnabled: true, inputMode: stickersAreEmoji ? .emoji : .stickers))
|
||||
if !canSendTextMessages {
|
||||
if stickersEnabled && !stickersAreEmoji {
|
||||
accessoryItems.append(.input(isEnabled: true, inputMode: .stickers))
|
||||
}
|
||||
} 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 {
|
||||
|
@ -283,7 +283,7 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS
|
||||
case .peer:
|
||||
if let channel = peer as? TelegramChannel {
|
||||
if case .member = channel.participationStatus {
|
||||
canReply = channel.hasPermission(.sendMessages)
|
||||
canReply = channel.hasPermission(.sendSomething)
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
if case .Member = group.membership {
|
||||
|
@ -196,22 +196,22 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||
}
|
||||
}
|
||||
|
||||
if isMember && channel.hasBannedPermission(.banSendMessages) != nil && !channel.flags.contains(.isGigagroup) {
|
||||
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
||||
if isMember && channel.hasBannedPermission(.banSendText) != nil && !channel.flags.contains(.isGigagroup) {
|
||||
/*if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
||||
return (currentPanel, nil)
|
||||
} else {
|
||||
let panel = ChatRestrictedInputPanelNode()
|
||||
panel.context = context
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return (panel, nil)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
switch channel.info {
|
||||
case .broadcast:
|
||||
if chatPresentationInterfaceState.interfaceState.editMessage != nil, channel.hasPermission(.editAllMessages) {
|
||||
displayInputTextPanel = true
|
||||
} else if !channel.hasPermission(.sendMessages) || !isMember {
|
||||
} else if !channel.hasPermission(.sendSomething) || !isMember {
|
||||
if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) {
|
||||
return (currentPanel, nil)
|
||||
} else {
|
||||
@ -235,7 +235,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||
}
|
||||
}
|
||||
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) {
|
||||
return (currentPanel, nil)
|
||||
} else {
|
||||
@ -280,15 +280,15 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||
break
|
||||
}
|
||||
|
||||
if group.hasBannedPermission(.banSendMessages) {
|
||||
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
||||
if group.hasBannedPermission(.banSendText) {
|
||||
/*if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
||||
return (currentPanel, nil)
|
||||
} else {
|
||||
let panel = ChatRestrictedInputPanelNode()
|
||||
panel.context = context
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return (panel, nil)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -660,7 +660,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
|
||||
let order: [(TelegramChatBannedRightsFlags, String)] = [
|
||||
(.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),
|
||||
(.banSendStickers, self.presentationData.strings.Channel_AdminLog_BanSendStickersAndGifs),
|
||||
(.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks),
|
||||
@ -1015,7 +1015,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
|
||||
let order: [(TelegramChatBannedRightsFlags, String)] = [
|
||||
(.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),
|
||||
(.banSendStickers, self.presentationData.strings.Channel_AdminLog_BanSendStickersAndGifs),
|
||||
(.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks),
|
||||
|
@ -32,9 +32,9 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
|
||||
|
||||
let bannedPermission: (Int32, Bool)?
|
||||
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 {
|
||||
if group.hasBannedPermission(.banSendMessages) {
|
||||
if group.hasBannedPermission(.banSendText) {
|
||||
bannedPermission = (Int32.max, false)
|
||||
} else {
|
||||
bannedPermission = nil
|
||||
|
@ -462,6 +462,7 @@ final class ChatTextViewForOverlayContent: UIView, ChatInputPanelViewForOverlayC
|
||||
class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
let clippingNode: ASDisplayNode
|
||||
var textPlaceholderNode: ImmediateTextNode
|
||||
var textLockIconNode: ASImageNode?
|
||||
var contextPlaceholderNode: TextNode?
|
||||
var slowmodePlaceholderNode: ChatTextInputSlowmodePlaceholderNode?
|
||||
let textInputContainerBackgroundNode: ASImageNode
|
||||
@ -516,6 +517,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
private var updatingInputState = false
|
||||
|
||||
private var currentPlaceholder: String?
|
||||
private var sendingTextDisabled: Bool = false
|
||||
|
||||
private var presentationInterfaceState: ChatPresentationInterfaceState?
|
||||
private var initializedPlaceholder = false
|
||||
@ -923,7 +925,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
|
||||
recognizer.touchDown = { [weak self] in
|
||||
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
|
||||
@ -997,6 +1007,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
textInputNode.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: -13.0)
|
||||
self.textInputContainer.addSubnode(textInputNode)
|
||||
textInputNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
textInputNode.isUserInteractionEnabled = !self.sendingTextDisabled
|
||||
self.textInputNode = textInputNode
|
||||
|
||||
var accessoryButtonsWidth: CGFloat = 0.0
|
||||
@ -1024,8 +1035,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.updateSpoiler()
|
||||
}
|
||||
|
||||
self.textInputBackgroundNode.isUserInteractionEnabled = false
|
||||
self.textInputBackgroundNode.view.removeGestureRecognizer(self.textInputBackgroundNode.view.gestureRecognizers![0])
|
||||
self.textInputBackgroundNode.isUserInteractionEnabled = !textInputNode.isUserInteractionEnabled
|
||||
//self.textInputBackgroundNode.view.removeGestureRecognizer(self.textInputBackgroundNode.view.gestureRecognizers![0])
|
||||
|
||||
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
|
||||
recognizer.touchDown = { [weak self] in
|
||||
@ -1198,6 +1209,18 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.attachmentButton.accessibilityTraits = (!isSlowmodeActive || isMediaEnabled) ? [.button] : [.button, .notEnabled]
|
||||
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 menuTextSize = self.menuButtonTextNode.frame.size
|
||||
if self.presentationInterfaceState != interfaceState {
|
||||
@ -1347,22 +1370,30 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.initializedPlaceholder = true
|
||||
|
||||
var placeholder: String
|
||||
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
if interfaceState.interfaceState.silentPosting {
|
||||
placeholder = interfaceState.strings.Conversation_InputTextSilentBroadcastPlaceholder
|
||||
} else {
|
||||
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 {
|
||||
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 {
|
||||
@ -2037,7 +2068,35 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: textInputBackgroundFrame)
|
||||
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
|
||||
if self.textPlaceholderNode.frame.width > (nextButtonTopRight.x - textInputBackgroundFrame.minX) - 32.0 {
|
||||
@ -3352,6 +3411,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
|
||||
func ensureFocused() {
|
||||
if self.sendingTextDisabled {
|
||||
return
|
||||
}
|
||||
|
||||
if self.textInputNode == nil {
|
||||
self.loadTextInputNode()
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import ShareController
|
||||
import LegacyUI
|
||||
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 legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
|
||||
@ -22,7 +22,16 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: Ch
|
||||
|
||||
let controller: TGCameraController
|
||||
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 {
|
||||
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
|
||||
switch channel.info {
|
||||
case let .broadcast(info):
|
||||
|
@ -33,7 +33,7 @@ final class WebSearchControllerInteraction {
|
||||
let openResult: (ChatContextResult) -> Void
|
||||
let setSearchQuery: (String) -> Void
|
||||
let deleteRecentQuery: (String) -> Void
|
||||
let toggleSelection: (ChatContextResult, Bool) -> Void
|
||||
let toggleSelection: (ChatContextResult, Bool) -> Bool
|
||||
let sendSelected: (ChatContextResult?, Bool, Int32?) -> Void
|
||||
let schedule: () -> Void
|
||||
let avatarCompleted: (UIImage) -> Void
|
||||
@ -41,7 +41,7 @@ final class WebSearchControllerInteraction {
|
||||
let editingState: TGMediaEditingContext
|
||||
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.setSearchQuery = setSearchQuery
|
||||
self.deleteRecentQuery = deleteRecentQuery
|
||||
@ -119,6 +119,8 @@ public final class WebSearchController: ViewController {
|
||||
|
||||
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) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
@ -233,8 +235,14 @@ public final class WebSearchController: ViewController {
|
||||
}
|
||||
}, toggleSelection: { [weak self] result, value in
|
||||
if let strongSelf = self {
|
||||
if !strongSelf.attemptItemSelection(result) {
|
||||
return false
|
||||
}
|
||||
let item = LegacyWebSearchItem(result: result)
|
||||
strongSelf.controllerInteraction?.selectionState?.setItem(item, selected: value)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}, sendSelected: { [weak self] current, silently, scheduleTime in
|
||||
if let selectionState = selectionState, let results = self?.controllerNode.currentExternalResults {
|
||||
@ -258,6 +266,18 @@ public final class WebSearchController: ViewController {
|
||||
}
|
||||
}, 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 {
|
||||
self.selectionDisposable = (selectionChangedSignal(selectionState: selectionState)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
|
@ -213,8 +213,13 @@ final class WebSearchItemNode: GridItemNode {
|
||||
func updateSelectionState(animated: Bool) {
|
||||
if self.checkNode == nil, let item = self.item, let _ = item.controllerInteraction.selectionState {
|
||||
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: item.theme, style: .overlay))
|
||||
checkNode.valueChanged = { value in
|
||||
item.controllerInteraction.toggleSelection(item.result, value)
|
||||
checkNode.valueChanged = { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if !item.controllerInteraction.toggleSelection(item.result, value) {
|
||||
self.checkNode?.setSelected(false, animated: false)
|
||||
}
|
||||
}
|
||||
self.addSubnode(checkNode)
|
||||
self.checkNode = checkNode
|
||||
|
Loading…
x
Reference in New Issue
Block a user