Slowmode UI fixes

This commit is contained in:
Peter 2019-07-23 15:51:58 +01:00
parent 51bf263135
commit b6380062dd
55 changed files with 3828 additions and 3380 deletions

View File

@ -4475,6 +4475,9 @@ Any member of this group will be able to see messages in the channel.";
"StickerPacksSettings.AnimatedStickers" = "Loop Animated Stickers"; "StickerPacksSettings.AnimatedStickers" = "Loop Animated Stickers";
"StickerPacksSettings.AnimatedStickersInfo" = "Animated stickers will play in chat continuously."; "StickerPacksSettings.AnimatedStickersInfo" = "Animated stickers will play in chat continuously.";
"GroupInfo.Permissions.SlowmodeHeader" = "SLOWMODE"; "GroupInfo.Permissions.SlowmodeHeader" = "SLOWMODE";
"GroupInfo.Permissions.SlowmodeInfo" = "Members will be restricted to send one message per this interval.";
"Channel.AdminLog.DisabledSlowmode" = "%@ disabled slowmode";
"Channel.AdminLog.SetSlowmode" = "%1$@ set slowmode to %2$@";
"Chat.SlowmodeTooltip" = "Slowmode is enabled. You can send\nyour next message in %@."; "Chat.SlowmodeTooltip" = "Slowmode is enabled. You can send\nyour next message in %@.";
"Chat.SlowmodeTooltipPending" = "Slowmode is enabled. You can't send more than one message at once."; "Chat.SlowmodeTooltipPending" = "Slowmode is enabled. You can't send more than one message at once.";

View File

@ -16,9 +16,9 @@ public struct PeekControllerMenuItem {
public let title: String public let title: String
public let color: PeekControllerMenuItemColor public let color: PeekControllerMenuItemColor
public let font: PeekControllerMenuItemFont public let font: PeekControllerMenuItemFont
public let action: () -> Void public let action: (ASDisplayNode, CGRect) -> Bool
public init(title: String, color: PeekControllerMenuItemColor, font: PeekControllerMenuItemFont = .default, action: @escaping () -> Void) { public init(title: String, color: PeekControllerMenuItemColor, font: PeekControllerMenuItemFont = .default, action: @escaping (ASDisplayNode, CGRect) -> Bool) {
self.title = title self.title = title
self.color = color self.color = color
self.font = font self.font = font
@ -100,7 +100,8 @@ final class PeekControllerMenuItemNode: HighlightTrackingButtonNode {
} }
@objc func buttonPressed() { @objc func buttonPressed() {
if self.item.action(self, self.bounds) {
self.activatedAction() self.activatedAction()
self.item.action() }
} }
} }

View File

@ -12,10 +12,11 @@ private final class ViewControllerPeekContent: PeekControllerContent {
self.controller = controller self.controller = controller
var menu: [PeekControllerMenuItem] = [] var menu: [PeekControllerMenuItem] = []
for item in controller.previewActionItems { for item in controller.previewActionItems {
menu.append(PeekControllerMenuItem(title: item.title, color: .accent, action: { [weak controller] in menu.append(PeekControllerMenuItem(title: item.title, color: .accent, action: { [weak controller] _, _ in
if let controller = controller, let item = item as? UIPreviewAction { if let controller = controller, let item = item as? UIPreviewAction {
item.handler(item, controller) item.handler(item, controller)
} }
return true
})) }))
} }
self.menu = menu self.menu = menu

View File

@ -294,6 +294,7 @@
} }
bool groupingButtonVisible = strongSelf->_selectionContext.allowGrouping && onlyGroupableMedia && strongSelf->_selectionContext.count > 1; bool groupingButtonVisible = strongSelf->_selectionContext.allowGrouping && onlyGroupableMedia && strongSelf->_selectionContext.count > 1;
groupingButtonVisible = false;
[strongSelf->_toolbarView setCenterButtonHidden:!groupingButtonVisible animated:true]; [strongSelf->_toolbarView setCenterButtonHidden:!groupingButtonVisible animated:true];
return groupingButtonVisible; return groupingButtonVisible;

View File

@ -24,12 +24,15 @@
@property (nonatomic, copy) void(^onStop)(void); @property (nonatomic, copy) void(^onStop)(void);
@property (nonatomic, copy) void(^onCancel)(void); @property (nonatomic, copy) void(^onCancel)(void);
@property (nonatomic, copy) void(^didDismiss)(void); @property (nonatomic, copy) void(^didDismiss)(void);
@property (nonatomic, copy) void(^displaySlowmodeTooltip)(void);
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context assets:(TGVideoMessageCaptureControllerAssets *)assets transitionInView:(UIView *(^)(void))transitionInView parentController:(TGViewController *)parentController controlsFrame:(CGRect)controlsFrame isAlreadyLocked:(bool (^)(void))isAlreadyLocked liveUploadInterface:(id<TGLiveUploadInterface>)liveUploadInterface pallete:(TGModernConversationInputMicPallete *)pallete slowmodeTimestamp:(int32_t)slowmodeTimestamp slowmodeView:(UIView *(^)(void))slowmodeView;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context assets:(TGVideoMessageCaptureControllerAssets *)assets transitionInView:(UIView *(^)())transitionInView parentController:(TGViewController *)parentController controlsFrame:(CGRect)controlsFrame isAlreadyLocked:(bool (^)(void))isAlreadyLocked liveUploadInterface:(id<TGLiveUploadInterface>)liveUploadInterface;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context assets:(TGVideoMessageCaptureControllerAssets *)assets transitionInView:(UIView *(^)())transitionInView parentController:(TGViewController *)parentController controlsFrame:(CGRect)controlsFrame isAlreadyLocked:(bool (^)(void))isAlreadyLocked liveUploadInterface:(id<TGLiveUploadInterface>)liveUploadInterface pallete:(TGModernConversationInputMicPallete *)pallete;
- (void)buttonInteractionUpdate:(CGPoint)value; - (void)buttonInteractionUpdate:(CGPoint)value;
- (void)setLocked; - (void)setLocked;
- (CGRect)frameForSendButton;
- (void)complete; - (void)complete;
- (void)dismiss; - (void)dismiss;
- (bool)stop; - (bool)stop;

View File

@ -125,6 +125,9 @@ typedef enum
UIView *(^_transitionInView)(); UIView *(^_transitionInView)();
id<TGLiveUploadInterface> _liveUploadInterface; id<TGLiveUploadInterface> _liveUploadInterface;
int32_t _slowmodeTimestamp;
UIView * (^_slowmodeView)(void);
TGVideoMessageCaptureControllerAssets *_assets; TGVideoMessageCaptureControllerAssets *_assets;
} }
@ -134,12 +137,7 @@ typedef enum
@implementation TGVideoMessageCaptureController @implementation TGVideoMessageCaptureController
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context assets:(TGVideoMessageCaptureControllerAssets *)assets transitionInView:(UIView *(^)())transitionInView parentController:(TGViewController *)parentController controlsFrame:(CGRect)controlsFrame isAlreadyLocked:(bool (^)(void))isAlreadyLocked liveUploadInterface:(id<TGLiveUploadInterface>)liveUploadInterface - (instancetype)initWithContext:(id<LegacyComponentsContext>)context assets:(TGVideoMessageCaptureControllerAssets *)assets transitionInView:(UIView *(^)(void))transitionInView parentController:(TGViewController *)parentController controlsFrame:(CGRect)controlsFrame isAlreadyLocked:(bool (^)(void))isAlreadyLocked liveUploadInterface:(id<TGLiveUploadInterface>)liveUploadInterface pallete:(TGModernConversationInputMicPallete *)pallete slowmodeTimestamp:(int32_t)slowmodeTimestamp slowmodeView:(UIView *(^)(void))slowmodeView
{
return [self initWithContext:context assets:assets transitionInView:transitionInView parentController:parentController controlsFrame:controlsFrame isAlreadyLocked:isAlreadyLocked liveUploadInterface:liveUploadInterface pallete:nil];
}
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context assets:(TGVideoMessageCaptureControllerAssets *)assets transitionInView:(UIView *(^)())transitionInView parentController:(TGViewController *)parentController controlsFrame:(CGRect)controlsFrame isAlreadyLocked:(bool (^)(void))isAlreadyLocked liveUploadInterface:(id<TGLiveUploadInterface>)liveUploadInterface pallete:(TGModernConversationInputMicPallete *)pallete
{ {
self = [super initWithContext:context]; self = [super initWithContext:context];
if (self != nil) if (self != nil)
@ -150,6 +148,8 @@ typedef enum
_liveUploadInterface = liveUploadInterface; _liveUploadInterface = liveUploadInterface;
_assets = assets; _assets = assets;
_pallete = pallete; _pallete = pallete;
_slowmodeTimestamp = slowmodeTimestamp;
_slowmodeView = [slowmodeView copy];
_url = [TGVideoMessageCaptureController tempOutputPath]; _url = [TGVideoMessageCaptureController tempOutputPath];
_queue = [[SQueue alloc] init]; _queue = [[SQueue alloc] init];
@ -325,7 +325,7 @@ typedef enum
CGRect controlsFrame = _controlsFrame; CGRect controlsFrame = _controlsFrame;
controlsFrame.size.width = _wrapperView.frame.size.width; controlsFrame.size.width = _wrapperView.frame.size.width;
_controlsView = [[TGVideoMessageControls alloc] initWithFrame:controlsFrame assets:_assets]; _controlsView = [[TGVideoMessageControls alloc] initWithFrame:controlsFrame assets:_assets slowmodeTimestamp:_slowmodeTimestamp slowmodeView:_slowmodeView];
_controlsView.pallete = self.pallete; _controlsView.pallete = self.pallete;
_controlsView.clipsToBounds = true; _controlsView.clipsToBounds = true;
_controlsView.parent = self; _controlsView.parent = self;
@ -358,13 +358,14 @@ typedef enum
}; };
}; };
_controlsView.sendPressed = ^ _controlsView.sendPressed = ^bool
{ {
__strong TGVideoMessageCaptureController *strongSelf = weakSelf; __strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf != nil) if (strongSelf != nil) {
{ return [strongSelf sendPressed];
[strongSelf sendPressed]; } else {
}; return false;
}
}; };
[self.view addSubview:_controlsView]; [self.view addSubview:_controlsView];
@ -647,6 +648,10 @@ typedef enum
[_controlsView setLocked]; [_controlsView setLocked];
} }
- (CGRect)frameForSendButton {
return [_controlsView convertRect:[_controlsView frameForSendButton] toView:self.view];
}
- (bool)stop - (bool)stop
{ {
if (!_capturePipeline.isRecording) if (!_capturePipeline.isRecording)
@ -664,12 +669,23 @@ typedef enum
return true; return true;
} }
- (void)sendPressed - (bool)sendPressed
{ {
if (_slowmodeTimestamp != 0) {
int32_t timestamp = (int32_t)[[NSDate date] timeIntervalSince1970];
if (timestamp < _slowmodeTimestamp) {
if (_displaySlowmodeTooltip) {
_displaySlowmodeTooltip();
}
return false;
}
}
[self finishWithURL:_url dimensions:CGSizeMake(240.0f, 240.0f) duration:_duration liveUploadData:_liveUploadData thumbnailImage:_thumbnailImage]; [self finishWithURL:_url dimensions:CGSizeMake(240.0f, 240.0f) duration:_duration liveUploadData:_liveUploadData thumbnailImage:_thumbnailImage];
_automaticDismiss = true; _automaticDismiss = true;
[self dismiss:false]; [self dismiss:false];
return true;
} }
- (void)unmutePressed - (void)unmutePressed

View File

@ -12,7 +12,7 @@
@property (nonatomic, copy) void (^positionChanged)(void); @property (nonatomic, copy) void (^positionChanged)(void);
@property (nonatomic, copy) void (^cancel)(void); @property (nonatomic, copy) void (^cancel)(void);
@property (nonatomic, copy) void (^deletePressed)(void); @property (nonatomic, copy) void (^deletePressed)(void);
@property (nonatomic, copy) void (^sendPressed)(void); @property (nonatomic, copy) bool (^sendPressed)(void);
@property (nonatomic, copy) bool(^isAlreadyLocked)(void); @property (nonatomic, copy) bool(^isAlreadyLocked)(void);
@ -22,7 +22,7 @@
@property (nonatomic, weak) id<TGVideoMessageScrubberDelegate, TGVideoMessageScrubberDataSource> parent; @property (nonatomic, weak) id<TGVideoMessageScrubberDelegate, TGVideoMessageScrubberDataSource> parent;
- (instancetype)initWithFrame:(CGRect)frame assets:(TGVideoMessageCaptureControllerAssets *)assets; - (instancetype)initWithFrame:(CGRect)frame assets:(TGVideoMessageCaptureControllerAssets *)assets slowmodeTimestamp:(int32_t)slowmodeTimestamp slowmodeView:(UIView *(^)(void))slowmodeView;
- (void)captureStarted; - (void)captureStarted;
- (void)recordingStarted; - (void)recordingStarted;
@ -35,4 +35,6 @@
- (void)setDurationString:(NSString *)string; - (void)setDurationString:(NSString *)string;
- (CGRect)frameForSendButton;
@end @end

View File

@ -5,6 +5,7 @@
#import <LegacyComponents/TGModernButton.h> #import <LegacyComponents/TGModernButton.h>
//#import "TGModernConversationInputMicButton.h" //#import "TGModernConversationInputMicButton.h"
#import <LegacyComponents/TGVideoMessageScrubber.h> #import <LegacyComponents/TGVideoMessageScrubber.h>
#import <SSignalKit/SSignalKit.h>
#import "LegacyComponentsInternal.h" #import "LegacyComponentsInternal.h"
#import "TGColor.h" #import "TGColor.h"
@ -41,25 +42,37 @@ static CGRect viewFrame(UIView *view)
TGModernButton *_deleteButton; TGModernButton *_deleteButton;
TGModernButton *_sendButton; TGModernButton *_sendButton;
int32_t _slowmodeTimestamp;
UIView * (^_generateSlowmodeView)(void);
UIView *_slowmodeView;
UIImageView *_recordIndicatorView; UIImageView *_recordIndicatorView;
UILabel *_recordDurationLabel; UILabel *_recordDurationLabel;
CFAbsoluteTime _recordingInterfaceShowTime; CFAbsoluteTime _recordingInterfaceShowTime;
TGVideoMessageCaptureControllerAssets *_assets; TGVideoMessageCaptureControllerAssets *_assets;
STimer *_slowmodeTimer;
} }
@end @end
@implementation TGVideoMessageControls @implementation TGVideoMessageControls
- (instancetype)initWithFrame:(CGRect)frame assets:(TGVideoMessageCaptureControllerAssets *)assets { - (instancetype)initWithFrame:(CGRect)frame assets:(TGVideoMessageCaptureControllerAssets *)assets slowmodeTimestamp:(int32_t)slowmodeTimestamp slowmodeView:(UIView *(^)(void))slowmodeView {
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
if (self != nil) { if (self != nil) {
_assets = assets; _assets = assets;
_slowmodeTimestamp = slowmodeTimestamp;
_generateSlowmodeView = [slowmodeView copy];
} }
return self; return self;
} }
- (void)dealloc {
[_slowmodeTimer invalidate];
}
- (void)captureStarted - (void)captureStarted
{ {
[UIView transitionWithView:_recordDurationLabel duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{ [UIView transitionWithView:_recordDurationLabel duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
@ -362,6 +375,34 @@ static CGRect viewFrame(UIView *view)
[_sendButton addTarget:self action:@selector(sendButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [_sendButton addTarget:self action:@selector(sendButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_sendButton]; [self addSubview:_sendButton];
if (_slowmodeTimestamp != 0) {
int32_t timestamp = (int32_t)[[NSDate date] timeIntervalSince1970];
if (timestamp < _slowmodeTimestamp) {
if (_generateSlowmodeView) {
_slowmodeView = _generateSlowmodeView();
}
if (_slowmodeView) {
_slowmodeView.alpha = 0.0f;
[self addSubview:_slowmodeView];
__weak TGVideoMessageControls *weakSelf = self;
_slowmodeTimer = [[STimer alloc] initWithTimeout:0.5 repeat:true completion:^{
__strong TGVideoMessageControls *strongSelf = weakSelf;
if (strongSelf != nil) {
int32_t timestamp = (int32_t)[[NSDate date] timeIntervalSince1970];
if (timestamp >= strongSelf->_slowmodeTimestamp) {
[strongSelf->_slowmodeTimer invalidate];
[strongSelf->_slowmodeView removeFromSuperview];
strongSelf->_slowmodeView = nil;
[strongSelf setNeedsLayout];
}
}
} queue:[SQueue mainQueue]];
[_slowmodeTimer start];
}
}
}
_scrubberView = [[TGVideoMessageScrubber alloc] init]; _scrubberView = [[TGVideoMessageScrubber alloc] init];
_scrubberView.pallete = self.pallete; _scrubberView.pallete = self.pallete;
_scrubberView.dataSource = self.parent; _scrubberView.dataSource = self.parent;
@ -413,6 +454,7 @@ static CGRect viewFrame(UIView *view)
[UIView animateWithDuration:0.3 animations:^ [UIView animateWithDuration:0.3 animations:^
{ {
_sendButton.alpha = 1.0f; _sendButton.alpha = 1.0f;
_slowmodeView.alpha = 1.0f;
}]; }];
} }
@ -440,8 +482,12 @@ static CGRect viewFrame(UIView *view)
{ {
_sendButton.userInteractionEnabled = false; _sendButton.userInteractionEnabled = false;
if (self.sendPressed != nil) if (self.sendPressed != nil) {
self.sendPressed(); if (!self.sendPressed()) {
_sendButton.userInteractionEnabled = true;
}
}
} }
- (void)cancelPressed - (void)cancelPressed
@ -479,6 +525,10 @@ static CGRect viewFrame(UIView *view)
[_recordIndicatorView.layer removeAnimationForKey:@"opacity-dot"]; [_recordIndicatorView.layer removeAnimationForKey:@"opacity-dot"];
} }
static CGFloat floorToScreenPixels(CGFloat value) {
return CGFloor(value * UIScreen.mainScreen.scale) / UIScreen.mainScreen.scale;
}
- (void)layoutSubviews - (void)layoutSubviews
{ {
if (_slideToCancelLabel != nil) if (_slideToCancelLabel != nil)
@ -491,8 +541,25 @@ static CGRect viewFrame(UIView *view)
} }
setViewFrame(_sendButton, CGRectMake(self.frame.size.width - _sendButton.frame.size.width, 0.0f, _sendButton.frame.size.width, self.frame.size.height)); setViewFrame(_sendButton, CGRectMake(self.frame.size.width - _sendButton.frame.size.width, 0.0f, _sendButton.frame.size.width, self.frame.size.height));
if (_slowmodeView) {
_sendButton.layer.sublayerTransform = CATransform3DMakeScale(0.7575, 0.7575, 1.0);
CGFloat defaultSendButtonSize = 25.0f;
CGFloat defaultOriginX = _sendButton.frame.origin.x + floorToScreenPixels((_sendButton.bounds.size.width - defaultSendButtonSize) / 2.0f);
CGFloat defaultOriginY = _sendButton.frame.origin.y + floorToScreenPixels((_sendButton.bounds.size.height - defaultSendButtonSize) / 2.0f);
CGRect radialStatusFrame = CGRectMake(defaultOriginX - 4.0f, defaultOriginY - 4.0f, 33.0f, 33.0f);
_slowmodeView.frame = radialStatusFrame;
} else {
_sendButton.layer.sublayerTransform = CATransform3DIdentity;
}
_deleteButton.center = CGPointMake(24.0f, self.bounds.size.height / 2.0f); _deleteButton.center = CGPointMake(24.0f, self.bounds.size.height / 2.0f);
setViewFrame(_scrubberView, CGRectMake(46.0f, (self.frame.size.height - 33.0f) / 2.0f, self.frame.size.width - 46.0f * 2.0f, 33.0f)); setViewFrame(_scrubberView, CGRectMake(46.0f, (self.frame.size.height - 33.0f) / 2.0f, self.frame.size.width - 46.0f * 2.0f, 33.0f));
} }
- (CGRect)frameForSendButton {
return _sendButton.frame;
}
@end @end

View File

@ -784,7 +784,7 @@ private final class MediaPlayerContext {
} }
} else if let worstStatus = worstStatus, case let .finished(finishedAt) = worstStatus, finishedAt.isFinite { } else if let worstStatus = worstStatus, case let .finished(finishedAt) = worstStatus, finishedAt.isFinite {
let nextTickDelay = max(0.0, finishedAt - timestamp) / self.baseRate let nextTickDelay = max(0.0, finishedAt - timestamp) / self.baseRate
if nextTickDelay.isLessThanOrEqualTo(0.0) { if nextTickDelay.isLessThanOrEqualTo(0.0) || timestamp.isEqual(to: 0.0) {
rate = 0.0 rate = 0.0
performActionAtEndNow = true performActionAtEndNow = true
} else { } else {

View File

@ -108,6 +108,8 @@ public final class MediaTrackFrameBuffer {
} }
bufferedDuration = CMTimeGetSeconds(bufferedUntilTime) - timestamp bufferedDuration = CMTimeGetSeconds(bufferedUntilTime) - timestamp
} else if self.endOfStream {
return .finished(at: CMTimeGetSeconds(self.duration))
} }
let minTimestamp = timestamp - 1.0 let minTimestamp = timestamp - 1.0

View File

@ -470,20 +470,8 @@ struct ctr_state {
assert(result == errSecSuccess); assert(result == errSecSuccess);
[helloData appendBytes:r1 length:32]; [helloData appendBytes:r1 length:32];
uint8_t s3[2] = { 0x00, 0x22 }; uint8_t s0[65] = { 0x00, 0x34, 0x13, 0x03, 0x13, 0x01, 0x13, 0x02, 0xc0, 0x2c, 0xc0, 0x2b, 0xc0, 0x24, 0xc0, 0x23, 0xc0, 0x0a, 0xc0, 0x09, 0xcc, 0xa9, 0xc0, 0x30, 0xc0, 0x2f, 0xc0, 0x28, 0xc0, 0x27, 0xc0, 0x14, 0xc0, 0x13, 0xcc, 0xa8, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0xc0, 0x08, 0xc0, 0x12, 0x00, 0x0a, 0x01, 0x00, 0x01, 0x7f, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00 };
[helloData appendBytes:s3 length:2]; [helloData appendBytes:s0 length:65];
[helloData appendBytes:&greaseBytes[0] length:1];
[helloData appendBytes:&greaseBytes[0] length:1];
uint8_t s0[36] = { 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00, 0x01, 0x91 };
[helloData appendBytes:s0 length:36];
[helloData appendBytes:&greaseBytes[2] length:1];
[helloData appendBytes:&greaseBytes[2] length:1];
uint8_t s4[4] = { 0x00, 0x00, 0x00, 0x00 };
[helloData appendBytes:s4 length:4];
uint8_t stackZ[2] = { 0x00, 0x00 }; uint8_t stackZ[2] = { 0x00, 0x00 };
@ -514,40 +502,16 @@ struct ctr_state {
stack1Value = OSSwapInt16(stack1Value); stack1Value = OSSwapInt16(stack1Value);
memcpy(((uint8_t *)helloData.mutableBytes) + stack1, &stack1Value, 2); memcpy(((uint8_t *)helloData.mutableBytes) + stack1, &stack1Value, 2);
uint8_t s6[15] = { 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08 }; uint8_t s6[117] = { 0x00, 0x17, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x18, 0x00, 0x16, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x02, 0x03, 0x08, 0x05, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x33, 0x74, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x2e, 0x02, 0x68, 0x32, 0x05, 0x68, 0x32, 0x2d, 0x31, 0x36, 0x05, 0x68, 0x32, 0x2d, 0x31, 0x35, 0x05, 0x68, 0x32, 0x2d, 0x31, 0x34, 0x08, 0x73, 0x70, 0x64, 0x79, 0x2f, 0x33, 0x2e, 0x31, 0x06, 0x73, 0x70, 0x64, 0x79, 0x2f, 0x33, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20 };
[helloData appendBytes:s6 length:15]; [helloData appendBytes:s6 length:117];
[helloData appendBytes:&greaseBytes[4] length:1];
[helloData appendBytes:&greaseBytes[4] length:1];
uint8_t s7[77] = { 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x2b, 0x00, 0x29 };
[helloData appendBytes:s7 length:77];
[helloData appendBytes:&greaseBytes[4] length:1];
[helloData appendBytes:&greaseBytes[4] length:1];
uint8_t s8[7] = { 0x00, 0x01, 0x00, 0x00, 0x1d, 0x00, 0x20 };
[helloData appendBytes:s8 length:7];
uint8_t r2[32]; uint8_t r2[32];
result = SecRandomCopyBytes(nil, 32, r2); result = SecRandomCopyBytes(nil, 32, r2);
assert(result == errSecSuccess); assert(result == errSecSuccess);
[helloData appendBytes:r2 length:32]; [helloData appendBytes:r2 length:32];
uint8_t s9[11] = { 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b, 0x00, 0x0b, 0x0a }; uint8_t s9[35] = { 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b, 0x00, 0x09, 0x08, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x15 };
[helloData appendBytes:s9 length:11]; [helloData appendBytes:s9 length:35];
[helloData appendBytes:&greaseBytes[6] length:1];
[helloData appendBytes:&greaseBytes[6] length:1];
uint8_t s10[15] = { 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02 };
[helloData appendBytes:s10 length:15];
[helloData appendBytes:&greaseBytes[3] length:1];
[helloData appendBytes:&greaseBytes[3] length:1];
uint8_t s11[5] = { 0x00, 0x01, 0x00, 0x00, 0x15 };
[helloData appendBytes:s11 length:5];
int stack4 = (int)helloData.length; int stack4 = (int)helloData.length;
[helloData appendBytes:stackZ length:2]; [helloData appendBytes:stackZ length:2];

View File

@ -380,7 +380,7 @@ final class MessageHistoryReadStateTable: Table {
return (nil, false, []) return (nil, false, [])
} }
func applyInteractiveMaxReadIndex(_ messageIndex: MessageIndex, incomingStatsInRange: (MessageId.Namespace, MessageId.Id, MessageId.Id) -> (count: Int, holes: Bool), incomingIndexStatsInRange: (MessageIndex, MessageIndex) -> (count: Int, holes: Bool, readMesageIds: [MessageId]), topMessageId: (MessageId.Id, Bool)?, topMessageIndexByNamespace: (MessageId.Namespace) -> MessageIndex?) -> (combinedState: CombinedPeerReadState?, ApplyInteractiveMaxReadIdResult, readMesageIds: [MessageId]) { func applyInteractiveMaxReadIndex(postbox: Postbox, messageIndex: MessageIndex, incomingStatsInRange: (MessageId.Namespace, MessageId.Id, MessageId.Id) -> (count: Int, holes: Bool), incomingIndexStatsInRange: (MessageIndex, MessageIndex) -> (count: Int, holes: Bool, readMesageIds: [MessageId]), topMessageId: (MessageId.Id, Bool)?, topMessageIndexByNamespace: (MessageId.Namespace) -> MessageIndex?) -> (combinedState: CombinedPeerReadState?, ApplyInteractiveMaxReadIdResult, readMesageIds: [MessageId]) {
if let states = self.get(messageIndex.id.peerId) { if let states = self.get(messageIndex.id.peerId) {
if let state = states.namespaces[messageIndex.id.namespace] { if let state = states.namespaces[messageIndex.id.namespace] {
switch state { switch state {

View File

@ -562,7 +562,7 @@ final class MessageHistoryTable: Table {
return messageIds return messageIds
} }
func applyInteractiveMaxReadIndex(_ messageIndex: MessageIndex, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?]) -> [MessageId] { func applyInteractiveMaxReadIndex(postbox: Postbox, messageIndex: MessageIndex, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?]) -> [MessageId] {
var topMessageId: (MessageId.Id, Bool)? var topMessageId: (MessageId.Id, Bool)?
if let index = self.topIndexEntry(peerId: messageIndex.id.peerId, namespace: messageIndex.id.namespace) { if let index = self.topIndexEntry(peerId: messageIndex.id.peerId, namespace: messageIndex.id.namespace) {
if let message = self.getMessage(index) { if let message = self.getMessage(index) {
@ -572,7 +572,7 @@ final class MessageHistoryTable: Table {
} }
} }
let (combinedState, result, messageIds) = self.readStateTable.applyInteractiveMaxReadIndex(messageIndex, incomingStatsInRange: { namespace, fromId, toId in let (combinedState, result, messageIds) = self.readStateTable.applyInteractiveMaxReadIndex(postbox: postbox, messageIndex: messageIndex, incomingStatsInRange: { namespace, fromId, toId in
return self.messageHistoryIndexTable.incomingMessageCountInRange(messageIndex.id.peerId, namespace: namespace, minId: fromId, maxId: toId) return self.messageHistoryIndexTable.incomingMessageCountInRange(messageIndex.id.peerId, namespace: namespace, minId: fromId, maxId: toId)
}, incomingIndexStatsInRange: { fromIndex, toIndex in }, incomingIndexStatsInRange: { fromIndex, toIndex in
return self.incomingMessageCountInRange(messageIndex.id.peerId, namespace: messageIndex.id.namespace, fromIndex: fromIndex, toIndex: toIndex) return self.incomingMessageCountInRange(messageIndex.id.peerId, namespace: messageIndex.id.namespace, fromIndex: fromIndex, toIndex: toIndex)

View File

@ -1551,13 +1551,25 @@ public final class Postbox {
case let .associated(_, messageId): case let .associated(_, messageId):
if let messageId = messageId, let readState = self.readStateTable.getCombinedState(messageId.peerId), readState.count != 0 { if let messageId = messageId, let readState = self.readStateTable.getCombinedState(messageId.peerId), readState.count != 0 {
if let topMessage = self.messageHistoryTable.topMessage(messageId.peerId) { if let topMessage = self.messageHistoryTable.topMessage(messageId.peerId) {
let _ = self.messageHistoryTable.applyInteractiveMaxReadIndex(topMessage.index, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations) let _ = self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: topMessage.index, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)
} }
} }
default: default:
break break
} }
return self.messageHistoryTable.applyInteractiveMaxReadIndex(messageIndex, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations) let initialCombinedStates = self.readStateTable.getCombinedState(messageIndex.id.peerId)
var resultIds = self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: messageIndex, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)
if let states = initialCombinedStates?.states {
for (namespace, state) in states {
if namespace != messageIndex.id.namespace && state.count != 0 {
if let item = self.messageHistoryTable.fetch(peerId: messageIndex.id.peerId, namespace: namespace, tag: nil, from: MessageIndex(id: MessageId(peerId: messageIndex.id.peerId, namespace: namespace, id: 1), timestamp: messageIndex.timestamp), includeFrom: true, to: MessageIndex.lowerBound(peerId: messageIndex.id.peerId, namespace: namespace), limit: 1).first {
resultIds.append(contentsOf: self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: item.index, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations))
}
}
}
}
return resultIds
} }
func applyMarkUnread(peerId: PeerId, namespace: MessageId.Namespace, value: Bool, interactive: Bool) { func applyMarkUnread(peerId: PeerId, namespace: MessageId.Namespace, value: Bool, interactive: Bool) {

View File

@ -62,6 +62,7 @@ public enum AdminLogEventAction {
case pollStopped(Message) case pollStopped(Message)
case linkedPeerUpdated(previous: Peer?, updated: Peer?) case linkedPeerUpdated(previous: Peer?, updated: Peer?)
case changeGeoLocation(previous: PeerGeoLocation?, updated: PeerGeoLocation?) case changeGeoLocation(previous: PeerGeoLocation?, updated: PeerGeoLocation?)
case updateSlowmode(previous: Int32?, updated: Int32?)
} }
public enum ChannelAdminLogEventError { public enum ChannelAdminLogEventError {
@ -219,7 +220,7 @@ public func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: Pe
case let .channelAdminLogEventActionChangeLocation(prevValue, newValue): case let .channelAdminLogEventActionChangeLocation(prevValue, newValue):
action = .changeGeoLocation(previous: PeerGeoLocation(apiLocation: prevValue), updated: PeerGeoLocation(apiLocation: newValue)) action = .changeGeoLocation(previous: PeerGeoLocation(apiLocation: prevValue), updated: PeerGeoLocation(apiLocation: newValue))
case let .channelAdminLogEventActionToggleSlowMode(prevValue, newValue): case let .channelAdminLogEventActionToggleSlowMode(prevValue, newValue):
break action = .updateSlowmode(previous: prevValue == 0 ? nil : prevValue, updated: newValue == 0 ? nil : newValue)
} }
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
if let action = action { if let action = action {

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "slowmodearrow@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "slowmodearrow@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "slowmode@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "slowmode@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -51,6 +51,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
case permission(PresentationTheme, Int, String, Bool, TelegramChatBannedRightsFlags, Bool?) case permission(PresentationTheme, Int, String, Bool, TelegramChatBannedRightsFlags, Bool?)
case slowmodeHeader(PresentationTheme, String) case slowmodeHeader(PresentationTheme, String)
case slowmode(PresentationTheme, PresentationStrings, Int32) case slowmode(PresentationTheme, PresentationStrings, Int32)
case slowmodeInfo(PresentationTheme, String)
case kicked(PresentationTheme, String, String) case kicked(PresentationTheme, String, String)
case exceptionsHeader(PresentationTheme, String) case exceptionsHeader(PresentationTheme, String)
case add(PresentationTheme, String) case add(PresentationTheme, String)
@ -60,7 +61,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
switch self { switch self {
case .permissionsHeader, .permission: case .permissionsHeader, .permission:
return ChannelPermissionsSection.permissions.rawValue return ChannelPermissionsSection.permissions.rawValue
case .slowmodeHeader, .slowmode: case .slowmodeHeader, .slowmode, .slowmodeInfo:
return ChannelPermissionsSection.slowmode.rawValue return ChannelPermissionsSection.slowmode.rawValue
case .kicked: case .kicked:
return ChannelPermissionsSection.kicked.rawValue return ChannelPermissionsSection.kicked.rawValue
@ -79,12 +80,14 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
return .index(998) return .index(998)
case .slowmode: case .slowmode:
return .index(999) return .index(999)
case .kicked: case .slowmodeInfo:
return .index(1000) return .index(1000)
case .exceptionsHeader: case .kicked:
return .index(1001) return .index(1001)
case .add: case .exceptionsHeader:
return .index(1002) return .index(1002)
case .add:
return .index(1003)
case let .peerItem(_, _, _, _, _, participant, _, _, _, _): case let .peerItem(_, _, _, _, _, participant, _, _, _, _):
return .peer(participant.peer.id) return .peer(participant.peer.id)
} }
@ -116,6 +119,12 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .slowmodeInfo(lhsTheme, lhsValue):
if case let .slowmodeInfo(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue {
return true
} else {
return false
}
case let .kicked(lhsTheme, lhsText, lhsValue): case let .kicked(lhsTheme, lhsText, lhsValue):
if case let .kicked(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .kicked(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true return true
@ -214,6 +223,8 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
return ChatSlowmodeItem(theme: theme, strings: strings, value: value, enabled: true, sectionId: self.section, updated: { value in return ChatSlowmodeItem(theme: theme, strings: strings, value: value, enabled: true, sectionId: self.section, updated: { value in
arguments.updateSlowmode(value) arguments.updateSlowmode(value)
}) })
case let .slowmodeInfo(theme, value):
return ItemListTextItem(theme: theme, text: .plain(value), sectionId: self.section)
case let .kicked(theme, text, value): case let .kicked(theme, text, value):
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openKicked() arguments.openKicked()
@ -395,6 +406,7 @@ private func channelPermissionsControllerEntries(presentationData: PresentationD
entries.append(.slowmodeHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SlowmodeHeader)) entries.append(.slowmodeHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SlowmodeHeader))
entries.append(.slowmode(presentationData.theme, presentationData.strings, state.modifiedSlowmodeTimeout ?? (cachedData.slowModeTimeout ?? 0))) entries.append(.slowmode(presentationData.theme, presentationData.strings, state.modifiedSlowmodeTimeout ?? (cachedData.slowModeTimeout ?? 0)))
entries.append(.slowmodeInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SlowmodeInfo))
entries.append(.kicked(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Removed, cachedData.participantsSummary.kickedCount.flatMap({ $0 == 0 ? "" : "\($0)" }) ?? "")) entries.append(.kicked(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Removed, cachedData.participantsSummary.kickedCount.flatMap({ $0 == 0 ? "" : "\($0)" }) ?? ""))
entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions)) entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions))
@ -438,6 +450,7 @@ public func channelPermissionsController(context: AccountContext, peerId: PeerId
var presentControllerImpl: ((ViewController, Any?) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)? var pushControllerImpl: ((ViewController) -> Void)?
var cancelSlowmodeDraggingImpl: (() -> Void)?
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
@ -624,7 +637,7 @@ public func channelPermissionsController(context: AccountContext, peerId: PeerId
let _ = (peerView.get() let _ = (peerView.get()
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { view in |> deliverOnMainQueue).start(next: { view in
if let channel = view.peers[peerId] as? TelegramChannel, let _ = view.cachedData as? CachedChannelData { if let _ = view.peers[peerId] as? TelegramChannel, let _ = view.cachedData as? CachedChannelData {
updateState { state in updateState { state in
var state = state var state = state
state.modifiedSlowmodeTimeout = value state.modifiedSlowmodeTimeout = value
@ -634,14 +647,52 @@ public func channelPermissionsController(context: AccountContext, peerId: PeerId
if let modifiedSlowmodeTimeout = state.modifiedSlowmodeTimeout { if let modifiedSlowmodeTimeout = state.modifiedSlowmodeTimeout {
updateDefaultRightsDisposable.set(updateChannelSlowModeInteractively(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, peerId: peerId, timeout: modifiedSlowmodeTimeout == 0 ? nil : value).start()) updateDefaultRightsDisposable.set(updateChannelSlowModeInteractively(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, peerId: peerId, timeout: modifiedSlowmodeTimeout == 0 ? nil : value).start())
} }
} else if let group = view.peers[peerId] as? TelegramGroup, let _ = view.cachedData as? CachedGroupData { } else if let _ = view.peers[peerId] as? TelegramGroup, let _ = view.cachedData as? CachedGroupData {
cancelSlowmodeDraggingImpl?()
updateState { state in updateState { state in
var state = state var state = state
state.modifiedSlowmodeTimeout = value state.modifiedSlowmodeTimeout = value
return state return state
} }
/*updateDefaultRightsDisposable.set((updateDefaultChannelMemberBannedRights(account: context.account, peerId: peerId, rights: TelegramChatBannedRights(flags: completeRights(modifiedRightsFlags), untilDate: Int32.max))
|> deliverOnMainQueue).start())*/ let state = stateValue.with { $0 }
guard let modifiedSlowmodeTimeout = state.modifiedSlowmodeTimeout else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let progress = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
presentControllerImpl?(progress, nil)
let signal = convertGroupToSupergroup(account: context.account, peerId: peerId)
|> mapError { _ -> UpdateChannelSlowModeError in
return .generic
}
|> map(Optional.init)
|> `catch` { _ -> Signal<PeerId?, UpdateChannelSlowModeError> in
return .single(nil)
}
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, UpdateChannelSlowModeError> in
guard let upgradedPeerId = upgradedPeerId else {
return .single(nil)
}
return updateChannelSlowModeInteractively(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, peerId: upgradedPeerId, timeout: modifiedSlowmodeTimeout == 0 ? nil : value)
|> mapToSignal { _ -> Signal<PeerId?, UpdateChannelSlowModeError> in
return .complete()
}
|> then(.single(upgradedPeerId))
}
|> deliverOnMainQueue
updateDefaultRightsDisposable.set((signal
|> deliverOnMainQueue).start(next: { [weak progress] peerId in
if let peerId = peerId {
upgradedToSupergroupImpl?(peerId, {})
}
progress?.dismiss()
}, error: { [weak progress] _ in
progress?.dismiss()
}))
} }
}) })
}) })
@ -716,6 +767,16 @@ public func channelPermissionsController(context: AccountContext, peerId: PeerId
(controller.navigationController as? NavigationController)?.pushViewController(c) (controller.navigationController as? NavigationController)?.pushViewController(c)
} }
} }
cancelSlowmodeDraggingImpl = { [weak controller] in
guard let controller = controller else {
return
}
controller.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatSlowmodeItemNode {
itemNode.cancelDragging()
}
}
}
upgradedToSupergroupImpl = { [weak controller] upgradedPeerId, f in upgradedToSupergroupImpl = { [weak controller] upgradedPeerId, f in
guard let controller = controller, let navigationController = controller.navigationController as? NavigationController else { guard let controller = controller, let navigationController = controller.navigationController as? NavigationController else {
return return

View File

@ -399,7 +399,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
}, enqueueMessage: { message in }, enqueueMessage: { message in
self?.sendMessages([message]) self?.sendMessages([message])
}, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in }, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in
self?.controllerInteraction?.sendSticker(fileReference, false, sourceNode, sourceRect) return self?.controllerInteraction?.sendSticker(fileReference, false, sourceNode, sourceRect) ?? false
} : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in } : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in
if let strongSelf = self { if let strongSelf = self {
strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { entry in strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { entry in
@ -578,12 +578,12 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
strongSelf.sendMessages([.message(text: text, attributes: attributes, mediaReference: nil, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)]) strongSelf.sendMessages([.message(text: text, attributes: attributes, mediaReference: nil, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
}, sendSticker: { [weak self] fileReference, clearInput, sourceNode, sourceRect in }, sendSticker: { [weak self] fileReference, clearInput, sourceNode, sourceRect in
guard let strongSelf = self else { guard let strongSelf = self else {
return return false
} }
if let _ = strongSelf.presentationInterfaceState.slowmodeState { if let _ = strongSelf.presentationInterfaceState.slowmodeState {
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceNode, sourceRect) strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceNode, sourceRect)
return return false
} }
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
@ -609,11 +609,12 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
} }
}) })
strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: fileReference.abstract, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)]) strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: fileReference.abstract, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
return true
}, sendGif: { [weak self] fileReference, sourceNode, sourceRect in }, sendGif: { [weak self] fileReference, sourceNode, sourceRect in
if let strongSelf = self { if let strongSelf = self {
if let _ = strongSelf.presentationInterfaceState.slowmodeState { if let _ = strongSelf.presentationInterfaceState.slowmodeState {
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceNode, sourceRect) strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceNode, sourceRect)
return return false
} }
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
@ -630,6 +631,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
}) })
strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: fileReference.abstract, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)]) strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: fileReference.abstract, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
} }
return true
}, requestMessageActionCallback: { [weak self] messageId, data, isGame in }, requestMessageActionCallback: { [weak self] messageId, data, isGame in
if let strongSelf = self { if let strongSelf = self {
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
@ -2049,7 +2051,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
var slowmodeState: ChatSlowmodeState? var slowmodeState: ChatSlowmodeState?
if let cachedData = combinedInitialData.cachedData as? CachedChannelData { if let cachedData = combinedInitialData.cachedData as? CachedChannelData {
pinnedMessageId = cachedData.pinnedMessageId pinnedMessageId = cachedData.pinnedMessageId
if let timeout = cachedData.slowModeTimeout { if let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout {
if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: strongSelf.context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) { if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: strongSelf.context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) {
slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp)) slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp))
} }
@ -2163,8 +2165,14 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
let hasPendingMessages: Signal<Bool, NoError> let hasPendingMessages: Signal<Bool, NoError>
if case let .peer(peerId) = self.chatLocation { if case let .peer(peerId) = self.chatLocation {
hasPendingMessages = self.context.account.pendingMessageManager.hasPendingMessages hasPendingMessages = self.context.account.pendingMessageManager.hasPendingMessages
|> map { peerIds -> Bool in |> mapToSignal { peerIds -> Signal<Bool, NoError> in
return peerIds.contains(peerId) let value = peerIds.contains(peerId)
if value {
return .single(true)
} else {
return .single(false)
|> delay(0.1, queue: .mainQueue())
}
} }
|> distinctUntilChanged |> distinctUntilChanged
} else { } else {
@ -2182,7 +2190,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
var slowmodeState: ChatSlowmodeState? var slowmodeState: ChatSlowmodeState?
if let cachedData = cachedData as? CachedChannelData { if let cachedData = cachedData as? CachedChannelData {
pinnedMessageId = cachedData.pinnedMessageId pinnedMessageId = cachedData.pinnedMessageId
if let timeout = cachedData.slowModeTimeout { if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout {
if hasPendingMessages { if hasPendingMessages {
slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .pendingMessages) slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .pendingMessages)
} else if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: strongSelf.context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) { } else if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: strongSelf.context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) {
@ -2920,8 +2928,17 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation {
let _ = togglePeerMuted(account: strongSelf.context.account, peerId: peerId).start() let _ = togglePeerMuted(account: strongSelf.context.account, peerId: peerId).start()
} }
}, sendContextResult: { [weak self] results, result in }, sendContextResult: { [weak self] results, result, node, rect in
self?.enqueueChatContextResult(results, result) guard let strongSelf = self else {
return false
}
if let _ = strongSelf.presentationInterfaceState.slowmodeState {
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(node, rect)
return false
}
strongSelf.enqueueChatContextResult(results, result)
return true
}, sendBotCommand: { [weak self] botPeer, command in }, sendBotCommand: { [weak self] botPeer, command in
if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) { if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) {
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
@ -3208,23 +3225,9 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
} }
}, sendSticker: { [weak self] file, sourceNode, sourceRect in }, sendSticker: { [weak self] file, sourceNode, sourceRect in
if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) { if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) {
strongSelf.controllerInteraction?.sendSticker(file, true, sourceNode, sourceRect) return strongSelf.controllerInteraction?.sendSticker(file, true, sourceNode, sourceRect) ?? false
/*strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ } else {
if let strongSelf = self { return false
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
$0.updatedInterfaceState {
$0.withUpdatedReplyMessageId(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreview(nil)
}.updatedInputMode { current in
if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil {
return .media(mode: mode, expanded: nil)
}
return current
}
})
}
})
strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: file.abstract, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])*/
} }
}, unblockPeer: { [weak self] in }, unblockPeer: { [weak self] in
self?.unblockPeer() self?.unblockPeer()
@ -3598,17 +3601,26 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
} }
let rect = node.view.convert(nodeRect, to: strongSelf.view) let rect = node.view.convert(nodeRect, to: strongSelf.view)
if let slowmodeTooltipController = strongSelf.slowmodeTooltipController { if let slowmodeTooltipController = strongSelf.slowmodeTooltipController {
if let arguments = slowmodeTooltipController.presentationArguments as? TooltipControllerPresentationArguments, case let .node(f) = arguments.sourceAndRect, let (previousNode, previousRect) = f() {
if previousNode === strongSelf.chatDisplayNode && previousRect == rect {
return
}
}
strongSelf.slowmodeTooltipController = nil strongSelf.slowmodeTooltipController = nil
slowmodeTooltipController.dismiss() slowmodeTooltipController.dismiss()
} }
let slowmodeTooltipController = ChatSlowmodeHintController(strings: strongSelf.presentationData.strings, slowmodeState: slowmodeState) let slowmodeTooltipController = ChatSlowmodeHintController(strings: strongSelf.presentationData.strings, slowmodeState:
strongSelf.slowmodeTooltipController = slowmodeTooltipController slowmodeState)
strongSelf.present(slowmodeTooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { slowmodeTooltipController.presentationArguments = TooltipControllerPresentationArguments(sourceNodeAndRect: {
if let strongSelf = self { if let strongSelf = self {
return (strongSelf.chatDisplayNode, rect) return (strongSelf.chatDisplayNode, rect)
} }
return nil return nil
})) })
strongSelf.slowmodeTooltipController = slowmodeTooltipController
strongSelf.window?.presentInGlobalOverlay(slowmodeTooltipController)
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get())) }, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get()))
switch self.chatLocation { switch self.chatLocation {
@ -5254,7 +5266,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
self.recorderFeedback?.prepareImpact(.light) self.recorderFeedback?.prepareImpact(.light)
} }
self.videoRecorder.set(.single(legacyInstantVideoController(theme: self.presentationData.theme, panelFrame: currentInputPanelFrame, context: self.context, peerId: peerId, send: { [weak self] message in self.videoRecorder.set(.single(legacyInstantVideoController(theme: self.presentationData.theme, panelFrame: currentInputPanelFrame, context: self.context, peerId: peerId, slowmodeState: self.presentationInterfaceState.slowmodeState, send: { [weak self] message in
if let strongSelf = self { if let strongSelf = self {
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
@ -5267,20 +5279,22 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
let updatedMessage = message.withUpdatedReplyToMessageId(replyMessageId) let updatedMessage = message.withUpdatedReplyToMessageId(replyMessageId)
strongSelf.sendMessages([updatedMessage]) strongSelf.sendMessages([updatedMessage])
} }
}, displaySlowmodeTooltip: { [weak self] node, rect in
self?.interfaceInteraction?.displaySlowmodeTooltip(node, rect)
}))) })))
} }
} }
} }
private func dismissMediaRecorder(_ action: ChatFinishMediaRecordingAction) { private func dismissMediaRecorder(_ action: ChatFinishMediaRecordingAction) {
if let audioRecorderValue = self.audioRecorderValue {
audioRecorderValue.stop()
var updatedAction = action var updatedAction = action
if let _ = self.presentationInterfaceState.slowmodeState { if let _ = self.presentationInterfaceState.slowmodeState {
updatedAction = .preview updatedAction = .preview
} }
if let audioRecorderValue = self.audioRecorderValue {
audioRecorderValue.stop()
switch updatedAction { switch updatedAction {
case .dismiss: case .dismiss:
break break
@ -5340,14 +5354,22 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
} }
self.audioRecorder.set(.single(nil)) self.audioRecorder.set(.single(nil))
} else if let videoRecorderValue = self.videoRecorderValue { } else if let videoRecorderValue = self.videoRecorderValue {
if case .send = action { if case .send = updatedAction {
videoRecorderValue.completeVideo() videoRecorderValue.completeVideo()
self.videoRecorder.set(.single(nil)) self.videoRecorder.set(.single(nil))
} else {
if videoRecorderValue.stopVideo() {
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
$0.updatedInputTextPanelState { panelState in
return panelState.withUpdatedMediaRecordingState(.video(status: .editing, isLocked: false))
}
})
} else { } else {
self.videoRecorder.set(.single(nil)) self.videoRecorder.set(.single(nil))
} }
} }
} }
}
private func stopMediaRecorder() { private func stopMediaRecorder() {
if let audioRecorderValue = self.audioRecorderValue { if let audioRecorderValue = self.audioRecorderValue {
@ -6283,7 +6305,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
break break
} }
}, sendSticker: { [weak self] f, sourceNode, sourceRect in }, sendSticker: { [weak self] f, sourceNode, sourceRect in
self?.interfaceInteraction?.sendSticker(f, sourceNode, sourceRect) return self?.interfaceInteraction?.sendSticker(f, sourceNode, sourceRect) ?? false
}, present: { [weak self] c, a in }, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a) self?.present(c, in: .window(.root), with: a)
}, dismissInput: { [weak self] in }, dismissInput: { [weak self] in

View File

@ -78,8 +78,8 @@ public final class ChatControllerInteraction {
let clickThroughMessage: () -> Void let clickThroughMessage: () -> Void
let toggleMessagesSelection: ([MessageId], Bool) -> Void let toggleMessagesSelection: ([MessageId], Bool) -> Void
let sendMessage: (String) -> Void let sendMessage: (String) -> Void
let sendSticker: (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Void let sendSticker: (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool
let sendGif: (FileMediaReference, ASDisplayNode, CGRect) -> Void let sendGif: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool) -> Void let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool) -> Void
let requestMessageActionUrlAuth: (String, MessageId, Int32) -> Void let requestMessageActionUrlAuth: (String, MessageId, Int32) -> Void
let activateSwitchInline: (PeerId?, String) -> Void let activateSwitchInline: (PeerId?, String) -> Void
@ -123,7 +123,7 @@ public final class ChatControllerInteraction {
var stickerSettings: ChatInterfaceStickerSettings var stickerSettings: ChatInterfaceStickerSettings
var searchTextHighightState: String? var searchTextHighightState: String?
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Void, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) { init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
self.openMessage = openMessage self.openMessage = openMessage
self.openPeer = openPeer self.openPeer = openPeer
self.openPeerMention = openPeerMention self.openPeerMention = openPeerMention
@ -176,7 +176,7 @@ public final class ChatControllerInteraction {
static var `default`: ChatControllerInteraction { static var `default`: ChatControllerInteraction {
return ChatControllerInteraction(openMessage: { _, _ in return ChatControllerInteraction(openMessage: { _, _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in }, sendGif: { _, _, _ in }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in }, navigationController: { }, presentController: { _, _ in }, navigationController: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in

View File

@ -842,12 +842,14 @@ final class ChatMediaInputNode: ChatInputNode {
if let strongSelf = self { if let strongSelf = self {
var menuItems: [PeekControllerMenuItem] = [] var menuItems: [PeekControllerMenuItem] = []
menuItems = [ menuItems = [
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { node, rect in
if let strongSelf = self { if let strongSelf = self {
strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false, itemNode, itemNode.bounds) return strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false, node, rect)
} else {
return false
} }
}), }),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
if let strongSelf = self { if let strongSelf = self {
if isStarred { if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start() let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -855,8 +857,9 @@ final class ChatMediaInputNode: ChatInputNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start() let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
} }
} }
return true
}), }),
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
if let strongSelf = self { if let strongSelf = self {
loop: for attribute in item.file.attributes { loop: for attribute in item.file.attributes {
switch attribute { switch attribute {
@ -865,7 +868,9 @@ final class ChatMediaInputNode: ChatInputNode {
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: strongSelf.controllerInteraction.navigationController()) let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: strongSelf.controllerInteraction.navigationController())
controller.sendSticker = { file, sourceNode, sourceRect in controller.sendSticker = { file, sourceNode, sourceRect in
if let strongSelf = self { if let strongSelf = self {
strongSelf.controllerInteraction.sendSticker(file, false, sourceNode, sourceRect) return strongSelf.controllerInteraction.sendSticker(file, false, sourceNode, sourceRect)
} else {
return false
} }
} }
@ -878,8 +883,9 @@ final class ChatMediaInputNode: ChatInputNode {
} }
} }
} }
return true
}), }),
PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: {}) PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { _, _ in return true })
] ]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems)) return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems))
} else { } else {
@ -888,15 +894,18 @@ final class ChatMediaInputNode: ChatInputNode {
} }
} else if let file = item as? FileMediaReference { } else if let file = item as? FileMediaReference {
return .single((strongSelf, ChatContextResultPeekContent(account: strongSelf.context.account, contextResult: .internalReference(queryId: 0, id: "", type: "gif", title: nil, description: nil, image: nil, file: file.media, message: .auto(caption: "", entities: nil, replyMarkup: nil)), menu: [ return .single((strongSelf, ChatContextResultPeekContent(account: strongSelf.context.account, contextResult: .internalReference(queryId: 0, id: "", type: "gif", title: nil, description: nil, image: nil, file: file.media, message: .auto(caption: "", entities: nil, replyMarkup: nil)), menu: [
PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in
if let strongSelf = self { if let strongSelf = self {
strongSelf.controllerInteraction.sendGif(file, itemNode, itemNode.bounds) return strongSelf.controllerInteraction.sendGif(file, node, rect)
} else {
return false
} }
}), }),
PeekControllerMenuItem(title: strongSelf.strings.Preview_SaveGif, color: .accent, action: { PeekControllerMenuItem(title: strongSelf.strings.Preview_SaveGif, color: .accent, action: { _, _ in
if let strongSelf = self { if let strongSelf = self {
let _ = addSavedGif(postbox: strongSelf.context.account.postbox, fileReference: file).start() let _ = addSavedGif(postbox: strongSelf.context.account.postbox, fileReference: file).start()
} }
return true
}) })
]))) ])))
} }
@ -914,15 +923,18 @@ final class ChatMediaInputNode: ChatInputNode {
if let pane = pane as? ChatMediaInputGifPane { if let pane = pane as? ChatMediaInputGifPane {
if let (file, rect) = pane.fileAt(point: point.offsetBy(dx: -pane.frame.minX, dy: -pane.frame.minY)) { if let (file, rect) = pane.fileAt(point: point.offsetBy(dx: -pane.frame.minX, dy: -pane.frame.minY)) {
return .single((strongSelf, ChatContextResultPeekContent(account: strongSelf.context.account, contextResult: .internalReference(queryId: 0, id: "", type: "gif", title: nil, description: nil, image: nil, file: file.media, message: .auto(caption: "", entities: nil, replyMarkup: nil)), menu: [ return .single((strongSelf, ChatContextResultPeekContent(account: strongSelf.context.account, contextResult: .internalReference(queryId: 0, id: "", type: "gif", title: nil, description: nil, image: nil, file: file.media, message: .auto(caption: "", entities: nil, replyMarkup: nil)), menu: [
PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in
if let strongSelf = self { if let strongSelf = self {
strongSelf.controllerInteraction.sendGif(file, pane, rect) return strongSelf.controllerInteraction.sendGif(file, node, rect)
} else {
return false
} }
}), }),
PeekControllerMenuItem(title: strongSelf.strings.Common_Delete, color: .destructive, action: { PeekControllerMenuItem(title: strongSelf.strings.Common_Delete, color: .destructive, action: { _, _ in
if let strongSelf = self { if let strongSelf = self {
let _ = removeSavedGif(postbox: strongSelf.context.account.postbox, mediaId: file.media.fileId).start() let _ = removeSavedGif(postbox: strongSelf.context.account.postbox, mediaId: file.media.fileId).start()
} }
return true
}) })
]))) ])))
} }
@ -943,12 +955,14 @@ final class ChatMediaInputNode: ChatInputNode {
if let strongSelf = self { if let strongSelf = self {
var menuItems: [PeekControllerMenuItem] = [] var menuItems: [PeekControllerMenuItem] = []
menuItems = [ menuItems = [
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { node, rect in
if let strongSelf = self { if let strongSelf = self {
strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false, itemNode, itemNode.bounds) return strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false, node, rect)
} else {
return false
} }
}), }),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
if let strongSelf = self { if let strongSelf = self {
if isStarred { if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start() let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -956,8 +970,9 @@ final class ChatMediaInputNode: ChatInputNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start() let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
} }
} }
return true
}), }),
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
if let strongSelf = self { if let strongSelf = self {
loop: for attribute in item.file.attributes { loop: for attribute in item.file.attributes {
switch attribute { switch attribute {
@ -966,7 +981,9 @@ final class ChatMediaInputNode: ChatInputNode {
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: strongSelf.controllerInteraction.navigationController()) let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: strongSelf.controllerInteraction.navigationController())
controller.sendSticker = { file, sourceNode, sourceRect in controller.sendSticker = { file, sourceNode, sourceRect in
if let strongSelf = self { if let strongSelf = self {
strongSelf.controllerInteraction.sendSticker(file, false, sourceNode, sourceRect) return strongSelf.controllerInteraction.sendSticker(file, false, sourceNode, sourceRect)
} else {
return false
} }
} }
@ -979,8 +996,9 @@ final class ChatMediaInputNode: ChatInputNode {
} }
} }
} }
return true
}), }),
PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: {}) PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { _, _ in return true })
] ]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems)) return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else { } else {

View File

@ -182,7 +182,9 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane {
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: .id(id: info.id.id, accessHash: info.accessHash), parentNavigationController: strongSelf.controllerInteraction.navigationController()) let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: .id(id: info.id.id, accessHash: info.accessHash), parentNavigationController: strongSelf.controllerInteraction.navigationController())
controller.sendSticker = { fileReference, sourceNode, sourceRect in controller.sendSticker = { fileReference, sourceNode, sourceRect in
if let strongSelf = self { if let strongSelf = self {
strongSelf.controllerInteraction.sendSticker(fileReference, false, sourceNode, sourceRect) return strongSelf.controllerInteraction.sendSticker(fileReference, false, sourceNode, sourceRect)
} else {
return false
} }
} }
strongSelf.controllerInteraction.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) strongSelf.controllerInteraction.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))

View File

@ -68,7 +68,7 @@ final class ChatPanelInterfaceInteraction {
let navigateToChat: (PeerId) -> Void let navigateToChat: (PeerId) -> Void
let openPeerInfo: () -> Void let openPeerInfo: () -> Void
let togglePeerNotifications: () -> Void let togglePeerNotifications: () -> Void
let sendContextResult: (ChatContextResultCollection, ChatContextResult) -> Void let sendContextResult: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool
let sendBotCommand: (Peer, String) -> Void let sendBotCommand: (Peer, String) -> Void
let sendBotStart: (String?) -> Void let sendBotStart: (String?) -> Void
let botSwitchChatWithPayload: (PeerId, String) -> Void let botSwitchChatWithPayload: (PeerId, String) -> Void
@ -82,7 +82,7 @@ final class ChatPanelInterfaceInteraction {
let displayVideoUnmuteTip: (CGPoint?) -> Void let displayVideoUnmuteTip: (CGPoint?) -> Void
let switchMediaRecordingMode: () -> Void let switchMediaRecordingMode: () -> Void
let setupMessageAutoremoveTimeout: () -> Void let setupMessageAutoremoveTimeout: () -> Void
let sendSticker: (FileMediaReference, ASDisplayNode, CGRect) -> Void let sendSticker: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
let unblockPeer: () -> Void let unblockPeer: () -> Void
let pinMessage: (MessageId) -> Void let pinMessage: (MessageId) -> Void
let unpinMessage: () -> Void let unpinMessage: () -> Void
@ -108,7 +108,7 @@ final class ChatPanelInterfaceInteraction {
let displaySlowmodeTooltip: (ASDisplayNode, CGRect) -> Void let displaySlowmodeTooltip: (ASDisplayNode, CGRect) -> Void
let statuses: ChatPanelInterfaceInteractionStatuses? let statuses: ChatPanelInterfaceInteractionStatuses?
init(setupReplyMessage: @escaping (MessageId) -> Void, setupEditMessage: @escaping (MessageId?) -> Void, beginMessageSelection: @escaping ([MessageId]) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message]) -> Void, deleteMessages: @escaping ([Message]) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult) -> Void, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Void, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) { init(setupReplyMessage: @escaping (MessageId) -> Void, setupEditMessage: @escaping (MessageId?) -> Void, beginMessageSelection: @escaping ([MessageId]) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message]) -> Void, deleteMessages: @escaping ([Message]) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) {
self.setupReplyMessage = setupReplyMessage self.setupReplyMessage = setupReplyMessage
self.setupEditMessage = setupEditMessage self.setupEditMessage = setupEditMessage
self.beginMessageSelection = beginMessageSelection self.beginMessageSelection = beginMessageSelection

View File

@ -23,6 +23,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
private var currentMessage: Message? private var currentMessage: Message?
private var previousMediaReference: AnyMediaReference? private var previousMediaReference: AnyMediaReference?
private let fetchDisposable = MetaDisposable()
private let queue = Queue() private let queue = Queue()
init(context: AccountContext) { init(context: AccountContext) {
@ -89,6 +91,10 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
self.addSubnode(self.separatorNode) self.addSubnode(self.separatorNode)
} }
deinit {
self.fetchDisposable.dispose()
}
private var theme: PresentationTheme? private var theme: PresentationTheme?
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat {
@ -204,12 +210,16 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
} }
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedFetchMediaSignal: Signal<FetchResourceSourceType, FetchResourceError>?
if mediaUpdated { if mediaUpdated {
if let updatedMediaReference = updatedMediaReference, imageDimensions != nil { if let updatedMediaReference = updatedMediaReference, imageDimensions != nil {
if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) { if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) {
updateImageSignal = chatMessagePhotoThumbnail(account: context.account, photoReference: imageReference) updateImageSignal = chatMessagePhotoThumbnail(account: context.account, photoReference: imageReference)
} else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) { } else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) {
if fileReference.media.isVideo { if fileReference.media.isAnimatedSticker {
updateImageSignal = chatMessageAnimatedSticker(postbox: context.account.postbox, file: fileReference.media, small: false, size: CGSize(width: 160.0, height: 160.0))
updatedFetchMediaSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: fileReference.resourceReference(fileReference.media.resource))
} else if fileReference.media.isVideo {
updateImageSignal = chatMessageVideoThumbnail(account: context.account, fileReference: fileReference) updateImageSignal = chatMessageVideoThumbnail(account: context.account, fileReference: fileReference)
} else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
updateImageSignal = chatWebpageSnippetFile(account: context.account, fileReference: fileReference, representation: iconImageRepresentation) updateImageSignal = chatWebpageSnippetFile(account: context.account, fileReference: fileReference, representation: iconImageRepresentation)
@ -247,6 +257,9 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
if let updateImageSignal = updateImageSignal { if let updateImageSignal = updateImageSignal {
strongSelf.imageNode.setSignal(updateImageSignal) strongSelf.imageNode.setSignal(updateImageSignal)
} }
if let updatedFetchMediaSignal = updatedFetchMediaSignal {
strongSelf.fetchDisposable.set(updatedFetchMediaSignal.start())
}
} }
} }
} }

View File

@ -64,7 +64,8 @@ final class ChatRecentActionsController: TelegramController {
}, navigateToChat: { _ in }, navigateToChat: { _ in
}, openPeerInfo: { }, openPeerInfo: {
}, togglePeerNotifications: { }, togglePeerNotifications: {
}, sendContextResult: { _, _ in }, sendContextResult: { _, _, _, _ in
return false
}, sendBotCommand: { _, _ in }, sendBotCommand: { _, _ in
}, sendBotStart: { _ in }, sendBotStart: { _ in
}, botSwitchChatWithPayload: { _, _ in }, botSwitchChatWithPayload: { _, _ in
@ -79,6 +80,7 @@ final class ChatRecentActionsController: TelegramController {
}, switchMediaRecordingMode: { }, switchMediaRecordingMode: {
}, setupMessageAutoremoveTimeout: { }, setupMessageAutoremoveTimeout: {
}, sendSticker: { _, _, _ in }, sendSticker: { _, _, _ in
return false
}, unblockPeer: { }, unblockPeer: {
}, pinMessage: { _ in }, pinMessage: { _ in
}, unpinMessage: { }, unpinMessage: {

View File

@ -181,7 +181,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self?.openPeerMention(name) self?.openPeerMention(name)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame in }, openMessageContextMenu: { [weak self] message, selectAll, node, frame in
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame) self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame)
}, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in }, sendGif: { _, _, _ in }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _ in
self?.openUrl(url) self?.openUrl(url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {

View File

@ -3,6 +3,7 @@ import UIKit
import Display import Display
import TelegramCore import TelegramCore
import Postbox import Postbox
import TelegramPresentationData
enum ChatRecentActionsEntryContentIndex: Int32 { enum ChatRecentActionsEntryContentIndex: Int32 {
case header = 0 case header = 0
@ -955,6 +956,36 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(rank: nil, isContact: false))) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(rank: nil, isContact: false)))
} }
case let .updateSlowmode(_, newValue):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
if let peer = self.entry.peers[self.entry.event.peerId] {
author = peer
peers[peer.id] = peer
}
var text: String = ""
var entities: [MessageTextEntity] = []
if let newValue = newValue {
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_SetSlowmode(author?.displayTitle ?? "", shortTimeIntervalString(strings: self.presentationData.strings, value: newValue)), generateEntities: { index in
if index == 0, let author = author {
return [.TextMention(peerId: author.id)]
}
return []
}, to: &text, entities: &entities)
} else {
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_DisabledSlowmode(author?.displayTitle ?? ""), generateEntities: { index in
if index == 0, let author = author {
return [.TextMention(peerId: author.id)]
}
return []
}, to: &text, entities: &entities)
}
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(rank: nil, isContact: false)))
} }
} }
} }

View File

@ -73,7 +73,7 @@ final class ChatSendButtonRadialStatusNode: ASDisplayNode {
let pathDiameter = bounds.size.width - lineWidth let pathDiameter = bounds.size.width - lineWidth
let path = UIBezierPath(arcCenter: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise:true) let path = UIBezierPath(arcCenter: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.lineWidth = lineWidth path.lineWidth = lineWidth
path.lineCapStyle = .round path.lineCapStyle = .round
path.stroke() path.stroke()
@ -98,34 +98,98 @@ final class ChatSendButtonRadialStatusNode: ASDisplayNode {
self?.updateProgress() self?.updateProgress()
}, queue: .mainQueue()) }, queue: .mainQueue())
self.updateTimer?.start() self.updateTimer?.start()
/*if progress.isNaN || !progress.isFinite {
self.pop_removeAnimation(forKey: "progress")
self.effectiveProgress = 0.0
} else { } else {
self.pop_removeAnimation(forKey: "progress")
let animation = POPBasicAnimation()
animation.property = POPAnimatableProperty.property(withName: "progress", initializer: { property in
property?.readBlock = { node, values in
values?.pointee = (node as! ChatSendButtonRadialStatusNode).effectiveProgress
}
property?.writeBlock = { node, values in
(node as! ChatSendButtonRadialStatusNode).effectiveProgress = values!.pointee
}
property?.threshold = 0.01
}) as? POPAnimatableProperty
animation.fromValue = progress as NSNumber
animation.toValue = 1.0 as NSNumber
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = max(0.0, duration - timestamp) / baseRate
animation.beginTime = CACurrentMediaTime()
self.pop_add(animation, forKey: "progress")
}*/
} else {
//self.pop_removeAnimation(forKey: "progress")
self.effectiveProgress = 0.0 self.effectiveProgress = 0.0
self.updateTimer?.invalidate() self.updateTimer?.invalidate()
self.updateTimer = nil self.updateTimer = nil
} }
} }
} }
final class ChatSendButtonRadialStatusView: UIView {
private let color: UIColor
private var effectiveProgress: CGFloat = 0.0 {
didSet {
self.setNeedsDisplay()
}
}
var slowmodeState: ChatSlowmodeState? = nil {
didSet {
if self.slowmodeState != oldValue {
self.updateProgress()
}
}
}
private var updateTimer: SwiftSignalKit.Timer?
init(color: UIColor) {
self.color = color
super.init(frame: CGRect())
self.isUserInteractionEnabled = false
self.isOpaque = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.updateTimer?.invalidate()
}
override func draw(_ rect: CGRect) {
if rect.isEmpty {
return
}
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(self.color.cgColor)
var progress = self.effectiveProgress
let startAngle = -CGFloat.pi / 2.0
let endAngle = CGFloat(progress) * 2.0 * CGFloat.pi + startAngle
progress = min(1.0, progress)
let lineWidth: CGFloat = 2.0
let pathDiameter = bounds.size.width - lineWidth
let path = UIBezierPath(arcCenter: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.lineWidth = lineWidth
path.lineCapStyle = .round
path.stroke()
}
private func updateProgress() {
if let slowmodeState = self.slowmodeState {
let progress: CGFloat
switch slowmodeState.variant {
case .pendingMessages:
progress = 1.0
case let .timestamp(validUntilTimestamp):
let timestamp = CGFloat(Date().timeIntervalSince1970)
let relativeTimestamp = CGFloat(validUntilTimestamp) - timestamp
progress = max(0.0, min(1.0, CGFloat(relativeTimestamp / CGFloat(slowmodeState.timeout))))
}
self.effectiveProgress = progress
self.updateTimer?.invalidate()
self.updateTimer = SwiftSignalKit.Timer(timeout: 1.0 / 60.0, repeat: false, completion: { [weak self] in
self?.updateProgress()
}, queue: .mainQueue())
self.updateTimer?.start()
} else {
self.effectiveProgress = 0.0
self.updateTimer?.invalidate()
self.updateTimer = nil
}
}
}

View File

@ -23,7 +23,7 @@ final class ChatSlowmodeHintController: TooltipController {
init(strings: PresentationStrings, slowmodeState: ChatSlowmodeState) { init(strings: PresentationStrings, slowmodeState: ChatSlowmodeState) {
self.strings = strings self.strings = strings
self.slowmodeState = slowmodeState self.slowmodeState = slowmodeState
super.init(content: .text(timeoutValue(strings: strings, slowmodeState: slowmodeState)), timeout: 2.0, dismissByTapOutside: true) super.init(content: .text(timeoutValue(strings: strings, slowmodeState: slowmodeState)), timeout: 2.0, dismissByTapOutside: false, dismissByTapOutsideSource: true)
} }
required init(coder aDecoder: NSCoder) { required init(coder aDecoder: NSCoder) {

View File

@ -125,6 +125,8 @@ class ChatSlowmodeItemNode: ListViewItemNode {
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
self.view.disablesInteractiveTransitionGestureRecognizer = true
let sliderView = TGPhotoEditorSliderView() let sliderView = TGPhotoEditorSliderView()
sliderView.enablePanHandling = true sliderView.enablePanHandling = true
sliderView.trackCornerRadius = 1.0 sliderView.trackCornerRadius = 1.0
@ -146,6 +148,7 @@ class ChatSlowmodeItemNode: ListViewItemNode {
} }
sliderView.value = CGFloat(value) sliderView.value = CGFloat(value)
self.reportedValue = item.value
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
sliderView.backColor = item.theme.list.disclosureArrowColor sliderView.backColor = item.theme.list.disclosureArrowColor
sliderView.startColor = item.theme.list.disclosureArrowColor sliderView.startColor = item.theme.list.disclosureArrowColor
@ -183,13 +186,9 @@ class ChatSlowmodeItemNode: ListViewItemNode {
let valueString: String let valueString: String
if value == 0 { if value == 0 {
valueString = "Off" valueString = item.strings.Profile_MessageLifetimeForever
} else if value < 60 {
valueString = "\(value)"
} else if value < 60 * 60 {
valueString = "\(value / 60)m"
} else { } else {
valueString = "\(value / (60 * 60))h" valueString = shortTimeIntervalString(strings: item.strings, value: value)
} }
let (textLayout, textApply) = makeTextLayouts[i](TextNodeLayoutArguments(attributedString: NSAttributedString(string: valueString, font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) let (textLayout, textApply) = makeTextLayouts[i](TextNodeLayoutArguments(attributedString: NSAttributedString(string: valueString, font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
textLayoutAndApply.append((textLayout, textApply)) textLayoutAndApply.append((textLayout, textApply))
@ -300,5 +299,9 @@ class ChatSlowmodeItemNode: ListViewItemNode {
item.updated(value) item.updated(value)
} }
} }
func cancelDragging() {
self.sliderView?.cancelTracking(with: nil)
}
} }

View File

@ -38,7 +38,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: size))
if let slowmodeState = interfaceState.slowmodeState { if let slowmodeState = interfaceState.slowmodeState, interfaceState.editMessageState == nil {
let sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode let sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode
if let current = self.sendButtonRadialStatusNode { if let current = self.sendButtonRadialStatusNode {
sendButtonRadialStatusNode = current sendButtonRadialStatusNode = current
@ -51,7 +51,12 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
transition.updateSublayerTransformScale(layer: self.sendButton.layer, scale: CGPoint(x: 0.7575, y: 0.7575)) transition.updateSublayerTransformScale(layer: self.sendButton.layer, scale: CGPoint(x: 0.7575, y: 0.7575))
sendButtonRadialStatusNode.frame = CGRect(origin: CGPoint(x: self.sendButton.frame.midX - 33.0 / 2.0, y: self.sendButton.frame.midY - 33.0 / 2.0), size: CGSize(width: 33.0, height: 33.0)) let defaultSendButtonSize: CGFloat = 25.0
let defaultOriginX = floorToScreenPixels((self.sendButton.bounds.width - defaultSendButtonSize) / 2.0)
let defaultOriginY = floorToScreenPixels((self.sendButton.bounds.height - defaultSendButtonSize) / 2.0)
let radialStatusFrame = CGRect(origin: CGPoint(x: defaultOriginX - 4.0, y: defaultOriginY - 4.0), size: CGSize(width: 33.0, height: 33.0))
sendButtonRadialStatusNode.frame = radialStatusFrame
sendButtonRadialStatusNode.slowmodeState = slowmodeState sendButtonRadialStatusNode.slowmodeState = slowmodeState
} else { } else {
if let sendButtonRadialStatusNode = self.sendButtonRadialStatusNode { if let sendButtonRadialStatusNode = self.sendButtonRadialStatusNode {

View File

@ -1088,11 +1088,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
inputHasText = true inputHasText = true
} }
if interfaceState.slowmodeState != nil || interfaceState.inputTextPanelState.contextPlaceholder != nil { if (interfaceState.slowmodeState != nil && interfaceState.editMessageState == nil) || interfaceState.inputTextPanelState.contextPlaceholder != nil {
self.textPlaceholderNode.isHidden = true self.textPlaceholderNode.isHidden = true
self.slowmodePlaceholderNode?.isHidden = inputHasText self.slowmodePlaceholderNode?.isHidden = inputHasText
} else { } else {
self.textPlaceholderNode.isHidden = inputHasText self.textPlaceholderNode.isHidden = inputHasText
self.slowmodePlaceholderNode?.isHidden = true
} }
transition.updateFrame(node: self.textPlaceholderNode, frame: CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + self.textInputViewRealInsets.top + audioRecordingItemsVerticalOffset + UIScreenPixel), size: self.textPlaceholderNode.frame.size)) transition.updateFrame(node: self.textPlaceholderNode, frame: CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + self.textInputViewRealInsets.top + audioRecordingItemsVerticalOffset + UIScreenPixel), size: self.textPlaceholderNode.frame.size))
@ -1170,14 +1171,24 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
} }
private func updateTextNodeText(animated: Bool) { private func updateTextNodeText(animated: Bool) {
var hasText = false var inputHasText = false
var hideMicButton = false var hideMicButton = false
if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 { if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 {
hasText = true inputHasText = true
hideMicButton = true hideMicButton = true
} }
self.textPlaceholderNode.isHidden = hasText
self.updateActionButtons(hasText: hasText, hideMicButton: hideMicButton, animated: animated) if let interfaceState = self.presentationInterfaceState {
if (interfaceState.slowmodeState != nil && interfaceState.editMessageState == nil) || interfaceState.inputTextPanelState.contextPlaceholder != nil {
self.textPlaceholderNode.isHidden = true
self.slowmodePlaceholderNode?.isHidden = inputHasText
} else {
self.textPlaceholderNode.isHidden = inputHasText
self.slowmodePlaceholderNode?.isHidden = true
}
}
self.updateActionButtons(hasText: inputHasText, hideMicButton: hideMicButton, animated: animated)
self.updateTextHeight() self.updateTextHeight()
} }

View File

@ -6,6 +6,8 @@ import TelegramPresentationData
final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode { final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
private var theme: PresentationTheme private var theme: PresentationTheme
private let iconNode: ASImageNode private let iconNode: ASImageNode
private let iconArrowContainerNode: ASDisplayNode
private let iconArrowNode: ASImageNode
private let textNode: ImmediateTextNode private let textNode: ImmediateTextNode
private var slowmodeState: ChatSlowmodeState? private var slowmodeState: ChatSlowmodeState?
@ -21,13 +23,23 @@ final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
self.iconNode = ASImageNode() self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true self.iconNode.displayWithoutProcessing = true
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconTimer"), color: theme.chat.inputPanel.inputPlaceholderColor) self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/SlowmodeFrame"), color: theme.chat.inputPanel.inputPlaceholderColor)
self.iconArrowNode = ASImageNode()
self.iconArrowNode.displaysAsynchronously = false
self.iconArrowNode.displayWithoutProcessing = true
self.iconArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/SlowmodeArrow"), color: theme.chat.inputPanel.inputPlaceholderColor)
self.iconArrowNode.frame = CGRect(origin: CGPoint(), size: self.iconArrowNode.image?.size ?? CGSize())
self.iconArrowContainerNode = ASDisplayNode()
self.iconArrowContainerNode.addSubnode(self.iconArrowNode)
super.init() super.init()
self.isUserInteractionEnabled = false self.isUserInteractionEnabled = false
self.addSubnode(self.iconNode) self.addSubnode(self.iconNode)
self.addSubnode(self.iconArrowContainerNode)
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
} }
@ -59,6 +71,20 @@ final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
let timestamp = Int32(Date().timeIntervalSince1970) let timestamp = Int32(Date().timeIntervalSince1970)
let timeout = max(0, timeoutTimestamp - timestamp) let timeout = max(0, timeoutTimestamp - timestamp)
self.textNode.attributedText = NSAttributedString(string: stringForDuration(timeout), font: Font.regular(17.0), textColor: self.theme.chat.inputPanel.inputPlaceholderColor) self.textNode.attributedText = NSAttributedString(string: stringForDuration(timeout), font: Font.regular(17.0), textColor: self.theme.chat.inputPanel.inputPlaceholderColor)
if timeout <= 30 {
if self.iconArrowNode.layer.animation(forKey: "rotation") == nil {
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
basicAnimation.duration = 1.0
basicAnimation.fromValue = NSNumber(value: Float(0.0))
basicAnimation.toValue = NSNumber(value: Float(Double.pi * 2.0))
basicAnimation.repeatCount = Float.infinity
basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
self.iconArrowNode.layer.add(basicAnimation, forKey: "rotation")
}
} else {
self.iconArrowNode.layer.removeAnimation(forKey: "rotation")
}
} }
} }
if let validLayout = self.validLayout { if let validLayout = self.validLayout {
@ -71,9 +97,14 @@ final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
var leftInset: CGFloat = 0.0 var leftInset: CGFloat = 0.0
if let image = self.iconNode.image { if let image = self.iconNode.image {
let imageSize = image.size.aspectFitted(CGSize(width: 20.0, height: 20.0)) let imageSize = image.size
leftInset += imageSize.width + 2.0 leftInset += imageSize.width + 4.0
self.iconNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: imageSize) let iconArrowFrame = CGRect(origin: CGPoint(x: 0.0, y: 1.0), size: imageSize)
self.iconNode.frame = iconArrowFrame
if let arrowImage = self.iconArrowNode.image {
self.iconArrowContainerNode.frame = CGRect(origin: CGPoint(x: iconArrowFrame.minX, y: iconArrowFrame.maxY - arrowImage.size.height), size: arrowImage.size)
}
} }
let textSize = self.textNode.updateLayout(size) let textSize = self.textNode.updateLayout(size)

View File

@ -591,7 +591,7 @@ private final class DeviceContactDataManagerImpl {
var importableContactData: [String: (DeviceContactStableId, ImportableDeviceContactData)] = [:] var importableContactData: [String: (DeviceContactStableId, ImportableDeviceContactData)] = [:]
for (stableId, basicData) in self.stableIdToBasicContactData { for (stableId, basicData) in self.stableIdToBasicContactData {
for phoneNumber in basicData.phoneNumbers { for phoneNumber in basicData.phoneNumbers {
let normalizedNumber = phoneNumber.value let normalizedNumber = formatPhoneNumber(phoneNumber.value)
var replace = false var replace = false
if let current = importableContactData[normalizedNumber] { if let current = importableContactData[normalizedNumber] {
if stableId < current.0 { if stableId < current.0 {

View File

@ -35,7 +35,7 @@ private struct HorizontalListContextResultsChatInputContextPanelEntry: Comparabl
return lhs.index < rhs.index return lhs.index < rhs.index
} }
func item(account: Account, resultSelected: @escaping (ChatContextResult) -> Void) -> ListViewItem { func item(account: Account, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> ListViewItem {
return HorizontalListContextResultsChatInputPanelItem(account: account, result: self.result, resultSelected: resultSelected) return HorizontalListContextResultsChatInputPanelItem(account: account, result: self.result, resultSelected: resultSelected)
} }
} }
@ -58,7 +58,7 @@ private final class HorizontalListContextResultsOpaqueState {
} }
} }
private func preparedTransition(from fromEntries: [HorizontalListContextResultsChatInputContextPanelEntry], to toEntries: [HorizontalListContextResultsChatInputContextPanelEntry], hasMore: Bool, account: Account, resultSelected: @escaping (ChatContextResult) -> Void) -> HorizontalListContextResultsChatInputContextPanelTransition { private func preparedTransition(from fromEntries: [HorizontalListContextResultsChatInputContextPanelEntry], to toEntries: [HorizontalListContextResultsChatInputContextPanelEntry], hasMore: Bool, account: Account, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> HorizontalListContextResultsChatInputContextPanelTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
@ -136,37 +136,41 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
if itemNode.frame.contains(convertedPoint), let itemNode = itemNode as? HorizontalListContextResultsChatInputPanelItemNode, let item = itemNode.item { if itemNode.frame.contains(convertedPoint), let itemNode = itemNode as? HorizontalListContextResultsChatInputPanelItemNode, let item = itemNode.item {
if case let .internalReference(internalReference) = item.result, let file = internalReference.file, file.isSticker { if case let .internalReference(internalReference) = item.result, let file = internalReference.file, file.isSticker {
var menuItems: [PeekControllerMenuItem] = [] var menuItems: [PeekControllerMenuItem] = []
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { _, _ in
item.resultSelected(item.result) return item.resultSelected(item.result, itemNode, itemNode.bounds)
})) }))
for case let .Sticker(_, packReference, _) in file.attributes { for case let .Sticker(_, packReference, _) in file.attributes {
guard let packReference = packReference else { guard let packReference = packReference else {
continue continue
} }
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
if let strongSelf = self { if let strongSelf = self {
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: strongSelf.interfaceInteraction?.getNavigationController()) let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: strongSelf.interfaceInteraction?.getNavigationController())
controller.sendSticker = { file, sourceNode, sourceRect in controller.sendSticker = { file, sourceNode, sourceRect in
if let strongSelf = self { if let strongSelf = self {
strongSelf.interfaceInteraction?.sendSticker(file, sourceNode, sourceRect) return strongSelf.interfaceInteraction?.sendSticker(file, sourceNode, sourceRect) ?? false
} else {
return false
} }
} }
strongSelf.interfaceInteraction?.getNavigationController()?.view.window?.endEditing(true) strongSelf.interfaceInteraction?.getNavigationController()?.view.window?.endEditing(true)
strongSelf.interfaceInteraction?.presentController(controller, nil) strongSelf.interfaceInteraction?.presentController(controller, nil)
} }
return true
})) }))
} }
selectedItemNodeAndContent = (itemNode, StickerPreviewPeekContent(account: item.account, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: menuItems)) selectedItemNodeAndContent = (itemNode, StickerPreviewPeekContent(account: item.account, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: menuItems))
} else { } else {
var menuItems: [PeekControllerMenuItem] = [] var menuItems: [PeekControllerMenuItem] = []
if case let .internalReference(internalReference) = item.result, let file = internalReference.file, file.isAnimated { if case let .internalReference(internalReference) = item.result, let file = internalReference.file, file.isAnimated {
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.Preview_SaveGif, color: .accent, action: { menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.Preview_SaveGif, color: .accent, action: { _, _ in
let _ = addSavedGif(postbox: strongSelf.context.account.postbox, fileReference: .standalone(media: file)).start() let _ = addSavedGif(postbox: strongSelf.context.account.postbox, fileReference: .standalone(media: file)).start()
return true
})) }))
} }
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { _, _ in
item.resultSelected(item.result) return item.resultSelected(item.result, itemNode, itemNode.bounds)
})) }))
selectedItemNodeAndContent = (itemNode, ChatContextResultPeekContent(account: item.account, contextResult: item.result, menu: menuItems)) selectedItemNodeAndContent = (itemNode, ChatContextResultPeekContent(account: item.account, contextResult: item.result, menu: menuItems))
} }
@ -244,9 +248,11 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
} }
let firstTime = self.currentEntries == nil let firstTime = self.currentEntries == nil
let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, hasMore: results.nextOffset != nil, account: self.context.account, resultSelected: { [weak self] result in let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, hasMore: results.nextOffset != nil, account: self.context.account, resultSelected: { [weak self] result, node, rect in
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
interfaceInteraction.sendContextResult(results, result) return interfaceInteraction.sendContextResult(results, result, node, rect)
} else {
return false
} }
}) })
self.currentEntries = entries self.currentEntries = entries

View File

@ -10,11 +10,11 @@ import AVFoundation
final class HorizontalListContextResultsChatInputPanelItem: ListViewItem { final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
let account: Account let account: Account
let result: ChatContextResult let result: ChatContextResult
let resultSelected: (ChatContextResult) -> Void let resultSelected: (ChatContextResult, ASDisplayNode, CGRect) -> Bool
let selectable: Bool = true let selectable: Bool = true
public init(account: Account, result: ChatContextResult, resultSelected: @escaping (ChatContextResult) -> Void) { public init(account: Account, result: ChatContextResult, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) {
self.account = account self.account = account
self.result = result self.result = result
self.resultSelected = resultSelected self.resultSelected = resultSelected
@ -66,10 +66,6 @@ final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
} }
} }
} }
func selected(listView: ListView) {
self.resultSelected(self.result)
}
} }
private let titleFont = Font.medium(16.0) private let titleFont = Font.medium(16.0)
@ -439,4 +435,11 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
}) })
} }
} }
override func selected() {
guard let item = self.item else {
return
}
item.resultSelected(item.result, self, self.bounds)
}
} }

View File

@ -164,10 +164,10 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
var menuItems: [PeekControllerMenuItem] = [] var menuItems: [PeekControllerMenuItem] = []
menuItems = [ menuItems = [
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { _, _ in
controllerInteraction.sendSticker(.standalone(media: item.file), true, itemNode, itemNode.bounds) return controllerInteraction.sendSticker(.standalone(media: item.file), true, itemNode, itemNode.bounds)
}), }),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
if let strongSelf = self { if let strongSelf = self {
if isStarred { if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start() let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -175,8 +175,9 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start() let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
} }
} }
return true
}), }),
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
loop: for attribute in item.file.attributes { loop: for attribute in item.file.attributes {
switch attribute { switch attribute {
@ -185,7 +186,9 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: controllerInteraction.navigationController()) let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: controllerInteraction.navigationController())
controller.sendSticker = { file, sourceNode, sourceRect in controller.sendSticker = { file, sourceNode, sourceRect in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
controllerInteraction.sendSticker(file, true, sourceNode, sourceRect) return controllerInteraction.sendSticker(file, true, sourceNode, sourceRect)
} else {
return false
} }
} }
@ -197,9 +200,11 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
break break
} }
} }
return true
} }
return true
}), }),
PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: {}) PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { _, _ in return true })
] ]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems)) return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else { } else {

View File

@ -91,7 +91,7 @@ func legacyInputMicPalette(from theme: PresentationTheme) -> TGModernConversatio
return TGModernConversationInputMicPallete(dark: theme.overallDarkAppearance, buttonColor: inputPanelTheme.actionControlFillColor, iconColor: inputPanelTheme.actionControlForegroundColor, backgroundColor: inputPanelTheme.panelBackgroundColor, borderColor: inputPanelTheme.panelSeparatorColor, lock: inputPanelTheme.panelControlAccentColor, textColor: inputPanelTheme.primaryTextColor, secondaryTextColor: inputPanelTheme.secondaryTextColor, recording: inputPanelTheme.mediaRecordingDotColor) return TGModernConversationInputMicPallete(dark: theme.overallDarkAppearance, buttonColor: inputPanelTheme.actionControlFillColor, iconColor: inputPanelTheme.actionControlForegroundColor, backgroundColor: inputPanelTheme.panelBackgroundColor, borderColor: inputPanelTheme.panelSeparatorColor, lock: inputPanelTheme.panelControlAccentColor, textColor: inputPanelTheme.primaryTextColor, secondaryTextColor: inputPanelTheme.secondaryTextColor, recording: inputPanelTheme.mediaRecordingDotColor)
} }
func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect, context: AccountContext, peerId: PeerId, send: @escaping (EnqueueMessage) -> Void) -> InstantVideoController { func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect, context: AccountContext, peerId: PeerId, slowmodeState: ChatSlowmodeState?, send: @escaping (EnqueueMessage) -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void) -> InstantVideoController {
let legacyController = InstantVideoController(presentation: .custom, theme: theme) let legacyController = InstantVideoController(presentation: .custom, theme: theme)
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
legacyController.lockOrientation = true legacyController.lockOrientation = true
@ -105,11 +105,21 @@ func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect,
if peerId.namespace != Namespaces.Peer.SecretChat { if peerId.namespace != Namespaces.Peer.SecretChat {
uploadInterface = LegacyLiveUploadInterface(account: context.account) uploadInterface = LegacyLiveUploadInterface(account: context.account)
} }
var slowmodeValidUntil: Int32 = 0
if let slowmodeState = slowmodeState, case let .timestamp(timestamp) = slowmodeState.variant {
slowmodeValidUntil = timestamp
}
let controller = TGVideoMessageCaptureController(context: legacyController.context, assets: TGVideoMessageCaptureControllerAssets(send: PresentationResourcesChat.chatInputPanelSendButtonImage(theme)!, slideToCancel: PresentationResourcesChat.chatInputPanelMediaRecordingCancelArrowImage(theme)!, actionDelete: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionThrash"), color: theme.chat.inputPanel.panelControlAccentColor))!, transitionInView: { let controller = TGVideoMessageCaptureController(context: legacyController.context, assets: TGVideoMessageCaptureControllerAssets(send: PresentationResourcesChat.chatInputPanelSendButtonImage(theme)!, slideToCancel: PresentationResourcesChat.chatInputPanelMediaRecordingCancelArrowImage(theme)!, actionDelete: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionThrash"), color: theme.chat.inputPanel.panelControlAccentColor))!, transitionInView: {
return nil return nil
}, parentController: baseController, controlsFrame: panelFrame, isAlreadyLocked: { }, parentController: baseController, controlsFrame: panelFrame, isAlreadyLocked: {
return false return false
}, liveUploadInterface: uploadInterface, pallete: legacyInputMicPalette(from: theme))! }, liveUploadInterface: uploadInterface, pallete: legacyInputMicPalette(from: theme), slowmodeTimestamp: slowmodeValidUntil, slowmodeView: {
let node = ChatSendButtonRadialStatusView(color: theme.chat.inputPanel.panelControlAccentColor)
node.slowmodeState = slowmodeState
return node
})!
controller.finishedWithVideo = { videoUrl, previewImage, _, duration, dimensions, liveUploadData, adjustments in controller.finishedWithVideo = { videoUrl, previewImage, _, duration, dimensions, liveUploadData, adjustments in
guard let videoUrl = videoUrl else { guard let videoUrl = videoUrl else {
return return
@ -169,6 +179,12 @@ func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect,
legacyController.dismiss() legacyController.dismiss()
} }
} }
controller.displaySlowmodeTooltip = { [weak legacyController, weak controller] in
if let legacyController = legacyController, let controller = controller {
let rect = controller.frameForSendButton()
displaySlowmodeTooltip(legacyController.displayNode, rect)
}
}
legacyController.bindCaptureController(controller) legacyController.bindCaptureController(controller)
let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in

View File

@ -196,7 +196,7 @@ func chatMessagePreviewControllerData(context: AccountContext, message: Message,
return nil return nil
} }
func openChatMessage(context: AccountContext, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode = .default, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> (UIView?, UIView?))?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal<InstantPageGalleryEntry?, NoError>, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, Media) -> Void, actionInteraction: GalleryControllerActionInteraction? = nil) -> Bool { func openChatMessage(context: AccountContext, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode = .default, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> (UIView?, UIView?))?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, setupTemporaryHiddenMedia: @escaping (Signal<InstantPageGalleryEntry?, NoError>, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, Media) -> Void, actionInteraction: GalleryControllerActionInteraction? = nil) -> Bool {
if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: mode, synchronousLoad: false, actionInteraction: actionInteraction) { if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: mode, synchronousLoad: false, actionInteraction: actionInteraction) {
switch mediaData { switch mediaData {
case let .url(url): case let .url(url):

View File

@ -22,7 +22,7 @@ private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatContr
} }
} }
func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext = .generic, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)? = nil, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Void)? = nil, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void) { func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext = .generic, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)? = nil, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? = nil, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
switch resolvedUrl { switch resolvedUrl {
case let .externalUrl(url): case let .externalUrl(url):

View File

@ -66,7 +66,9 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec
}, toggleMessagesSelection: { _, _ in }, toggleMessagesSelection: { _, _ in
}, sendMessage: { _ in }, sendMessage: { _ in
}, sendSticker: { _, _, _, _ in }, sendSticker: { _, _, _, _ in
return false
}, sendGif: { _, _, _ in }, sendGif: { _, _, _ in
return false
}, requestMessageActionCallback: { _, _, _ in }, requestMessageActionCallback: { _, _, _ in
}, requestMessageActionUrlAuth: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in
}, activateSwitchInline: { _, _ in }, activateSwitchInline: { _, _ in

View File

@ -181,8 +181,10 @@ public class PeerMediaCollectionController: TelegramController {
strongSelf.updateInterfaceState(animated: true, { $0.withToggledSelectedMessages(ids, value: value) }) strongSelf.updateInterfaceState(animated: true, { $0.withToggledSelectedMessages(ids, value: value) })
} }
}, sendMessage: { _ in }, sendMessage: { _ in
},sendSticker: { _, _, _, _ in }, sendSticker: { _, _, _, _ in
return false
}, sendGif: { _, _, _ in }, sendGif: { _, _, _ in
return false
}, requestMessageActionCallback: { _, _, _ in }, requestMessageActionCallback: { _, _, _ in
}, requestMessageActionUrlAuth: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in
}, activateSwitchInline: { _, _ in }, activateSwitchInline: { _, _ in
@ -336,7 +338,8 @@ public class PeerMediaCollectionController: TelegramController {
}, navigateToChat: { _ in }, navigateToChat: { _ in
}, openPeerInfo: { }, openPeerInfo: {
}, togglePeerNotifications: { }, togglePeerNotifications: {
}, sendContextResult: { _, _ in }, sendContextResult: { _, _, _, _ in
return false
}, sendBotCommand: { _, _ in }, sendBotCommand: { _, _ in
}, sendBotStart: { _ in }, sendBotStart: { _ in
}, botSwitchChatWithPayload: { _, _ in }, botSwitchChatWithPayload: { _, _ in
@ -351,6 +354,7 @@ public class PeerMediaCollectionController: TelegramController {
}, switchMediaRecordingMode: { }, switchMediaRecordingMode: {
}, setupMessageAutoremoveTimeout: { }, setupMessageAutoremoveTimeout: {
}, sendSticker: { _, _, _ in }, sendSticker: { _, _, _ in
return false
}, unblockPeer: { }, unblockPeer: {
}, pinMessage: { _ in }, pinMessage: { _ in
}, unpinMessage: { }, unpinMessage: {

View File

@ -38,13 +38,17 @@ final class StickerPackPreviewController: ViewController {
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
var sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Void)? { var sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? {
didSet { didSet {
if self.isNodeLoaded { if self.isNodeLoaded {
if let sendSticker = self.sendSticker { if let sendSticker = self.sendSticker {
self.controllerNode.sendSticker = { [weak self] file, sourceNode, sourceRect in self.controllerNode.sendSticker = { [weak self] file, sourceNode, sourceRect in
sendSticker(file, sourceNode, sourceRect) if sendSticker(file, sourceNode, sourceRect) {
self?.dismiss() self?.dismiss()
return true
} else {
return false
}
} }
} else { } else {
self.controllerNode.sendSticker = nil self.controllerNode.sendSticker = nil
@ -135,8 +139,12 @@ final class StickerPackPreviewController: ViewController {
} }
if let sendSticker = self.sendSticker { if let sendSticker = self.sendSticker {
self.controllerNode.sendSticker = { [weak self] file, sourceNode, sourceRect in self.controllerNode.sendSticker = { [weak self] file, sourceNode, sourceRect in
sendSticker(file, sourceNode, sourceRect) if sendSticker(file, sourceNode, sourceRect) {
self?.dismiss() self?.dismiss()
return true
} else {
return false
}
} }
} }
let account = self.context.account let account = self.context.account

View File

@ -68,7 +68,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
var presentInGlobalOverlay: ((ViewController, Any?) -> Void)? var presentInGlobalOverlay: ((ViewController, Any?) -> Void)?
var dismiss: (() -> Void)? var dismiss: (() -> Void)?
var cancel: (() -> Void)? var cancel: (() -> Void)?
var sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Void)? var sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
let ready = Promise<Bool>() let ready = Promise<Bool>()
private var didSetReady = false private var didSetReady = false
@ -200,13 +200,15 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
var menuItems: [PeekControllerMenuItem] = [] var menuItems: [PeekControllerMenuItem] = []
if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
if strongSelf.sendSticker != nil { if strongSelf.sendSticker != nil {
menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.ShareMenu_Send, color: .accent, font: .bold, action: { menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.ShareMenu_Send, color: .accent, font: .bold, action: { _, _ in
if let strongSelf = self { if let strongSelf = self {
strongSelf.sendSticker?(.standalone(media: item.file), itemNode, itemNode.bounds) return strongSelf.sendSticker?(.standalone(media: item.file), itemNode, itemNode.bounds) ?? false
} else {
return false
} }
})) }))
} }
menuItems.append(PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { menuItems.append(PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
if let strongSelf = self { if let strongSelf = self {
if isStarred { if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start() let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -214,8 +216,9 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start() let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
} }
} }
return true
})) }))
menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: {})) menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { _, _ in return true }))
} }
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems)) return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else { } else {

View File

@ -216,7 +216,9 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: .id(id: info.id.id, accessHash: info.accessHash), parentNavigationController: strongSelf.controllerInteraction.navigationController()) let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: .id(id: info.id.id, accessHash: info.accessHash), parentNavigationController: strongSelf.controllerInteraction.navigationController())
controller.sendSticker = { [weak self] fileReference, sourceNode, sourceRect in controller.sendSticker = { [weak self] fileReference, sourceNode, sourceRect in
if let strongSelf = self { if let strongSelf = self {
strongSelf.controllerInteraction.sendSticker(fileReference, false, sourceNode, sourceRect) return strongSelf.controllerInteraction.sendSticker(fileReference, false, sourceNode, sourceRect)
} else {
return false
} }
} }
strongSelf.controllerInteraction.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) strongSelf.controllerInteraction.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))

View File

@ -140,10 +140,10 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
var menuItems: [PeekControllerMenuItem] = [] var menuItems: [PeekControllerMenuItem] = []
menuItems = [ menuItems = [
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { _, _ in
controllerInteraction.sendSticker(.standalone(media: item.file), true, itemNode, itemNode.bounds) return controllerInteraction.sendSticker(.standalone(media: item.file), true, itemNode, itemNode.bounds)
}), }),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
if let strongSelf = self { if let strongSelf = self {
if isStarred { if isStarred {
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start() let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
@ -151,8 +151,9 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start() let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
} }
} }
return true
}), }),
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { _, _ in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
loop: for attribute in item.file.attributes { loop: for attribute in item.file.attributes {
switch attribute { switch attribute {
@ -161,7 +162,9 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: controllerInteraction.navigationController()) let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: controllerInteraction.navigationController())
controller.sendSticker = { file, sourceNode, sourceRect in controller.sendSticker = { file, sourceNode, sourceRect in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
controllerInteraction.sendSticker(file, true, sourceNode, sourceRect) return controllerInteraction.sendSticker(file, true, sourceNode, sourceRect)
} else {
return false
} }
} }
@ -174,8 +177,9 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
} }
} }
} }
return true
}), }),
PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: {}) PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { _, _ in return true })
] ]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems)) return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else { } else {

View File

@ -90,7 +90,7 @@ private enum VerticalListContextResultsChatInputContextPanelEntry: Comparable, I
} }
} }
func item(account: Account, actionSelected: @escaping () -> Void, resultSelected: @escaping (ChatContextResult) -> Void) -> ListViewItem { func item(account: Account, actionSelected: @escaping () -> Void, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> ListViewItem {
switch self { switch self {
case let .action(theme, title): case let .action(theme, title):
return VerticalListContextResultsChatInputPanelButtonItem(theme: theme, title: title, pressed: actionSelected) return VerticalListContextResultsChatInputPanelButtonItem(theme: theme, title: title, pressed: actionSelected)
@ -106,7 +106,7 @@ private struct VerticalListContextResultsChatInputContextPanelTransition {
let updates: [ListViewUpdateItem] let updates: [ListViewUpdateItem]
} }
private func preparedTransition(from fromEntries: [VerticalListContextResultsChatInputContextPanelEntry], to toEntries: [VerticalListContextResultsChatInputContextPanelEntry], account: Account, actionSelected: @escaping () -> Void, resultSelected: @escaping (ChatContextResult) -> Void) -> VerticalListContextResultsChatInputContextPanelTransition { private func preparedTransition(from fromEntries: [VerticalListContextResultsChatInputContextPanelEntry], to toEntries: [VerticalListContextResultsChatInputContextPanelEntry], account: Account, actionSelected: @escaping () -> Void, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> VerticalListContextResultsChatInputContextPanelTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
@ -172,17 +172,18 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction, let switchPeer = results.switchPeer { if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction, let switchPeer = results.switchPeer {
interfaceInteraction.botSwitchChatWithPayload(results.botId, switchPeer.startParam) interfaceInteraction.botSwitchChatWithPayload(results.botId, switchPeer.startParam)
} }
}, resultSelected: { [weak self] result in }, resultSelected: { [weak self] result, node, rect in
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
interfaceInteraction.sendContextResult(results, result) strongSelf.listView.clearHighlightAnimated(true)
return interfaceInteraction.sendContextResult(results, result, node, rect)
} else {
return false
} }
}) })
self.currentEntries = to self.currentEntries = to
self.enqueueTransition(transition, firstTime: firstTime) self.enqueueTransition(transition, firstTime: firstTime)
} }
private func enqueueTransition(_ transition: VerticalListContextResultsChatInputContextPanelTransition, firstTime: Bool) { private func enqueueTransition(_ transition: VerticalListContextResultsChatInputContextPanelTransition, firstTime: Bool) {
enqueuedTransitions.append((transition, firstTime)) enqueuedTransitions.append((transition, firstTime))

View File

@ -11,11 +11,11 @@ final class VerticalListContextResultsChatInputPanelItem: ListViewItem {
fileprivate let account: Account fileprivate let account: Account
fileprivate let theme: PresentationTheme fileprivate let theme: PresentationTheme
fileprivate let result: ChatContextResult fileprivate let result: ChatContextResult
private let resultSelected: (ChatContextResult) -> Void fileprivate let resultSelected: (ChatContextResult, ASDisplayNode, CGRect) -> Bool
let selectable: Bool = true let selectable: Bool = true
public init(account: Account, theme: PresentationTheme, result: ChatContextResult, resultSelected: @escaping (ChatContextResult) -> Void) { public init(account: Account, theme: PresentationTheme, result: ChatContextResult, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) {
self.account = account self.account = account
self.theme = theme self.theme = theme
self.result = result self.result = result
@ -68,10 +68,6 @@ final class VerticalListContextResultsChatInputPanelItem: ListViewItem {
} }
} }
} }
func selected(listView: ListView) {
self.resultSelected(self.result)
}
} }
private let titleFont = Font.medium(16.0) private let titleFont = Font.medium(16.0)
@ -96,6 +92,8 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
private var currentIconImageResource: TelegramMediaResource? private var currentIconImageResource: TelegramMediaResource?
private var item: VerticalListContextResultsChatInputPanelItem?
init() { init() {
self.titleNode = TextNode() self.titleNode = TextNode()
self.textNode = TextNode() self.textNode = TextNode()
@ -267,6 +265,8 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
return (nodeLayout, { _ in return (nodeLayout, { _ in
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor strongSelf.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.backgroundColor = item.theme.list.plainBackgroundColor strongSelf.backgroundColor = item.theme.list.plainBackgroundColor
@ -391,4 +391,11 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
} }
} }
} }
override func selected() {
guard let item = self.item else {
return
}
item.resultSelected(item.result, self, self.bounds)
}
} }