Merge commit 'b1f40bf0aafc9cf0fceec175339d4d0329a92bbb'

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

View File

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

View File

@ -446,8 +446,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
public var sendPressed: ((NSAttributedString?) -> Void)?
public var 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()
}

View File

@ -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)

View File

@ -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)

View File

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

View File

@ -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 }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -459,7 +459,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
}
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: imageFlags)
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 {

View File

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

View File

@ -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)

View File

@ -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: {})
}
}
}

View File

@ -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))
}
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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,

View File

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

View File

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

View File

@ -111,8 +111,12 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
switch self.content {
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:

View File

@ -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)
}

View File

@ -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)

View File

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

View File

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

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -542,10 +542,10 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
switch preparePosition {
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
}

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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)