mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-24 01:00:16 +00:00
Slowmode UI fixes
This commit is contained in:
parent
51bf263135
commit
b6380062dd
@ -4475,6 +4475,9 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"StickerPacksSettings.AnimatedStickers" = "Loop Animated Stickers";
|
||||
"StickerPacksSettings.AnimatedStickersInfo" = "Animated stickers will play in chat continuously.";
|
||||
"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.SlowmodeTooltipPending" = "Slowmode is enabled. You can't send more than one message at once.";
|
||||
|
||||
@ -16,9 +16,9 @@ public struct PeekControllerMenuItem {
|
||||
public let title: String
|
||||
public let color: PeekControllerMenuItemColor
|
||||
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.color = color
|
||||
self.font = font
|
||||
@ -100,7 +100,8 @@ final class PeekControllerMenuItemNode: HighlightTrackingButtonNode {
|
||||
}
|
||||
|
||||
@objc func buttonPressed() {
|
||||
self.activatedAction()
|
||||
self.item.action()
|
||||
if self.item.action(self, self.bounds) {
|
||||
self.activatedAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,10 +12,11 @@ private final class ViewControllerPeekContent: PeekControllerContent {
|
||||
self.controller = controller
|
||||
var menu: [PeekControllerMenuItem] = []
|
||||
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 {
|
||||
item.handler(item, controller)
|
||||
}
|
||||
return true
|
||||
}))
|
||||
}
|
||||
self.menu = menu
|
||||
|
||||
@ -294,6 +294,7 @@
|
||||
}
|
||||
|
||||
bool groupingButtonVisible = strongSelf->_selectionContext.allowGrouping && onlyGroupableMedia && strongSelf->_selectionContext.count > 1;
|
||||
groupingButtonVisible = false;
|
||||
[strongSelf->_toolbarView setCenterButtonHidden:!groupingButtonVisible animated:true];
|
||||
|
||||
return groupingButtonVisible;
|
||||
|
||||
@ -24,12 +24,15 @@
|
||||
@property (nonatomic, copy) void(^onStop)(void);
|
||||
@property (nonatomic, copy) void(^onCancel)(void);
|
||||
@property (nonatomic, copy) void(^didDismiss)(void);
|
||||
|
||||
- (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;
|
||||
@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;
|
||||
|
||||
- (void)buttonInteractionUpdate:(CGPoint)value;
|
||||
- (void)setLocked;
|
||||
|
||||
- (CGRect)frameForSendButton;
|
||||
|
||||
- (void)complete;
|
||||
- (void)dismiss;
|
||||
- (bool)stop;
|
||||
|
||||
@ -125,6 +125,9 @@ typedef enum
|
||||
UIView *(^_transitionInView)();
|
||||
id<TGLiveUploadInterface> _liveUploadInterface;
|
||||
|
||||
int32_t _slowmodeTimestamp;
|
||||
UIView * (^_slowmodeView)(void);
|
||||
|
||||
TGVideoMessageCaptureControllerAssets *_assets;
|
||||
}
|
||||
|
||||
@ -134,12 +137,7 @@ typedef enum
|
||||
|
||||
@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
|
||||
{
|
||||
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
|
||||
- (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
|
||||
{
|
||||
self = [super initWithContext:context];
|
||||
if (self != nil)
|
||||
@ -150,6 +148,8 @@ typedef enum
|
||||
_liveUploadInterface = liveUploadInterface;
|
||||
_assets = assets;
|
||||
_pallete = pallete;
|
||||
_slowmodeTimestamp = slowmodeTimestamp;
|
||||
_slowmodeView = [slowmodeView copy];
|
||||
|
||||
_url = [TGVideoMessageCaptureController tempOutputPath];
|
||||
_queue = [[SQueue alloc] init];
|
||||
@ -325,7 +325,7 @@ typedef enum
|
||||
CGRect controlsFrame = _controlsFrame;
|
||||
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.clipsToBounds = true;
|
||||
_controlsView.parent = self;
|
||||
@ -358,13 +358,14 @@ typedef enum
|
||||
|
||||
};
|
||||
};
|
||||
_controlsView.sendPressed = ^
|
||||
_controlsView.sendPressed = ^bool
|
||||
{
|
||||
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
|
||||
if (strongSelf != nil)
|
||||
{
|
||||
[strongSelf sendPressed];
|
||||
};
|
||||
if (strongSelf != nil) {
|
||||
return [strongSelf sendPressed];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
[self.view addSubview:_controlsView];
|
||||
|
||||
@ -647,6 +648,10 @@ typedef enum
|
||||
[_controlsView setLocked];
|
||||
}
|
||||
|
||||
- (CGRect)frameForSendButton {
|
||||
return [_controlsView convertRect:[_controlsView frameForSendButton] toView:self.view];
|
||||
}
|
||||
|
||||
- (bool)stop
|
||||
{
|
||||
if (!_capturePipeline.isRecording)
|
||||
@ -664,12 +669,23 @@ typedef enum
|
||||
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];
|
||||
|
||||
_automaticDismiss = true;
|
||||
[self dismiss:false];
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)unmutePressed
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
@property (nonatomic, copy) void (^positionChanged)(void);
|
||||
@property (nonatomic, copy) void (^cancel)(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);
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
@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)recordingStarted;
|
||||
@ -35,4 +35,6 @@
|
||||
|
||||
- (void)setDurationString:(NSString *)string;
|
||||
|
||||
- (CGRect)frameForSendButton;
|
||||
|
||||
@end
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#import <LegacyComponents/TGModernButton.h>
|
||||
//#import "TGModernConversationInputMicButton.h"
|
||||
#import <LegacyComponents/TGVideoMessageScrubber.h>
|
||||
#import <SSignalKit/SSignalKit.h>
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
#import "TGColor.h"
|
||||
@ -41,25 +42,37 @@ static CGRect viewFrame(UIView *view)
|
||||
TGModernButton *_deleteButton;
|
||||
TGModernButton *_sendButton;
|
||||
|
||||
int32_t _slowmodeTimestamp;
|
||||
UIView * (^_generateSlowmodeView)(void);
|
||||
UIView *_slowmodeView;
|
||||
|
||||
UIImageView *_recordIndicatorView;
|
||||
UILabel *_recordDurationLabel;
|
||||
|
||||
CFAbsoluteTime _recordingInterfaceShowTime;
|
||||
|
||||
TGVideoMessageCaptureControllerAssets *_assets;
|
||||
|
||||
STimer *_slowmodeTimer;
|
||||
}
|
||||
@end
|
||||
|
||||
@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];
|
||||
if (self != nil) {
|
||||
_assets = assets;
|
||||
_slowmodeTimestamp = slowmodeTimestamp;
|
||||
_generateSlowmodeView = [slowmodeView copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_slowmodeTimer invalidate];
|
||||
}
|
||||
|
||||
- (void)captureStarted
|
||||
{
|
||||
[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];
|
||||
[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.pallete = self.pallete;
|
||||
_scrubberView.dataSource = self.parent;
|
||||
@ -413,6 +454,7 @@ static CGRect viewFrame(UIView *view)
|
||||
[UIView animateWithDuration:0.3 animations:^
|
||||
{
|
||||
_sendButton.alpha = 1.0f;
|
||||
_slowmodeView.alpha = 1.0f;
|
||||
}];
|
||||
}
|
||||
|
||||
@ -440,8 +482,12 @@ static CGRect viewFrame(UIView *view)
|
||||
{
|
||||
_sendButton.userInteractionEnabled = false;
|
||||
|
||||
if (self.sendPressed != nil)
|
||||
self.sendPressed();
|
||||
if (self.sendPressed != nil) {
|
||||
if (!self.sendPressed()) {
|
||||
_sendButton.userInteractionEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)cancelPressed
|
||||
@ -478,6 +524,10 @@ static CGRect viewFrame(UIView *view)
|
||||
- (void)removeDotAnimation {
|
||||
[_recordIndicatorView.layer removeAnimationForKey:@"opacity-dot"];
|
||||
}
|
||||
|
||||
static CGFloat floorToScreenPixels(CGFloat value) {
|
||||
return CGFloor(value * UIScreen.mainScreen.scale) / UIScreen.mainScreen.scale;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
@ -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));
|
||||
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);
|
||||
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
|
||||
|
||||
@ -784,7 +784,7 @@ private final class MediaPlayerContext {
|
||||
}
|
||||
} else if let worstStatus = worstStatus, case let .finished(finishedAt) = worstStatus, finishedAt.isFinite {
|
||||
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
|
||||
performActionAtEndNow = true
|
||||
} else {
|
||||
|
||||
@ -108,6 +108,8 @@ public final class MediaTrackFrameBuffer {
|
||||
}
|
||||
|
||||
bufferedDuration = CMTimeGetSeconds(bufferedUntilTime) - timestamp
|
||||
} else if self.endOfStream {
|
||||
return .finished(at: CMTimeGetSeconds(self.duration))
|
||||
}
|
||||
|
||||
let minTimestamp = timestamp - 1.0
|
||||
|
||||
@ -470,20 +470,8 @@ struct ctr_state {
|
||||
assert(result == errSecSuccess);
|
||||
[helloData appendBytes:r1 length:32];
|
||||
|
||||
uint8_t s3[2] = { 0x00, 0x22 };
|
||||
[helloData appendBytes:s3 length:2];
|
||||
|
||||
[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 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:s0 length:65];
|
||||
|
||||
uint8_t stackZ[2] = { 0x00, 0x00 };
|
||||
|
||||
@ -514,40 +502,16 @@ struct ctr_state {
|
||||
stack1Value = OSSwapInt16(stack1Value);
|
||||
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 };
|
||||
[helloData appendBytes:s6 length:15];
|
||||
|
||||
[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 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:117];
|
||||
|
||||
uint8_t r2[32];
|
||||
result = SecRandomCopyBytes(nil, 32, r2);
|
||||
assert(result == errSecSuccess);
|
||||
[helloData appendBytes:r2 length:32];
|
||||
|
||||
uint8_t s9[11] = { 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b, 0x00, 0x0b, 0x0a };
|
||||
[helloData appendBytes:s9 length:11];
|
||||
|
||||
[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];
|
||||
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:35];
|
||||
|
||||
int stack4 = (int)helloData.length;
|
||||
[helloData appendBytes:stackZ length:2];
|
||||
|
||||
@ -380,7 +380,7 @@ final class MessageHistoryReadStateTable: Table {
|
||||
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 state = states.namespaces[messageIndex.id.namespace] {
|
||||
switch state {
|
||||
|
||||
@ -562,7 +562,7 @@ final class MessageHistoryTable: Table {
|
||||
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)?
|
||||
if let index = self.topIndexEntry(peerId: messageIndex.id.peerId, namespace: messageIndex.id.namespace) {
|
||||
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)
|
||||
}, incomingIndexStatsInRange: { fromIndex, toIndex in
|
||||
return self.incomingMessageCountInRange(messageIndex.id.peerId, namespace: messageIndex.id.namespace, fromIndex: fromIndex, toIndex: toIndex)
|
||||
|
||||
@ -1551,13 +1551,25 @@ public final class Postbox {
|
||||
case let .associated(_, messageId):
|
||||
if let messageId = messageId, let readState = self.readStateTable.getCombinedState(messageId.peerId), readState.count != 0 {
|
||||
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:
|
||||
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) {
|
||||
|
||||
@ -62,6 +62,7 @@ public enum AdminLogEventAction {
|
||||
case pollStopped(Message)
|
||||
case linkedPeerUpdated(previous: Peer?, updated: Peer?)
|
||||
case changeGeoLocation(previous: PeerGeoLocation?, updated: PeerGeoLocation?)
|
||||
case updateSlowmode(previous: Int32?, updated: Int32?)
|
||||
}
|
||||
|
||||
public enum ChannelAdminLogEventError {
|
||||
@ -219,7 +220,7 @@ public func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: Pe
|
||||
case let .channelAdminLogEventActionChangeLocation(prevValue, newValue):
|
||||
action = .changeGeoLocation(previous: PeerGeoLocation(apiLocation: prevValue), updated: PeerGeoLocation(apiLocation: 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)
|
||||
if let action = action {
|
||||
|
||||
@ -86,7 +86,7 @@ public func deviceContactsImportedByCount(postbox: Postbox, contacts: [(String,
|
||||
var maxCount: Int32 = 0
|
||||
for number in numbers {
|
||||
if let value = transaction.getDeviceContactImportInfo(TelegramDeviceContactImportIdentifier.phoneNumber(number).key) as? TelegramDeviceContactImportedData, case let .imported(imported) = value {
|
||||
|
||||
|
||||
maxCount = max(maxCount, imported.importedByCount)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
22
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeArrow.imageset/Contents.json
vendored
Normal file
22
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeArrow.imageset/Contents.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeArrow.imageset/slowmodearrow@2x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeArrow.imageset/slowmodearrow@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeArrow.imageset/slowmodearrow@3x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeArrow.imageset/slowmodearrow@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
22
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeFrame.imageset/Contents.json
vendored
Normal file
22
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeFrame.imageset/Contents.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeFrame.imageset/slowmode@2x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeFrame.imageset/slowmode@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 831 B |
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeFrame.imageset/slowmode@3x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/SlowmodeFrame.imageset/slowmode@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
@ -51,6 +51,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
|
||||
case permission(PresentationTheme, Int, String, Bool, TelegramChatBannedRightsFlags, Bool?)
|
||||
case slowmodeHeader(PresentationTheme, String)
|
||||
case slowmode(PresentationTheme, PresentationStrings, Int32)
|
||||
case slowmodeInfo(PresentationTheme, String)
|
||||
case kicked(PresentationTheme, String, String)
|
||||
case exceptionsHeader(PresentationTheme, String)
|
||||
case add(PresentationTheme, String)
|
||||
@ -60,7 +61,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
|
||||
switch self {
|
||||
case .permissionsHeader, .permission:
|
||||
return ChannelPermissionsSection.permissions.rawValue
|
||||
case .slowmodeHeader, .slowmode:
|
||||
case .slowmodeHeader, .slowmode, .slowmodeInfo:
|
||||
return ChannelPermissionsSection.slowmode.rawValue
|
||||
case .kicked:
|
||||
return ChannelPermissionsSection.kicked.rawValue
|
||||
@ -79,12 +80,14 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
|
||||
return .index(998)
|
||||
case .slowmode:
|
||||
return .index(999)
|
||||
case .kicked:
|
||||
case .slowmodeInfo:
|
||||
return .index(1000)
|
||||
case .exceptionsHeader:
|
||||
case .kicked:
|
||||
return .index(1001)
|
||||
case .add:
|
||||
case .exceptionsHeader:
|
||||
return .index(1002)
|
||||
case .add:
|
||||
return .index(1003)
|
||||
case let .peerItem(_, _, _, _, _, participant, _, _, _, _):
|
||||
return .peer(participant.peer.id)
|
||||
}
|
||||
@ -116,6 +119,12 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
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):
|
||||
if case let .kicked(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
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
|
||||
arguments.updateSlowmode(value)
|
||||
})
|
||||
case let .slowmodeInfo(theme, value):
|
||||
return ItemListTextItem(theme: theme, text: .plain(value), sectionId: self.section)
|
||||
case let .kicked(theme, text, value):
|
||||
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openKicked()
|
||||
@ -395,6 +406,7 @@ private func channelPermissionsControllerEntries(presentationData: PresentationD
|
||||
|
||||
entries.append(.slowmodeHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SlowmodeHeader))
|
||||
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(.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 pushControllerImpl: ((ViewController) -> Void)?
|
||||
var cancelSlowmodeDraggingImpl: (() -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
@ -624,7 +637,7 @@ public func channelPermissionsController(context: AccountContext, peerId: PeerId
|
||||
let _ = (peerView.get()
|
||||
|> take(1)
|
||||
|> 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
|
||||
var state = state
|
||||
state.modifiedSlowmodeTimeout = value
|
||||
@ -634,14 +647,52 @@ public func channelPermissionsController(context: AccountContext, peerId: PeerId
|
||||
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())
|
||||
}
|
||||
} 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
|
||||
var state = state
|
||||
state.modifiedSlowmodeTimeout = value
|
||||
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)
|
||||
}
|
||||
}
|
||||
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
|
||||
guard let controller = controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
|
||||
@ -399,7 +399,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
||||
}, enqueueMessage: { message in
|
||||
self?.sendMessages([message])
|
||||
}, 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
|
||||
if let strongSelf = self {
|
||||
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)])
|
||||
}, sendSticker: { [weak self] fileReference, clearInput, sourceNode, sourceRect in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if let _ = strongSelf.presentationInterfaceState.slowmodeState {
|
||||
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceNode, sourceRect)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
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)])
|
||||
return true
|
||||
}, sendGif: { [weak self] fileReference, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
if let _ = strongSelf.presentationInterfaceState.slowmodeState {
|
||||
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceNode, sourceRect)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
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)])
|
||||
}
|
||||
return true
|
||||
}, requestMessageActionCallback: { [weak self] messageId, data, isGame in
|
||||
if let strongSelf = self {
|
||||
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
|
||||
@ -2049,7 +2051,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
||||
var slowmodeState: ChatSlowmodeState?
|
||||
if let cachedData = combinedInitialData.cachedData as? CachedChannelData {
|
||||
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) {
|
||||
slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp))
|
||||
}
|
||||
@ -2163,8 +2165,14 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
||||
let hasPendingMessages: Signal<Bool, NoError>
|
||||
if case let .peer(peerId) = self.chatLocation {
|
||||
hasPendingMessages = self.context.account.pendingMessageManager.hasPendingMessages
|
||||
|> map { peerIds -> Bool in
|
||||
return peerIds.contains(peerId)
|
||||
|> mapToSignal { peerIds -> Signal<Bool, NoError> in
|
||||
let value = peerIds.contains(peerId)
|
||||
if value {
|
||||
return .single(true)
|
||||
} else {
|
||||
return .single(false)
|
||||
|> delay(0.1, queue: .mainQueue())
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
@ -2182,7 +2190,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
||||
var slowmodeState: ChatSlowmodeState?
|
||||
if let cachedData = cachedData as? CachedChannelData {
|
||||
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 {
|
||||
slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .pendingMessages)
|
||||
} 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 {
|
||||
let _ = togglePeerMuted(account: strongSelf.context.account, peerId: peerId).start()
|
||||
}
|
||||
}, sendContextResult: { [weak self] results, result in
|
||||
self?.enqueueChatContextResult(results, result)
|
||||
}, sendContextResult: { [weak self] results, result, node, rect in
|
||||
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
|
||||
if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) {
|
||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
||||
@ -3208,23 +3225,9 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
||||
}
|
||||
}, sendSticker: { [weak self] file, sourceNode, sourceRect in
|
||||
if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) {
|
||||
strongSelf.controllerInteraction?.sendSticker(file, true, sourceNode, sourceRect)
|
||||
/*strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
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)])*/
|
||||
return strongSelf.controllerInteraction?.sendSticker(file, true, sourceNode, sourceRect) ?? false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}, unblockPeer: { [weak self] in
|
||||
self?.unblockPeer()
|
||||
@ -3598,17 +3601,26 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
||||
}
|
||||
let rect = node.view.convert(nodeRect, to: strongSelf.view)
|
||||
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
|
||||
slowmodeTooltipController.dismiss()
|
||||
}
|
||||
let slowmodeTooltipController = ChatSlowmodeHintController(strings: strongSelf.presentationData.strings, slowmodeState: slowmodeState)
|
||||
strongSelf.slowmodeTooltipController = slowmodeTooltipController
|
||||
strongSelf.present(slowmodeTooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
|
||||
let slowmodeTooltipController = ChatSlowmodeHintController(strings: strongSelf.presentationData.strings, slowmodeState:
|
||||
slowmodeState)
|
||||
slowmodeTooltipController.presentationArguments = TooltipControllerPresentationArguments(sourceNodeAndRect: {
|
||||
if let strongSelf = self {
|
||||
return (strongSelf.chatDisplayNode, rect)
|
||||
}
|
||||
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()))
|
||||
|
||||
switch self.chatLocation {
|
||||
@ -5254,7 +5266,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
||||
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 {
|
||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
@ -5267,20 +5279,22 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
||||
let updatedMessage = message.withUpdatedReplyToMessageId(replyMessageId)
|
||||
strongSelf.sendMessages([updatedMessage])
|
||||
}
|
||||
}, displaySlowmodeTooltip: { [weak self] node, rect in
|
||||
self?.interfaceInteraction?.displaySlowmodeTooltip(node, rect)
|
||||
})))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dismissMediaRecorder(_ action: ChatFinishMediaRecordingAction) {
|
||||
var updatedAction = action
|
||||
if let _ = self.presentationInterfaceState.slowmodeState {
|
||||
updatedAction = .preview
|
||||
}
|
||||
|
||||
if let audioRecorderValue = self.audioRecorderValue {
|
||||
audioRecorderValue.stop()
|
||||
|
||||
var updatedAction = action
|
||||
if let _ = self.presentationInterfaceState.slowmodeState {
|
||||
updatedAction = .preview
|
||||
}
|
||||
|
||||
switch updatedAction {
|
||||
case .dismiss:
|
||||
break
|
||||
@ -5340,11 +5354,19 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
||||
}
|
||||
self.audioRecorder.set(.single(nil))
|
||||
} else if let videoRecorderValue = self.videoRecorderValue {
|
||||
if case .send = action {
|
||||
if case .send = updatedAction {
|
||||
videoRecorderValue.completeVideo()
|
||||
self.videoRecorder.set(.single(nil))
|
||||
} else {
|
||||
self.videoRecorder.set(.single(nil))
|
||||
if videoRecorderValue.stopVideo() {
|
||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
$0.updatedInputTextPanelState { panelState in
|
||||
return panelState.withUpdatedMediaRecordingState(.video(status: .editing, isLocked: false))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.videoRecorder.set(.single(nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6283,7 +6305,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
||||
break
|
||||
}
|
||||
}, 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
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
}, dismissInput: { [weak self] in
|
||||
|
||||
@ -78,8 +78,8 @@ public final class ChatControllerInteraction {
|
||||
let clickThroughMessage: () -> Void
|
||||
let toggleMessagesSelection: ([MessageId], Bool) -> Void
|
||||
let sendMessage: (String) -> Void
|
||||
let sendSticker: (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Void
|
||||
let sendGif: (FileMediaReference, ASDisplayNode, CGRect) -> Void
|
||||
let sendSticker: (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool
|
||||
let sendGif: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
|
||||
let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool) -> Void
|
||||
let requestMessageActionUrlAuth: (String, MessageId, Int32) -> Void
|
||||
let activateSwitchInline: (PeerId?, String) -> Void
|
||||
@ -123,7 +123,7 @@ public final class ChatControllerInteraction {
|
||||
var stickerSettings: ChatInterfaceStickerSettings
|
||||
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.openPeer = openPeer
|
||||
self.openPeerMention = openPeerMention
|
||||
@ -176,7 +176,7 @@ public final class ChatControllerInteraction {
|
||||
|
||||
static var `default`: ChatControllerInteraction {
|
||||
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: {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
|
||||
|
||||
@ -842,12 +842,14 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
if let strongSelf = self {
|
||||
var menuItems: [PeekControllerMenuItem] = []
|
||||
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 {
|
||||
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 isStarred {
|
||||
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()
|
||||
}
|
||||
}
|
||||
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 {
|
||||
loop: for attribute in item.file.attributes {
|
||||
switch attribute {
|
||||
@ -865,7 +868,9 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: strongSelf.controllerInteraction.navigationController())
|
||||
controller.sendSticker = { file, sourceNode, sourceRect in
|
||||
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))
|
||||
} else {
|
||||
@ -888,15 +894,18 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
} 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: [
|
||||
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 {
|
||||
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 {
|
||||
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 (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: [
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
var menuItems: [PeekControllerMenuItem] = []
|
||||
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 {
|
||||
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 isStarred {
|
||||
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()
|
||||
}
|
||||
}
|
||||
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 {
|
||||
loop: for attribute in item.file.attributes {
|
||||
switch attribute {
|
||||
@ -966,7 +981,9 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: strongSelf.controllerInteraction.navigationController())
|
||||
controller.sendSticker = { file, sourceNode, sourceRect in
|
||||
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))
|
||||
} else {
|
||||
|
||||
@ -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())
|
||||
controller.sendSticker = { fileReference, sourceNode, sourceRect in
|
||||
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))
|
||||
|
||||
@ -68,7 +68,7 @@ final class ChatPanelInterfaceInteraction {
|
||||
let navigateToChat: (PeerId) -> Void
|
||||
let openPeerInfo: () -> Void
|
||||
let togglePeerNotifications: () -> Void
|
||||
let sendContextResult: (ChatContextResultCollection, ChatContextResult) -> Void
|
||||
let sendContextResult: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool
|
||||
let sendBotCommand: (Peer, String) -> Void
|
||||
let sendBotStart: (String?) -> Void
|
||||
let botSwitchChatWithPayload: (PeerId, String) -> Void
|
||||
@ -82,7 +82,7 @@ final class ChatPanelInterfaceInteraction {
|
||||
let displayVideoUnmuteTip: (CGPoint?) -> Void
|
||||
let switchMediaRecordingMode: () -> Void
|
||||
let setupMessageAutoremoveTimeout: () -> Void
|
||||
let sendSticker: (FileMediaReference, ASDisplayNode, CGRect) -> Void
|
||||
let sendSticker: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
|
||||
let unblockPeer: () -> Void
|
||||
let pinMessage: (MessageId) -> Void
|
||||
let unpinMessage: () -> Void
|
||||
@ -108,7 +108,7 @@ final class ChatPanelInterfaceInteraction {
|
||||
let displaySlowmodeTooltip: (ASDisplayNode, CGRect) -> Void
|
||||
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.setupEditMessage = setupEditMessage
|
||||
self.beginMessageSelection = beginMessageSelection
|
||||
|
||||
@ -22,6 +22,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
private var currentLayout: (CGFloat, CGFloat, CGFloat)?
|
||||
private var currentMessage: Message?
|
||||
private var previousMediaReference: AnyMediaReference?
|
||||
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
|
||||
private let queue = Queue()
|
||||
|
||||
@ -89,6 +91,10 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.addSubnode(self.separatorNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.fetchDisposable.dispose()
|
||||
}
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
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 updatedFetchMediaSignal: Signal<FetchResourceSourceType, FetchResourceError>?
|
||||
if mediaUpdated {
|
||||
if let updatedMediaReference = updatedMediaReference, imageDimensions != nil {
|
||||
if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) {
|
||||
updateImageSignal = chatMessagePhotoThumbnail(account: context.account, photoReference: imageReference)
|
||||
} 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)
|
||||
} else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
|
||||
updateImageSignal = chatWebpageSnippetFile(account: context.account, fileReference: fileReference, representation: iconImageRepresentation)
|
||||
@ -247,6 +257,9 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
if let updateImageSignal = updateImageSignal {
|
||||
strongSelf.imageNode.setSignal(updateImageSignal)
|
||||
}
|
||||
if let updatedFetchMediaSignal = updatedFetchMediaSignal {
|
||||
strongSelf.fetchDisposable.set(updatedFetchMediaSignal.start())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,8 @@ final class ChatRecentActionsController: TelegramController {
|
||||
}, navigateToChat: { _ in
|
||||
}, openPeerInfo: {
|
||||
}, togglePeerNotifications: {
|
||||
}, sendContextResult: { _, _ in
|
||||
}, sendContextResult: { _, _, _, _ in
|
||||
return false
|
||||
}, sendBotCommand: { _, _ in
|
||||
}, sendBotStart: { _ in
|
||||
}, botSwitchChatWithPayload: { _, _ in
|
||||
@ -79,6 +80,7 @@ final class ChatRecentActionsController: TelegramController {
|
||||
}, switchMediaRecordingMode: {
|
||||
}, setupMessageAutoremoveTimeout: {
|
||||
}, sendSticker: { _, _, _ in
|
||||
return false
|
||||
}, unblockPeer: {
|
||||
}, pinMessage: { _ in
|
||||
}, unpinMessage: {
|
||||
|
||||
@ -181,7 +181,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
self?.openPeerMention(name)
|
||||
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame in
|
||||
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)
|
||||
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
|
||||
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
|
||||
|
||||
@ -3,6 +3,7 @@ import UIKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
|
||||
enum ChatRecentActionsEntryContentIndex: Int32 {
|
||||
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: [])
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ final class ChatSendButtonRadialStatusNode: ASDisplayNode {
|
||||
|
||||
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.lineCapStyle = .round
|
||||
path.stroke()
|
||||
@ -98,34 +98,98 @@ final class ChatSendButtonRadialStatusNode: ASDisplayNode {
|
||||
self?.updateProgress()
|
||||
}, queue: .mainQueue())
|
||||
self.updateTimer?.start()
|
||||
/*if progress.isNaN || !progress.isFinite {
|
||||
self.pop_removeAnimation(forKey: "progress")
|
||||
self.effectiveProgress = 0.0
|
||||
} 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.updateTimer?.invalidate()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ final class ChatSlowmodeHintController: TooltipController {
|
||||
init(strings: PresentationStrings, slowmodeState: ChatSlowmodeState) {
|
||||
self.strings = strings
|
||||
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) {
|
||||
|
||||
@ -125,6 +125,8 @@ class ChatSlowmodeItemNode: ListViewItemNode {
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
|
||||
let sliderView = TGPhotoEditorSliderView()
|
||||
sliderView.enablePanHandling = true
|
||||
sliderView.trackCornerRadius = 1.0
|
||||
@ -146,6 +148,7 @@ class ChatSlowmodeItemNode: ListViewItemNode {
|
||||
}
|
||||
|
||||
sliderView.value = CGFloat(value)
|
||||
self.reportedValue = item.value
|
||||
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
sliderView.backColor = item.theme.list.disclosureArrowColor
|
||||
sliderView.startColor = item.theme.list.disclosureArrowColor
|
||||
@ -183,13 +186,9 @@ class ChatSlowmodeItemNode: ListViewItemNode {
|
||||
|
||||
let valueString: String
|
||||
if value == 0 {
|
||||
valueString = "Off"
|
||||
} else if value < 60 {
|
||||
valueString = "\(value)"
|
||||
} else if value < 60 * 60 {
|
||||
valueString = "\(value / 60)m"
|
||||
valueString = item.strings.Profile_MessageLifetimeForever
|
||||
} 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()))
|
||||
textLayoutAndApply.append((textLayout, textApply))
|
||||
@ -300,5 +299,9 @@ class ChatSlowmodeItemNode: ListViewItemNode {
|
||||
item.updated(value)
|
||||
}
|
||||
}
|
||||
|
||||
func cancelDragging() {
|
||||
self.sliderView?.cancelTracking(with: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
|
||||
|
||||
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
|
||||
if let current = self.sendButtonRadialStatusNode {
|
||||
sendButtonRadialStatusNode = current
|
||||
@ -51,7 +51,12 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
|
||||
|
||||
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
|
||||
} else {
|
||||
if let sendButtonRadialStatusNode = self.sendButtonRadialStatusNode {
|
||||
|
||||
@ -1088,11 +1088,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
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.slowmodePlaceholderNode?.isHidden = inputHasText
|
||||
} else {
|
||||
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))
|
||||
@ -1170,14 +1171,24 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
|
||||
private func updateTextNodeText(animated: Bool) {
|
||||
var hasText = false
|
||||
var inputHasText = false
|
||||
var hideMicButton = false
|
||||
if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 {
|
||||
hasText = true
|
||||
inputHasText = 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()
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,8 @@ import TelegramPresentationData
|
||||
final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
|
||||
private var theme: PresentationTheme
|
||||
private let iconNode: ASImageNode
|
||||
private let iconArrowContainerNode: ASDisplayNode
|
||||
private let iconArrowNode: ASImageNode
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private var slowmodeState: ChatSlowmodeState?
|
||||
@ -21,13 +23,23 @@ final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
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()
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.iconArrowContainerNode)
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
@ -59,6 +71,20 @@ final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
|
||||
let timestamp = Int32(Date().timeIntervalSince1970)
|
||||
let timeout = max(0, timeoutTimestamp - timestamp)
|
||||
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 {
|
||||
@ -71,9 +97,14 @@ final class ChatTextInputSlowmodePlaceholderNode: ASDisplayNode {
|
||||
|
||||
var leftInset: CGFloat = 0.0
|
||||
if let image = self.iconNode.image {
|
||||
let imageSize = image.size.aspectFitted(CGSize(width: 20.0, height: 20.0))
|
||||
leftInset += imageSize.width + 2.0
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: imageSize)
|
||||
let imageSize = image.size
|
||||
leftInset += imageSize.width + 4.0
|
||||
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)
|
||||
|
||||
@ -591,7 +591,7 @@ private final class DeviceContactDataManagerImpl {
|
||||
var importableContactData: [String: (DeviceContactStableId, ImportableDeviceContactData)] = [:]
|
||||
for (stableId, basicData) in self.stableIdToBasicContactData {
|
||||
for phoneNumber in basicData.phoneNumbers {
|
||||
let normalizedNumber = phoneNumber.value
|
||||
let normalizedNumber = formatPhoneNumber(phoneNumber.value)
|
||||
var replace = false
|
||||
if let current = importableContactData[normalizedNumber] {
|
||||
if stableId < current.0 {
|
||||
|
||||
@ -35,7 +35,7 @@ private struct HorizontalListContextResultsChatInputContextPanelEntry: Comparabl
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -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 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 case let .internalReference(internalReference) = item.result, let file = internalReference.file, file.isSticker {
|
||||
var menuItems: [PeekControllerMenuItem] = []
|
||||
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: {
|
||||
item.resultSelected(item.result)
|
||||
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { _, _ in
|
||||
return item.resultSelected(item.result, itemNode, itemNode.bounds)
|
||||
}))
|
||||
for case let .Sticker(_, packReference, _) in file.attributes {
|
||||
guard let packReference = packReference else {
|
||||
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 {
|
||||
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: strongSelf.interfaceInteraction?.getNavigationController())
|
||||
controller.sendSticker = { file, sourceNode, sourceRect in
|
||||
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?.presentController(controller, nil)
|
||||
}
|
||||
return true
|
||||
}))
|
||||
}
|
||||
selectedItemNodeAndContent = (itemNode, StickerPreviewPeekContent(account: item.account, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: menuItems))
|
||||
} else {
|
||||
var menuItems: [PeekControllerMenuItem] = []
|
||||
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()
|
||||
return true
|
||||
}))
|
||||
}
|
||||
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: {
|
||||
item.resultSelected(item.result)
|
||||
menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { _, _ in
|
||||
return item.resultSelected(item.result, itemNode, itemNode.bounds)
|
||||
}))
|
||||
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 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 {
|
||||
interfaceInteraction.sendContextResult(results, result)
|
||||
return interfaceInteraction.sendContextResult(results, result, node, rect)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
self.currentEntries = entries
|
||||
|
||||
@ -10,11 +10,11 @@ import AVFoundation
|
||||
final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
|
||||
let account: Account
|
||||
let result: ChatContextResult
|
||||
let resultSelected: (ChatContextResult) -> Void
|
||||
let resultSelected: (ChatContextResult, ASDisplayNode, CGRect) -> Bool
|
||||
|
||||
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.result = result
|
||||
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)
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,10 +164,10 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
|
||||
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
|
||||
var menuItems: [PeekControllerMenuItem] = []
|
||||
menuItems = [
|
||||
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: {
|
||||
controllerInteraction.sendSticker(.standalone(media: item.file), true, itemNode, itemNode.bounds)
|
||||
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { _, _ in
|
||||
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 isStarred {
|
||||
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()
|
||||
}
|
||||
}
|
||||
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 {
|
||||
loop: for attribute in item.file.attributes {
|
||||
switch attribute {
|
||||
@ -185,7 +186,9 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
|
||||
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: controllerInteraction.navigationController())
|
||||
controller.sendSticker = { file, sourceNode, sourceRect in
|
||||
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
|
||||
}
|
||||
}
|
||||
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))
|
||||
} else {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
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)
|
||||
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
|
||||
legacyController.lockOrientation = true
|
||||
@ -105,11 +105,21 @@ func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect,
|
||||
if peerId.namespace != Namespaces.Peer.SecretChat {
|
||||
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: {
|
||||
return nil
|
||||
}, parentController: baseController, controlsFrame: panelFrame, isAlreadyLocked: {
|
||||
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
|
||||
guard let videoUrl = videoUrl else {
|
||||
return
|
||||
@ -169,6 +179,12 @@ func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect,
|
||||
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)
|
||||
|
||||
let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in
|
||||
|
||||
@ -196,7 +196,7 @@ func chatMessagePreviewControllerData(context: AccountContext, message: Message,
|
||||
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) {
|
||||
switch mediaData {
|
||||
case let .url(url):
|
||||
|
||||
@ -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 }
|
||||
switch resolvedUrl {
|
||||
case let .externalUrl(url):
|
||||
|
||||
@ -66,7 +66,9 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec
|
||||
}, toggleMessagesSelection: { _, _ in
|
||||
}, sendMessage: { _ in
|
||||
}, sendSticker: { _, _, _, _ in
|
||||
return false
|
||||
}, sendGif: { _, _, _ in
|
||||
return false
|
||||
}, requestMessageActionCallback: { _, _, _ in
|
||||
}, requestMessageActionUrlAuth: { _, _, _ in
|
||||
}, activateSwitchInline: { _, _ in
|
||||
|
||||
@ -181,8 +181,10 @@ public class PeerMediaCollectionController: TelegramController {
|
||||
strongSelf.updateInterfaceState(animated: true, { $0.withToggledSelectedMessages(ids, value: value) })
|
||||
}
|
||||
}, sendMessage: { _ in
|
||||
},sendSticker: { _, _, _, _ in
|
||||
}, sendSticker: { _, _, _, _ in
|
||||
return false
|
||||
}, sendGif: { _, _, _ in
|
||||
return false
|
||||
}, requestMessageActionCallback: { _, _, _ in
|
||||
}, requestMessageActionUrlAuth: { _, _, _ in
|
||||
}, activateSwitchInline: { _, _ in
|
||||
@ -336,7 +338,8 @@ public class PeerMediaCollectionController: TelegramController {
|
||||
}, navigateToChat: { _ in
|
||||
}, openPeerInfo: {
|
||||
}, togglePeerNotifications: {
|
||||
}, sendContextResult: { _, _ in
|
||||
}, sendContextResult: { _, _, _, _ in
|
||||
return false
|
||||
}, sendBotCommand: { _, _ in
|
||||
}, sendBotStart: { _ in
|
||||
}, botSwitchChatWithPayload: { _, _ in
|
||||
@ -351,6 +354,7 @@ public class PeerMediaCollectionController: TelegramController {
|
||||
}, switchMediaRecordingMode: {
|
||||
}, setupMessageAutoremoveTimeout: {
|
||||
}, sendSticker: { _, _, _ in
|
||||
return false
|
||||
}, unblockPeer: {
|
||||
}, pinMessage: { _ in
|
||||
}, unpinMessage: {
|
||||
|
||||
Binary file not shown.
@ -38,13 +38,17 @@ final class StickerPackPreviewController: ViewController {
|
||||
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
var sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Void)? {
|
||||
var sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? {
|
||||
didSet {
|
||||
if self.isNodeLoaded {
|
||||
if let sendSticker = self.sendSticker {
|
||||
self.controllerNode.sendSticker = { [weak self] file, sourceNode, sourceRect in
|
||||
sendSticker(file, sourceNode, sourceRect)
|
||||
self?.dismiss()
|
||||
if sendSticker(file, sourceNode, sourceRect) {
|
||||
self?.dismiss()
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.controllerNode.sendSticker = nil
|
||||
@ -135,8 +139,12 @@ final class StickerPackPreviewController: ViewController {
|
||||
}
|
||||
if let sendSticker = self.sendSticker {
|
||||
self.controllerNode.sendSticker = { [weak self] file, sourceNode, sourceRect in
|
||||
sendSticker(file, sourceNode, sourceRect)
|
||||
self?.dismiss()
|
||||
if sendSticker(file, sourceNode, sourceRect) {
|
||||
self?.dismiss()
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
let account = self.context.account
|
||||
|
||||
@ -68,7 +68,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|
||||
var presentInGlobalOverlay: ((ViewController, Any?) -> Void)?
|
||||
var dismiss: (() -> Void)?
|
||||
var cancel: (() -> Void)?
|
||||
var sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Void)?
|
||||
var sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
|
||||
|
||||
let ready = Promise<Bool>()
|
||||
private var didSetReady = false
|
||||
@ -200,13 +200,15 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|
||||
var menuItems: [PeekControllerMenuItem] = []
|
||||
if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
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 {
|
||||
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 isStarred {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}))
|
||||
menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: {}))
|
||||
return true
|
||||
}))
|
||||
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))
|
||||
} else {
|
||||
|
||||
@ -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())
|
||||
controller.sendSticker = { [weak self] fileReference, sourceNode, sourceRect in
|
||||
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))
|
||||
|
||||
@ -140,10 +140,10 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
|
||||
var menuItems: [PeekControllerMenuItem] = []
|
||||
menuItems = [
|
||||
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: {
|
||||
controllerInteraction.sendSticker(.standalone(media: item.file), true, itemNode, itemNode.bounds)
|
||||
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { _, _ in
|
||||
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 isStarred {
|
||||
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()
|
||||
}
|
||||
}
|
||||
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 {
|
||||
loop: for attribute in item.file.attributes {
|
||||
switch attribute {
|
||||
@ -161,7 +162,9 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: controllerInteraction.navigationController())
|
||||
controller.sendSticker = { file, sourceNode, sourceRect in
|
||||
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))
|
||||
} else {
|
||||
|
||||
@ -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 {
|
||||
case let .action(theme, title):
|
||||
return VerticalListContextResultsChatInputPanelButtonItem(theme: theme, title: title, pressed: actionSelected)
|
||||
@ -106,7 +106,7 @@ private struct VerticalListContextResultsChatInputContextPanelTransition {
|
||||
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 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 {
|
||||
interfaceInteraction.botSwitchChatWithPayload(results.botId, switchPeer.startParam)
|
||||
}
|
||||
}, resultSelected: { [weak self] result in
|
||||
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
||||
interfaceInteraction.sendContextResult(results, result)
|
||||
}
|
||||
}, resultSelected: { [weak self] result, node, rect in
|
||||
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
||||
strongSelf.listView.clearHighlightAnimated(true)
|
||||
return interfaceInteraction.sendContextResult(results, result, node, rect)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
self.currentEntries = to
|
||||
self.enqueueTransition(transition, firstTime: firstTime)
|
||||
}
|
||||
|
||||
|
||||
|
||||
private func enqueueTransition(_ transition: VerticalListContextResultsChatInputContextPanelTransition, firstTime: Bool) {
|
||||
enqueuedTransitions.append((transition, firstTime))
|
||||
|
||||
|
||||
@ -11,11 +11,11 @@ final class VerticalListContextResultsChatInputPanelItem: ListViewItem {
|
||||
fileprivate let account: Account
|
||||
fileprivate let theme: PresentationTheme
|
||||
fileprivate let result: ChatContextResult
|
||||
private let resultSelected: (ChatContextResult) -> Void
|
||||
fileprivate let resultSelected: (ChatContextResult, ASDisplayNode, CGRect) -> Bool
|
||||
|
||||
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.theme = theme
|
||||
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)
|
||||
@ -96,6 +92,8 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
|
||||
|
||||
private var currentIconImageResource: TelegramMediaResource?
|
||||
|
||||
private var item: VerticalListContextResultsChatInputPanelItem?
|
||||
|
||||
init() {
|
||||
self.titleNode = TextNode()
|
||||
self.textNode = TextNode()
|
||||
@ -267,6 +265,8 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
|
||||
|
||||
return (nodeLayout, { _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
||||
strongSelf.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user