Merge commit 'b1f40bf0aafc9cf0fceec175339d4d0329a92bbb'

This commit is contained in:
Ali 2023-08-30 22:03:24 +04:00
commit 5aa90ba419
42 changed files with 1244 additions and 303 deletions

View File

@ -9863,3 +9863,10 @@ Sorry for the inconvenience.";
"Story.ViewList.ViewerCount_any" = "%d Viewers"; "Story.ViewList.ViewerCount_any" = "%d Viewers";
"AuthSessions.MessageApp" = "You allowed this bot to message you when you opened %@."; "AuthSessions.MessageApp" = "You allowed this bot to message you when you opened %@.";
"Story.Privacy.PostStoryAs" = "Post Story As";
"Story.Privacy.PostStoryAsHeader" = "POST STORY AS";
"Story.Privacy.KeepOnChannelPage" = "Post to Channel Profile";
"Story.Privacy.KeepOnChannelPageInfo" = "Keep this story on channel profile even after it expires in %@.";
"Story.Editor.TooltipPremiumReaction" = "Subscribe to [Telegram Premium]() to use this reaction.";

View File

@ -446,8 +446,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
public var sendPressed: ((NSAttributedString?) -> Void)? public var sendPressed: ((NSAttributedString?) -> Void)?
public var focusUpdated: ((Bool) -> Void)? public var focusUpdated: ((Bool) -> Void)?
public var heightUpdated: ((Bool) -> Void)? public var heightUpdated: ((Bool) -> Void)?
public var timerUpdated: ((NSNumber?) -> Void)?
public func updateLayoutSize(_ size: CGSize, sideInset: CGFloat, animated: Bool) -> CGFloat { public func updateLayoutSize(_ size: CGSize, keyboardHeight: CGFloat, sideInset: CGFloat, animated: Bool) -> CGFloat {
guard let presentationInterfaceState = self.presentationInterfaceState else { guard let presentationInterfaceState = self.presentationInterfaceState else {
return 0.0 return 0.0
} }
@ -460,6 +461,16 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
} }
} }
public func setTimeout(_ timeout: Int32) {
}
public func animate(_ view: UIView, frame: CGRect) {
}
public func onAnimateOut() {
}
public func dismissInput() { public func dismissInput() {
self.ensureUnfocused() self.ensureUnfocused()
} }

View File

@ -266,16 +266,16 @@ private final class VisualMediaItemNode: ASDisplayNode {
switch status { switch status {
case let .Fetching(_, progress): case let .Fetching(_, progress):
let progressString = String(format: "%d%%", Int(progress * 100.0)) let progressString = String(format: "%d%%", Int(progress * 100.0))
badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: progressString)) badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: progressString), iconName: nil)
mediaDownloadState = .compactFetching(progress: 0.0) mediaDownloadState = .compactFetching(progress: 0.0)
case .Local: case .Local:
badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil)
case .Remote, .Paused: case .Remote, .Paused:
badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil)
mediaDownloadState = .compactRemote mediaDownloadState = .compactRemote
} }
} else { } else {
badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil)
} }
strongSelf.mediaBadgeNode.update(theme: nil, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false) strongSelf.mediaBadgeNode.update(theme: nil, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false)

View File

@ -18,13 +18,13 @@ public enum ChatMessageInteractiveMediaDownloadState: Equatable {
} }
public enum ChatMessageInteractiveMediaBadgeContent: Equatable { public enum ChatMessageInteractiveMediaBadgeContent: Equatable {
case text(inset: CGFloat, backgroundColor: UIColor, foregroundColor: UIColor, text: NSAttributedString) case text(inset: CGFloat, backgroundColor: UIColor, foregroundColor: UIColor, text: NSAttributedString, iconName: String?)
case mediaDownload(backgroundColor: UIColor, foregroundColor: UIColor, duration: String, size: String?, muted: Bool, active: Bool) case mediaDownload(backgroundColor: UIColor, foregroundColor: UIColor, duration: String, size: String?, muted: Bool, active: Bool)
public static func ==(lhs: ChatMessageInteractiveMediaBadgeContent, rhs: ChatMessageInteractiveMediaBadgeContent) -> Bool { public static func ==(lhs: ChatMessageInteractiveMediaBadgeContent, rhs: ChatMessageInteractiveMediaBadgeContent) -> Bool {
switch lhs { switch lhs {
case let .text(lhsInset, lhsBackgroundColor, lhsForegroundColor, lhsText): case let .text(lhsInset, lhsBackgroundColor, lhsForegroundColor, lhsText, lhsIconName):
if case let .text(rhsInset, rhsBackgroundColor, rhsForegroundColor, rhsText) = rhs, lhsInset.isEqual(to: rhsInset), lhsBackgroundColor.isEqual(rhsBackgroundColor), lhsForegroundColor.isEqual(rhsForegroundColor), lhsText.isEqual(to: rhsText) { if case let .text(rhsInset, rhsBackgroundColor, rhsForegroundColor, rhsText, rhsIconName) = rhs, lhsInset.isEqual(to: rhsInset), lhsBackgroundColor.isEqual(rhsBackgroundColor), lhsForegroundColor.isEqual(rhsForegroundColor), lhsText.isEqual(to: rhsText), lhsIconName == rhsIconName {
return true return true
} else { } else {
return false return false
@ -48,6 +48,7 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
private var previousContentSize: CGSize? private var previousContentSize: CGSize?
private var backgroundNodeColor: UIColor? private var backgroundNodeColor: UIColor?
private var foregroundColor: UIColor? private var foregroundColor: UIColor?
private var iconName: String?
private let backgroundNode: ASImageNode private let backgroundNode: ASImageNode
private let durationNode: ASTextNode private let durationNode: ASTextNode
@ -107,14 +108,18 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
} }
switch content { switch content {
case let .text(inset, backgroundColor, foregroundColor, text): case let .text(inset, backgroundColor, foregroundColor, text, iconName):
transition = .immediate transition = .immediate
if self.backgroundNodeColor != backgroundColor { if self.backgroundNodeColor != backgroundColor {
self.backgroundNodeColor = backgroundColor self.backgroundNodeColor = backgroundColor
self.backgroundNode.image = generateStretchableFilledCircleImage(radius: 9.0, color: backgroundColor) self.backgroundNode.image = generateStretchableFilledCircleImage(radius: 9.0, color: backgroundColor)
} }
let convertedText = NSMutableAttributedString(string: text.string, attributes: [.font: font, .foregroundColor: foregroundColor]) var textFont = font
if iconName != nil {
textFont = boldFont
}
let convertedText = NSMutableAttributedString(string: text.string, attributes: [.font: textFont, .foregroundColor: foregroundColor])
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), options: []) { attributes, range, _ in text.enumerateAttributes(in: NSRange(location: 0, length: text.length), options: []) { attributes, range, _ in
if let _ = attributes[ChatTextInputAttributes.bold] { if let _ = attributes[ChatTextInputAttributes.bold] {
convertedText.addAttribute(.font, value: boldFont, range: range) convertedText.addAttribute(.font, value: boldFont, range: range)
@ -122,13 +127,34 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
} }
self.durationNode.attributedText = convertedText self.durationNode.attributedText = convertedText
let durationSize = self.durationNode.measure(CGSize(width: 160.0, height: 160.0)) let durationSize = self.durationNode.measure(CGSize(width: 160.0, height: 160.0))
self.durationNode.frame = CGRect(x: 7.0 + inset, y: 3.0, width: durationSize.width, height: durationSize.height) self.durationNode.frame = CGRect(x: 7.0 + inset, y: 2.0 + UIScreenPixel, width: durationSize.width, height: durationSize.height)
currentContentSize = CGSize(width: widthForString(text.string) + 14.0 + inset, height: 18.0) currentContentSize = CGSize(width: widthForString(text.string) + 14.0 + inset, height: 18.0)
if let iconName {
let iconNode: ASImageNode
if let current = self.iconNode {
iconNode = current
} else {
iconNode = ASImageNode()
self.iconNode = iconNode
self.backgroundNode.addSubnode(iconNode)
}
if self.foregroundColor != foregroundColor || self.iconName != iconName {
self.foregroundColor = foregroundColor
self.iconName = iconName
iconNode.image = generateTintedImage(image: UIImage(bundleImageName: iconName), color: foregroundColor)
}
transition.updateAlpha(node: iconNode, alpha: 1.0)
transition.updateTransformScale(node: iconNode, scale: 1.0)
iconNode.frame = CGRect(x: 3.0, y: 2.0, width: 12.0, height: 14.0)
} else {
if let iconNode = self.iconNode { if let iconNode = self.iconNode {
transition.updateTransformScale(node: iconNode, scale: 0.001) transition.updateTransformScale(node: iconNode, scale: 0.001)
transition.updateAlpha(node: iconNode, alpha: 0.0) transition.updateAlpha(node: iconNode, alpha: 0.0)
} }
}
case let .mediaDownload(backgroundColor, foregroundColor, duration, size, muted, active): case let .mediaDownload(backgroundColor, foregroundColor, duration, size, muted, active):
if self.backgroundNodeColor != backgroundColor { if self.backgroundNodeColor != backgroundColor {
self.backgroundNodeColor = backgroundColor self.backgroundNodeColor = backgroundColor

View File

@ -89,7 +89,7 @@ extension UIView {
} }
open class ComponentState { open class ComponentState {
var _updated: ((Transition) -> Void)? open var _updated: ((Transition) -> Void)?
var isUpdated: Bool = false var isUpdated: Bool = false
public init() { public init() {

View File

@ -75,6 +75,8 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
public var getEntityAdditionalScale: () -> CGFloat = { return 1.0 } public var getEntityAdditionalScale: () -> CGFloat = { return 1.0 }
public var getAvailableReactions: () -> [ReactionItem] = { return [] } public var getAvailableReactions: () -> [ReactionItem] = { return [] }
public var present: (ViewController) -> Void = { _ in }
public var push: (ViewController) -> Void = { _ in }
public var hasSelectionChanged: (Bool) -> Void = { _ in } public var hasSelectionChanged: (Bool) -> Void = { _ in }
var selectionChanged: (DrawingEntity?) -> Void = { _ in } var selectionChanged: (DrawingEntity?) -> Void = { _ in }

View File

@ -33,7 +33,7 @@ public final class DrawingStickerEntityView: DrawingEntityView {
private var outlineView: UIImageView? private var outlineView: UIImageView?
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
private var animationNode: AnimatedStickerNode? private var animationNode: DefaultAnimatedStickerNodeImpl?
private var videoNode: UniversalVideoNode? private var videoNode: UniversalVideoNode?
private var didSetUpAnimationNode = false private var didSetUpAnimationNode = false
@ -125,6 +125,24 @@ public final class DrawingStickerEntityView: DrawingEntityView {
} }
} }
private func updateAnimationColor() {
let color: UIColor?
if case let .file(file, type) = self.stickerEntity.content, file.isCustomTemplateEmoji {
if case let .reaction(_, style) = type {
if case .white = style {
color = UIColor(rgb: 0x000000)
} else {
color = UIColor(rgb: 0xffffff)
}
} else {
color = UIColor(rgb: 0xffffff)
}
} else {
color = nil
}
self.animationNode?.dynamicColor = color
}
private func setup() { private func setup() {
if let file = self.file { if let file = self.file {
if let dimensions = file.dimensions { if let dimensions = file.dimensions {
@ -149,9 +167,7 @@ public final class DrawingStickerEntityView: DrawingEntityView {
} }
self.addSubnode(animationNode) self.addSubnode(animationNode)
if file.isCustomTemplateEmoji { self.updateAnimationColor()
animationNode.dynamicColor = UIColor(rgb: 0xffffff)
}
if !self.stickerEntity.isAnimated { if !self.stickerEntity.isAnimated {
self.imageNode.isHidden = true self.imageNode.isHidden = true
@ -525,50 +541,21 @@ public final class DrawingStickerEntityView: DrawingEntityView {
} }
reactionContextNode.premiumReactionsSelected = { [weak self] file in reactionContextNode.premiumReactionsSelected = { [weak self] file in
let _ = self guard let self, let file else {
let _ = file return
// guard let self, let component = self.component else { }
// return
// } let context = self.context
// let presentationData = context.sharedContext.currentPresentationData.with { $0 }
// guard let file else {
// let context = component.context let controller = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.Story_Editor_TooltipPremiumReaction, undoText: nil, customAction: nil), elevatedLayout: true, animateInAsReplacement: false, blurred: true, action: { [weak self] action in
// var replaceImpl: ((ViewController) -> Void)? if case .info = action, let self {
// let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, forceDark: true, action: { let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil)
// let controller = PremiumIntroScreen(context: context, source: .reactions) self.containerView?.push(controller)
// replaceImpl?(controller) }
// }) return false
// controller.disposed = { [weak self] in })
// self?.updateIsProgressPaused() self.containerView?.present(controller)
// }
// replaceImpl = { [weak controller] c in
// controller?.replace(with: c)
// }
// component.controller()?.push(controller)
// return
// }
//
// let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
// let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: component.context, file: file, loop: true, title: nil, text: presentationData.strings.Chat_PremiumReactionToastTitle, undoText: presentationData.strings.Chat_PremiumReactionToastAction, customAction: { [weak self] in
// guard let self, let component = self.component else {
// return
// }
//
// let context = component.context
// var replaceImpl: ((ViewController) -> Void)?
// let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, forceDark: true, action: {
// let controller = PremiumIntroScreen(context: context, source: .reactions)
// replaceImpl?(controller)
// })
// controller.disposed = { [weak self] in
// self?.updateIsProgressPaused()
// }
// replaceImpl = { [weak controller] c in
// controller?.replace(with: c)
// }
// component.controller()?.push(controller)
// }), elevatedLayout: false, animateInAsReplacement: false, blurred: true, action: { _ in true })
// component.controller()?.present(undoController, in: .current)
} }
let anchorRect = self.convert(self.bounds, to: superview).offsetBy(dx: 0.0, dy: -20.0) let anchorRect = self.convert(self.bounds, to: superview).offsetBy(dx: 0.0, dy: -20.0)
@ -639,6 +626,7 @@ public final class DrawingStickerEntityView: DrawingEntityView {
self.outlineView?.tintColor = UIColor(rgb: 0x000000, alpha: 0.5) self.outlineView?.tintColor = UIColor(rgb: 0x000000, alpha: 0.5)
} }
} }
self.updateAnimationColor()
let isReaction = self.isReaction let isReaction = self.isReaction
let staticTransform = CATransform3DMakeScale(self.stickerEntity.mirrored ? -1.0 : 1.0, 1.0, 1.0) let staticTransform = CATransform3DMakeScale(self.stickerEntity.mirrored ? -1.0 : 1.0, 1.0, 1.0)

View File

@ -22,18 +22,22 @@
@property (nonatomic, copy) void (^panelFocused)(void); @property (nonatomic, copy) void (^panelFocused)(void);
@property (nonatomic, copy) void (^finishedWithCaption)(NSAttributedString *caption); @property (nonatomic, copy) void (^finishedWithCaption)(NSAttributedString *caption);
@property (nonatomic, copy) void (^keyboardHeightChanged)(CGFloat keyboardHeight, NSTimeInterval duration, NSInteger animationCurve); @property (nonatomic, copy) void (^keyboardHeightChanged)(CGFloat keyboardHeight, NSTimeInterval duration, NSInteger animationCurve);
@property (nonatomic, copy) void (^timerUpdated)(NSNumber *timeout);
- (void)createInputPanelIfNeeded; - (void)createInputPanelIfNeeded;
- (void)beginEditing; - (void)beginEditing;
- (void)enableDismissal; - (void)enableDismissal;
- (void)onAnimateOut;
- (void)destroy; - (void)destroy;
@property (nonatomic, strong) NSAttributedString *caption; @property (nonatomic, strong) NSAttributedString *caption;
- (void)setCaption:(NSAttributedString *)caption animated:(bool)animated; - (void)setCaption:(NSAttributedString *)caption animated:(bool)animated;
- (void)setCaptionPanelHidden:(bool)hidden animated:(bool)animated; - (void)setCaptionPanelHidden:(bool)hidden animated:(bool)animated;
- (void)setTimeout:(int32_t)timeout;
- (void)updateLayoutWithFrame:(CGRect)frame edgeInsets:(UIEdgeInsets)edgeInsets animated:(bool)animated; - (void)updateLayoutWithFrame:(CGRect)frame edgeInsets:(UIEdgeInsets)edgeInsets animated:(bool)animated;
@end @end

View File

@ -22,15 +22,22 @@
@property (nonatomic, readonly) UIView * _Nonnull view; @property (nonatomic, readonly) UIView * _Nonnull view;
- (void)setTimeout:(int32_t)timeout;
- (NSAttributedString * _Nonnull)caption; - (NSAttributedString * _Nonnull)caption;
- (void)setCaption:(NSAttributedString * _Nullable)caption; - (void)setCaption:(NSAttributedString * _Nullable)caption;
- (void)dismissInput; - (void)dismissInput;
- (void)animateView:(UIView * _Nonnull)view frame:(CGRect)frame;
- (void)onAnimateOut;
@property (nonatomic, copy) void(^ _Nullable sendPressed)(NSAttributedString * _Nullable string); @property (nonatomic, copy) void(^ _Nullable sendPressed)(NSAttributedString * _Nullable string);
@property (nonatomic, copy) void(^ _Nullable focusUpdated)(BOOL focused); @property (nonatomic, copy) void(^ _Nullable focusUpdated)(BOOL focused);
@property (nonatomic, copy) void(^ _Nullable heightUpdated)(BOOL animated); @property (nonatomic, copy) void(^ _Nullable heightUpdated)(BOOL animated);
@property (nonatomic, copy) void(^ _Nullable timerUpdated)(NSNumber * _Nullable value);
- (CGFloat)updateLayoutSize:(CGSize)size sideInset:(CGFloat)sideInset animated:(bool)animated; - (CGFloat)updateLayoutSize:(CGSize)size keyboardHeight:(CGFloat)keyboardHeight sideInset:(CGFloat)sideInset animated:(bool)animated;
- (CGFloat)baseHeight; - (CGFloat)baseHeight;
@end @end

View File

@ -42,6 +42,28 @@
#import <LegacyComponents/TGPhotoCaptionInputMixin.h> #import <LegacyComponents/TGPhotoCaptionInputMixin.h>
@interface TGMediaPickerGalleryWrapperView: UIView
{
}
@end
@implementation TGMediaPickerGalleryWrapperView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
__block UIView *result = nil;
[self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull view, NSUInteger idx, BOOL * _Nonnull stop) {
UIView *hitTestView = [view hitTest:[self convertPoint:point toView:view] withEvent:event];
if (hitTestView != nil) {
*stop = true;
result = hitTestView;
}
}];
return result;
}
@end
@interface TGMediaPickerGalleryInterfaceView () <ASWatcher> @interface TGMediaPickerGalleryInterfaceView () <ASWatcher>
{ {
id<TGModernGalleryItem> _currentItem; id<TGModernGalleryItem> _currentItem;
@ -121,7 +143,7 @@
_itemHeaderViews = [[NSMutableArray alloc] init]; _itemHeaderViews = [[NSMutableArray alloc] init];
_itemFooterViews = [[NSMutableArray alloc] init]; _itemFooterViews = [[NSMutableArray alloc] init];
_wrapperView = [[UIView alloc] initWithFrame:CGRectZero]; _wrapperView = [[TGMediaPickerGalleryWrapperView alloc] initWithFrame:CGRectZero];
[self addSubview:_wrapperView]; [self addSubview:_wrapperView];
_headerWrapperView = [[UIView alloc] init]; _headerWrapperView = [[UIView alloc] init];
@ -148,6 +170,8 @@
strongSelf->_portraitToolbarView.doneButton.userInteractionEnabled = false; strongSelf->_portraitToolbarView.doneButton.userInteractionEnabled = false;
strongSelf->_landscapeToolbarView.doneButton.userInteractionEnabled = false; strongSelf->_landscapeToolbarView.doneButton.userInteractionEnabled = false;
strongSelf->_donePressed(strongSelf->_currentItem); strongSelf->_donePressed(strongSelf->_currentItem);
[strongSelf->_captionMixin onAnimateOut];
}; };
void(^toolbarDoneLongPressed)(id) = ^(id sender) void(^toolbarDoneLongPressed)(id) = ^(id sender)
{ {
@ -350,6 +374,18 @@
} completion:nil]; } completion:nil];
}; };
_captionMixin.timerUpdated = ^(NSNumber *timeout) {
__strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (![strongSelf->_currentItem conformsToProtocol:@protocol(TGModernGalleryEditableItem)])
return;
id<TGModernGalleryEditableItem> galleryEditableItem = (id<TGModernGalleryEditableItem>)strongSelf->_currentItem;
[strongSelf->_editingContext setTimer:timeout forItem:galleryEditableItem.editableMediaItem];
};
_captionMixin.stickersContext = stickersContext; _captionMixin.stickersContext = stickersContext;
[_captionMixin createInputPanelIfNeeded]; [_captionMixin createInputPanelIfNeeded];
@ -800,6 +836,8 @@
id<TGMediaEditAdjustments> adjustments = dict[@"adjustments"]; id<TGMediaEditAdjustments> adjustments = dict[@"adjustments"];
NSNumber *timer = dict[@"timer"]; NSNumber *timer = dict[@"timer"];
[strongSelf->_captionMixin setTimeout:[timer intValue]];
if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]]) if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]])
{ {
TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments; TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments;
@ -1285,7 +1323,7 @@
- (void)animateTransitionOutWithDuration:(NSTimeInterval)__unused duration - (void)animateTransitionOutWithDuration:(NSTimeInterval)__unused duration
{ {
[_captionMixin onAnimateOut];
} }
- (void)setTransitionOutProgress:(CGFloat)transitionOutProgress manual:(bool)manual - (void)setTransitionOutProgress:(CGFloat)transitionOutProgress manual:(bool)manual

View File

@ -87,6 +87,14 @@
[strongSelf updateLayoutWithFrame:strongSelf->_currentFrame edgeInsets:strongSelf->_currentEdgeInsets animated:animated]; [strongSelf updateLayoutWithFrame:strongSelf->_currentFrame edgeInsets:strongSelf->_currentEdgeInsets animated:animated];
}; };
_inputPanel.timerUpdated = ^(NSNumber *value) {
__strong TGPhotoCaptionInputMixin *strongSelf = weakSelf;
if (strongSelf.timerUpdated != nil) {
strongSelf.timerUpdated(value);
}
};
_inputPanelView = inputPanel.view; _inputPanelView = inputPanel.view;
_backgroundView = [[UIView alloc] init]; _backgroundView = [[UIView alloc] init];
@ -95,6 +103,10 @@
[parentView addSubview:_inputPanelView]; [parentView addSubview:_inputPanelView];
} }
- (void)onAnimateOut {
[_inputPanel onAnimateOut];
}
- (void)destroy - (void)destroy
{ {
[_inputPanelView removeFromSuperview]; [_inputPanelView removeFromSuperview];
@ -129,6 +141,10 @@
[_inputPanel setCaption:caption]; [_inputPanel setCaption:caption];
} }
- (void)setTimeout:(int32_t)timeout {
[_inputPanel setTimeout:timeout];
}
- (void)setCaptionPanelHidden:(bool)hidden animated:(bool)__unused animated - (void)setCaptionPanelHidden:(bool)hidden animated:(bool)__unused animated
{ {
_inputPanelView.hidden = hidden; _inputPanelView.hidden = hidden;
@ -204,7 +220,7 @@
CGRect frame = _currentFrame; CGRect frame = _currentFrame;
UIEdgeInsets edgeInsets = _currentEdgeInsets; UIEdgeInsets edgeInsets = _currentEdgeInsets;
CGFloat panelHeight = [_inputPanel updateLayoutSize:frame.size sideInset:0.0 animated:false]; CGFloat panelHeight = [_inputPanel updateLayoutSize:frame.size keyboardHeight:keyboardHeight sideInset:0.0 animated:false];
[UIView animateWithDuration:duration delay:0.0f options:(curve << 16) animations:^{ [UIView animateWithDuration:duration delay:0.0f options:(curve << 16) animations:^{
_inputPanelView.frame = CGRectMake(edgeInsets.left, frame.size.height - panelHeight - MAX(edgeInsets.bottom, _keyboardHeight), frame.size.width, panelHeight); _inputPanelView.frame = CGRectMake(edgeInsets.left, frame.size.height - panelHeight - MAX(edgeInsets.bottom, _keyboardHeight), frame.size.width, panelHeight);
@ -224,7 +240,7 @@
_currentFrame = frame; _currentFrame = frame;
_currentEdgeInsets = edgeInsets; _currentEdgeInsets = edgeInsets;
CGFloat panelHeight = [_inputPanel updateLayoutSize:frame.size sideInset:0.0 animated:animated]; CGFloat panelHeight = [_inputPanel updateLayoutSize:frame.size keyboardHeight:_keyboardHeight sideInset:0.0 animated:animated];
CGFloat y = 0.0; CGFloat y = 0.0;
if (frame.size.width > frame.size.height && !TGIsPad()) { if (frame.size.width > frame.size.height && !TGIsPad()) {
@ -238,14 +254,15 @@
backgroundHeight += _keyboardHeight - edgeInsets.bottom; backgroundHeight += _keyboardHeight - edgeInsets.bottom;
} }
CGRect panelFrame = CGRectMake(edgeInsets.left, y, frame.size.width, panelHeight);
CGRect backgroundFrame = CGRectMake(edgeInsets.left, y, frame.size.width, backgroundHeight);
if (animated) { if (animated) {
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ [_inputPanel animateView:_inputPanelView frame:panelFrame];
_inputPanelView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, panelHeight); [_inputPanel animateView:_backgroundView frame:backgroundFrame];
_backgroundView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, backgroundHeight);
} completion:nil];
} else { } else {
_inputPanelView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, panelHeight); _inputPanelView.frame = panelFrame;
_backgroundView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, backgroundHeight); _backgroundView.frame = backgroundFrame;
} }
} }

View File

@ -459,7 +459,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
} }
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: imageFlags) let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: imageFlags)
if let timer = item.timer, timer > 0 && timer <= 60 { if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
} }
if let spoiler = item.spoiler, spoiler { if let spoiler = item.spoiler, spoiler {
@ -531,7 +531,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
]) ])
var attributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = []
if let timer = item.timer, timer > 0 && timer <= 60 { if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
} }
if let spoiler = item.spoiler, spoiler { if let spoiler = item.spoiler, spoiler {
@ -581,7 +581,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
var attributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = []
if let timer = item.timer, timer > 0 && timer <= 60 { if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
} }
if let spoiler = item.spoiler, spoiler { if let spoiler = item.spoiler, spoiler {
@ -827,7 +827,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes) let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes)
if let timer = item.timer, timer > 0 && timer <= 60 { if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
} }
if let spoiler = item.spoiler, spoiler { if let spoiler = item.spoiler, spoiler {

View File

@ -14,6 +14,7 @@ swift_library(
"//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/LegacyComponents:LegacyComponents", "//submodules/LegacyComponents:LegacyComponents",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/ManagedAnimationNode:ManagedAnimationNode"
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -5,6 +5,7 @@ import AsyncDisplayKit
enum RadialStatusIcon { enum RadialStatusIcon {
case custom(UIImage) case custom(UIImage)
case timeout
case play(UIColor) case play(UIColor)
case pause(UIColor) case pause(UIColor)
} }
@ -22,14 +23,28 @@ private final class RadialStatusIconContentNodeParameters: NSObject {
final class RadialStatusIconContentNode: RadialStatusContentNode { final class RadialStatusIconContentNode: RadialStatusContentNode {
private let icon: RadialStatusIcon private let icon: RadialStatusIcon
private var animationNode: FireIconNode?
init(icon: RadialStatusIcon, synchronous: Bool) { init(icon: RadialStatusIcon, synchronous: Bool) {
self.icon = icon self.icon = icon
super.init() super.init()
self.displaysAsynchronously = !synchronous self.displaysAsynchronously = !synchronous
self.isLayerBacked = true // self.isLayerBacked = true
self.isOpaque = false self.isOpaque = false
if case .timeout = icon {
let animationNode = FireIconNode()
self.animationNode = animationNode
self.addSubnode(animationNode)
}
}
override func layout() {
super.layout()
self.animationNode?.frame = CGRect(x: 6.0, y: 2.0, width: 36.0, height: 36.0)
} }
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
@ -48,6 +63,8 @@ final class RadialStatusIconContentNode: RadialStatusContentNode {
if let parameters = parameters as? RadialStatusIconContentNodeParameters { if let parameters = parameters as? RadialStatusIconContentNodeParameters {
let diameter = min(bounds.size.width, bounds.size.height) let diameter = min(bounds.size.width, bounds.size.height)
switch parameters.icon { switch parameters.icon {
case .timeout:
break
case let .play(color): case let .play(color):
context.setFillColor(color.cgColor) context.setFillColor(color.cgColor)

View File

@ -12,6 +12,7 @@ public enum RadialStatusNodeState: Equatable {
case cloudProgress(color: UIColor, strokeBackgroundColor: UIColor, lineWidth: CGFloat, value: CGFloat?) case cloudProgress(color: UIColor, strokeBackgroundColor: UIColor, lineWidth: CGFloat, value: CGFloat?)
case check(UIColor) case check(UIColor)
case customIcon(UIImage) case customIcon(UIImage)
case staticTimeout
case secretTimeout(color: UIColor, icon: UIImage?, beginTime: Double, timeout: Double, sparks: Bool) case secretTimeout(color: UIColor, icon: UIImage?, beginTime: Double, timeout: Double, sparks: Bool)
public static func ==(lhs: RadialStatusNodeState, rhs: RadialStatusNodeState) -> Bool { public static func ==(lhs: RadialStatusNodeState, rhs: RadialStatusNodeState) -> Bool {
@ -64,6 +65,12 @@ public enum RadialStatusNodeState: Equatable {
} else { } else {
return false return false
} }
case .staticTimeout:
if case .staticTimeout = rhs {
return true
} else {
return false
}
case let .secretTimeout(lhsColor, lhsIcon, lhsBeginTime, lhsTimeout, lhsSparks): case let .secretTimeout(lhsColor, lhsIcon, lhsBeginTime, lhsTimeout, lhsSparks):
if case let .secretTimeout(rhsColor, rhsIcon, rhsBeginTime, rhsTimeout, rhsSparks) = rhs, lhsColor.isEqual(rhsColor), lhsIcon === rhsIcon, lhsBeginTime.isEqual(to: rhsBeginTime), lhsTimeout.isEqual(to: rhsTimeout), lhsSparks == rhsSparks { if case let .secretTimeout(rhsColor, rhsIcon, rhsBeginTime, rhsTimeout, rhsSparks) = rhs, lhsColor.isEqual(rhsColor), lhsIcon === rhsIcon, lhsBeginTime.isEqual(to: rhsBeginTime), lhsTimeout.isEqual(to: rhsTimeout), lhsSparks == rhsSparks {
return true return true
@ -123,6 +130,12 @@ public enum RadialStatusNodeState: Equatable {
} else { } else {
return false return false
} }
case .staticTimeout:
if case .staticTimeout = rhs{
return true
} else {
return false
}
case let .secretTimeout(lhsColor, lhsIcon, lhsBeginTime, lhsTimeout, lhsSparks): case let .secretTimeout(lhsColor, lhsIcon, lhsBeginTime, lhsTimeout, lhsSparks):
if case let .secretTimeout(rhsColor, rhsIcon, rhsBeginTime, rhsTimeout, rhsSparks) = rhs, lhsColor.isEqual(rhsColor), lhsIcon === rhsIcon, lhsBeginTime.isEqual(to: rhsBeginTime), lhsTimeout.isEqual(to: rhsTimeout), lhsSparks == rhsSparks { if case let .secretTimeout(rhsColor, rhsIcon, rhsBeginTime, rhsTimeout, rhsSparks) = rhs, lhsColor.isEqual(rhsColor), lhsIcon === rhsIcon, lhsBeginTime.isEqual(to: rhsBeginTime), lhsTimeout.isEqual(to: rhsTimeout), lhsSparks == rhsSparks {
return true return true
@ -179,6 +192,8 @@ public enum RadialStatusNodeState: Equatable {
node.progress = value node.progress = value
return node return node
} }
case .staticTimeout:
return RadialStatusIconContentNode(icon: .timeout, synchronous: synchronous)
case let .secretTimeout(color, icon, beginTime, timeout, sparks): case let .secretTimeout(color, icon, beginTime, timeout, sparks):
return RadialStatusSecretTimeoutContentNode(color: color, beginTime: beginTime, timeout: timeout, icon: icon, sparks: sparks) return RadialStatusSecretTimeoutContentNode(color: color, beginTime: beginTime, timeout: timeout, icon: icon, sparks: sparks)
} }
@ -188,9 +203,11 @@ public enum RadialStatusNodeState: Equatable {
public final class RadialStatusNode: ASControlNode { public final class RadialStatusNode: ASControlNode {
public var backgroundNodeColor: UIColor { public var backgroundNodeColor: UIColor {
didSet { didSet {
if self.backgroundNodeColor != oldValue {
self.transitionToBackgroundColor(self.state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: false, synchronous: false, completion: {}) self.transitionToBackgroundColor(self.state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: false, synchronous: false, completion: {})
} }
} }
}
private let enableBlur: Bool private let enableBlur: Bool

View File

@ -3,6 +3,7 @@ import UIKit
import Display import Display
import AsyncDisplayKit import AsyncDisplayKit
import LegacyComponents import LegacyComponents
import ManagedAnimationNode
private struct ContentParticle { private struct ContentParticle {
var position: CGPoint var position: CGPoint
@ -53,6 +54,8 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
private var progress: CGFloat = 0.0 private var progress: CGFloat = 0.0
private var particles: [ContentParticle] = [] private var particles: [ContentParticle] = []
private let animationNode = FireIconNode()
private var displayLink: CADisplayLink? private var displayLink: CADisplayLink?
init(color: UIColor, beginTime: Double, timeout: Double, icon: UIImage?, sparks: Bool) { init(color: UIColor, beginTime: Double, timeout: Double, icon: UIImage?, sparks: Bool) {
@ -65,7 +68,7 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
super.init() super.init()
self.isOpaque = false self.isOpaque = false
self.isLayerBacked = true // self.isLayerBacked = true
class DisplayLinkProxy: NSObject { class DisplayLinkProxy: NSObject {
weak var target: RadialStatusSecretTimeoutContentNode? weak var target: RadialStatusSecretTimeoutContentNode?
@ -81,6 +84,8 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent))
self.displayLink?.isPaused = true self.displayLink?.isPaused = true
self.displayLink?.add(to: RunLoop.main, forMode: .common) self.displayLink?.add(to: RunLoop.main, forMode: .common)
self.addSubnode(self.animationNode)
} }
deinit { deinit {
@ -89,6 +94,8 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
override func layout() { override func layout() {
super.layout() super.layout()
self.animationNode.frame = CGRect(x: 6.0, y: 2.0, width: 36.0, height: 36.0)
} }
override func animateOut(to: RadialStatusNodeState, completion: @escaping () -> Void) { override func animateOut(to: RadialStatusNodeState, completion: @escaping () -> Void) {
@ -231,3 +238,10 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
} }
} }
final class FireIconNode: ManagedAnimationNode {
init() {
super.init(size: CGSize(width: 100.0, height: 100.0))
self.trackTo(item: ManagedAnimationItem(source: .local("anim_autoremove_on"), frames: .range(startFrame: 0, endFrame: 120), duration: 2.0))
}
}

View File

@ -9,11 +9,11 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
var hasUnseenReactions = false var hasUnseenReactions = false
for attribute in attributes { for attribute in attributes {
if let timerAttribute = attribute as? AutoclearTimeoutMessageAttribute { if let timerAttribute = attribute as? AutoclearTimeoutMessageAttribute {
if timerAttribute.timeout > 0 && timerAttribute.timeout <= 60 { if timerAttribute.timeout > 0 && (timerAttribute.timeout <= 60 || timerAttribute.timeout == viewOnceTimeout) {
isSecret = true isSecret = true
} }
} else if let timerAttribute = attribute as? AutoremoveTimeoutMessageAttribute { } else if let timerAttribute = attribute as? AutoremoveTimeoutMessageAttribute {
if timerAttribute.timeout > 0 && timerAttribute.timeout <= 60 { if timerAttribute.timeout > 0 && (timerAttribute.timeout <= 60 || timerAttribute.timeout == viewOnceTimeout) {
isSecret = true isSecret = true
} }
} else if let mentionAttribute = attribute as? ConsumablePersonalMentionMessageAttribute { } else if let mentionAttribute = attribute as? ConsumablePersonalMentionMessageAttribute {

View File

@ -1,6 +1,8 @@
import Foundation import Foundation
import Postbox import Postbox
public let viewOnceTimeout: Int32 = 0x7fffffff
public class AutoremoveTimeoutMessageAttribute: MessageAttribute { public class AutoremoveTimeoutMessageAttribute: MessageAttribute {
public let timeout: Int32 public let timeout: Int32
public let countdownBeginTime: Int32? public let countdownBeginTime: Int32?
@ -124,7 +126,7 @@ public extension Message {
guard let timeout = self.minAutoremoveOrClearTimeout else { guard let timeout = self.minAutoremoveOrClearTimeout else {
return false return false
} }
if timeout > 1 * 60 { if timeout > 1 * 60 && timeout != viewOnceTimeout {
return false return false
} }

View File

@ -339,6 +339,7 @@ swift_library(
"//submodules/TelegramUI/Components/PeerSelectionController", "//submodules/TelegramUI/Components/PeerSelectionController",
"//submodules/TelegramUI/Components/Chat/AccessoryPanelNode", "//submodules/TelegramUI/Components/Chat/AccessoryPanelNode",
"//submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode", "//submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode",
"//submodules/TelegramUI/Components/LegacyMessageInputPanel",
] + select({ ] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -0,0 +1,29 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "LegacyMessageInputPanel",
module_name = "LegacyMessageInputPanel",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display",
"//submodules/TelegramCore",
"//submodules/Postbox",
"//submodules/AccountContext",
"//submodules/LegacyComponents",
"//submodules/ComponentFlow",
"//submodules/TelegramPresentationData",
"//submodules/ContextUI",
"//submodules/TooltipUI",
"//submodules/TelegramUI/Components/MessageInputPanelComponent",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,370 @@
import Foundation
import UIKit
import AsyncDisplayKit
import LegacyComponents
import Display
import TelegramCore
import Postbox
import SwiftSignalKit
import AccountContext
import LegacyComponents
import ComponentFlow
import MessageInputPanelComponent
import TelegramPresentationData
import ContextUI
import TooltipUI
public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
private let context: AccountContext
private let chatLocation: ChatLocation
private let present: (ViewController) -> Void
private let presentInGlobalOverlay: (ViewController) -> Void
private let state = ComponentState()
private let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
private let inputPanel = ComponentView<Empty>()
private var currentTimeout: Int32?
private var currentIsEditing = false
private var currentHeight: CGFloat?
private let hapticFeedback = HapticFeedback()
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, metrics: LayoutMetrics)?
public init(
context: AccountContext,
chatLocation: ChatLocation,
present: @escaping (ViewController) -> Void,
presentInGlobalOverlay: @escaping (ViewController) -> Void
) {
self.context = context
self.chatLocation = chatLocation
self.present = present
self.presentInGlobalOverlay = presentInGlobalOverlay
super.init()
self.state._updated = { [weak self] transition in
if let self {
self.update(transition: transition.containedViewLayoutTransition)
}
}
}
public var sendPressed: ((NSAttributedString?) -> Void)?
public var focusUpdated: ((Bool) -> Void)?
public var heightUpdated: ((Bool) -> Void)?
public var timerUpdated: ((NSNumber?) -> Void)?
public func updateLayoutSize(_ size: CGSize, keyboardHeight: CGFloat, sideInset: CGFloat, animated: Bool) -> CGFloat {
return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), isMediaInputExpanded: false)
}
public func caption() -> NSAttributedString {
if let view = self.inputPanel.view as? MessageInputPanelComponent.View, case let .text(caption) = view.getSendMessageInput() {
return caption
} else {
return NSAttributedString()
}
}
public func setCaption(_ caption: NSAttributedString?) {
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
view.setSendMessageInput(value: .text(caption ?? NSAttributedString()), updateState: true)
}
}
public func animate(_ view: UIView, frame: CGRect) {
let transition = Transition.spring(duration: 0.4)
transition.setFrame(view: view, frame: frame)
}
public func setTimeout(_ timeout: Int32) {
var timeout: Int32? = timeout
if timeout == 0 {
timeout = nil
}
self.currentTimeout = timeout
}
public func dismissInput() {
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
view.deactivateInput()
}
}
public func onAnimateOut() {
self.tooltipController?.dismiss()
}
public func baseHeight() -> CGFloat {
return 52.0
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func update(transition: ContainedViewLayoutTransition) {
if let (width, leftInset, rightInset, bottomInset, keyboardHeight, additionalSideInsets, maxHeight, isSecondary, metrics) = self.validLayout {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: keyboardHeight, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, metrics: metrics, isMediaInputExpanded: false)
}
}
public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
let previousLayout = self.validLayout
self.validLayout = (width, leftInset, rightInset, bottomInset, keyboardHeight, additionalSideInsets, maxHeight, isSecondary, metrics)
var transition = transition
if keyboardHeight.isZero, let previousKeyboardHeight = previousLayout?.keyboardHeight, previousKeyboardHeight > 0.0, !transition.isAnimated {
transition = .animated(duration: 0.4, curve: .spring)
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let theme = defaultDarkColorPresentationTheme
var timeoutValue: String
var timeoutSelected = false
if let timeout = self.currentTimeout {
if timeout == viewOnceTimeout {
timeoutValue = "1"
} else {
timeoutValue = "\(timeout)"
}
timeoutSelected = true
} else {
timeoutValue = "1"
}
var maxInputPanelHeight = maxHeight
if keyboardHeight.isZero {
maxInputPanelHeight = 60.0
}
self.inputPanel.parentState = self.state
let inputPanelSize = self.inputPanel.update(
transition: Transition(transition),
component: AnyComponent(
MessageInputPanelComponent(
externalState: self.inputPanelExternalState,
context: self.context,
theme: theme,
strings: presentationData.strings,
style: .media,
placeholder: .plain(presentationData.strings.MediaPicker_AddCaption),
maxLength: 1024,
queryTypes: [.mention],
alwaysDarkWhenHasText: false,
resetInputContents: nil,
nextInputMode: { _ in
return .emoji
},
areVoiceMessagesAvailable: false,
presentController: self.present,
presentInGlobalOverlay: self.presentInGlobalOverlay,
sendMessageAction: { [weak self] in
if let self {
self.sendPressed?(self.caption())
self.dismissInput()
}
},
sendMessageOptionsAction: nil,
sendStickerAction: { _ in },
setMediaRecordingActive: nil,
lockMediaRecording: nil,
stopAndPreviewMediaRecording: nil,
discardMediaRecordingPreview: nil,
attachmentAction: nil,
myReaction: nil,
likeAction: nil,
likeOptionsAction: nil,
inputModeAction: nil,
timeoutAction: { [weak self] sourceView in
if let self {
self.presentTimeoutSetup(sourceView: sourceView)
}
},
forwardAction: nil,
moreAction: nil,
presentVoiceMessagesUnavailableTooltip: nil,
presentTextLengthLimitTooltip: nil,
presentTextFormattingTooltip: nil,
paste: { _ in },
audioRecorder: nil,
videoRecordingStatus: nil,
isRecordingLocked: false,
recordedAudioPreview: nil,
hasRecordedVideoPreview: false,
wasRecordingDismissed: false,
timeoutValue: timeoutValue,
timeoutSelected: timeoutSelected,
displayGradient: false,
bottomInset: 0.0,
isFormattingLocked: false,
hideKeyboard: false,
forceIsEditing: false,
disabledPlaceholder: nil,
isChannel: false,
storyItem: nil,
chatLocation: self.chatLocation
)
),
environment: {},
containerSize: CGSize(width: width, height: maxInputPanelHeight)
)
if let view = self.inputPanel.view {
if view.superview == nil {
self.view.addSubview(view)
}
let inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: inputPanelSize)
transition.updateFrame(view: view, frame: inputPanelFrame)
}
if self.currentIsEditing != self.inputPanelExternalState.isEditing {
self.currentIsEditing = self.inputPanelExternalState.isEditing
self.focusUpdated?(self.currentIsEditing)
}
if self.currentHeight != inputPanelSize.height {
self.currentHeight = inputPanelSize.height
self.heightUpdated?(transition.isAnimated)
}
return inputPanelSize.height - 8.0
}
private func presentTimeoutSetup(sourceView: UIView) {
self.hapticFeedback.impact(.light)
var items: [ContextMenuItem] = []
let updateTimeout: (Int32?) -> Void = { [weak self] timeout in
if let self {
self.currentTimeout = timeout
self.timerUpdated?(timeout as? NSNumber)
self.update(transition: .immediate)
self.presentTimeoutTooltip(sourceView: sourceView, timeout: timeout)
}
}
let currentValue = self.currentTimeout
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
let title = "Choose how long the media will be kept after opening."
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
items.append(.action(ContextMenuActionItem(text: "View Once", icon: { theme in
return currentValue == viewOnceTimeout ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in
a(.default)
updateTimeout(viewOnceTimeout)
})))
items.append(.action(ContextMenuActionItem(text: "3 Seconds", icon: { theme in
return currentValue == 3 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in
a(.default)
updateTimeout(3)
})))
items.append(.action(ContextMenuActionItem(text: "10 Seconds", icon: { theme in
return currentValue == 10 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in
a(.default)
updateTimeout(10)
})))
items.append(.action(ContextMenuActionItem(text: "30 Seconds", icon: { theme in
return currentValue == 30 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in
a(.default)
updateTimeout(30)
})))
items.append(.action(ContextMenuActionItem(text: "Do Not Delete", icon: { theme in
return currentValue == nil ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in
a(.default)
updateTimeout(nil)
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
self.present(contextController)
}
private weak var tooltipController: TooltipScreen?
private func presentTimeoutTooltip(sourceView: UIView, timeout: Int32?) {
guard let superview = self.view.superview?.superview else {
return
}
if let tooltipController = self.tooltipController {
self.tooltipController = nil
tooltipController.dismiss()
}
let parentFrame = superview.convert(superview.bounds, to: nil)
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 2.0), size: CGSize())
let text: String
let iconName: String
if timeout == viewOnceTimeout {
text = "Photo set to view once."
iconName = "anim_autoremove_on"
} else if let timeout {
text = "Photo will be deleted in \(timeout) seconds after opening."
iconName = "anim_autoremove_on"
} else {
text = "Photo will be kept in chat."
iconName = "anim_autoremove_off"
}
let tooltipController = TooltipScreen(
account: self.context.account,
sharedContext: self.context.sharedContext,
text: .plain(text: text),
balancedTextLayout: true,
style: .customBlur(UIColor(rgb: 0x18181a), 0.0),
arrowStyle: .small,
icon: .animation(name: iconName, delay: 0.1, tintColor: nil),
location: .point(location, .bottom),
displayDuration: .default,
inset: 8.0,
shouldDismissOnTouch: { _, _ in
return .ignore
}
)
self.tooltipController = tooltipController
self.present(tooltipController)
}
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if let view = self.inputPanel.view, let panelResult = view.hitTest(self.view.convert(point, to: view), with: event) {
return panelResult
}
return result
}
}
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let sourceView: UIView
var keepInPlace: Bool {
return true
}
init(sourceView: UIView) {
self.sourceView = sourceView
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: .top)
}
}

View File

@ -111,8 +111,12 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
switch self.content { switch self.content {
case let .image(image, _): case let .image(image, _):
dimensions = image.size dimensions = image.size
case let .file(file, _): case let .file(file, type):
if case .reaction = type {
dimensions = CGSize(width: 512.0, height: 512.0)
} else {
dimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) dimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
}
case let .video(file): case let .video(file):
dimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) dimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
case .dualVideoReference: case .dualVideoReference:

View File

@ -415,7 +415,6 @@ final class MediaEditorScreenComponent: Component {
guard let _ = self.inputPanel.view as? MessageInputPanelComponent.View else { guard let _ = self.inputPanel.view as? MessageInputPanelComponent.View else {
return return
} }
// if view.canDeactivateInput() {
self.currentInputMode = .text self.currentInputMode = .text
if hasFirstResponder(self) { if hasFirstResponder(self) {
if let view = self.inputPanel.view as? MessageInputPanelComponent.View { if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
@ -429,12 +428,6 @@ final class MediaEditorScreenComponent: Component {
} else { } else {
self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(kind: .textFocusChanged))) self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(kind: .textFocusChanged)))
} }
// } else {
// if let controller = self.environment?.controller() as? MediaEditorScreen {
// controller.presentCaptionLimitPremiumSuggestion(isPremium: self.sta)
// }
// view.animateError()
// }
} }
private var animatingButtons = false private var animatingButtons = false
@ -939,7 +932,6 @@ final class MediaEditorScreenComponent: Component {
self.appliedAudioData = audioData self.appliedAudioData = audioData
var timeoutValue: String var timeoutValue: String
let timeoutSelected: Bool
switch component.privacy.timeout { switch component.privacy.timeout {
case 21600: case 21600:
timeoutValue = "6" timeoutValue = "6"
@ -952,7 +944,6 @@ final class MediaEditorScreenComponent: Component {
default: default:
timeoutValue = "24" timeoutValue = "24"
} }
timeoutSelected = false
var inputPanelAvailableWidth = previewSize.width var inputPanelAvailableWidth = previewSize.width
var inputPanelAvailableHeight = 103.0 var inputPanelAvailableHeight = 103.0
@ -1192,7 +1183,7 @@ final class MediaEditorScreenComponent: Component {
hasRecordedVideoPreview: false, hasRecordedVideoPreview: false,
wasRecordingDismissed: false, wasRecordingDismissed: false,
timeoutValue: timeoutValue, timeoutValue: timeoutValue,
timeoutSelected: timeoutSelected, timeoutSelected: false,
displayGradient: false, displayGradient: false,
bottomInset: 0.0, bottomInset: 0.0,
isFormattingLocked: !state.isPremium, isFormattingLocked: !state.isPremium,
@ -1200,7 +1191,8 @@ final class MediaEditorScreenComponent: Component {
forceIsEditing: self.currentInputMode == .emoji, forceIsEditing: self.currentInputMode == .emoji,
disabledPlaceholder: nil, disabledPlaceholder: nil,
isChannel: false, isChannel: false,
storyItem: nil storyItem: nil,
chatLocation: nil
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: inputPanelAvailableWidth, height: inputPanelAvailableHeight) containerSize: CGSize(width: inputPanelAvailableWidth, height: inputPanelAvailableHeight)
@ -1952,6 +1944,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.entitiesView.getAvailableReactions = { [weak self] in self.entitiesView.getAvailableReactions = { [weak self] in
return self?.availableReactions ?? [] return self?.availableReactions ?? []
} }
self.entitiesView.present = { [weak self] c in
if let self {
self.controller?.dismissAllTooltips()
self.controller?.present(c, in: .current)
}
}
self.entitiesView.push = { [weak self] c in
if let self {
self.controller?.push(c)
}
}
self.availableReactionsDisposable = (allowedStoryReactions(context: controller.context) self.availableReactionsDisposable = (allowedStoryReactions(context: controller.context)
|> deliverOnMainQueue).start(next: { [weak self] reactions in |> deliverOnMainQueue).start(next: { [weak self] reactions in
@ -2585,6 +2588,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
controller.statusBar.statusBarStyle = .Ignore controller.statusBar.statusBarStyle = .Ignore
self.isUserInteractionEnabled = false self.isUserInteractionEnabled = false
if self.entitiesView.hasSelection {
self.entitiesView.selectEntity(nil)
}
let previousDimAlpha = self.backgroundDimView.alpha let previousDimAlpha = self.backgroundDimView.alpha
self.backgroundDimView.alpha = 0.0 self.backgroundDimView.alpha = 0.0
self.backgroundDimView.layer.animateAlpha(from: previousDimAlpha, to: 0.0, duration: 0.15) self.backgroundDimView.layer.animateAlpha(from: previousDimAlpha, to: 0.0, duration: 0.15)
@ -3795,6 +3802,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}) })
} }
) )
controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in
if let self, let controller {
let transitionFactor = controller.modalStyleOverlayTransitionFactor
self.node.updateModalTransitionFactor(transitionFactor, transition: transition)
}
}
controller.dismissed = { controller.dismissed = {
self.node.mediaEditor?.play() self.node.mediaEditor?.play()
} }
@ -3846,6 +3859,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
editCategory: { _, _, _ in }, editCategory: { _, _, _ in },
editBlockedPeers: { _, _, _ in } editBlockedPeers: { _, _, _ in }
) )
controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in
if let self, let controller {
let transitionFactor = controller.modalStyleOverlayTransitionFactor
self.node.updateModalTransitionFactor(transitionFactor, transition: transition)
}
}
controller.dismissed = { controller.dismissed = {
self.node.mediaEditor?.play() self.node.mediaEditor?.play()
} }
@ -3949,8 +3968,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil)
self.push(controller) self.push(controller)
} }
return false } return false
) })
self.present(controller, in: .current) self.present(controller, in: .current)
} }
@ -3970,8 +3989,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}) })
self.push(controller) self.push(controller)
} }
return false } return false
) })
self.present(controller, in: .current) self.present(controller, in: .current)
} }

View File

@ -293,7 +293,8 @@ final class StoryPreviewComponent: Component {
forceIsEditing: false, forceIsEditing: false,
disabledPlaceholder: nil, disabledPlaceholder: nil,
isChannel: false, isChannel: false,
storyItem: nil storyItem: nil,
chatLocation: nil
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width, height: 200.0) containerSize: CGSize(width: availableSize.width, height: 200.0)

View File

@ -581,6 +581,7 @@ final class VideoScrubberComponent: Component {
audioTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isAudioSelected || component.audioOnly ? 0.0 : 6.0), size: audioWaveformSize)) audioTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isAudioSelected || component.audioOnly ? 0.0 : 6.0), size: audioWaveformSize))
} }
} }
self.cursorView.isHidden = component.audioOnly
let bounds = CGRect(origin: .zero, size: scrubberSize) let bounds = CGRect(origin: .zero, size: scrubberSize)

View File

@ -36,6 +36,7 @@ swift_library(
"//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/AnimatedCountLabelNode", "//submodules/AnimatedCountLabelNode",
"//submodules/TelegramUI/Components/MessageInputActionButtonComponent", "//submodules/TelegramUI/Components/MessageInputActionButtonComponent",
"//submodules/SearchPeerMembers",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -5,6 +5,7 @@ import TextFieldComponent
import ChatContextQuery import ChatContextQuery
import AccountContext import AccountContext
import TelegramUIPreferences import TelegramUIPreferences
import SearchPeerMembers
func textInputStateContextQueryRangeAndType(inputState: TextFieldComponent.InputState) -> [(NSRange, PossibleContextQueryTypes, NSRange?)] { func textInputStateContextQueryRangeAndType(inputState: TextFieldComponent.InputState) -> [(NSRange, PossibleContextQueryTypes, NSRange?)] {
return textInputStateContextQueryRangeAndType(inputText: inputState.inputText, selectionRange: inputState.selectionRange) return textInputStateContextQueryRangeAndType(inputText: inputState.inputText, selectionRange: inputState.selectionRange)
@ -38,7 +39,7 @@ func inputContextQueries(_ inputState: TextFieldComponent.InputState) -> [ChatPr
return result return result
} }
func contextQueryResultState(context: AccountContext, inputState: TextFieldComponent.InputState, availableTypes: [ChatPresentationInputQueryKind], currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)]) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] { func contextQueryResultState(context: AccountContext, inputState: TextFieldComponent.InputState, availableTypes: [ChatPresentationInputQueryKind], chatLocation: ChatLocation?, currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)]) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] {
let inputQueries = inputContextQueries(inputState).filter({ query in let inputQueries = inputContextQueries(inputState).filter({ query in
return availableTypes.contains(query.kind) return availableTypes.contains(query.kind)
}) })
@ -48,7 +49,7 @@ func contextQueryResultState(context: AccountContext, inputState: TextFieldCompo
for query in inputQueries { for query in inputQueries {
let previousQuery = currentQueryStates[query.kind]?.0 let previousQuery = currentQueryStates[query.kind]?.0
if previousQuery != query { if previousQuery != query {
let signal = updatedContextQueryResultStateForQuery(context: context, inputQuery: query, previousQuery: previousQuery) let signal = updatedContextQueryResultStateForQuery(context: context, chatLocation: chatLocation, inputQuery: query, previousQuery: previousQuery)
updates[query.kind] = .update(query, signal) updates[query.kind] = .update(query, signal)
} }
} }
@ -69,7 +70,7 @@ func contextQueryResultState(context: AccountContext, inputState: TextFieldCompo
return updates return updates
} }
private func updatedContextQueryResultStateForQuery(context: AccountContext, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> { private func updatedContextQueryResultStateForQuery(context: AccountContext, chatLocation: ChatLocation?, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> {
switch inputQuery { switch inputQuery {
case let .emoji(query): case let .emoji(query):
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete() var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
@ -149,7 +150,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp
|> castError(ChatContextQueryError.self) |> castError(ChatContextQueryError.self)
return signal |> then(hashtags) return signal |> then(hashtags)
case let .mention(query, _): case let .mention(query, types):
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete() var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
if let previousQuery = previousQuery { if let previousQuery = previousQuery {
switch previousQuery { switch previousQuery {
@ -163,6 +164,50 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp
} }
let normalizedQuery = query.lowercased() let normalizedQuery = query.lowercased()
if let chatLocation, let peerId = chatLocation.peerId {
let inlineBots: Signal<[(EnginePeer, Double)], NoError> = types.contains(.contextBots) ? context.engine.peers.recentlyUsedInlineBots() : .single([])
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
let participants = combineLatest(inlineBots, searchPeerMembers(context: context, peerId: peerId, chatLocation: chatLocation, query: query, scope: .mention))
|> map { inlineBots, peers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
let filteredInlineBots = inlineBots.sorted(by: { $0.1 > $1.1 }).filter { peer, rating in
if rating < 0.14 {
return false
}
if peer.indexName.matchesByTokens(normalizedQuery) {
return true
}
if let addressName = peer.addressName, addressName.lowercased().hasPrefix(normalizedQuery) {
return true
}
return false
}.map { $0.0 }
let inlineBotPeerIds = Set(filteredInlineBots.map { $0.id })
let filteredPeers = peers.filter { peer in
if inlineBotPeerIds.contains(peer.id) {
return false
}
if !types.contains(.accountPeer) && peer.id == context.account.peerId {
return false
}
return true
}
var sortedPeers = filteredInlineBots
sortedPeers.append(contentsOf: filteredPeers.sorted(by: { lhs, rhs in
let result = lhs.indexName.stringRepresentation(lastNameFirst: true).compare(rhs.indexName.stringRepresentation(lastNameFirst: true))
return result == .orderedAscending
}))
sortedPeers = sortedPeers.filter { peer in
return !peer.displayTitle(strings: strings, displayOrder: .firstLast).isEmpty
}
return { _ in return .mentions(sortedPeers) }
}
|> castError(ChatContextQueryError.self)
return signal |> then(participants)
} else {
if normalizedQuery.isEmpty { if normalizedQuery.isEmpty {
let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.peers.recentPeers() let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.peers.recentPeers()
|> map { recentPeers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in |> map { recentPeers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
@ -192,6 +237,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp
|> castError(ChatContextQueryError.self) |> castError(ChatContextQueryError.self)
return signal |> then(peers) return signal |> then(peers)
} }
}
case let .emojiSearch(query, languageCode, range): case let .emojiSearch(query, languageCode, range):
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in |> map { peer -> Bool in

View File

@ -39,6 +39,7 @@ public final class MessageInputPanelComponent: Component {
public enum Style { public enum Style {
case story case story
case editor case editor
case media
} }
public enum InputMode: Hashable { public enum InputMode: Hashable {
@ -142,6 +143,7 @@ public final class MessageInputPanelComponent: Component {
public let disabledPlaceholder: String? public let disabledPlaceholder: String?
public let isChannel: Bool public let isChannel: Bool
public let storyItem: EngineStoryItem? public let storyItem: EngineStoryItem?
public let chatLocation: ChatLocation?
public init( public init(
externalState: ExternalState, externalState: ExternalState,
@ -192,7 +194,8 @@ public final class MessageInputPanelComponent: Component {
forceIsEditing: Bool, forceIsEditing: Bool,
disabledPlaceholder: String?, disabledPlaceholder: String?,
isChannel: Bool, isChannel: Bool,
storyItem: EngineStoryItem? storyItem: EngineStoryItem?,
chatLocation: ChatLocation?
) { ) {
self.externalState = externalState self.externalState = externalState
self.context = context self.context = context
@ -243,6 +246,7 @@ public final class MessageInputPanelComponent: Component {
self.disabledPlaceholder = disabledPlaceholder self.disabledPlaceholder = disabledPlaceholder
self.isChannel = isChannel self.isChannel = isChannel
self.storyItem = storyItem self.storyItem = storyItem
self.chatLocation = chatLocation
} }
public static func ==(lhs: MessageInputPanelComponent, rhs: MessageInputPanelComponent) -> Bool { public static func ==(lhs: MessageInputPanelComponent, rhs: MessageInputPanelComponent) -> Bool {
@ -345,6 +349,9 @@ public final class MessageInputPanelComponent: Component {
if lhs.storyItem != rhs.storyItem { if lhs.storyItem != rhs.storyItem {
return false return false
} }
if lhs.chatLocation != rhs.chatLocation {
return false
}
return true return true
} }
@ -558,7 +565,7 @@ public final class MessageInputPanelComponent: Component {
if component.queryTypes.contains(.emoji) { if component.queryTypes.contains(.emoji) {
availableTypes.append(.emoji) availableTypes.append(.emoji)
} }
let contextQueryUpdates = contextQueryResultState(context: context, inputState: inputState, availableTypes: availableTypes, currentQueryStates: &self.contextQueryStates) let contextQueryUpdates = contextQueryResultState(context: context, inputState: inputState, availableTypes: availableTypes, chatLocation: component.chatLocation, currentQueryStates: &self.contextQueryStates)
for (kind, update) in contextQueryUpdates { for (kind, update) in contextQueryUpdates {
switch update { switch update {
@ -631,7 +638,12 @@ public final class MessageInputPanelComponent: Component {
insets.right = 41.0 insets.right = 41.0
} }
let mediaInsets = UIEdgeInsets(top: insets.top, left: 9.0, bottom: insets.bottom, right: 41.0) var textFieldSideInset = 9.0
if case .media = component.style {
textFieldSideInset = 8.0
}
let mediaInsets = UIEdgeInsets(top: insets.top, left: textFieldSideInset, bottom: insets.bottom, right: 41.0)
let baseFieldHeight: CGFloat = 40.0 let baseFieldHeight: CGFloat = 40.0
@ -699,6 +711,7 @@ public final class MessageInputPanelComponent: Component {
return value return value
} }
}, },
resetScrollOnFocusChange: component.style == .media,
formatMenuAvailability: component.isFormattingLocked ? .locked : .available, formatMenuAvailability: component.isFormattingLocked ? .locked : .available,
lockedFormatAction: { lockedFormatAction: {
component.presentTextFormattingTooltip?() component.presentTextFormattingTooltip?()
@ -761,7 +774,7 @@ public final class MessageInputPanelComponent: Component {
var fieldBackgroundFrame: CGRect var fieldBackgroundFrame: CGRect
if hasMediaRecording { if hasMediaRecording {
fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - mediaInsets.right, height: textFieldSize.height)) fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - mediaInsets.right, height: textFieldSize.height))
} else if isEditing || component.style == .editor { } else if isEditing || component.style == .editor || component.style == .media {
fieldBackgroundFrame = fieldFrame fieldBackgroundFrame = fieldFrame
} else { } else {
if component.forwardAction != nil && component.likeAction != nil { if component.forwardAction != nil && component.likeAction != nil {
@ -774,6 +787,7 @@ public final class MessageInputPanelComponent: Component {
} }
transition.setFrame(view: self.vibrancyEffectView, frame: CGRect(origin: CGPoint(), size: fieldBackgroundFrame.size)) transition.setFrame(view: self.vibrancyEffectView, frame: CGRect(origin: CGPoint(), size: fieldBackgroundFrame.size))
self.vibrancyEffectView.isHidden = component.style == .media
transition.setFrame(view: self.fieldBackgroundView, frame: fieldBackgroundFrame) transition.setFrame(view: self.fieldBackgroundView, frame: fieldBackgroundFrame)
self.fieldBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, transition: transition.containedViewLayoutTransition) self.fieldBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, transition: transition.containedViewLayoutTransition)
@ -1117,6 +1131,8 @@ public final class MessageInputPanelComponent: Component {
let inputActionButtonMode: MessageInputActionButtonComponent.Mode let inputActionButtonMode: MessageInputActionButtonComponent.Mode
if case .editor = component.style { if case .editor = component.style {
inputActionButtonMode = isEditing ? .apply : .none inputActionButtonMode = isEditing ? .apply : .none
} else if case .media = component.style {
inputActionButtonMode = isEditing ? .apply : .none
} else { } else {
if hasMediaEditing { if hasMediaEditing {
inputActionButtonMode = .send inputActionButtonMode = .send
@ -1438,8 +1454,9 @@ public final class MessageInputPanelComponent: Component {
} }
} }
let accentColor = component.theme.chat.inputPanel.panelControlAccentColor
if let timeoutAction = component.timeoutAction, let timeoutValue = component.timeoutValue { if let timeoutAction = component.timeoutAction, let timeoutValue = component.timeoutValue {
func generateIcon(value: String) -> UIImage? { func generateIcon(value: String, selected: Bool) -> UIImage? {
let image = UIImage(bundleImageName: "Media Editor/Timeout")! let image = UIImage(bundleImageName: "Media Editor/Timeout")!
let valueString = NSAttributedString(string: value, font: Font.with(size: value.count == 1 ? 12.0 : 10.0, design: .round, weight: .semibold), textColor: .white, paragraphAlignment: .center) let valueString = NSAttributedString(string: value, font: Font.with(size: value.count == 1 ? 12.0 : 10.0, design: .round, weight: .semibold), textColor: .white, paragraphAlignment: .center)
@ -1447,9 +1464,14 @@ public final class MessageInputPanelComponent: Component {
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds) context.clear(bounds)
if selected {
context.setFillColor(accentColor.cgColor)
context.fillEllipse(in: CGRect(origin: .zero, size: size))
} else {
if let cgImage = image.cgImage { if let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(origin: .zero, size: size)) context.draw(cgImage, in: CGRect(origin: .zero, size: size))
} }
}
var offset: CGPoint = CGPoint(x: 0.0, y: -3.0 - UIScreenPixel) var offset: CGPoint = CGPoint(x: 0.0, y: -3.0 - UIScreenPixel)
if value == "" { if value == "" {
@ -1462,14 +1484,14 @@ public final class MessageInputPanelComponent: Component {
let valueFramesetter = CTFramesetterCreateWithAttributedString(valueString as CFAttributedString) let valueFramesetter = CTFramesetterCreateWithAttributedString(valueString as CFAttributedString)
let valyeFrame = CTFramesetterCreateFrame(valueFramesetter, CFRangeMake(0, valueString.length), valuePath, nil) let valyeFrame = CTFramesetterCreateFrame(valueFramesetter, CFRangeMake(0, valueString.length), valuePath, nil)
CTFrameDraw(valyeFrame, context) CTFrameDraw(valyeFrame, context)
})?.withRenderingMode(.alwaysTemplate) })
} }
let icon = generateIcon(value: timeoutValue) let icon = generateIcon(value: timeoutValue, selected: component.timeoutSelected)
let timeoutButtonSize = self.timeoutButton.update( let timeoutButtonSize = self.timeoutButton.update(
transition: transition, transition: transition,
component: AnyComponent(Button( component: AnyComponent(Button(
content: AnyComponent(Image(image: icon, tintColor: component.timeoutSelected ? UIColor(rgb: 0xf8d74a) : UIColor(white: 1.0, alpha: 1.0), size: CGSize(width: 20.0, height: 20.0))), content: AnyComponent(Image(image: icon, size: CGSize(width: 20.0, height: 20.0))),
action: { [weak self] in action: { [weak self] in
guard let self, let timeoutButtonView = self.timeoutButton.view else { guard let self, let timeoutButtonView = self.timeoutButton.view else {
return return
@ -1495,7 +1517,9 @@ public final class MessageInputPanelComponent: Component {
} }
var fieldBackgroundIsDark = false var fieldBackgroundIsDark = false
if self.textFieldExternalState.hasText && component.alwaysDarkWhenHasText { if component.style == .media {
} else if self.textFieldExternalState.hasText && component.alwaysDarkWhenHasText {
fieldBackgroundIsDark = true fieldBackgroundIsDark = true
} else if isEditing || component.style == .editor { } else if isEditing || component.style == .editor {
fieldBackgroundIsDark = true fieldBackgroundIsDark = true
@ -1667,8 +1691,12 @@ public final class MessageInputPanelComponent: Component {
self.updateContextQueries() self.updateContextQueries()
let panelLeftInset: CGFloat = max(insets.left, 7.0) var panelLeftInset: CGFloat = max(insets.left, 7.0)
let panelRightInset: CGFloat = max(insets.right, 41.0) var panelRightInset: CGFloat = max(insets.right, 41.0)
if case .media = component.style {
panelLeftInset = 0.0
panelRightInset = 0.0
}
var contextResults: ContextResultPanelComponent.Results? var contextResults: ContextResultPanelComponent.Results?
if let result = self.contextQueryResults[.mention], case let .mentions(mentions) = result, !mentions.isEmpty { if let result = self.contextQueryResults[.mention], case let .mentions(mentions) = result, !mentions.isEmpty {
@ -1800,7 +1828,13 @@ public final class MessageInputPanelComponent: Component {
containerSize: CGSize(width: availableSize.width - panelLeftInset - panelRightInset, height: availablePanelHeight) containerSize: CGSize(width: availableSize.width - panelLeftInset - panelRightInset, height: availablePanelHeight)
) )
let panelFrame = CGRect(origin: CGPoint(x: insets.left, y: -panelSize.height + 14.0), size: CGSize(width: panelSize.width, height: panelSize.height + 19.0)) var panelOriginY = -panelSize.height + 14.0
var panelHeight = panelSize.height + 19.0
if case .media = component.style {
panelOriginY -= 6.0
panelHeight = panelSize.height
}
let panelFrame = CGRect(origin: CGPoint(x: panelLeftInset, y: panelOriginY), size: CGSize(width: panelSize.width, height: panelHeight))
if let panelView = panel.view as? ContextResultPanelComponent.View { if let panelView = panel.view as? ContextResultPanelComponent.View {
if panelView.superview == nil { if panelView.superview == nil {
self.insertSubview(panelView, at: 0) self.insertSubview(panelView, at: 0)

View File

@ -135,7 +135,7 @@ final class ShareWithPeersScreenComponent: Component {
self.itemHeight = itemHeight self.itemHeight = itemHeight
self.itemCount = itemCount self.itemCount = itemCount
self.totalHeight = insets.top + itemHeight * CGFloat(itemCount) self.totalHeight = insets.top + itemHeight * CGFloat(itemCount) + insets.bottom
} }
} }
@ -818,8 +818,16 @@ final class ShareWithPeersScreenComponent: Component {
var topOffsetFraction = topOffset / topOffsetDistance var topOffsetFraction = topOffset / topOffsetDistance
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
//let transitionFactor: CGFloat = 1.0 - topOffsetFraction let transitionFactor: CGFloat = 1.0 - topOffsetFraction
//controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition) if let controller = environment.controller() {
Queue.mainQueue().justDispatch {
var transition = transition
if controller.modalStyleOverlayTransitionFactor.isZero && transitionFactor > 0.0, transition.animation.isImmediate {
transition = .spring(duration: 0.4)
}
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition)
}
}
var visibleBounds = self.scrollView.bounds var visibleBounds = self.scrollView.bounds
visibleBounds.origin.y -= itemLayout.topInset visibleBounds.origin.y -= itemLayout.topInset
@ -907,7 +915,7 @@ final class ShareWithPeersScreenComponent: Component {
let sectionTitle: String let sectionTitle: String
if section.id == 0, case .stories = component.stateContext.subject { if section.id == 0, case .stories = component.stateContext.subject {
sectionTitle = "POST STORY AS" sectionTitle = environment.strings.Story_Privacy_PostStoryAsHeader
} else if section.id == 2 { } else if section.id == 2 {
sectionTitle = environment.strings.Story_Privacy_WhoCanViewHeader sectionTitle = environment.strings.Story_Privacy_WhoCanViewHeader
} else if section.id == 1 { } else if section.id == 1 {
@ -1362,11 +1370,16 @@ final class ShareWithPeersScreenComponent: Component {
self.visibleItems[itemId] = visibleItem self.visibleItems[itemId] = visibleItem
} }
var title = item.title
if item.id == .pin && !hasCategories {
title = environment.strings.Story_Privacy_KeepOnChannelPage
}
let _ = visibleItem.update( let _ = visibleItem.update(
transition: itemTransition, transition: itemTransition,
component: AnyComponent(OptionListItemComponent( component: AnyComponent(OptionListItemComponent(
theme: environment.theme, theme: environment.theme,
title: item.title, title: title,
hasNext: i != component.optionItems.count - 1, hasNext: i != component.optionItems.count - 1,
selected: self.selectedOptions.contains(item.id), selected: self.selectedOptions.contains(item.id),
selectionChanged: { [weak self] selected in selectionChanged: { [weak self] selected in
@ -1414,7 +1427,7 @@ final class ShareWithPeersScreenComponent: Component {
var footerText = environment.strings.Story_Privacy_KeepOnMyPageInfo(footerValue).string var footerText = environment.strings.Story_Privacy_KeepOnMyPageInfo(footerValue).string
if self.sendAsPeerId?.isGroupOrChannel == true { if self.sendAsPeerId?.isGroupOrChannel == true {
footerText = "Keep this story on channel profile even after it expires in 24 hours." footerText = environment.strings.Story_Privacy_KeepOnChannelPageInfo(footerValue).string
} }
let footerSize = sectionFooter.update( let footerSize = sectionFooter.update(
@ -1508,6 +1521,9 @@ final class ShareWithPeersScreenComponent: Component {
for id in removeSectionBackgroundIds { for id in removeSectionBackgroundIds {
self.visibleSectionBackgrounds.removeValue(forKey: id) self.visibleSectionBackgrounds.removeValue(forKey: id)
} }
for id in removeSectionFooterIds {
self.visibleSectionFooters.removeValue(forKey: id)
}
let fadeTransition = Transition.easeInOut(duration: 0.25) let fadeTransition = Transition.easeInOut(duration: 0.25)
if let searchStateContext = self.searchStateContext, case let .search(query, _) = searchStateContext.subject, let value = searchStateContext.stateValue, value.peers.isEmpty { if let searchStateContext = self.searchStateContext, case let .search(query, _) = searchStateContext.subject, let value = searchStateContext.stateValue, value.peers.isEmpty {
@ -1932,11 +1948,16 @@ final class ShareWithPeersScreenComponent: Component {
) )
var hasCategories = false var hasCategories = false
var hasChannels = false
if case .stories = component.stateContext.subject { if case .stories = component.stateContext.subject {
if let peerId = self.sendAsPeerId, peerId.isGroupOrChannel { if let peerId = self.sendAsPeerId, peerId.isGroupOrChannel {
} else { } else {
hasCategories = true hasCategories = true
} }
let sendAsPeersCount = component.stateContext.stateValue?.sendAsPeers.count ?? 1
if sendAsPeersCount > 1 {
hasChannels = true
}
} }
var footersTotalHeight: CGFloat = 0.0 var footersTotalHeight: CGFloat = 0.0
@ -2016,16 +2037,15 @@ final class ShareWithPeersScreenComponent: Component {
if case let .peers(peers, _) = component.stateContext.subject { if case let .peers(peers, _) = component.stateContext.subject {
sections.append(ItemLayout.Section( sections.append(ItemLayout.Section(
id: 0, id: 0,
insets: UIEdgeInsets(top: 12.0, left: 0.0, bottom: 24.0, right: 0.0), insets: UIEdgeInsets(top: 12.0, left: 0.0, bottom: 0.0, right: 0.0),
itemHeight: peerItemSize.height, itemHeight: peerItemSize.height,
itemCount: peers.count itemCount: peers.count
)) ))
} else if case let .stories(editing) = component.stateContext.subject { } else if case let .stories(editing) = component.stateContext.subject {
let sendAsPeersCount = component.stateContext.stateValue?.sendAsPeers.count ?? 1 if !editing && hasChannels {
if !editing && sendAsPeersCount > 1 {
sections.append(ItemLayout.Section( sections.append(ItemLayout.Section(
id: 0, id: 0,
insets: UIEdgeInsets(top: 28.0, left: 0.0, bottom: 24.0, right: 0.0), insets: UIEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0),
itemHeight: peerItemSize.height, itemHeight: peerItemSize.height,
itemCount: 1 itemCount: 1
)) ))
@ -2040,7 +2060,7 @@ final class ShareWithPeersScreenComponent: Component {
} }
sections.append(ItemLayout.Section( sections.append(ItemLayout.Section(
id: 3, id: 3,
insets: UIEdgeInsets(top: 28.0, left: 0.0, bottom: 24.0, right: 0.0), insets: UIEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0),
itemHeight: optionItemSize.height, itemHeight: optionItemSize.height,
itemCount: component.optionItems.count itemCount: component.optionItems.count
)) ))
@ -2110,7 +2130,7 @@ final class ShareWithPeersScreenComponent: Component {
let title: String let title: String
switch component.stateContext.subject { switch component.stateContext.subject {
case .peers: case .peers:
title = "Post Story As" title = environment.strings.Story_Privacy_PostStoryAs
case let .stories(editing): case let .stories(editing):
if editing { if editing {
title = environment.strings.Story_Privacy_EditStory title = environment.strings.Story_Privacy_EditStory
@ -2178,11 +2198,16 @@ final class ShareWithPeersScreenComponent: Component {
inset = 351.0 inset = 351.0
inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight
} else { } else {
if hasCategories { if !hasCategories {
inset = 1000.0
} else {
inset = 314.0 inset = 314.0
inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight
} else {
if hasChannels {
inset = 1000.0
} else {
inset = 464.0
inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight
}
} }
} }
} else if case .peers = component.stateContext.subject { } else if case .peers = component.stateContext.subject {
@ -2201,7 +2226,7 @@ final class ShareWithPeersScreenComponent: Component {
var bottomPanelHeight: CGFloat = 0.0 var bottomPanelHeight: CGFloat = 0.0
var bottomPanelInset: CGFloat = 0.0 var bottomPanelInset: CGFloat = 0.0
if case .peers = component.stateContext.subject { if case .peers = component.stateContext.subject {
bottomPanelInset = environment.safeInsets.bottom
} else { } else {
let badge: Int let badge: Int
if case .stories = component.stateContext.subject { if case .stories = component.stateContext.subject {
@ -2452,7 +2477,7 @@ final class ShareWithPeersScreenComponent: Component {
let previousItemLayout = self.itemLayout let previousItemLayout = self.itemLayout
self.itemLayout = itemLayout self.itemLayout = itemLayout
contentTransition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: containerWidth, height: itemLayout.contentHeight))) contentTransition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: containerWidth, height: itemLayout.contentHeight + footersTotalHeight)))
let scrollContentHeight = max(topInset + itemLayout.contentHeight + containerInset, availableSize.height - containerInset) let scrollContentHeight = max(topInset + itemLayout.contentHeight + containerInset, availableSize.height - containerInset)

View File

@ -2860,7 +2860,8 @@ public final class StoryItemSetContainerComponent: Component {
forceIsEditing: self.sendMessageContext.currentInputMode == .media, forceIsEditing: self.sendMessageContext.currentInputMode == .media,
disabledPlaceholder: disabledPlaceholder, disabledPlaceholder: disabledPlaceholder,
isChannel: isChannel, isChannel: isChannel,
storyItem: component.slice.item.storyItem storyItem: component.slice.item.storyItem,
chatLocation: nil
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0) containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0)

View File

@ -91,6 +91,7 @@ public final class TextFieldComponent: Component {
public let insets: UIEdgeInsets public let insets: UIEdgeInsets
public let hideKeyboard: Bool public let hideKeyboard: Bool
public let resetText: NSAttributedString? public let resetText: NSAttributedString?
public let resetScrollOnFocusChange: Bool
public let formatMenuAvailability: FormatMenuAvailability public let formatMenuAvailability: FormatMenuAvailability
public let lockedFormatAction: () -> Void public let lockedFormatAction: () -> Void
public let present: (ViewController) -> Void public let present: (ViewController) -> Void
@ -105,6 +106,7 @@ public final class TextFieldComponent: Component {
insets: UIEdgeInsets, insets: UIEdgeInsets,
hideKeyboard: Bool, hideKeyboard: Bool,
resetText: NSAttributedString?, resetText: NSAttributedString?,
resetScrollOnFocusChange: Bool,
formatMenuAvailability: FormatMenuAvailability, formatMenuAvailability: FormatMenuAvailability,
lockedFormatAction: @escaping () -> Void, lockedFormatAction: @escaping () -> Void,
present: @escaping (ViewController) -> Void, present: @escaping (ViewController) -> Void,
@ -118,6 +120,7 @@ public final class TextFieldComponent: Component {
self.insets = insets self.insets = insets
self.hideKeyboard = hideKeyboard self.hideKeyboard = hideKeyboard
self.resetText = resetText self.resetText = resetText
self.resetScrollOnFocusChange = resetScrollOnFocusChange
self.formatMenuAvailability = formatMenuAvailability self.formatMenuAvailability = formatMenuAvailability
self.lockedFormatAction = lockedFormatAction self.lockedFormatAction = lockedFormatAction
self.present = present self.present = present
@ -146,6 +149,9 @@ public final class TextFieldComponent: Component {
if lhs.resetText != rhs.resetText { if lhs.resetText != rhs.resetText {
return false return false
} }
if lhs.resetScrollOnFocusChange != rhs.resetScrollOnFocusChange {
return false
}
if lhs.formatMenuAvailability != rhs.formatMenuAvailability { if lhs.formatMenuAvailability != rhs.formatMenuAvailability {
return false return false
} }
@ -235,6 +241,10 @@ public final class TextFieldComponent: Component {
self.textContainer.widthTracksTextView = false self.textContainer.widthTracksTextView = false
self.textContainer.heightTracksTextView = false self.textContainer.heightTracksTextView = false
if #available(iOS 13.0, *) {
self.textView.overrideUserInterfaceStyle = .dark
}
self.textView.typingAttributes = [ self.textView.typingAttributes = [
NSAttributedString.Key.font: Font.regular(17.0), NSAttributedString.Key.font: Font.regular(17.0),
NSAttributedString.Key.foregroundColor: UIColor.white NSAttributedString.Key.foregroundColor: UIColor.white
@ -823,7 +833,10 @@ public final class TextFieldComponent: Component {
let wasEditing = component.externalState.isEditing let wasEditing = component.externalState.isEditing
let isEditing = self.textView.isFirstResponder let isEditing = self.textView.isFirstResponder
let refreshScrolling = self.textView.bounds.size != size var refreshScrolling = self.textView.bounds.size != size
if component.resetScrollOnFocusChange && !isEditing && isEditing != wasEditing {
refreshScrolling = true
}
self.textView.frame = CGRect(origin: CGPoint(), size: size) self.textView.frame = CGRect(origin: CGPoint(), size: size)
self.textView.panGestureRecognizer.isEnabled = isEditing self.textView.panGestureRecognizer.isEnabled = isEditing

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "miniplayonce.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,107 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 2.000000 1.669922 cm
0.000000 0.000000 0.000000 scn
4.000000 8.665078 m
4.367270 8.665078 4.665000 8.962809 4.665000 9.330078 c
4.665000 9.697348 4.367270 9.995078 4.000000 9.995078 c
4.000000 8.665078 l
h
8.665000 5.330078 m
8.665000 5.697348 8.367270 5.995078 8.000000 5.995078 c
7.632730 5.995078 7.335000 5.697348 7.335000 5.330078 c
8.665000 5.330078 l
h
4.000000 9.995078 m
1.423591 9.995078 -0.665000 7.906487 -0.665000 5.330078 c
0.665000 5.330078 l
0.665000 7.171948 2.158130 8.665078 4.000000 8.665078 c
4.000000 9.995078 l
h
-0.665000 5.330078 m
-0.665000 2.753670 1.423591 0.665078 4.000000 0.665078 c
4.000000 1.995078 l
2.158130 1.995078 0.665000 3.488208 0.665000 5.330078 c
-0.665000 5.330078 l
h
4.000000 0.665078 m
6.576408 0.665078 8.665000 2.753670 8.665000 5.330078 c
7.335000 5.330078 l
7.335000 3.488208 5.841870 1.995078 4.000000 1.995078 c
4.000000 0.665078 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 6.000000 8.705566 cm
0.000000 0.000000 0.000000 scn
0.800000 4.694434 m
0.470382 4.941647 0.000000 4.706456 0.000000 4.294434 c
0.000000 0.294434 l
0.000000 -0.117589 0.470382 -0.352780 0.800000 -0.105567 c
3.466667 1.894433 l
3.733333 2.094434 3.733333 2.494434 3.466667 2.694434 c
0.800000 4.694434 l
h
f*
n
Q
endstream
endobj
3 0 obj
1311
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 12.000000 14.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001401 00000 n
0000001424 00000 n
0000001597 00000 n
0000001671 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1730
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "miniplay.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,73 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 3.000000 2.780273 cm
0.000000 0.000000 0.000000 scn
2.332051 -0.225573 m
7.127887 2.971651 l
8.018486 3.565384 8.018486 4.874069 7.127886 5.467803 c
2.332050 8.665027 l
1.335218 9.329581 0.000000 8.614994 0.000000 7.416951 c
0.000000 1.022502 l
0.000000 -0.175541 1.335219 -0.890127 2.332051 -0.225573 c
h
f
n
Q
endstream
endobj
3 0 obj
379
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 12.000000 14.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000000469 00000 n
0000000491 00000 n
0000000664 00000 n
0000000738 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
797
%%EOF

View File

@ -542,10 +542,10 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
switch preparePosition { switch preparePosition {
case .linear(_, .None), .linear(_, .Neighbour(true, _, _)): case .linear(_, .None), .linear(_, .Neighbour(true, _, _)):
if let count = webpageGalleryMediaCount { if let count = webpageGalleryMediaCount {
additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: presentationData.strings.Items_NOfM("1", "\(count)").string)) additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: presentationData.strings.Items_NOfM("1", "\(count)").string), iconName: nil)
skipStandardStatus = isImage skipStandardStatus = isImage
} else if let mediaBadge = mediaBadge { } else if let mediaBadge = mediaBadge {
additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: mediaBadge)) additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: mediaBadge), iconName: nil)
} else { } else {
skipStandardStatus = isFile skipStandardStatus = isFile
} }

View File

@ -860,7 +860,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
let maxWidth: CGFloat let maxWidth: CGFloat
if isSecretMedia { if isSecretMedia {
maxWidth = 180.0 maxWidth = 200.0
} else { } else {
maxWidth = maxDimensions.width maxWidth = maxDimensions.width
} }
@ -898,7 +898,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
switch sizeCalculation { switch sizeCalculation {
case .constrained: case .constrained:
if isSecretMedia { if isSecretMedia {
boundingSize = CGSize(width: maxWidth, height: maxWidth) boundingSize = CGSize(width: maxWidth, height: maxWidth / 5.0 * 3.0)
drawingSize = nativeSize.aspectFilled(boundingSize) drawingSize = nativeSize.aspectFilled(boundingSize)
} else { } else {
let fittedSize = nativeSize.fittedToWidthOrSmaller(boundingWidth) let fittedSize = nativeSize.fittedToWidthOrSmaller(boundingWidth)
@ -1764,7 +1764,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} }
} }
let radialStatusSize: CGFloat = wideLayout ? 50.0 : 32.0 var radialStatusSize: CGFloat
if isSecretMedia {
radialStatusSize = 48.0
} else {
radialStatusSize = wideLayout ? 50.0 : 32.0
}
if progressRequired { if progressRequired {
if self.statusNode == nil { if self.statusNode == nil {
let statusNode = RadialStatusNode(backgroundNodeColor: theme.chat.message.mediaOverlayControlColors.fillColor) let statusNode = RadialStatusNode(backgroundNodeColor: theme.chat.message.mediaOverlayControlColors.fillColor)
@ -1782,14 +1787,17 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} }
} }
let messageTheme = theme.chat.message
var state: RadialStatusNodeState = .none var state: RadialStatusNodeState = .none
var backgroundColor = messageTheme.mediaOverlayControlColors.fillColor
var badgeContent: ChatMessageInteractiveMediaBadgeContent? var badgeContent: ChatMessageInteractiveMediaBadgeContent?
var mediaDownloadState: ChatMessageInteractiveMediaDownloadState? var mediaDownloadState: ChatMessageInteractiveMediaDownloadState?
let messageTheme = theme.chat.message
if let invoice = invoice { if let invoice = invoice {
if let extendedMedia = invoice.extendedMedia { if let extendedMedia = invoice.extendedMedia {
if case let .preview(_, _, maybeVideoDuration) = extendedMedia, let videoDuration = maybeVideoDuration { if case let .preview(_, _, maybeVideoDuration) = extendedMedia, let videoDuration = maybeVideoDuration {
badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: stringForDuration(videoDuration, position: nil))) badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: stringForDuration(videoDuration, position: nil)), iconName: nil)
} }
} else { } else {
let string = NSMutableAttributedString() let string = NSMutableAttributedString()
@ -1808,7 +1816,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} }
string.append(NSAttributedString(string: title)) string.append(NSAttributedString(string: title))
} }
badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: string) badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: string, iconName: nil)
} }
} }
var animated = animated var animated = animated
@ -1944,7 +1952,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} }
} else { } else {
let progressString = String(format: "%d%%", Int(progress * 100.0)) let progressString = String(format: "%d%%", Int(progress * 100.0))
badgeContent = .text(inset: message.flags.contains(.Unsent) ? 0.0 : 12.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: progressString)) badgeContent = .text(inset: message.flags.contains(.Unsent) ? 0.0 : 12.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: progressString), iconName: nil)
mediaDownloadState = automaticPlayback ? .none : .compactFetching(progress: 0.0) mediaDownloadState = automaticPlayback ? .none : .compactFetching(progress: 0.0)
} }
@ -1980,8 +1988,10 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} }
if isSecretMedia, let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout, let beginTime = maybeBeginTime { if isSecretMedia, let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout, let beginTime = maybeBeginTime {
state = .secretTimeout(color: messageTheme.mediaOverlayControlColors.foregroundColor, icon: secretProgressIcon, beginTime: beginTime, timeout: timeout, sparks: true) state = .secretTimeout(color: messageTheme.mediaOverlayControlColors.foregroundColor, icon: secretProgressIcon, beginTime: beginTime, timeout: timeout, sparks: true)
} else if isSecretMedia, let secretProgressIcon = secretProgressIcon { backgroundColor = messageTheme.mediaDateAndStatusFillColor
state = .customIcon(secretProgressIcon) } else if isSecretMedia, let _ = secretProgressIcon {
state = .staticTimeout
backgroundColor = messageTheme.mediaDateAndStatusFillColor
} else if let file = media as? TelegramMediaFile, !file.isVideoSticker { } else if let file = media as? TelegramMediaFile, !file.isVideoSticker {
let isInlinePlayableVideo = file.isVideo && !isSecretMedia && (self.automaticPlayback ?? false) let isInlinePlayableVideo = file.isVideo && !isSecretMedia && (self.automaticPlayback ?? false)
if (!isInlinePlayableVideo || isStory) && file.isVideo { if (!isInlinePlayableVideo || isStory) && file.isVideo {
@ -2017,11 +2027,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} else { } else {
if isMediaStreamable(message: message, media: file) { if isMediaStreamable(message: message, media: file) {
state = automaticPlayback ? .none : .play(messageTheme.mediaOverlayControlColors.foregroundColor) state = automaticPlayback ? .none : .play(messageTheme.mediaOverlayControlColors.foregroundColor)
badgeContent = .text(inset: 12.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: durationString)) badgeContent = .text(inset: 12.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: durationString), iconName: nil)
mediaDownloadState = .compactRemote mediaDownloadState = .compactRemote
} else { } else {
state = automaticPlayback ? .none : state state = automaticPlayback ? .none : state
badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: durationString)) badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: durationString), iconName: nil)
} }
} }
} }
@ -2031,16 +2041,32 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} }
} }
if isSecretMedia, let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout { if isSecretMedia {
let remainingTime: Int32 let remainingTime: Int32?
if let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout {
if let beginTime = maybeBeginTime { if let beginTime = maybeBeginTime {
let elapsedTime = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - beginTime let elapsedTime = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - beginTime
remainingTime = Int32(max(0.0, timeout - elapsedTime)) remainingTime = Int32(max(0.0, timeout - elapsedTime))
} else { } else {
remainingTime = Int32(timeout) remainingTime = Int32(timeout)
} }
} else {
if let attribute = message.autoclearAttribute {
remainingTime = attribute.timeout
} else if let attribute = message.autoremoveAttribute {
remainingTime = attribute.timeout
} else {
remainingTime = nil
}
}
badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: strings.MessageTimer_ShortSeconds(Int32(remainingTime)))) if let remainingTime {
if remainingTime == viewOnceTimeout {
badgeContent = .text(inset: 10.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: "1"), iconName: "Chat/Message/SecretMediaOnce")
} else {
badgeContent = .text(inset: 10.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: strings.MessageTimer_ShortSeconds(Int32(remainingTime))), iconName: "Chat/Message/SecretMediaPlay")
}
}
} }
if let statusNode = self.statusNode { if let statusNode = self.statusNode {
@ -2062,6 +2088,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
statusNode?.removeFromSupernode() statusNode?.removeFromSupernode()
} }
}) })
statusNode.backgroundNodeColor = backgroundColor
} }
if let badgeContent = badgeContent { if let badgeContent = badgeContent {
if self.badgeNode == nil { if self.badgeNode == nil {
@ -2100,6 +2127,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
displaySpoiler = true displaySpoiler = true
} else if message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) { } else if message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) {
displaySpoiler = true displaySpoiler = true
} else if isSecretMedia {
displaySpoiler = true
} }
if displaySpoiler { if displaySpoiler {
@ -2115,12 +2144,14 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
self.extendedMediaOverlayNode?.frame = self.imageNode.frame self.extendedMediaOverlayNode?.frame = self.imageNode.frame
var tappable = false var tappable = false
if !isSecretMedia {
switch state { switch state {
case .play, .pause, .download, .none: case .play, .pause, .download, .none:
tappable = true tappable = true
default: default:
break break
} }
}
self.extendedMediaOverlayNode?.isUserInteractionEnabled = tappable self.extendedMediaOverlayNode?.isUserInteractionEnabled = tappable

View File

@ -281,16 +281,16 @@ final class GridMessageItemNode: GridItemNode {
switch status { switch status {
case let .Fetching(_, progress): case let .Fetching(_, progress):
let progressString = String(format: "%d%%", Int(progress * 100.0)) let progressString = String(format: "%d%%", Int(progress * 100.0))
badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: progressString)) badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: progressString), iconName: nil)
mediaDownloadState = .compactFetching(progress: 0.0) mediaDownloadState = .compactFetching(progress: 0.0)
case .Local: case .Local:
badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil)
case .Remote, .Paused: case .Remote, .Paused:
badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil)
mediaDownloadState = .compactRemote mediaDownloadState = .compactRemote
} }
} else { } else {
badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil)
} }
strongSelf.mediaBadgeNode.update(theme: item.theme, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false) strongSelf.mediaBadgeNode.update(theme: item.theme, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false)

View File

@ -385,16 +385,16 @@ private final class VisualMediaItemNode: ASDisplayNode {
switch status { switch status {
case let .Fetching(_, progress): case let .Fetching(_, progress):
let progressString = String(format: "%d%%", Int(progress * 100.0)) let progressString = String(format: "%d%%", Int(progress * 100.0))
badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: progressString)) badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: progressString), iconName: nil)
mediaDownloadState = .compactFetching(progress: 0.0) mediaDownloadState = .compactFetching(progress: 0.0)
case .Local: case .Local:
badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil)
case .Remote, .Paused: case .Remote, .Paused:
badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil)
mediaDownloadState = .compactRemote mediaDownloadState = .compactRemote
} }
} else { } else {
badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil)
} }
strongSelf.mediaBadgeNode.update(theme: nil, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false) strongSelf.mediaBadgeNode.update(theme: nil, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false)

View File

@ -41,6 +41,7 @@ import HashtagSearchUI
import PeerInfoStoryGridScreen import PeerInfoStoryGridScreen
import TelegramAccountAuxiliaryMethods import TelegramAccountAuxiliaryMethods
import PeerSelectionController import PeerSelectionController
import LegacyMessageInputPanel
private final class AccountUserInterfaceInUseContext { private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>() let subscribers = Bag<(Bool) -> Void>()
@ -1626,98 +1627,107 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} }
public func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? { public func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? {
var presentationData = context.sharedContext.currentPresentationData.with { $0 } // var presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) // presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
//
// var presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: presentationData.chatFontSize, bubbleCorners: presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(previewing: false), chatLocation: chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil)
//
// var updateChatPresentationInterfaceStateImpl: (((ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void)?
// var ensureFocusedImpl: (() -> Void)?
//
// let interfaceInteraction = ChatPanelInterfaceInteraction(updateTextInputStateAndMode: { f in
// updateChatPresentationInterfaceStateImpl?({
// let (updatedState, updatedMode) = f($0.interfaceState.effectiveInputState, $0.inputMode)
// return $0.updatedInterfaceState { interfaceState in
// return interfaceState.withUpdatedEffectiveInputState(updatedState)
// }.updatedInputMode({ _ in updatedMode })
// })
// }, updateInputModeAndDismissedButtonKeyboardMessageId: { f in
// updateChatPresentationInterfaceStateImpl?({
// let (updatedInputMode, updatedClosedButtonKeyboardMessageId) = f($0)
// return $0.updatedInputMode({ _ in return updatedInputMode }).updatedInterfaceState({
// $0.withUpdatedMessageActionsState({ value in
// var value = value
// value.closedButtonKeyboardMessageId = updatedClosedButtonKeyboardMessageId
// return value
// })
// })
// })
// }, openLinkEditing: {
// var selectionRange: Range<Int>?
// var text: NSAttributedString?
// var inputMode: ChatInputMode?
// updateChatPresentationInterfaceStateImpl?({ state in
// selectionRange = state.interfaceState.effectiveInputState.selectionRange
// if let selectionRange = selectionRange {
// text = state.interfaceState.effectiveInputState.inputText.attributedSubstring(from: NSRange(location: selectionRange.startIndex, length: selectionRange.count))
// }
// inputMode = state.inputMode
// return state
// })
//
// var link: String?
// if let text {
// text.enumerateAttributes(in: NSMakeRange(0, text.length)) { attributes, _, _ in
// if let linkAttribute = attributes[ChatTextInputAttributes.textUrl] as? ChatTextInputTextUrlAttribute {
// link = linkAttribute.url
// }
// }
// }
//
// let controller = chatTextLinkEditController(sharedContext: context.sharedContext, updatedPresentationData: (presentationData, .never()), account: context.account, text: text?.string ?? "", link: link, apply: { link in
// if let inputMode = inputMode, let selectionRange = selectionRange {
// if let link = link {
// updateChatPresentationInterfaceStateImpl?({
// return $0.updatedInterfaceState({
// $0.withUpdatedEffectiveInputState(chatTextInputAddLinkAttribute($0.effectiveInputState, selectionRange: selectionRange, url: link))
// })
// })
// }
// ensureFocusedImpl?()
// updateChatPresentationInterfaceStateImpl?({
// return $0.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({
// $0.withUpdatedEffectiveInputState(ChatTextInputState(inputText: $0.effectiveInputState.inputText, selectionRange: selectionRange.endIndex ..< selectionRange.endIndex))
// })
// })
// }
// })
// present(controller)
// })
//
// let inputPanelNode = AttachmentTextInputPanelNode(context: context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { c in
// presentInGlobalOverlay(c)
// }, makeEntityInputView: {
// return EntityInputView(context: context, isDark: true, areCustomEmojiEnabled: customEmojiAvailable)
// })
// inputPanelNode.interfaceInteraction = interfaceInteraction
// inputPanelNode.effectivePresentationInterfaceState = {
// return presentationInterfaceState
// }
//
// updateChatPresentationInterfaceStateImpl = { [weak inputPanelNode] f in
// let updatedPresentationInterfaceState = f(presentationInterfaceState)
// let updateInputTextState = presentationInterfaceState.interfaceState.effectiveInputState != updatedPresentationInterfaceState.interfaceState.effectiveInputState
//
// presentationInterfaceState = updatedPresentationInterfaceState
//
// if let inputPanelNode = inputPanelNode, updateInputTextState {
// inputPanelNode.updateInputTextState(updatedPresentationInterfaceState.interfaceState.effectiveInputState, animated: true)
// }
// }
//
// ensureFocusedImpl = { [weak inputPanelNode] in
// inputPanelNode?.ensureFocused()
// }
//
// return inputPanelNode
var presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: presentationData.chatFontSize, bubbleCorners: presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(previewing: false), chatLocation: chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil) let inputPanelNode = LegacyMessageInputPanelNode(
context: context,
var updateChatPresentationInterfaceStateImpl: (((ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void)? chatLocation: chatLocation,
var ensureFocusedImpl: (() -> Void)? present: present,
presentInGlobalOverlay: presentInGlobalOverlay
let interfaceInteraction = ChatPanelInterfaceInteraction(updateTextInputStateAndMode: { f in )
updateChatPresentationInterfaceStateImpl?({
let (updatedState, updatedMode) = f($0.interfaceState.effectiveInputState, $0.inputMode)
return $0.updatedInterfaceState { interfaceState in
return interfaceState.withUpdatedEffectiveInputState(updatedState)
}.updatedInputMode({ _ in updatedMode })
})
}, updateInputModeAndDismissedButtonKeyboardMessageId: { f in
updateChatPresentationInterfaceStateImpl?({
let (updatedInputMode, updatedClosedButtonKeyboardMessageId) = f($0)
return $0.updatedInputMode({ _ in return updatedInputMode }).updatedInterfaceState({
$0.withUpdatedMessageActionsState({ value in
var value = value
value.closedButtonKeyboardMessageId = updatedClosedButtonKeyboardMessageId
return value
})
})
})
}, openLinkEditing: {
var selectionRange: Range<Int>?
var text: NSAttributedString?
var inputMode: ChatInputMode?
updateChatPresentationInterfaceStateImpl?({ state in
selectionRange = state.interfaceState.effectiveInputState.selectionRange
if let selectionRange = selectionRange {
text = state.interfaceState.effectiveInputState.inputText.attributedSubstring(from: NSRange(location: selectionRange.startIndex, length: selectionRange.count))
}
inputMode = state.inputMode
return state
})
var link: String?
if let text {
text.enumerateAttributes(in: NSMakeRange(0, text.length)) { attributes, _, _ in
if let linkAttribute = attributes[ChatTextInputAttributes.textUrl] as? ChatTextInputTextUrlAttribute {
link = linkAttribute.url
}
}
}
let controller = chatTextLinkEditController(sharedContext: context.sharedContext, updatedPresentationData: (presentationData, .never()), account: context.account, text: text?.string ?? "", link: link, apply: { link in
if let inputMode = inputMode, let selectionRange = selectionRange {
if let link = link {
updateChatPresentationInterfaceStateImpl?({
return $0.updatedInterfaceState({
$0.withUpdatedEffectiveInputState(chatTextInputAddLinkAttribute($0.effectiveInputState, selectionRange: selectionRange, url: link))
})
})
}
ensureFocusedImpl?()
updateChatPresentationInterfaceStateImpl?({
return $0.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({
$0.withUpdatedEffectiveInputState(ChatTextInputState(inputText: $0.effectiveInputState.inputText, selectionRange: selectionRange.endIndex ..< selectionRange.endIndex))
})
})
}
})
present(controller)
})
let inputPanelNode = AttachmentTextInputPanelNode(context: context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { c in
presentInGlobalOverlay(c)
}, makeEntityInputView: {
return EntityInputView(context: context, isDark: true, areCustomEmojiEnabled: customEmojiAvailable)
})
inputPanelNode.interfaceInteraction = interfaceInteraction
inputPanelNode.effectivePresentationInterfaceState = {
return presentationInterfaceState
}
updateChatPresentationInterfaceStateImpl = { [weak inputPanelNode] f in
let updatedPresentationInterfaceState = f(presentationInterfaceState)
let updateInputTextState = presentationInterfaceState.interfaceState.effectiveInputState != updatedPresentationInterfaceState.interfaceState.effectiveInputState
presentationInterfaceState = updatedPresentationInterfaceState
if let inputPanelNode = inputPanelNode, updateInputTextState {
inputPanelNode.updateInputTextState(updatedPresentationInterfaceState.interfaceState.effectiveInputState, animated: true)
}
}
ensureFocusedImpl = { [weak inputPanelNode] in
inputPanelNode?.ensureFocused()
}
return inputPanelNode return inputPanelNode
} }

View File

@ -477,7 +477,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
} else { } else {
animationSize = CGSize(width: 32.0, height: 32.0) animationSize = CGSize(width: 32.0, height: 32.0)
} }
if animationName == "anim_autoremove_on" { if ["anim_autoremove_on", "anim_autoremove_off"].contains(animationName) {
animationOffset = -3.0 animationOffset = -3.0
} else if animationName == "ChatListFoldersTooltip" { } else if animationName == "ChatListFoldersTooltip" {
animationInset = (70.0 - animationSize.width) / 2.0 animationInset = (70.0 - animationSize.width) / 2.0

View File

@ -766,7 +766,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor) let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents) return ("URL", contents)
}), textAlignment: .natural) }), textAlignment: .natural)