diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 9ee3fe6e40..2f3ea5fe2b 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9863,3 +9863,10 @@ Sorry for the inconvenience."; "Story.ViewList.ViewerCount_any" = "%d Viewers"; "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."; diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index 05217f486c..ef2e88af01 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -446,8 +446,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS 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, sideInset: CGFloat, animated: Bool) -> CGFloat { + public func updateLayoutSize(_ size: CGSize, keyboardHeight: CGFloat, sideInset: CGFloat, animated: Bool) -> CGFloat { guard let presentationInterfaceState = self.presentationInterfaceState else { 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() { self.ensureUnfocused() } diff --git a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift index 0923c3c18f..d8b21b9fd6 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift @@ -264,18 +264,18 @@ private final class VisualMediaItemNode: ASDisplayNode { if isStreamable { switch status { - case let .Fetching(_, progress): - let progressString = String(format: "%d%%", Int(progress * 100.0)) - badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: progressString)) - mediaDownloadState = .compactFetching(progress: 0.0) - case .Local: - badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) - case .Remote, .Paused: - badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) - mediaDownloadState = .compactRemote + case let .Fetching(_, progress): + let progressString = String(format: "%d%%", Int(progress * 100.0)) + badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: progressString), iconName: nil) + mediaDownloadState = .compactFetching(progress: 0.0) + case .Local: + badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil) + case .Remote, .Paused: + badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil) + mediaDownloadState = .compactRemote } } 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) diff --git a/submodules/ChatMessageInteractiveMediaBadge/Sources/ChatMessageInteractiveMediaBadge.swift b/submodules/ChatMessageInteractiveMediaBadge/Sources/ChatMessageInteractiveMediaBadge.swift index c97b5b013b..611a6966d5 100644 --- a/submodules/ChatMessageInteractiveMediaBadge/Sources/ChatMessageInteractiveMediaBadge.swift +++ b/submodules/ChatMessageInteractiveMediaBadge/Sources/ChatMessageInteractiveMediaBadge.swift @@ -18,13 +18,13 @@ public enum ChatMessageInteractiveMediaDownloadState: 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) public static func ==(lhs: ChatMessageInteractiveMediaBadgeContent, rhs: ChatMessageInteractiveMediaBadgeContent) -> Bool { switch lhs { - case let .text(lhsInset, lhsBackgroundColor, lhsForegroundColor, lhsText): - if case let .text(rhsInset, rhsBackgroundColor, rhsForegroundColor, rhsText) = rhs, lhsInset.isEqual(to: rhsInset), lhsBackgroundColor.isEqual(rhsBackgroundColor), lhsForegroundColor.isEqual(rhsForegroundColor), lhsText.isEqual(to: rhsText) { + case let .text(lhsInset, lhsBackgroundColor, lhsForegroundColor, lhsText, lhsIconName): + 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 } else { return false @@ -48,6 +48,7 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode { private var previousContentSize: CGSize? private var backgroundNodeColor: UIColor? private var foregroundColor: UIColor? + private var iconName: String? private let backgroundNode: ASImageNode private let durationNode: ASTextNode @@ -107,14 +108,18 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode { } switch content { - case let .text(inset, backgroundColor, foregroundColor, text): + case let .text(inset, backgroundColor, foregroundColor, text, iconName): transition = .immediate if self.backgroundNodeColor != backgroundColor { self.backgroundNodeColor = 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 if let _ = attributes[ChatTextInputAttributes.bold] { convertedText.addAttribute(.font, value: boldFont, range: range) @@ -122,12 +127,33 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode { } self.durationNode.attributedText = convertedText 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) - if let iconNode = self.iconNode { - transition.updateTransformScale(node: iconNode, scale: 0.001) - transition.updateAlpha(node: iconNode, alpha: 0.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 { + transition.updateTransformScale(node: iconNode, scale: 0.001) + transition.updateAlpha(node: iconNode, alpha: 0.0) + } } case let .mediaDownload(backgroundColor, foregroundColor, duration, size, muted, active): if self.backgroundNodeColor != backgroundColor { @@ -209,7 +235,7 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode { let durationWidth = widthForString(duration) transition.updatePosition(node: iconNode, position: CGPoint(x: (active ? 42.0 : 7.0) + durationWidth + 4.0 + 7.0, y: (active ? 8.0 : 4.0) + 5.0)) - + if muted { transition.updateAlpha(node: iconNode, alpha: 1.0) transition.updateTransformScale(node: iconNode, scale: 1.0) diff --git a/submodules/ComponentFlow/Source/Base/Component.swift b/submodules/ComponentFlow/Source/Base/Component.swift index 71bafb2dd5..7716349577 100644 --- a/submodules/ComponentFlow/Source/Base/Component.swift +++ b/submodules/ComponentFlow/Source/Base/Component.swift @@ -89,7 +89,7 @@ extension UIView { } open class ComponentState { - var _updated: ((Transition) -> Void)? + open var _updated: ((Transition) -> Void)? var isUpdated: Bool = false public init() { diff --git a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift index 505d0748bb..c16940bbef 100644 --- a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift +++ b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift @@ -75,6 +75,8 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { public var getEntityAdditionalScale: () -> CGFloat = { return 1.0 } public var getAvailableReactions: () -> [ReactionItem] = { return [] } + public var present: (ViewController) -> Void = { _ in } + public var push: (ViewController) -> Void = { _ in } public var hasSelectionChanged: (Bool) -> Void = { _ in } var selectionChanged: (DrawingEntity?) -> Void = { _ in } diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift index d9976fb3b7..c31b1e5969 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift @@ -33,7 +33,7 @@ public final class DrawingStickerEntityView: DrawingEntityView { private var outlineView: UIImageView? private let imageNode: TransformImageNode - private var animationNode: AnimatedStickerNode? + private var animationNode: DefaultAnimatedStickerNodeImpl? private var videoNode: UniversalVideoNode? 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() { if let file = self.file { if let dimensions = file.dimensions { @@ -149,9 +167,7 @@ public final class DrawingStickerEntityView: DrawingEntityView { } self.addSubnode(animationNode) - if file.isCustomTemplateEmoji { - animationNode.dynamicColor = UIColor(rgb: 0xffffff) - } + self.updateAnimationColor() if !self.stickerEntity.isAnimated { self.imageNode.isHidden = true @@ -525,50 +541,21 @@ public final class DrawingStickerEntityView: DrawingEntityView { } reactionContextNode.premiumReactionsSelected = { [weak self] file in - let _ = self - let _ = file -// guard let self, let component = self.component else { -// return -// } -// -// guard let file else { -// 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) -// 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) + guard let self, let file else { + return + } + + let context = self.context + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + 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 + if case .info = action, let self { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil) + self.containerView?.push(controller) + } + return false + }) + self.containerView?.present(controller) } 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.updateAnimationColor() let isReaction = self.isReaction let staticTransform = CATransform3DMakeScale(self.stickerEntity.mirrored ? -1.0 : 1.0, 1.0, 1.0) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h index 3a155692c6..e420bc3634 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h @@ -22,18 +22,22 @@ @property (nonatomic, copy) void (^panelFocused)(void); @property (nonatomic, copy) void (^finishedWithCaption)(NSAttributedString *caption); @property (nonatomic, copy) void (^keyboardHeightChanged)(CGFloat keyboardHeight, NSTimeInterval duration, NSInteger animationCurve); +@property (nonatomic, copy) void (^timerUpdated)(NSNumber *timeout); - (void)createInputPanelIfNeeded; - (void)beginEditing; - (void)enableDismissal; +- (void)onAnimateOut; + - (void)destroy; @property (nonatomic, strong) NSAttributedString *caption; - (void)setCaption:(NSAttributedString *)caption 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; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h index 0bfdbec4aa..eaf71260ae 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h @@ -22,15 +22,22 @@ @property (nonatomic, readonly) UIView * _Nonnull view; +- (void)setTimeout:(int32_t)timeout; + - (NSAttributedString * _Nonnull)caption; - (void)setCaption:(NSAttributedString * _Nullable)caption; - (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 focusUpdated)(BOOL focused); @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; @end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index 785952c7c6..08ba0caa6a 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -42,6 +42,28 @@ #import +@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 () { id _currentItem; @@ -121,7 +143,7 @@ _itemHeaderViews = [[NSMutableArray alloc] init]; _itemFooterViews = [[NSMutableArray alloc] init]; - _wrapperView = [[UIView alloc] initWithFrame:CGRectZero]; + _wrapperView = [[TGMediaPickerGalleryWrapperView alloc] initWithFrame:CGRectZero]; [self addSubview:_wrapperView]; _headerWrapperView = [[UIView alloc] init]; @@ -148,6 +170,8 @@ strongSelf->_portraitToolbarView.doneButton.userInteractionEnabled = false; strongSelf->_landscapeToolbarView.doneButton.userInteractionEnabled = false; strongSelf->_donePressed(strongSelf->_currentItem); + + [strongSelf->_captionMixin onAnimateOut]; }; void(^toolbarDoneLongPressed)(id) = ^(id sender) { @@ -350,6 +374,18 @@ } completion:nil]; }; + _captionMixin.timerUpdated = ^(NSNumber *timeout) { + __strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf; + if (strongSelf == nil) + return; + + if (![strongSelf->_currentItem conformsToProtocol:@protocol(TGModernGalleryEditableItem)]) + return; + + id galleryEditableItem = (id)strongSelf->_currentItem; + [strongSelf->_editingContext setTimer:timeout forItem:galleryEditableItem.editableMediaItem]; + }; + _captionMixin.stickersContext = stickersContext; [_captionMixin createInputPanelIfNeeded]; @@ -800,6 +836,8 @@ id adjustments = dict[@"adjustments"]; NSNumber *timer = dict[@"timer"]; + [strongSelf->_captionMixin setTimeout:[timer intValue]]; + if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]]) { TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments; @@ -1285,7 +1323,7 @@ - (void)animateTransitionOutWithDuration:(NSTimeInterval)__unused duration { - + [_captionMixin onAnimateOut]; } - (void)setTransitionOutProgress:(CGFloat)transitionOutProgress manual:(bool)manual diff --git a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m index 98f1744ecb..08d8017a9f 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m +++ b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m @@ -87,6 +87,14 @@ [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; _backgroundView = [[UIView alloc] init]; @@ -95,6 +103,10 @@ [parentView addSubview:_inputPanelView]; } +- (void)onAnimateOut { + [_inputPanel onAnimateOut]; +} + - (void)destroy { [_inputPanelView removeFromSuperview]; @@ -129,6 +141,10 @@ [_inputPanel setCaption:caption]; } +- (void)setTimeout:(int32_t)timeout { + [_inputPanel setTimeout:timeout]; +} + - (void)setCaptionPanelHidden:(bool)hidden animated:(bool)__unused animated { _inputPanelView.hidden = hidden; @@ -204,7 +220,7 @@ CGRect frame = _currentFrame; 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:^{ _inputPanelView.frame = CGRectMake(edgeInsets.left, frame.size.height - panelHeight - MAX(edgeInsets.bottom, _keyboardHeight), frame.size.width, panelHeight); @@ -224,7 +240,7 @@ _currentFrame = frame; _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; if (frame.size.width > frame.size.height && !TGIsPad()) { @@ -238,14 +254,15 @@ 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) { - [UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ - _inputPanelView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, panelHeight); - _backgroundView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, backgroundHeight); - } completion:nil]; + [_inputPanel animateView:_inputPanelView frame:panelFrame]; + [_inputPanel animateView:_backgroundView frame:backgroundFrame]; } else { - _inputPanelView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, panelHeight); - _backgroundView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, backgroundHeight); + _inputPanelView.frame = panelFrame; + _backgroundView.frame = backgroundFrame; } } diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index 9a698f5f21..f8874c03f0 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -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) - 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)) } if let spoiler = item.spoiler, spoiler { @@ -531,7 +531,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A ]) 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)) } 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: []) 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)) } 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) - 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)) } if let spoiler = item.spoiler, spoiler { diff --git a/submodules/RadialStatusNode/BUILD b/submodules/RadialStatusNode/BUILD index 264f6f4f6d..5fb0386325 100644 --- a/submodules/RadialStatusNode/BUILD +++ b/submodules/RadialStatusNode/BUILD @@ -14,6 +14,7 @@ swift_library( "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/LegacyComponents:LegacyComponents", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/ManagedAnimationNode:ManagedAnimationNode" ], visibility = [ "//visibility:public", diff --git a/submodules/RadialStatusNode/Sources/RadialStatusIconContentNode.swift b/submodules/RadialStatusNode/Sources/RadialStatusIconContentNode.swift index d0b5f9e2af..0d1510195a 100644 --- a/submodules/RadialStatusNode/Sources/RadialStatusIconContentNode.swift +++ b/submodules/RadialStatusNode/Sources/RadialStatusIconContentNode.swift @@ -5,6 +5,7 @@ import AsyncDisplayKit enum RadialStatusIcon { case custom(UIImage) + case timeout case play(UIColor) case pause(UIColor) } @@ -22,14 +23,28 @@ private final class RadialStatusIconContentNodeParameters: NSObject { final class RadialStatusIconContentNode: RadialStatusContentNode { private let icon: RadialStatusIcon + private var animationNode: FireIconNode? + init(icon: RadialStatusIcon, synchronous: Bool) { self.icon = icon super.init() self.displaysAsynchronously = !synchronous - self.isLayerBacked = true +// self.isLayerBacked = true 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? { @@ -48,6 +63,8 @@ final class RadialStatusIconContentNode: RadialStatusContentNode { if let parameters = parameters as? RadialStatusIconContentNodeParameters { let diameter = min(bounds.size.width, bounds.size.height) switch parameters.icon { + case .timeout: + break case let .play(color): context.setFillColor(color.cgColor) diff --git a/submodules/RadialStatusNode/Sources/RadialStatusNode.swift b/submodules/RadialStatusNode/Sources/RadialStatusNode.swift index 28670b84b2..3c1a78394d 100644 --- a/submodules/RadialStatusNode/Sources/RadialStatusNode.swift +++ b/submodules/RadialStatusNode/Sources/RadialStatusNode.swift @@ -12,6 +12,7 @@ public enum RadialStatusNodeState: Equatable { case cloudProgress(color: UIColor, strokeBackgroundColor: UIColor, lineWidth: CGFloat, value: CGFloat?) case check(UIColor) case customIcon(UIImage) + case staticTimeout case secretTimeout(color: UIColor, icon: UIImage?, beginTime: Double, timeout: Double, sparks: Bool) public static func ==(lhs: RadialStatusNodeState, rhs: RadialStatusNodeState) -> Bool { @@ -64,6 +65,12 @@ public enum RadialStatusNodeState: Equatable { } else { return false } + case .staticTimeout: + if case .staticTimeout = rhs { + return true + } else { + return false + } 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 { return true @@ -123,6 +130,12 @@ public enum RadialStatusNodeState: Equatable { } else { return false } + case .staticTimeout: + if case .staticTimeout = rhs{ + return true + } else { + return false + } 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 { return true @@ -179,6 +192,8 @@ public enum RadialStatusNodeState: Equatable { node.progress = value return node } + case .staticTimeout: + return RadialStatusIconContentNode(icon: .timeout, synchronous: synchronous) case let .secretTimeout(color, icon, beginTime, timeout, sparks): return RadialStatusSecretTimeoutContentNode(color: color, beginTime: beginTime, timeout: timeout, icon: icon, sparks: sparks) } @@ -188,7 +203,9 @@ public enum RadialStatusNodeState: Equatable { public final class RadialStatusNode: ASControlNode { public var backgroundNodeColor: UIColor { didSet { - self.transitionToBackgroundColor(self.state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: false, synchronous: false, completion: {}) + if self.backgroundNodeColor != oldValue { + self.transitionToBackgroundColor(self.state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: false, synchronous: false, completion: {}) + } } } diff --git a/submodules/RadialStatusNode/Sources/RadialStatusSecretTimeoutContentNode.swift b/submodules/RadialStatusNode/Sources/RadialStatusSecretTimeoutContentNode.swift index ac5fcf9e02..f790cf06d9 100644 --- a/submodules/RadialStatusNode/Sources/RadialStatusSecretTimeoutContentNode.swift +++ b/submodules/RadialStatusNode/Sources/RadialStatusSecretTimeoutContentNode.swift @@ -3,6 +3,7 @@ import UIKit import Display import AsyncDisplayKit import LegacyComponents +import ManagedAnimationNode private struct ContentParticle { var position: CGPoint @@ -53,6 +54,8 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode { private var progress: CGFloat = 0.0 private var particles: [ContentParticle] = [] + private let animationNode = FireIconNode() + private var displayLink: CADisplayLink? init(color: UIColor, beginTime: Double, timeout: Double, icon: UIImage?, sparks: Bool) { @@ -65,7 +68,7 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode { super.init() self.isOpaque = false - self.isLayerBacked = true +// self.isLayerBacked = true class DisplayLinkProxy: NSObject { 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?.isPaused = true self.displayLink?.add(to: RunLoop.main, forMode: .common) + + self.addSubnode(self.animationNode) } deinit { @@ -89,6 +94,8 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode { override func 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) { @@ -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)) + } +} diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 8e44ce05b9..2982656c33 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -9,11 +9,11 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], var hasUnseenReactions = false for attribute in attributes { 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 } } 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 } } else if let mentionAttribute = attribute as? ConsumablePersonalMentionMessageAttribute { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutoremoveTimeoutMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutoremoveTimeoutMessageAttribute.swift index 77e1eec8dc..2ec5399c1e 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutoremoveTimeoutMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutoremoveTimeoutMessageAttribute.swift @@ -1,6 +1,8 @@ import Foundation import Postbox +public let viewOnceTimeout: Int32 = 0x7fffffff + public class AutoremoveTimeoutMessageAttribute: MessageAttribute { public let timeout: Int32 public let countdownBeginTime: Int32? @@ -124,7 +126,7 @@ public extension Message { guard let timeout = self.minAutoremoveOrClearTimeout else { return false } - if timeout > 1 * 60 { + if timeout > 1 * 60 && timeout != viewOnceTimeout { return false } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 3893ab216a..dd2452c540 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -339,6 +339,7 @@ swift_library( "//submodules/TelegramUI/Components/PeerSelectionController", "//submodules/TelegramUI/Components/Chat/AccessoryPanelNode", "//submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode", + "//submodules/TelegramUI/Components/LegacyMessageInputPanel", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD b/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD new file mode 100644 index 0000000000..9ffc71d6c7 --- /dev/null +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD @@ -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", + ], +) diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift new file mode 100644 index 0000000000..e3f3dd33f3 --- /dev/null +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift @@ -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() + + 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) + } +} diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift index f36e0c4256..d18276e8d0 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift @@ -111,8 +111,12 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { switch self.content { case let .image(image, _): dimensions = image.size - case let .file(file, _): - dimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) + 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) + } case let .video(file): dimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) case .dualVideoReference: diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 970b64d2f0..618e1f5dce 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -415,7 +415,6 @@ final class MediaEditorScreenComponent: Component { guard let _ = self.inputPanel.view as? MessageInputPanelComponent.View else { return } -// if view.canDeactivateInput() { self.currentInputMode = .text if hasFirstResponder(self) { if let view = self.inputPanel.view as? MessageInputPanelComponent.View { @@ -429,12 +428,6 @@ final class MediaEditorScreenComponent: Component { } else { 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 @@ -511,7 +504,7 @@ final class MediaEditorScreenComponent: Component { func animateOut(to source: TransitionAnimationSource) { self.isDismissed = true - + let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut)) if let view = self.cancelButton.view { transition.setAlpha(view: view, alpha: 0.0) @@ -939,7 +932,6 @@ final class MediaEditorScreenComponent: Component { self.appliedAudioData = audioData var timeoutValue: String - let timeoutSelected: Bool switch component.privacy.timeout { case 21600: timeoutValue = "6" @@ -952,7 +944,6 @@ final class MediaEditorScreenComponent: Component { default: timeoutValue = "24" } - timeoutSelected = false var inputPanelAvailableWidth = previewSize.width var inputPanelAvailableHeight = 103.0 @@ -1192,7 +1183,7 @@ final class MediaEditorScreenComponent: Component { hasRecordedVideoPreview: false, wasRecordingDismissed: false, timeoutValue: timeoutValue, - timeoutSelected: timeoutSelected, + timeoutSelected: false, displayGradient: false, bottomInset: 0.0, isFormattingLocked: !state.isPremium, @@ -1200,7 +1191,8 @@ final class MediaEditorScreenComponent: Component { forceIsEditing: self.currentInputMode == .emoji, disabledPlaceholder: nil, isChannel: false, - storyItem: nil + storyItem: nil, + chatLocation: nil )), environment: {}, containerSize: CGSize(width: inputPanelAvailableWidth, height: inputPanelAvailableHeight) @@ -1952,6 +1944,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.entitiesView.getAvailableReactions = { [weak self] in 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) |> deliverOnMainQueue).start(next: { [weak self] reactions in @@ -2585,6 +2588,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate controller.statusBar.statusBarStyle = .Ignore self.isUserInteractionEnabled = false + if self.entitiesView.hasSelection { + self.entitiesView.selectEntity(nil) + } + let previousDimAlpha = self.backgroundDimView.alpha self.backgroundDimView.alpha = 0.0 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 = { self.node.mediaEditor?.play() } @@ -3846,6 +3859,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate editCategory: { _, _, _ 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 = { 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) self.push(controller) } - return false } - ) + return false + }) self.present(controller, in: .current) } @@ -3970,8 +3989,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate }) self.push(controller) } - return false } - ) + return false + }) self.present(controller, in: .current) } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift index 0a2e183631..1cd86d4e2d 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift @@ -293,7 +293,8 @@ final class StoryPreviewComponent: Component { forceIsEditing: false, disabledPlaceholder: nil, isChannel: false, - storyItem: nil + storyItem: nil, + chatLocation: nil )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 200.0) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/VideoScrubberComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/VideoScrubberComponent.swift index 79a2e2ac22..a4c46e7f31 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/VideoScrubberComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/VideoScrubberComponent.swift @@ -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)) } } + self.cursorView.isHidden = component.audioOnly let bounds = CGRect(origin: .zero, size: scrubberSize) diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD index 3c9954c354..76e70fbb35 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD @@ -36,6 +36,7 @@ swift_library( "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/AnimatedCountLabelNode", "//submodules/TelegramUI/Components/MessageInputActionButtonComponent", + "//submodules/SearchPeerMembers", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift index a0bf9324a3..0546d1cf9e 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift @@ -5,6 +5,7 @@ import TextFieldComponent import ChatContextQuery import AccountContext import TelegramUIPreferences +import SearchPeerMembers func textInputStateContextQueryRangeAndType(inputState: TextFieldComponent.InputState) -> [(NSRange, PossibleContextQueryTypes, NSRange?)] { return textInputStateContextQueryRangeAndType(inputText: inputState.inputText, selectionRange: inputState.selectionRange) @@ -38,7 +39,7 @@ func inputContextQueries(_ inputState: TextFieldComponent.InputState) -> [ChatPr 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 return availableTypes.contains(query.kind) }) @@ -48,7 +49,7 @@ func contextQueryResultState(context: AccountContext, inputState: TextFieldCompo for query in inputQueries { let previousQuery = currentQueryStates[query.kind]?.0 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) } } @@ -69,7 +70,7 @@ func contextQueryResultState(context: AccountContext, inputState: TextFieldCompo 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 { case let .emoji(query): var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete() @@ -149,7 +150,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp |> castError(ChatContextQueryError.self) return signal |> then(hashtags) - case let .mention(query, _): + case let .mention(query, types): var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete() if let previousQuery = previousQuery { switch previousQuery { @@ -163,34 +164,79 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp } let normalizedQuery = query.lowercased() - if normalizedQuery.isEmpty { - let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.peers.recentPeers() - |> map { recentPeers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in - if case let .peers(peers) = recentPeers { - let peers = peers.filter { peer in - return peer.addressName != nil - }.compactMap { EnginePeer($0) } - return { _ in return .mentions(peers) } - } else { - return { _ in return .mentions([]) } - } - } - |> castError(ChatContextQueryError.self) - return signal |> then(peers) - } else { - let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.contacts.searchLocalPeers(query: normalizedQuery) - |> map { peersAndPresences -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in - let peers = peersAndPresences.filter { peer in - if let peer = peer.peer, case .user = peer, peer.addressName != nil { - return true - } else { + + 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 } - }.compactMap { $0.peer } - return { _ in return .mentions(peers) } + 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(peers) + + return signal |> then(participants) + } else { + if normalizedQuery.isEmpty { + let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.peers.recentPeers() + |> map { recentPeers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + if case let .peers(peers) = recentPeers { + let peers = peers.filter { peer in + return peer.addressName != nil + }.compactMap { EnginePeer($0) } + return { _ in return .mentions(peers) } + } else { + return { _ in return .mentions([]) } + } + } + |> castError(ChatContextQueryError.self) + return signal |> then(peers) + } else { + let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.contacts.searchLocalPeers(query: normalizedQuery) + |> map { peersAndPresences -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + let peers = peersAndPresences.filter { peer in + if let peer = peer.peer, case .user = peer, peer.addressName != nil { + return true + } else { + return false + } + }.compactMap { $0.peer } + return { _ in return .mentions(peers) } + } + |> castError(ChatContextQueryError.self) + return signal |> then(peers) + } } case let .emojiSearch(query, languageCode, range): let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 29313ed18f..911e501c3a 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -39,6 +39,7 @@ public final class MessageInputPanelComponent: Component { public enum Style { case story case editor + case media } public enum InputMode: Hashable { @@ -142,6 +143,7 @@ public final class MessageInputPanelComponent: Component { public let disabledPlaceholder: String? public let isChannel: Bool public let storyItem: EngineStoryItem? + public let chatLocation: ChatLocation? public init( externalState: ExternalState, @@ -192,7 +194,8 @@ public final class MessageInputPanelComponent: Component { forceIsEditing: Bool, disabledPlaceholder: String?, isChannel: Bool, - storyItem: EngineStoryItem? + storyItem: EngineStoryItem?, + chatLocation: ChatLocation? ) { self.externalState = externalState self.context = context @@ -243,6 +246,7 @@ public final class MessageInputPanelComponent: Component { self.disabledPlaceholder = disabledPlaceholder self.isChannel = isChannel self.storyItem = storyItem + self.chatLocation = chatLocation } public static func ==(lhs: MessageInputPanelComponent, rhs: MessageInputPanelComponent) -> Bool { @@ -345,6 +349,9 @@ public final class MessageInputPanelComponent: Component { if lhs.storyItem != rhs.storyItem { return false } + if lhs.chatLocation != rhs.chatLocation { + return false + } return true } @@ -558,7 +565,7 @@ public final class MessageInputPanelComponent: Component { if component.queryTypes.contains(.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 { switch update { @@ -631,7 +638,12 @@ public final class MessageInputPanelComponent: Component { 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 @@ -699,6 +711,7 @@ public final class MessageInputPanelComponent: Component { return value } }, + resetScrollOnFocusChange: component.style == .media, formatMenuAvailability: component.isFormattingLocked ? .locked : .available, lockedFormatAction: { component.presentTextFormattingTooltip?() @@ -761,7 +774,7 @@ public final class MessageInputPanelComponent: Component { var fieldBackgroundFrame: CGRect if hasMediaRecording { 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 } else { 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)) + self.vibrancyEffectView.isHidden = component.style == .media transition.setFrame(view: self.fieldBackgroundView, frame: fieldBackgroundFrame) 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 if case .editor = component.style { inputActionButtonMode = isEditing ? .apply : .none + } else if case .media = component.style { + inputActionButtonMode = isEditing ? .apply : .none } else { if hasMediaEditing { 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 { - func generateIcon(value: String) -> UIImage? { + func generateIcon(value: String, selected: Bool) -> UIImage? { 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) @@ -1447,8 +1464,13 @@ public final class MessageInputPanelComponent: Component { let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) - if let cgImage = image.cgImage { - context.draw(cgImage, in: CGRect(origin: .zero, size: size)) + if selected { + context.setFillColor(accentColor.cgColor) + context.fillEllipse(in: CGRect(origin: .zero, size: size)) + } else { + if let cgImage = image.cgImage { + context.draw(cgImage, in: CGRect(origin: .zero, size: size)) + } } var offset: CGPoint = CGPoint(x: 0.0, y: -3.0 - UIScreenPixel) @@ -1462,14 +1484,14 @@ public final class MessageInputPanelComponent: Component { let valueFramesetter = CTFramesetterCreateWithAttributedString(valueString as CFAttributedString) let valyeFrame = CTFramesetterCreateFrame(valueFramesetter, CFRangeMake(0, valueString.length), valuePath, nil) CTFrameDraw(valyeFrame, context) - })?.withRenderingMode(.alwaysTemplate) + }) } - let icon = generateIcon(value: timeoutValue) + let icon = generateIcon(value: timeoutValue, selected: component.timeoutSelected) let timeoutButtonSize = self.timeoutButton.update( transition: transition, 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 guard let self, let timeoutButtonView = self.timeoutButton.view else { return @@ -1495,7 +1517,9 @@ public final class MessageInputPanelComponent: Component { } var fieldBackgroundIsDark = false - if self.textFieldExternalState.hasText && component.alwaysDarkWhenHasText { + if component.style == .media { + + } else if self.textFieldExternalState.hasText && component.alwaysDarkWhenHasText { fieldBackgroundIsDark = true } else if isEditing || component.style == .editor { fieldBackgroundIsDark = true @@ -1667,8 +1691,12 @@ public final class MessageInputPanelComponent: Component { self.updateContextQueries() - let panelLeftInset: CGFloat = max(insets.left, 7.0) - let panelRightInset: CGFloat = max(insets.right, 41.0) + var panelLeftInset: CGFloat = max(insets.left, 7.0) + var panelRightInset: CGFloat = max(insets.right, 41.0) + if case .media = component.style { + panelLeftInset = 0.0 + panelRightInset = 0.0 + } var contextResults: ContextResultPanelComponent.Results? 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) ) - 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 panelView.superview == nil { self.insertSubview(panelView, at: 0) diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index fc8af9c569..56dbc068be 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -135,7 +135,7 @@ final class ShareWithPeersScreenComponent: Component { self.itemHeight = itemHeight 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 topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) - //let transitionFactor: CGFloat = 1.0 - topOffsetFraction - //controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition) + let transitionFactor: CGFloat = 1.0 - topOffsetFraction + 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 visibleBounds.origin.y -= itemLayout.topInset @@ -907,7 +915,7 @@ final class ShareWithPeersScreenComponent: Component { let sectionTitle: String if section.id == 0, case .stories = component.stateContext.subject { - sectionTitle = "POST STORY AS" + sectionTitle = environment.strings.Story_Privacy_PostStoryAsHeader } else if section.id == 2 { sectionTitle = environment.strings.Story_Privacy_WhoCanViewHeader } else if section.id == 1 { @@ -1362,11 +1370,16 @@ final class ShareWithPeersScreenComponent: Component { self.visibleItems[itemId] = visibleItem } + var title = item.title + if item.id == .pin && !hasCategories { + title = environment.strings.Story_Privacy_KeepOnChannelPage + } + let _ = visibleItem.update( transition: itemTransition, component: AnyComponent(OptionListItemComponent( theme: environment.theme, - title: item.title, + title: title, hasNext: i != component.optionItems.count - 1, selected: self.selectedOptions.contains(item.id), selectionChanged: { [weak self] selected in @@ -1414,7 +1427,7 @@ final class ShareWithPeersScreenComponent: Component { var footerText = environment.strings.Story_Privacy_KeepOnMyPageInfo(footerValue).string 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( @@ -1508,6 +1521,9 @@ final class ShareWithPeersScreenComponent: Component { for id in removeSectionBackgroundIds { self.visibleSectionBackgrounds.removeValue(forKey: id) } + for id in removeSectionFooterIds { + self.visibleSectionFooters.removeValue(forKey: id) + } 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 { @@ -1932,11 +1948,16 @@ final class ShareWithPeersScreenComponent: Component { ) var hasCategories = false + var hasChannels = false if case .stories = component.stateContext.subject { if let peerId = self.sendAsPeerId, peerId.isGroupOrChannel { } else { hasCategories = true } + let sendAsPeersCount = component.stateContext.stateValue?.sendAsPeers.count ?? 1 + if sendAsPeersCount > 1 { + hasChannels = true + } } var footersTotalHeight: CGFloat = 0.0 @@ -2016,16 +2037,15 @@ final class ShareWithPeersScreenComponent: Component { if case let .peers(peers, _) = component.stateContext.subject { sections.append(ItemLayout.Section( 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, itemCount: peers.count )) } else if case let .stories(editing) = component.stateContext.subject { - let sendAsPeersCount = component.stateContext.stateValue?.sendAsPeers.count ?? 1 - if !editing && sendAsPeersCount > 1 { + if !editing && hasChannels { sections.append(ItemLayout.Section( 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, itemCount: 1 )) @@ -2040,7 +2060,7 @@ final class ShareWithPeersScreenComponent: Component { } sections.append(ItemLayout.Section( 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, itemCount: component.optionItems.count )) @@ -2110,7 +2130,7 @@ final class ShareWithPeersScreenComponent: Component { let title: String switch component.stateContext.subject { case .peers: - title = "Post Story As" + title = environment.strings.Story_Privacy_PostStoryAs case let .stories(editing): if editing { title = environment.strings.Story_Privacy_EditStory @@ -2178,11 +2198,16 @@ final class ShareWithPeersScreenComponent: Component { inset = 351.0 inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight } else { - if hasCategories { - inset = 1000.0 - } else { + if !hasCategories { inset = 314.0 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 { @@ -2201,7 +2226,7 @@ final class ShareWithPeersScreenComponent: Component { var bottomPanelHeight: CGFloat = 0.0 var bottomPanelInset: CGFloat = 0.0 if case .peers = component.stateContext.subject { - + bottomPanelInset = environment.safeInsets.bottom } else { let badge: Int if case .stories = component.stateContext.subject { @@ -2367,7 +2392,7 @@ final class ShareWithPeersScreenComponent: Component { } return Array(filteredMentions) } - |> deliverOnMainQueue).start(next: { mentions in + |> deliverOnMainQueue).start(next: { mentions in if mentions.isEmpty { proceed() } else { @@ -2377,7 +2402,7 @@ final class ShareWithPeersScreenComponent: Component { } } else if case .contacts = base { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.List(includePresences: false)) - |> map { contacts -> [String] in + |> map { contacts -> [String] in var filteredMentions = Set(component.mentions) let peers = contacts.peers for peer in peers { @@ -2390,7 +2415,7 @@ final class ShareWithPeersScreenComponent: Component { } return Array(filteredMentions) } - |> deliverOnMainQueue).start(next: { mentions in + |> deliverOnMainQueue).start(next: { mentions in if mentions.isEmpty { proceed() } else { @@ -2399,7 +2424,7 @@ final class ShareWithPeersScreenComponent: Component { }) } else if case .closeFriends = base { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.List(includePresences: false)) - |> map { contacts -> [String] in + |> map { contacts -> [String] in var filteredMentions = Set(component.mentions) let peers = contacts.peers for peer in peers { @@ -2409,7 +2434,7 @@ final class ShareWithPeersScreenComponent: Component { } return Array(filteredMentions) } - |> deliverOnMainQueue).start(next: { mentions in + |> deliverOnMainQueue).start(next: { mentions in if mentions.isEmpty { proceed() } else { @@ -2452,7 +2477,7 @@ final class ShareWithPeersScreenComponent: Component { let previousItemLayout = self.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) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 28895c0aa0..4bdf5a82cb 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -2860,7 +2860,8 @@ public final class StoryItemSetContainerComponent: Component { forceIsEditing: self.sendMessageContext.currentInputMode == .media, disabledPlaceholder: disabledPlaceholder, isChannel: isChannel, - storyItem: component.slice.item.storyItem + storyItem: component.slice.item.storyItem, + chatLocation: nil )), environment: {}, containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0) diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index 82ab128d91..93284de3ad 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -91,6 +91,7 @@ public final class TextFieldComponent: Component { public let insets: UIEdgeInsets public let hideKeyboard: Bool public let resetText: NSAttributedString? + public let resetScrollOnFocusChange: Bool public let formatMenuAvailability: FormatMenuAvailability public let lockedFormatAction: () -> Void public let present: (ViewController) -> Void @@ -105,6 +106,7 @@ public final class TextFieldComponent: Component { insets: UIEdgeInsets, hideKeyboard: Bool, resetText: NSAttributedString?, + resetScrollOnFocusChange: Bool, formatMenuAvailability: FormatMenuAvailability, lockedFormatAction: @escaping () -> Void, present: @escaping (ViewController) -> Void, @@ -118,6 +120,7 @@ public final class TextFieldComponent: Component { self.insets = insets self.hideKeyboard = hideKeyboard self.resetText = resetText + self.resetScrollOnFocusChange = resetScrollOnFocusChange self.formatMenuAvailability = formatMenuAvailability self.lockedFormatAction = lockedFormatAction self.present = present @@ -146,6 +149,9 @@ public final class TextFieldComponent: Component { if lhs.resetText != rhs.resetText { return false } + if lhs.resetScrollOnFocusChange != rhs.resetScrollOnFocusChange { + return false + } if lhs.formatMenuAvailability != rhs.formatMenuAvailability { return false } @@ -235,6 +241,10 @@ public final class TextFieldComponent: Component { self.textContainer.widthTracksTextView = false self.textContainer.heightTracksTextView = false + if #available(iOS 13.0, *) { + self.textView.overrideUserInterfaceStyle = .dark + } + self.textView.typingAttributes = [ NSAttributedString.Key.font: Font.regular(17.0), NSAttributedString.Key.foregroundColor: UIColor.white @@ -823,7 +833,10 @@ public final class TextFieldComponent: Component { let wasEditing = component.externalState.isEditing 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.panGestureRecognizer.isEnabled = isEditing diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaOnce.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaOnce.imageset/Contents.json new file mode 100644 index 0000000000..ba266bf499 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaOnce.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "miniplayonce.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaOnce.imageset/miniplayonce.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaOnce.imageset/miniplayonce.pdf new file mode 100644 index 0000000000..7ec1ebc79c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaOnce.imageset/miniplayonce.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaPlay.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaPlay.imageset/Contents.json new file mode 100644 index 0000000000..865478dba9 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaPlay.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "miniplay.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaPlay.imageset/miniplay.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaPlay.imageset/miniplay.pdf new file mode 100644 index 0000000000..62377ab473 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaPlay.imageset/miniplay.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index 9ab5f25b15..cfbd1db7e2 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -542,10 +542,10 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { switch preparePosition { case .linear(_, .None), .linear(_, .Neighbour(true, _, _)): 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 } 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 { skipStandardStatus = isFile } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index f48f5dcaf8..f73d32a596 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -860,7 +860,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio let maxWidth: CGFloat if isSecretMedia { - maxWidth = 180.0 + maxWidth = 200.0 } else { maxWidth = maxDimensions.width } @@ -898,7 +898,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio switch sizeCalculation { case .constrained: if isSecretMedia { - boundingSize = CGSize(width: maxWidth, height: maxWidth) + boundingSize = CGSize(width: maxWidth, height: maxWidth / 5.0 * 3.0) drawingSize = nativeSize.aspectFilled(boundingSize) } else { 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 self.statusNode == nil { 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 backgroundColor = messageTheme.mediaOverlayControlColors.fillColor var badgeContent: ChatMessageInteractiveMediaBadgeContent? var mediaDownloadState: ChatMessageInteractiveMediaDownloadState? - let messageTheme = theme.chat.message + if let invoice = invoice { if let extendedMedia = invoice.extendedMedia { 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 { let string = NSMutableAttributedString() @@ -1808,7 +1816,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } 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 @@ -1944,7 +1952,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } } else { 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) } @@ -1980,8 +1988,10 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } if isSecretMedia, let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout, let beginTime = maybeBeginTime { state = .secretTimeout(color: messageTheme.mediaOverlayControlColors.foregroundColor, icon: secretProgressIcon, beginTime: beginTime, timeout: timeout, sparks: true) - } else if isSecretMedia, let secretProgressIcon = secretProgressIcon { - state = .customIcon(secretProgressIcon) + backgroundColor = messageTheme.mediaDateAndStatusFillColor + } else if isSecretMedia, let _ = secretProgressIcon { + state = .staticTimeout + backgroundColor = messageTheme.mediaDateAndStatusFillColor } else if let file = media as? TelegramMediaFile, !file.isVideoSticker { let isInlinePlayableVideo = file.isVideo && !isSecretMedia && (self.automaticPlayback ?? false) if (!isInlinePlayableVideo || isStory) && file.isVideo { @@ -2017,11 +2027,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } else { if isMediaStreamable(message: message, media: file) { 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 } else { 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 { - let remainingTime: Int32 - if let beginTime = maybeBeginTime { - let elapsedTime = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - beginTime - remainingTime = Int32(max(0.0, timeout - elapsedTime)) + if isSecretMedia { + let remainingTime: Int32? + if let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout { + if let beginTime = maybeBeginTime { + let elapsedTime = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - beginTime + remainingTime = Int32(max(0.0, timeout - elapsedTime)) + } else { + remainingTime = Int32(timeout) + } } else { - remainingTime = Int32(timeout) + 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 { @@ -2062,6 +2088,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio statusNode?.removeFromSupernode() } }) + statusNode.backgroundNodeColor = backgroundColor } if let badgeContent = badgeContent { if self.badgeNode == nil { @@ -2100,6 +2127,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio displaySpoiler = true } else if message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) { displaySpoiler = true + } else if isSecretMedia { + displaySpoiler = true } if displaySpoiler { @@ -2115,11 +2144,13 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio self.extendedMediaOverlayNode?.frame = self.imageNode.frame var tappable = false - switch state { - case .play, .pause, .download, .none: - tappable = true - default: - break + if !isSecretMedia { + switch state { + case .play, .pause, .download, .none: + tappable = true + default: + break + } } self.extendedMediaOverlayNode?.isUserInteractionEnabled = tappable diff --git a/submodules/TelegramUI/Sources/GridMessageItem.swift b/submodules/TelegramUI/Sources/GridMessageItem.swift index 5d6c0903fb..20db9157d1 100644 --- a/submodules/TelegramUI/Sources/GridMessageItem.swift +++ b/submodules/TelegramUI/Sources/GridMessageItem.swift @@ -281,16 +281,16 @@ final class GridMessageItemNode: GridItemNode { switch status { case let .Fetching(_, progress): 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) 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: - 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 } } 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) diff --git a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift index 0f606a494e..974594da51 100644 --- a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift @@ -385,16 +385,16 @@ private final class VisualMediaItemNode: ASDisplayNode { switch status { case let .Fetching(_, progress): 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) 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: - 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 } } 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) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 5c768a3f50..0e266bca3a 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -41,6 +41,7 @@ import HashtagSearchUI import PeerInfoStoryGridScreen import TelegramAccountAuxiliaryMethods import PeerSelectionController +import LegacyMessageInputPanel private final class AccountUserInterfaceInUseContext { 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? { - var presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) +// var presentationData = context.sharedContext.currentPresentationData.with { $0 } +// 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? +// 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) - - 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? - 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() - } + let inputPanelNode = LegacyMessageInputPanelNode( + context: context, + chatLocation: chatLocation, + present: present, + presentInGlobalOverlay: presentInGlobalOverlay + ) return inputPanelNode } diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index da91ad8b42..cb25975c33 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -477,7 +477,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { } else { 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 } else if animationName == "ChatListFoldersTooltip" { animationInset = (70.0 - animationSize.width) / 2.0 diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 3b3df76ba3..656409d491 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -766,7 +766,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let body = MarkdownAttributeSet(font: Font.regular(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 return ("URL", contents) }), textAlignment: .natural)