mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00
Merge commit 'b1f40bf0aafc9cf0fceec175339d4d0329a92bbb'
This commit is contained in:
commit
5aa90ba419
@ -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.";
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -89,7 +89,7 @@ extension UIView {
|
||||
}
|
||||
|
||||
open class ComponentState {
|
||||
var _updated: ((Transition) -> Void)?
|
||||
open var _updated: ((Transition) -> Void)?
|
||||
var isUpdated: Bool = false
|
||||
|
||||
public init() {
|
||||
|
@ -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 }
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -42,6 +42,28 @@
|
||||
|
||||
#import <LegacyComponents/TGPhotoCaptionInputMixin.h>
|
||||
|
||||
@interface TGMediaPickerGalleryWrapperView: UIView
|
||||
{
|
||||
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TGMediaPickerGalleryWrapperView
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
__block UIView *result = nil;
|
||||
[self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull view, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
UIView *hitTestView = [view hitTest:[self convertPoint:point toView:view] withEvent:event];
|
||||
if (hitTestView != nil) {
|
||||
*stop = true;
|
||||
result = hitTestView;
|
||||
}
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TGMediaPickerGalleryInterfaceView () <ASWatcher>
|
||||
{
|
||||
id<TGModernGalleryItem> _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<TGModernGalleryEditableItem> galleryEditableItem = (id<TGModernGalleryEditableItem>)strongSelf->_currentItem;
|
||||
[strongSelf->_editingContext setTimer:timeout forItem:galleryEditableItem.editableMediaItem];
|
||||
};
|
||||
|
||||
_captionMixin.stickersContext = stickersContext;
|
||||
[_captionMixin createInputPanelIfNeeded];
|
||||
|
||||
@ -800,6 +836,8 @@
|
||||
id<TGMediaEditAdjustments> 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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -14,6 +14,7 @@ swift_library(
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/ManagedAnimationNode:ManagedAnimationNode"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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: {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
@ -0,0 +1,370 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import LegacyComponents
|
||||
import Display
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import LegacyComponents
|
||||
import ComponentFlow
|
||||
import MessageInputPanelComponent
|
||||
import TelegramPresentationData
|
||||
import ContextUI
|
||||
import TooltipUI
|
||||
|
||||
public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
||||
private let context: AccountContext
|
||||
private let chatLocation: ChatLocation
|
||||
private let present: (ViewController) -> Void
|
||||
private let presentInGlobalOverlay: (ViewController) -> Void
|
||||
|
||||
private let state = ComponentState()
|
||||
private let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
|
||||
private let inputPanel = ComponentView<Empty>()
|
||||
|
||||
private var currentTimeout: Int32?
|
||||
private var currentIsEditing = false
|
||||
private var currentHeight: CGFloat?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, metrics: LayoutMetrics)?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
chatLocation: ChatLocation,
|
||||
present: @escaping (ViewController) -> Void,
|
||||
presentInGlobalOverlay: @escaping (ViewController) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.chatLocation = chatLocation
|
||||
self.present = present
|
||||
self.presentInGlobalOverlay = presentInGlobalOverlay
|
||||
|
||||
super.init()
|
||||
|
||||
self.state._updated = { [weak self] transition in
|
||||
if let self {
|
||||
self.update(transition: transition.containedViewLayoutTransition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var sendPressed: ((NSAttributedString?) -> Void)?
|
||||
public var focusUpdated: ((Bool) -> Void)?
|
||||
public var heightUpdated: ((Bool) -> Void)?
|
||||
public var timerUpdated: ((NSNumber?) -> Void)?
|
||||
|
||||
public func updateLayoutSize(_ size: CGSize, keyboardHeight: CGFloat, sideInset: CGFloat, animated: Bool) -> CGFloat {
|
||||
return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), isMediaInputExpanded: false)
|
||||
}
|
||||
|
||||
public func caption() -> NSAttributedString {
|
||||
if let view = self.inputPanel.view as? MessageInputPanelComponent.View, case let .text(caption) = view.getSendMessageInput() {
|
||||
return caption
|
||||
} else {
|
||||
return NSAttributedString()
|
||||
}
|
||||
}
|
||||
|
||||
public func setCaption(_ caption: NSAttributedString?) {
|
||||
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
|
||||
view.setSendMessageInput(value: .text(caption ?? NSAttributedString()), updateState: true)
|
||||
}
|
||||
}
|
||||
|
||||
public func animate(_ view: UIView, frame: CGRect) {
|
||||
let transition = Transition.spring(duration: 0.4)
|
||||
transition.setFrame(view: view, frame: frame)
|
||||
}
|
||||
|
||||
public func setTimeout(_ timeout: Int32) {
|
||||
var timeout: Int32? = timeout
|
||||
if timeout == 0 {
|
||||
timeout = nil
|
||||
}
|
||||
self.currentTimeout = timeout
|
||||
}
|
||||
|
||||
public func dismissInput() {
|
||||
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
|
||||
view.deactivateInput()
|
||||
}
|
||||
}
|
||||
|
||||
public func onAnimateOut() {
|
||||
self.tooltipController?.dismiss()
|
||||
}
|
||||
|
||||
public func baseHeight() -> CGFloat {
|
||||
return 52.0
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func update(transition: ContainedViewLayoutTransition) {
|
||||
if let (width, leftInset, rightInset, bottomInset, keyboardHeight, additionalSideInsets, maxHeight, isSecondary, metrics) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: keyboardHeight, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, metrics: metrics, isMediaInputExpanded: false)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
let previousLayout = self.validLayout
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, keyboardHeight, additionalSideInsets, maxHeight, isSecondary, metrics)
|
||||
|
||||
var transition = transition
|
||||
if keyboardHeight.isZero, let previousKeyboardHeight = previousLayout?.keyboardHeight, previousKeyboardHeight > 0.0, !transition.isAnimated {
|
||||
transition = .animated(duration: 0.4, curve: .spring)
|
||||
}
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let theme = defaultDarkColorPresentationTheme
|
||||
|
||||
var timeoutValue: String
|
||||
var timeoutSelected = false
|
||||
if let timeout = self.currentTimeout {
|
||||
if timeout == viewOnceTimeout {
|
||||
timeoutValue = "1"
|
||||
} else {
|
||||
timeoutValue = "\(timeout)"
|
||||
}
|
||||
timeoutSelected = true
|
||||
} else {
|
||||
timeoutValue = "1"
|
||||
}
|
||||
|
||||
var maxInputPanelHeight = maxHeight
|
||||
if keyboardHeight.isZero {
|
||||
maxInputPanelHeight = 60.0
|
||||
}
|
||||
|
||||
self.inputPanel.parentState = self.state
|
||||
let inputPanelSize = self.inputPanel.update(
|
||||
transition: Transition(transition),
|
||||
component: AnyComponent(
|
||||
MessageInputPanelComponent(
|
||||
externalState: self.inputPanelExternalState,
|
||||
context: self.context,
|
||||
theme: theme,
|
||||
strings: presentationData.strings,
|
||||
style: .media,
|
||||
placeholder: .plain(presentationData.strings.MediaPicker_AddCaption),
|
||||
maxLength: 1024,
|
||||
queryTypes: [.mention],
|
||||
alwaysDarkWhenHasText: false,
|
||||
resetInputContents: nil,
|
||||
nextInputMode: { _ in
|
||||
return .emoji
|
||||
},
|
||||
areVoiceMessagesAvailable: false,
|
||||
presentController: self.present,
|
||||
presentInGlobalOverlay: self.presentInGlobalOverlay,
|
||||
sendMessageAction: { [weak self] in
|
||||
if let self {
|
||||
self.sendPressed?(self.caption())
|
||||
self.dismissInput()
|
||||
}
|
||||
},
|
||||
sendMessageOptionsAction: nil,
|
||||
sendStickerAction: { _ in },
|
||||
setMediaRecordingActive: nil,
|
||||
lockMediaRecording: nil,
|
||||
stopAndPreviewMediaRecording: nil,
|
||||
discardMediaRecordingPreview: nil,
|
||||
attachmentAction: nil,
|
||||
myReaction: nil,
|
||||
likeAction: nil,
|
||||
likeOptionsAction: nil,
|
||||
inputModeAction: nil,
|
||||
timeoutAction: { [weak self] sourceView in
|
||||
if let self {
|
||||
self.presentTimeoutSetup(sourceView: sourceView)
|
||||
}
|
||||
},
|
||||
forwardAction: nil,
|
||||
moreAction: nil,
|
||||
presentVoiceMessagesUnavailableTooltip: nil,
|
||||
presentTextLengthLimitTooltip: nil,
|
||||
presentTextFormattingTooltip: nil,
|
||||
paste: { _ in },
|
||||
audioRecorder: nil,
|
||||
videoRecordingStatus: nil,
|
||||
isRecordingLocked: false,
|
||||
recordedAudioPreview: nil,
|
||||
hasRecordedVideoPreview: false,
|
||||
wasRecordingDismissed: false,
|
||||
timeoutValue: timeoutValue,
|
||||
timeoutSelected: timeoutSelected,
|
||||
displayGradient: false,
|
||||
bottomInset: 0.0,
|
||||
isFormattingLocked: false,
|
||||
hideKeyboard: false,
|
||||
forceIsEditing: false,
|
||||
disabledPlaceholder: nil,
|
||||
isChannel: false,
|
||||
storyItem: nil,
|
||||
chatLocation: self.chatLocation
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: width, height: maxInputPanelHeight)
|
||||
)
|
||||
if let view = self.inputPanel.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
let inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: inputPanelSize)
|
||||
transition.updateFrame(view: view, frame: inputPanelFrame)
|
||||
}
|
||||
|
||||
if self.currentIsEditing != self.inputPanelExternalState.isEditing {
|
||||
self.currentIsEditing = self.inputPanelExternalState.isEditing
|
||||
self.focusUpdated?(self.currentIsEditing)
|
||||
}
|
||||
|
||||
if self.currentHeight != inputPanelSize.height {
|
||||
self.currentHeight = inputPanelSize.height
|
||||
self.heightUpdated?(transition.isAnimated)
|
||||
}
|
||||
|
||||
return inputPanelSize.height - 8.0
|
||||
}
|
||||
|
||||
private func presentTimeoutSetup(sourceView: UIView) {
|
||||
self.hapticFeedback.impact(.light)
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
let updateTimeout: (Int32?) -> Void = { [weak self] timeout in
|
||||
if let self {
|
||||
self.currentTimeout = timeout
|
||||
self.timerUpdated?(timeout as? NSNumber)
|
||||
self.update(transition: .immediate)
|
||||
self.presentTimeoutTooltip(sourceView: sourceView, timeout: timeout)
|
||||
}
|
||||
}
|
||||
|
||||
let currentValue = self.currentTimeout
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
|
||||
let title = "Choose how long the media will be kept after opening."
|
||||
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "View Once", icon: { theme in
|
||||
return currentValue == viewOnceTimeout ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(viewOnceTimeout)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "3 Seconds", icon: { theme in
|
||||
return currentValue == 3 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(3)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "10 Seconds", icon: { theme in
|
||||
return currentValue == 10 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(10)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "30 Seconds", icon: { theme in
|
||||
return currentValue == 30 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(30)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Do Not Delete", icon: { theme in
|
||||
return currentValue == nil ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(nil)
|
||||
})))
|
||||
|
||||
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
||||
self.present(contextController)
|
||||
}
|
||||
|
||||
private weak var tooltipController: TooltipScreen?
|
||||
private func presentTimeoutTooltip(sourceView: UIView, timeout: Int32?) {
|
||||
guard let superview = self.view.superview?.superview else {
|
||||
return
|
||||
}
|
||||
if let tooltipController = self.tooltipController {
|
||||
self.tooltipController = nil
|
||||
tooltipController.dismiss()
|
||||
}
|
||||
|
||||
let parentFrame = superview.convert(superview.bounds, to: nil)
|
||||
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 2.0), size: CGSize())
|
||||
|
||||
let text: String
|
||||
let iconName: String
|
||||
if timeout == viewOnceTimeout {
|
||||
text = "Photo set to view once."
|
||||
iconName = "anim_autoremove_on"
|
||||
} else if let timeout {
|
||||
text = "Photo will be deleted in \(timeout) seconds after opening."
|
||||
iconName = "anim_autoremove_on"
|
||||
} else {
|
||||
text = "Photo will be kept in chat."
|
||||
iconName = "anim_autoremove_off"
|
||||
}
|
||||
|
||||
let tooltipController = TooltipScreen(
|
||||
account: self.context.account,
|
||||
sharedContext: self.context.sharedContext,
|
||||
text: .plain(text: text),
|
||||
balancedTextLayout: true,
|
||||
style: .customBlur(UIColor(rgb: 0x18181a), 0.0),
|
||||
arrowStyle: .small,
|
||||
icon: .animation(name: iconName, delay: 0.1, tintColor: nil),
|
||||
location: .point(location, .bottom),
|
||||
displayDuration: .default,
|
||||
inset: 8.0,
|
||||
shouldDismissOnTouch: { _, _ in
|
||||
return .ignore
|
||||
}
|
||||
)
|
||||
self.tooltipController = tooltipController
|
||||
self.present(tooltipController)
|
||||
}
|
||||
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let result = super.hitTest(point, with: event)
|
||||
if let view = self.inputPanel.view, let panelResult = view.hitTest(self.view.convert(point, to: view), with: event) {
|
||||
return panelResult
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let sourceView: UIView
|
||||
var keepInPlace: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
init(sourceView: UIView) {
|
||||
self.sourceView = sourceView
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: .top)
|
||||
}
|
||||
}
|
@ -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:
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -36,6 +36,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/AnimatedCountLabelNode",
|
||||
"//submodules/TelegramUI/Components/MessageInputActionButtonComponent",
|
||||
"//submodules/SearchPeerMembers",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaOnce.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaOnce.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "miniplayonce.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
107
submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaOnce.imageset/miniplayonce.pdf
vendored
Normal file
107
submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaOnce.imageset/miniplayonce.pdf
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 2.000000 1.669922 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
4.000000 8.665078 m
|
||||
4.367270 8.665078 4.665000 8.962809 4.665000 9.330078 c
|
||||
4.665000 9.697348 4.367270 9.995078 4.000000 9.995078 c
|
||||
4.000000 8.665078 l
|
||||
h
|
||||
8.665000 5.330078 m
|
||||
8.665000 5.697348 8.367270 5.995078 8.000000 5.995078 c
|
||||
7.632730 5.995078 7.335000 5.697348 7.335000 5.330078 c
|
||||
8.665000 5.330078 l
|
||||
h
|
||||
4.000000 9.995078 m
|
||||
1.423591 9.995078 -0.665000 7.906487 -0.665000 5.330078 c
|
||||
0.665000 5.330078 l
|
||||
0.665000 7.171948 2.158130 8.665078 4.000000 8.665078 c
|
||||
4.000000 9.995078 l
|
||||
h
|
||||
-0.665000 5.330078 m
|
||||
-0.665000 2.753670 1.423591 0.665078 4.000000 0.665078 c
|
||||
4.000000 1.995078 l
|
||||
2.158130 1.995078 0.665000 3.488208 0.665000 5.330078 c
|
||||
-0.665000 5.330078 l
|
||||
h
|
||||
4.000000 0.665078 m
|
||||
6.576408 0.665078 8.665000 2.753670 8.665000 5.330078 c
|
||||
7.335000 5.330078 l
|
||||
7.335000 3.488208 5.841870 1.995078 4.000000 1.995078 c
|
||||
4.000000 0.665078 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 6.000000 8.705566 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.800000 4.694434 m
|
||||
0.470382 4.941647 0.000000 4.706456 0.000000 4.294434 c
|
||||
0.000000 0.294434 l
|
||||
0.000000 -0.117589 0.470382 -0.352780 0.800000 -0.105567 c
|
||||
3.466667 1.894433 l
|
||||
3.733333 2.094434 3.733333 2.494434 3.466667 2.694434 c
|
||||
0.800000 4.694434 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1311
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 12.000000 14.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001401 00000 n
|
||||
0000001424 00000 n
|
||||
0000001597 00000 n
|
||||
0000001671 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1730
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaPlay.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaPlay.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "miniplay.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
73
submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaPlay.imageset/miniplay.pdf
vendored
Normal file
73
submodules/TelegramUI/Images.xcassets/Chat/Message/SecretMediaPlay.imageset/miniplay.pdf
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.000000 2.780273 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
2.332051 -0.225573 m
|
||||
7.127887 2.971651 l
|
||||
8.018486 3.565384 8.018486 4.874069 7.127886 5.467803 c
|
||||
2.332050 8.665027 l
|
||||
1.335218 9.329581 0.000000 8.614994 0.000000 7.416951 c
|
||||
0.000000 1.022502 l
|
||||
0.000000 -0.175541 1.335219 -0.890127 2.332051 -0.225573 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
379
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 12.000000 14.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000000469 00000 n
|
||||
0000000491 00000 n
|
||||
0000000664 00000 n
|
||||
0000000738 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
797
|
||||
%%EOF
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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<Int>?
|
||||
// var text: NSAttributedString?
|
||||
// var inputMode: ChatInputMode?
|
||||
// updateChatPresentationInterfaceStateImpl?({ state in
|
||||
// selectionRange = state.interfaceState.effectiveInputState.selectionRange
|
||||
// if let selectionRange = selectionRange {
|
||||
// text = state.interfaceState.effectiveInputState.inputText.attributedSubstring(from: NSRange(location: selectionRange.startIndex, length: selectionRange.count))
|
||||
// }
|
||||
// inputMode = state.inputMode
|
||||
// return state
|
||||
// })
|
||||
//
|
||||
// var link: String?
|
||||
// if let text {
|
||||
// text.enumerateAttributes(in: NSMakeRange(0, text.length)) { attributes, _, _ in
|
||||
// if let linkAttribute = attributes[ChatTextInputAttributes.textUrl] as? ChatTextInputTextUrlAttribute {
|
||||
// link = linkAttribute.url
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let controller = chatTextLinkEditController(sharedContext: context.sharedContext, updatedPresentationData: (presentationData, .never()), account: context.account, text: text?.string ?? "", link: link, apply: { link in
|
||||
// if let inputMode = inputMode, let selectionRange = selectionRange {
|
||||
// if let link = link {
|
||||
// updateChatPresentationInterfaceStateImpl?({
|
||||
// return $0.updatedInterfaceState({
|
||||
// $0.withUpdatedEffectiveInputState(chatTextInputAddLinkAttribute($0.effectiveInputState, selectionRange: selectionRange, url: link))
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
// ensureFocusedImpl?()
|
||||
// updateChatPresentationInterfaceStateImpl?({
|
||||
// return $0.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({
|
||||
// $0.withUpdatedEffectiveInputState(ChatTextInputState(inputText: $0.effectiveInputState.inputText, selectionRange: selectionRange.endIndex ..< selectionRange.endIndex))
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// present(controller)
|
||||
// })
|
||||
//
|
||||
// let inputPanelNode = AttachmentTextInputPanelNode(context: context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { c in
|
||||
// presentInGlobalOverlay(c)
|
||||
// }, makeEntityInputView: {
|
||||
// return EntityInputView(context: context, isDark: true, areCustomEmojiEnabled: customEmojiAvailable)
|
||||
// })
|
||||
// inputPanelNode.interfaceInteraction = interfaceInteraction
|
||||
// inputPanelNode.effectivePresentationInterfaceState = {
|
||||
// return presentationInterfaceState
|
||||
// }
|
||||
//
|
||||
// updateChatPresentationInterfaceStateImpl = { [weak inputPanelNode] f in
|
||||
// let updatedPresentationInterfaceState = f(presentationInterfaceState)
|
||||
// let updateInputTextState = presentationInterfaceState.interfaceState.effectiveInputState != updatedPresentationInterfaceState.interfaceState.effectiveInputState
|
||||
//
|
||||
// presentationInterfaceState = updatedPresentationInterfaceState
|
||||
//
|
||||
// if let inputPanelNode = inputPanelNode, updateInputTextState {
|
||||
// inputPanelNode.updateInputTextState(updatedPresentationInterfaceState.interfaceState.effectiveInputState, animated: true)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// ensureFocusedImpl = { [weak inputPanelNode] in
|
||||
// inputPanelNode?.ensureFocused()
|
||||
// }
|
||||
//
|
||||
// return inputPanelNode
|
||||
|
||||
var presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: presentationData.chatFontSize, bubbleCorners: presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(previewing: false), chatLocation: chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil)
|
||||
|
||||
var updateChatPresentationInterfaceStateImpl: (((ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void)?
|
||||
var ensureFocusedImpl: (() -> Void)?
|
||||
|
||||
let interfaceInteraction = ChatPanelInterfaceInteraction(updateTextInputStateAndMode: { f in
|
||||
updateChatPresentationInterfaceStateImpl?({
|
||||
let (updatedState, updatedMode) = f($0.interfaceState.effectiveInputState, $0.inputMode)
|
||||
return $0.updatedInterfaceState { interfaceState in
|
||||
return interfaceState.withUpdatedEffectiveInputState(updatedState)
|
||||
}.updatedInputMode({ _ in updatedMode })
|
||||
})
|
||||
}, updateInputModeAndDismissedButtonKeyboardMessageId: { f in
|
||||
updateChatPresentationInterfaceStateImpl?({
|
||||
let (updatedInputMode, updatedClosedButtonKeyboardMessageId) = f($0)
|
||||
return $0.updatedInputMode({ _ in return updatedInputMode }).updatedInterfaceState({
|
||||
$0.withUpdatedMessageActionsState({ value in
|
||||
var value = value
|
||||
value.closedButtonKeyboardMessageId = updatedClosedButtonKeyboardMessageId
|
||||
return value
|
||||
})
|
||||
})
|
||||
})
|
||||
}, openLinkEditing: {
|
||||
var selectionRange: Range<Int>?
|
||||
var text: NSAttributedString?
|
||||
var inputMode: ChatInputMode?
|
||||
updateChatPresentationInterfaceStateImpl?({ state in
|
||||
selectionRange = state.interfaceState.effectiveInputState.selectionRange
|
||||
if let selectionRange = selectionRange {
|
||||
text = state.interfaceState.effectiveInputState.inputText.attributedSubstring(from: NSRange(location: selectionRange.startIndex, length: selectionRange.count))
|
||||
}
|
||||
inputMode = state.inputMode
|
||||
return state
|
||||
})
|
||||
|
||||
var link: String?
|
||||
if let text {
|
||||
text.enumerateAttributes(in: NSMakeRange(0, text.length)) { attributes, _, _ in
|
||||
if let linkAttribute = attributes[ChatTextInputAttributes.textUrl] as? ChatTextInputTextUrlAttribute {
|
||||
link = linkAttribute.url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let controller = chatTextLinkEditController(sharedContext: context.sharedContext, updatedPresentationData: (presentationData, .never()), account: context.account, text: text?.string ?? "", link: link, apply: { link in
|
||||
if let inputMode = inputMode, let selectionRange = selectionRange {
|
||||
if let link = link {
|
||||
updateChatPresentationInterfaceStateImpl?({
|
||||
return $0.updatedInterfaceState({
|
||||
$0.withUpdatedEffectiveInputState(chatTextInputAddLinkAttribute($0.effectiveInputState, selectionRange: selectionRange, url: link))
|
||||
})
|
||||
})
|
||||
}
|
||||
ensureFocusedImpl?()
|
||||
updateChatPresentationInterfaceStateImpl?({
|
||||
return $0.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({
|
||||
$0.withUpdatedEffectiveInputState(ChatTextInputState(inputText: $0.effectiveInputState.inputText, selectionRange: selectionRange.endIndex ..< selectionRange.endIndex))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
present(controller)
|
||||
})
|
||||
|
||||
let inputPanelNode = AttachmentTextInputPanelNode(context: context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { c in
|
||||
presentInGlobalOverlay(c)
|
||||
}, makeEntityInputView: {
|
||||
return EntityInputView(context: context, isDark: true, areCustomEmojiEnabled: customEmojiAvailable)
|
||||
})
|
||||
inputPanelNode.interfaceInteraction = interfaceInteraction
|
||||
inputPanelNode.effectivePresentationInterfaceState = {
|
||||
return presentationInterfaceState
|
||||
}
|
||||
|
||||
updateChatPresentationInterfaceStateImpl = { [weak inputPanelNode] f in
|
||||
let updatedPresentationInterfaceState = f(presentationInterfaceState)
|
||||
let updateInputTextState = presentationInterfaceState.interfaceState.effectiveInputState != updatedPresentationInterfaceState.interfaceState.effectiveInputState
|
||||
|
||||
presentationInterfaceState = updatedPresentationInterfaceState
|
||||
|
||||
if let inputPanelNode = inputPanelNode, updateInputTextState {
|
||||
inputPanelNode.updateInputTextState(updatedPresentationInterfaceState.interfaceState.effectiveInputState, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
ensureFocusedImpl = { [weak inputPanelNode] in
|
||||
inputPanelNode?.ensureFocused()
|
||||
}
|
||||
let inputPanelNode = LegacyMessageInputPanelNode(
|
||||
context: context,
|
||||
chatLocation: chatLocation,
|
||||
present: present,
|
||||
presentInGlobalOverlay: presentInGlobalOverlay
|
||||
)
|
||||
|
||||
return inputPanelNode
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user