From 9166178440abce9bafc5f84dbd522a49fd3e5976 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 24 Mar 2024 15:14:58 +0400 Subject: [PATCH 1/4] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 3 + .../ContextControllerActionsStackNode.swift | 2 +- .../ContextUI/Sources/PeekController.swift | 3 + .../Sources/PeekControllerNode.swift | 54 +- .../Public/FFMpegBinding/FFMpegAVFrame.h | 1 + .../Public/FFMpegBinding/FFMpegVideoWriter.h | 6 +- .../FFMpegBinding/Sources/FFMpegAVFrame.m | 20 + .../FFMpegBinding/Sources/FFMpegVideoWriter.m | 80 +- submodules/PremiumUI/BUILD | 1 + .../Sources/BusinessPageComponent.swift | 22 +- .../Sources/LimitsPageComponent.swift | 1 + .../Sources/PremiumBoostLevelsScreen.swift | 1 + .../PremiumUI/Sources/PremiumGiftScreen.swift | 1 + .../Sources/PremiumIntroScreen.swift | 1 + .../PremiumUI/Sources/ScrollComponent.swift | 146 --- .../Sources/StoriesPageComponent.swift | 1 + .../Sources/StickerPreviewPeekContent.swift | 6 +- .../Sources/ApiUtils/AdMessageAttribute.swift | 4 +- .../TelegramEngine/Messages/AdMessages.swift | 19 +- submodules/TelegramUI/BUILD | 2 + .../Components/Ads/AdsInfoScreen/BUILD | 1 - .../AdsInfoScreen/Sources/AdsInfoScreen.swift | 32 +- .../Components/Ads/AdsReportScreen/BUILD | 6 +- .../Sources/AdsReportScreen.swift | 888 ++++++++++-------- .../PublicHeaders/ImageDCT/YuvConversion.h | 2 +- .../ImageDCT/Sources/YuvConversion.m | 7 +- .../AnimationCache/Sources/ImageData.swift | 3 +- .../ChatMessageAttachedContentNode.swift | 4 +- ...entLogPreviousDescriptionContentNode.swift | 2 +- ...ssageEventLogPreviousLinkContentNode.swift | 2 +- ...geEventLogPreviousMessageContentNode.swift | 2 +- .../ChatMessageGameBubbleContentNode.swift | 2 +- .../ChatMessageInvoiceBubbleContentNode.swift | 2 +- .../ChatMessageWebpageBubbleContentNode.swift | 5 +- .../Sources/EmojiPagerContentSignals.swift | 2 + .../TelegramUI/Components/MediaEditor/BUILD | 1 + .../Sources/MediaEditorComposer.swift | 12 +- .../MediaEditorVideoFFMpegWriter.swift | 109 +-- .../Sources/MediaEditorScreen.swift | 15 +- .../Sources/StickerPackListContextItem.swift | 3 + .../Sources/PeerInfoScreen.swift | 30 +- .../Business/Links.imageset/Contents.json | 12 + .../Business/Links.imageset/linktochat_30.pdf | 158 ++++ .../ChatInterfaceStateContextMenus.swift | 246 +++-- 44 files changed, 1096 insertions(+), 824 deletions(-) delete mode 100644 submodules/PremiumUI/Sources/ScrollComponent.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Business/Links.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Business/Links.imageset/linktochat_30.pdf diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a3f0e41bea..16c85b22b9 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11351,6 +11351,9 @@ Sorry for the inconvenience."; "Premium.Business.Away.Title" = "Away Messages"; "Premium.Business.Away.Text" = "Define messages that are automatically sent when you are off."; +"Premium.Business.Links.Title" = "Links"; +"Premium.Business.Links.Text" = "Create links that start a chat with you, suggesting the first message."; + "Premium.Business.Intro.Title" = "Intro"; "Premium.Business.Intro.Text" = "Customize the message people see before they start a chat with you."; diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index fca0ee07ae..2e6c0cece5 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -1518,7 +1518,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { let transition: ContainedViewLayoutTransition if animated { - transition = .animated(duration: self.itemContainers.count == 1 ? 0.3 : 0.45, curve: .spring) + transition = .animated(duration: self.itemContainers.count == 1 ? 0.3 : 0.45, curve: .spring) } else { transition = .immediate } diff --git a/submodules/ContextUI/Sources/PeekController.swift b/submodules/ContextUI/Sources/PeekController.swift index e834f079ff..74af9c181d 100644 --- a/submodules/ContextUI/Sources/PeekController.swift +++ b/submodules/ContextUI/Sources/PeekController.swift @@ -69,6 +69,9 @@ public final class PeekController: ViewController, ContextControllerProtocol { public var getOverlayViews: (() -> [UIView])? + public var appeared: (() -> Void)? + public var disappeared: (() -> Void)? + private var animatedIn = false private let _ready = Promise() diff --git a/submodules/ContextUI/Sources/PeekControllerNode.swift b/submodules/ContextUI/Sources/PeekControllerNode.swift index f0b7293cbc..632d501ce1 100644 --- a/submodules/ContextUI/Sources/PeekControllerNode.swift +++ b/submodules/ContextUI/Sources/PeekControllerNode.swift @@ -245,7 +245,11 @@ final class PeekControllerNode: ViewControllerTracingNode { case .contained: containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize) case .freeform: - containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) / 3.0)), size: contentSize) + var fraction: CGFloat = 1.0 / 3.0 + if let _ = self.controller?.appeared { + fraction *= 1.33 + } + containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) * fraction)), size: contentSize) } actionsFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - actionsSize.width) / 2.0), y: containerFrame.maxY + 64.0), size: actionsSize) } @@ -262,13 +266,24 @@ final class PeekControllerNode: ViewControllerTracingNode { } func animateIn(from rect: CGRect) { + if let appeared = self.controller?.appeared { + appeared() + } else { + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.blurView.layer.animateAlpha(from: 0.0, to: self.blurView.alpha, duration: 0.3) let offset = CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y) self.containerNode.layer.animateSpring(from: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true) - self.containerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0) - self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + + if rect.width > 10.0 { + let scale = rect.width / self.contentNode.frame.width + self.containerNode.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0) + } else { + self.containerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0) + } if let topAccessoryNode = self.topAccessoryNode { topAccessoryNode.layer.animateSpring(from: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true) @@ -293,13 +308,38 @@ final class PeekControllerNode: ViewControllerTracingNode { let springDuration: Double = 0.42 * animationDurationFactor let springDamping: CGFloat = 104.0 + var scaleCompleted = false + var positionCompleted = false + let outCompletion = { [weak self] in + if scaleCompleted && positionCompleted { + self?.controller?.disappeared?() + } + } + let offset = CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y) - self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint()), to: NSValue(cgPoint: offset), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true, completion: { _ in + self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint()), to: NSValue(cgPoint: offset), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false, additive: true, completion: { _ in + positionCompleted = true + outCompletion() completion() }) - self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.containerNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false) - + if let _ = self.controller?.disappeared { + } else { + self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + + if rect.width > 10.0 { + let scale = rect.width / self.contentNode.frame.width + self.containerNode.layer.animateScale(from: 1.0, to: scale, duration: 0.25, removeOnCompletion: false, completion: { _ in + scaleCompleted = true + outCompletion() + }) + } else { + self.containerNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false, completion: { _ in + scaleCompleted = true + outCompletion() + }) + } + if !self.actionsStackNode.alpha.isZero { let actionsOffset = CGPoint(x: rect.midX - self.actionsStackNode.position.x, y: rect.midY - self.actionsStackNode.position.y) self.actionsStackNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false) diff --git a/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFrame.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFrame.h index 03451f42c4..3b244d6fb8 100644 --- a/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFrame.h +++ b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFrame.h @@ -24,6 +24,7 @@ typedef NS_ENUM(NSUInteger, FFMpegAVFramePixelFormat) { @property (nonatomic, readonly) FFMpegAVFramePixelFormat pixelFormat; - (instancetype)init; +- (instancetype)initWithPixelFormat:(FFMpegAVFramePixelFormat)pixelFormat width:(int32_t)width height:(int32_t)height; - (void *)impl; diff --git a/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegVideoWriter.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegVideoWriter.h index 31b007f5a5..53ebe444b5 100755 --- a/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegVideoWriter.h +++ b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegVideoWriter.h @@ -3,10 +3,12 @@ NS_ASSUME_NONNULL_BEGIN +@class FFMpegAVFrame; + @interface FFMpegVideoWriter : NSObject -- (bool)setupWithOutputPath:(NSString *)outputPath width:(int)width height:(int)height; -- (bool)encodeFrame:(CVPixelBufferRef)pixelBuffer; +- (bool)setupWithOutputPath:(NSString *)outputPath width:(int)width height:(int)height bitrate:(int64_t)bitrate framerate:(int32_t)framerate; +- (bool)encodeFrame:(FFMpegAVFrame *)frame; - (bool)finalizeVideo; @end diff --git a/submodules/FFMpegBinding/Sources/FFMpegAVFrame.m b/submodules/FFMpegBinding/Sources/FFMpegAVFrame.m index cab3d75beb..754f88f230 100644 --- a/submodules/FFMpegBinding/Sources/FFMpegAVFrame.m +++ b/submodules/FFMpegBinding/Sources/FFMpegAVFrame.m @@ -18,6 +18,26 @@ return self; } +-(instancetype)initWithPixelFormat:(FFMpegAVFramePixelFormat)pixelFormat width:(int32_t)width height:(int32_t)height { + self = [super init]; + if (self != nil) { + _impl = av_frame_alloc(); + switch (pixelFormat) { + case FFMpegAVFramePixelFormatYUV: + _impl->format = AV_PIX_FMT_YUV420P; + break; + case FFMpegAVFramePixelFormatYUVA: + _impl->format = AV_PIX_FMT_YUVA420P; + break; + } + _impl->width = width; + _impl->height = height; + + av_frame_get_buffer(_impl, 0); + } + return self; +} + - (void)dealloc { if (_impl) { av_frame_free(&_impl); diff --git a/submodules/FFMpegBinding/Sources/FFMpegVideoWriter.m b/submodules/FFMpegBinding/Sources/FFMpegVideoWriter.m index 56fbb4dafe..03114fdc99 100755 --- a/submodules/FFMpegBinding/Sources/FFMpegVideoWriter.m +++ b/submodules/FFMpegBinding/Sources/FFMpegVideoWriter.m @@ -1,5 +1,5 @@ #import -#import +#import #include "libavformat/avformat.h" #include "libavcodec/avcodec.h" @@ -24,7 +24,7 @@ return self; } -- (bool)setupWithOutputPath:(NSString *)outputPath width:(int)width height:(int)height { +- (bool)setupWithOutputPath:(NSString *)outputPath width:(int)width height:(int)height bitrate:(int64_t)bitrate framerate:(int32_t)framerate { avformat_alloc_output_context2(&_formatContext, NULL, "matroska", [outputPath UTF8String]); if (!_formatContext) { return false; @@ -49,11 +49,9 @@ _codecContext->pix_fmt = AV_PIX_FMT_YUVA420P; _codecContext->width = width; _codecContext->height = height; - _codecContext->time_base = (AVRational){1, 30}; - _codecContext->framerate = (AVRational){30, 1}; - _codecContext->bit_rate = 200000; -// _codecContext->gop_size = 10; -// _codecContext->max_b_frames = 1; + _codecContext->time_base = (AVRational){1, framerate}; + _codecContext->framerate = (AVRational){framerate, 1}; + _codecContext->bit_rate = bitrate; if (_formatContext->oformat->flags & AVFMT_GLOBALHEADER) { _codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; @@ -88,62 +86,19 @@ return true; } -- (bool)encodeFrame:(CVPixelBufferRef)pixelBuffer { +- (bool)encodeFrame:(FFMpegAVFrame *)frame { if (!_codecContext || !_stream) { return false; } self.framePts++; - AVFrame *frame = av_frame_alloc(); - if (!frame) { - return false; - } + AVFrame *frameImpl = (AVFrame *)[frame impl]; - int width = (int)CVPixelBufferGetWidth(pixelBuffer); - int height = (int)CVPixelBufferGetHeight(pixelBuffer); - - frame->format = _codecContext->pix_fmt; - frame->width = width; - frame->height = height; + frameImpl->pts = self.framePts; - if (av_frame_get_buffer(frame, 0) < 0) { - return false; - } - - CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); - - uint8_t *yBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); - size_t yStride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); - - uint8_t *uvBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); - size_t uvStride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); - - uint8_t *aBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 2); - size_t aStride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 2); - - for (int i = 0; i < height; i++) { - memcpy(frame->data[0] + i * frame->linesize[0], yBaseAddress + i * yStride, width); - } - - for (int i = 0; i < height / 2; i++) { - for (int j = 0; j < width / 2; j++) { - frame->data[1][i * frame->linesize[1] + j] = uvBaseAddress[i * uvStride + 2 * j]; - frame->data[2][i * frame->linesize[2] + j] = uvBaseAddress[i * uvStride + 2 * j + 1]; - } - } - - for (int i = 0; i < height; i++) { - memcpy(frame->data[3] + i * frame->linesize[3], aBaseAddress + i * aStride, width); - } - - CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); - - frame->pts = self.framePts; - - int sendRet = avcodec_send_frame(_codecContext, frame); + int sendRet = avcodec_send_frame(_codecContext, frameImpl); if (sendRet < 0) { - av_frame_free(&frame); return false; } @@ -170,11 +125,26 @@ } } - av_frame_free(&frame); return true; } - (bool)finalizeVideo { + int sendRet = avcodec_send_frame(_codecContext, NULL); + if (sendRet >= 0) { + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + + while (avcodec_receive_packet(_codecContext, &pkt) == 0) { + av_packet_rescale_ts(&pkt, _codecContext->time_base, _stream->time_base); + pkt.stream_index = _stream->index; + + av_interleaved_write_frame(_formatContext, &pkt); + av_packet_unref(&pkt); + } + } + av_write_trailer(_formatContext); avio_closep(&_formatContext->pb); diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD index 87252b60f1..15fcbb0807 100644 --- a/submodules/PremiumUI/BUILD +++ b/submodules/PremiumUI/BUILD @@ -118,6 +118,7 @@ swift_library( "//submodules/TelegramUI/Components/EntityKeyboard", "//submodules/TelegramUI/Components/PremiumPeerShortcutComponent", "//submodules/TelegramUI/Components/EmojiActionIconComponent", + "//submodules/TelegramUI/Components/ScrollComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/PremiumUI/Sources/BusinessPageComponent.swift b/submodules/PremiumUI/Sources/BusinessPageComponent.swift index f9e44ead0c..f5fe82ec6f 100644 --- a/submodules/PremiumUI/Sources/BusinessPageComponent.swift +++ b/submodules/PremiumUI/Sources/BusinessPageComponent.swift @@ -10,6 +10,7 @@ import BlurredBackgroundComponent import Markdown import TelegramPresentationData import BundleIconComponent +import ScrollComponent private final class HeaderComponent: Component { let context: AccountContext @@ -307,7 +308,8 @@ private final class BusinessListComponent: CombinedComponent { UIColor(rgb: 0xbc4395), UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), - UIColor(rgb: 0x676bff) + UIColor(rgb: 0x676bff), + UIColor(rgb: 0x007aff) ] let titleColor = theme.list.itemPrimaryTextColor @@ -396,6 +398,20 @@ private final class BusinessListComponent: CombinedComponent { ) ) + items.append( + AnyComponentWithIdentity( + id: "links", + component: AnyComponent(ParagraphComponent( + title: strings.Premium_Business_Links_Title, + titleColor: titleColor, + text: strings.Premium_Business_Links_Text, + textColor: textColor, + iconName: "Premium/Business/Links", + iconColor: colors[5] + )) + ) + ) + items.append( AnyComponentWithIdentity( id: "intro", @@ -405,7 +421,7 @@ private final class BusinessListComponent: CombinedComponent { text: strings.Premium_Business_Intro_Text, textColor: textColor, iconName: "Premium/Business/Intro", - iconColor: colors[5] + iconColor: colors[6] )) ) ) @@ -419,7 +435,7 @@ private final class BusinessListComponent: CombinedComponent { text: strings.Premium_Business_Chatbots_Text, textColor: textColor, iconName: "Premium/Business/Chatbots", - iconColor: colors[6] + iconColor: colors[7] )) ) ) diff --git a/submodules/PremiumUI/Sources/LimitsPageComponent.swift b/submodules/PremiumUI/Sources/LimitsPageComponent.swift index 709e075f86..c5ea6cf0ad 100644 --- a/submodules/PremiumUI/Sources/LimitsPageComponent.swift +++ b/submodules/PremiumUI/Sources/LimitsPageComponent.swift @@ -9,6 +9,7 @@ import MultilineTextComponent import BlurredBackgroundComponent import Markdown import TelegramPresentationData +import ScrollComponent private final class LimitComponent: CombinedComponent { let title: String diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift index 2cae1c711a..eda6b3e74f 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift @@ -21,6 +21,7 @@ import BlurredBackgroundComponent import UndoUI import ConfettiEffect import PremiumPeerShortcutComponent +import ScrollComponent func requiredBoostSubjectLevel(subject: BoostSubject, group: Bool, context: AccountContext, configuration: PremiumConfiguration) -> Int32 { switch subject { diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index e29e5855ec..f730f6c389 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -20,6 +20,7 @@ import ConfettiEffect import TextFormat import UniversalMediaPlayer import InstantPageCache +import ScrollComponent extension PremiumGiftSource { var identifier: String? { diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 0151854418..c9a5d2b66e 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -33,6 +33,7 @@ import EmojiStatusSelectionComponent import EmojiStatusComponent import EntityKeyboard import EmojiActionIconComponent +import ScrollComponent public enum PremiumSource: Equatable { public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool { diff --git a/submodules/PremiumUI/Sources/ScrollComponent.swift b/submodules/PremiumUI/Sources/ScrollComponent.swift deleted file mode 100644 index 554396c193..0000000000 --- a/submodules/PremiumUI/Sources/ScrollComponent.swift +++ /dev/null @@ -1,146 +0,0 @@ -import Foundation -import UIKit -import ComponentFlow -import Display - -final class ScrollChildEnvironment: Equatable { - public let insets: UIEdgeInsets - - public init(insets: UIEdgeInsets) { - self.insets = insets - } - - public static func ==(lhs: ScrollChildEnvironment, rhs: ScrollChildEnvironment) -> Bool { - if lhs.insets != rhs.insets { - return false - } - - return true - } -} - -final class ScrollComponent: Component { - typealias EnvironmentType = ChildEnvironment - - class ExternalState { - var contentHeight: CGFloat = 0.0 - } - - let content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)> - let externalState: ExternalState? - let contentInsets: UIEdgeInsets - let contentOffsetUpdated: (_ top: CGFloat, _ bottom: CGFloat) -> Void - let contentOffsetWillCommit: (UnsafeMutablePointer) -> Void - let resetScroll: ActionSlot - - public init( - content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>, - externalState: ExternalState? = nil, - contentInsets: UIEdgeInsets, - contentOffsetUpdated: @escaping (_ top: CGFloat, _ bottom: CGFloat) -> Void, - contentOffsetWillCommit: @escaping (UnsafeMutablePointer) -> Void, - resetScroll: ActionSlot = ActionSlot() - ) { - self.content = content - self.externalState = externalState - self.contentInsets = contentInsets - self.contentOffsetUpdated = contentOffsetUpdated - self.contentOffsetWillCommit = contentOffsetWillCommit - self.resetScroll = resetScroll - } - - public static func ==(lhs: ScrollComponent, rhs: ScrollComponent) -> Bool { - if lhs.content != rhs.content { - return false - } - if lhs.contentInsets != rhs.contentInsets { - return false - } - return true - } - - public final class View: UIScrollView, UIScrollViewDelegate { - private var component: ScrollComponent? - private let contentView: ComponentHostView<(ChildEnvironment, ScrollChildEnvironment)> - - override init(frame: CGRect) { - self.contentView = ComponentHostView() - - super.init(frame: frame) - - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.contentInsetAdjustmentBehavior = .never - } - self.delegate = self - self.showsVerticalScrollIndicator = false - self.showsHorizontalScrollIndicator = false - self.canCancelContentTouches = true - - self.addSubview(self.contentView) - } - - public override func touchesShouldCancel(in view: UIView) -> Bool { - return true - } - - private var ignoreDidScroll = false - public func scrollViewDidScroll(_ scrollView: UIScrollView) { - guard let component = self.component, !self.ignoreDidScroll else { - return - } - let topOffset = scrollView.contentOffset.y - let bottomOffset = max(0.0, scrollView.contentSize.height - scrollView.contentOffset.y - scrollView.frame.height) - component.contentOffsetUpdated(topOffset, bottomOffset) - } - - public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - guard let component = self.component, !self.ignoreDidScroll else { - return - } - component.contentOffsetWillCommit(targetContentOffset) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(component: ScrollComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - let contentSize = self.contentView.update( - transition: transition, - component: component.content, - environment: { - environment[ChildEnvironment.self] - ScrollChildEnvironment(insets: component.contentInsets) - }, - containerSize: CGSize(width: availableSize.width, height: .greatestFiniteMagnitude) - ) - transition.setFrame(view: self.contentView, frame: CGRect(origin: .zero, size: contentSize), completion: nil) - - component.resetScroll.connect { [weak self] _ in - self?.setContentOffset(.zero, animated: false) - } - - if self.contentSize != contentSize { - self.ignoreDidScroll = true - self.contentSize = contentSize - self.ignoreDidScroll = false - } - if self.scrollIndicatorInsets != component.contentInsets { - self.scrollIndicatorInsets = component.contentInsets - } - component.externalState?.contentHeight = contentSize.height - - self.component = component - - return availableSize - } - } - - public func makeView() -> View { - return View(frame: CGRect()) - } - - public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} diff --git a/submodules/PremiumUI/Sources/StoriesPageComponent.swift b/submodules/PremiumUI/Sources/StoriesPageComponent.swift index d03b7a9752..106d512df0 100644 --- a/submodules/PremiumUI/Sources/StoriesPageComponent.swift +++ b/submodules/PremiumUI/Sources/StoriesPageComponent.swift @@ -12,6 +12,7 @@ import TelegramPresentationData import BundleIconComponent import AvatarNode import AvatarStoryIndicatorComponent +import ScrollComponent private final class AvatarComponent: Component { let context: AccountContext diff --git a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift index 8f0ec3483c..4077f75afb 100644 --- a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift @@ -252,6 +252,10 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC } let textSize = self.textNode.measure(CGSize(width: 100.0, height: 100.0)) + if textSize.height.isZero { + topOffset = 0.0 + textSpacing = 0.0 + } let imageSize = dimensions.cgSize.aspectFitted(boundingSize) self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() @@ -275,7 +279,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC self.textNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - textSize.width) / 2.0) - centerOffset, y: -textSize.height - textSpacing), size: textSize) - if self.item.file?.isCustomEmoji == true { + if self.item.file?.isCustomEmoji == true || textSize.height.isZero { return CGSize(width: boundingSize.width, height: imageFrame.height) } else { return CGSize(width: boundingSize.width, height: imageFrame.height + textSize.height + textSpacing) diff --git a/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift index 732fbe6727..913cefbac1 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift @@ -21,8 +21,9 @@ public final class AdMessageAttribute: MessageAttribute { public let buttonText: String? public let sponsorInfo: String? public let additionalInfo: String? + public let canReport: Bool - public init(opaqueId: Data, messageType: MessageType, displayAvatar: Bool, target: MessageTarget, buttonText: String?, sponsorInfo: String?, additionalInfo: String?) { + public init(opaqueId: Data, messageType: MessageType, displayAvatar: Bool, target: MessageTarget, buttonText: String?, sponsorInfo: String?, additionalInfo: String?, canReport: Bool) { self.opaqueId = opaqueId self.messageType = messageType self.displayAvatar = displayAvatar @@ -30,6 +31,7 @@ public final class AdMessageAttribute: MessageAttribute { self.buttonText = buttonText self.sponsorInfo = sponsorInfo self.additionalInfo = additionalInfo + self.canReport = canReport } public init(decoder: PostboxDecoder) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index e16c15a59a..f4b5546dbf 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -18,6 +18,7 @@ private class AdMessagesHistoryContextImpl { case buttonText case sponsorInfo case additionalInfo + case canReport } enum MessageType: Int32, Codable { @@ -168,6 +169,7 @@ private class AdMessagesHistoryContextImpl { public let buttonText: String? public let sponsorInfo: String? public let additionalInfo: String? + public let canReport: Bool public init( opaqueId: Data, @@ -181,7 +183,8 @@ private class AdMessagesHistoryContextImpl { startParam: String?, buttonText: String?, sponsorInfo: String?, - additionalInfo: String? + additionalInfo: String?, + canReport: Bool ) { self.opaqueId = opaqueId self.messageType = messageType @@ -195,6 +198,7 @@ private class AdMessagesHistoryContextImpl { self.buttonText = buttonText self.sponsorInfo = sponsorInfo self.additionalInfo = additionalInfo + self.canReport = canReport } public init(from decoder: Decoder) throws { @@ -225,6 +229,8 @@ private class AdMessagesHistoryContextImpl { self.sponsorInfo = try container.decodeIfPresent(String.self, forKey: .sponsorInfo) self.additionalInfo = try container.decodeIfPresent(String.self, forKey: .additionalInfo) + + self.canReport = try container.decodeIfPresent(Bool.self, forKey: .displayAvatar) ?? false } public func encode(to encoder: Encoder) throws { @@ -250,6 +256,8 @@ private class AdMessagesHistoryContextImpl { try container.encodeIfPresent(self.sponsorInfo, forKey: .sponsorInfo) try container.encodeIfPresent(self.additionalInfo, forKey: .additionalInfo) + + try container.encode(self.canReport, forKey: .canReport) } public static func ==(lhs: CachedMessage, rhs: CachedMessage) -> Bool { @@ -291,6 +299,9 @@ private class AdMessagesHistoryContextImpl { if lhs.additionalInfo != rhs.additionalInfo { return false } + if lhs.canReport != rhs.canReport { + return false + } return true } @@ -315,7 +326,7 @@ private class AdMessagesHistoryContextImpl { case .recommended: mappedMessageType = .recommended } - attributes.append(AdMessageAttribute(opaqueId: self.opaqueId, messageType: mappedMessageType, displayAvatar: self.displayAvatar, target: target, buttonText: self.buttonText, sponsorInfo: self.sponsorInfo, additionalInfo: self.additionalInfo)) + attributes.append(AdMessageAttribute(opaqueId: self.opaqueId, messageType: mappedMessageType, displayAvatar: self.displayAvatar, target: target, buttonText: self.buttonText, sponsorInfo: self.sponsorInfo, additionalInfo: self.additionalInfo, canReport: self.canReport)) if !self.textEntities.isEmpty { let attribute = TextEntitiesMessageAttribute(entities: self.textEntities) attributes.append(attribute) @@ -604,6 +615,7 @@ private class AdMessagesHistoryContextImpl { let isRecommended = (flags & (1 << 5)) != 0 var displayAvatar = (flags & (1 << 6)) != 0 + let canReport = (flags & (1 << 12)) != 0 var target: CachedMessage.Target? if let fromId = fromId { @@ -685,7 +697,8 @@ private class AdMessagesHistoryContextImpl { startParam: startParam, buttonText: buttonText, sponsorInfo: sponsorInfo, - additionalInfo: additionalInfo + additionalInfo: additionalInfo, + canReport: canReport )) } } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index a32a83ba44..7722c07dca 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -442,6 +442,8 @@ swift_library( "//submodules/TelegramUI/Components/StickerPickerScreen", "//submodules/TelegramUI/Components/Chat/ChatEmptyNode", "//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem", + "//submodules/TelegramUI/Components/Ads/AdsInfoScreen", + "//submodules/TelegramUI/Components/Ads/AdsReportScreen", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD index bece3cf0d8..54bbbf40c8 100644 --- a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD @@ -28,7 +28,6 @@ swift_library( "//submodules/AppBundle", "//submodules/TelegramStringFormatting", "//submodules/PresentationDataUtils", - "//submodules/Components/SheetComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift index c0935aacd2..e9a40c756d 100644 --- a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift +++ b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift @@ -65,6 +65,7 @@ private final class ScrollContent: CombinedComponent { let icon = Child(BundleIconComponent.self) let title = Child(BalancedTextComponent.self) + let text = Child(BalancedTextComponent.self) let list = Child(List.self) let actionButton = Child(SolidRoundedButtonComponent.self) @@ -98,7 +99,7 @@ private final class ScrollContent: CombinedComponent { //TODO:localize let spacing: CGFloat = 16.0 - var contentSize = CGSize(width: context.availableSize.width, height: 32.0) + var contentSize = CGSize(width: context.availableSize.width, height: 30.0) let iconSize = CGSize(width: 90.0, height: 90.0) let gradientImage: UIImage @@ -128,12 +129,11 @@ private final class ScrollContent: CombinedComponent { availableSize: CGSize(width: 90, height: 90), transition: .immediate ) - context.add(icon .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0)) ) contentSize.height += iconSize.height - contentSize.height += spacing + 5.0 + contentSize.height += spacing + 1.0 let title = title.update( component: BalancedTextComponent( @@ -149,6 +149,22 @@ private final class ScrollContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) ) contentSize.height += title.size.height + contentSize.height += spacing - 8.0 + + let text = text.update( + component: BalancedTextComponent( + text: .plain(NSAttributedString(string: "Telegram Ads are very different from ads on other platforms. Ads such as this one:", font: textFont, textColor: secondaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(text + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0)) + ) + contentSize.height += text.size.height contentSize.height += spacing @@ -215,7 +231,7 @@ private final class ScrollContent: CombinedComponent { maximumNumberOfLines: 0, lineSpacing: 0.2 ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.4, height: context.availableSize.height), + availableSize: CGSize(width: context.availableSize.width - sideInset * 3.5, height: context.availableSize.height), transition: .immediate ) @@ -223,7 +239,7 @@ private final class ScrollContent: CombinedComponent { state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme) } - let infoString = "Anyone can create an ad to display in this channel – with minimal budgets. Check out the Telegram Ad Platform for details. [Learn More >]()" + let infoString = "Anyone can create ads to display in this channel – with minimal budgets. Check out the Telegram Ad Platform for details. [Learn More >]()" let infoAttributedString = parseMarkdownIntoAttributedString(infoString, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString if let range = infoAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { infoAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: infoAttributedString.string)) @@ -235,12 +251,12 @@ private final class ScrollContent: CombinedComponent { maximumNumberOfLines: 0, lineSpacing: 0.2 ), - availableSize: CGSize(width: context.availableSize.width - (textSideInset + sideInset - 2.0) * 2.0, height: context.availableSize.height), + availableSize: CGSize(width: context.availableSize.width - sideInset * 3.5, height: context.availableSize.height), transition: .immediate ) - let infoPadding: CGFloat = 17.0 - let infoSpacing: CGFloat = 12.0 + let infoPadding: CGFloat = 13.0 + let infoSpacing: CGFloat = 6.0 let totalInfoHeight = infoPadding + infoTitle.size.height + infoSpacing + infoText.size.height + infoPadding let infoBackground = infoBackground.update( diff --git a/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD b/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD index a59c794d82..1cab69556f 100644 --- a/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD +++ b/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD @@ -20,14 +20,16 @@ swift_library( "//submodules/Components/ComponentDisplayAdapters", "//submodules/Components/MultilineTextComponent", "//submodules/Components/BalancedTextComponent", - "//submodules/Components/SolidRoundedButtonComponent", - "//submodules/Components/BundleIconComponent", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/AppBundle", + "//submodules/ItemListUI", "//submodules/TelegramStringFormatting", "//submodules/PresentationDataUtils", "//submodules/Components/SheetComponent", + "//submodules/UndoUI", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift b/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift index 8027ffe47a..ae0a05c41a 100644 --- a/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift +++ b/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift @@ -9,297 +9,312 @@ import TextFormat import TelegramPresentationData import ViewControllerComponent import SheetComponent -import BundleIconComponent import BalancedTextComponent import MultilineTextComponent -import SolidRoundedButtonComponent +import ListSectionComponent +import ListActionItemComponent +import ItemListUI +import UndoUI import AccountContext +private final class SheetPageContent: CombinedComponent { + struct Item: Equatable { + let title: String + let subItems: [Item] + } + + let context: AccountContext + let title: String? + let items: [Item] + let action: (Item) -> Void + let pop: () -> Void + + init( + context: AccountContext, + title: String?, + items: [Item], + action: @escaping (Item) -> Void, + pop: @escaping () -> Void + ) { + self.context = context + self.title = title + self.items = items + self.action = action + self.pop = pop + } + + static func ==(lhs: SheetPageContent, rhs: SheetPageContent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.items != rhs.items { + return false + } + return true + } + + static var body: Body { + let background = Child(RoundedRectangle.self) + let back = Child(Button.self) + let title = Child(Text.self) + let subtitle = Child(Text.self) + let section = Child(ListSectionComponent.self) + + return { context in + let component = context.component + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let theme = presentationData.theme +// let strings = environment.strings + + let sideInset: CGFloat = 16.0 + + var contentSize = CGSize(width: context.availableSize.width, height: 18.0) + + let background = background.update( + component: RoundedRectangle(color: theme.list.modalBlocksBackgroundColor, cornerRadius: 8.0), + availableSize: CGSize(width: context.availableSize.width, height: 1000.0), + transition: .immediate + ) + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0)) + ) + + let back = back.update( + component: Button( + content: AnyComponent( + Text(text: "Cancel", font: Font.regular(17.0), color: theme.list.itemAccentColor) + ), + action: { + component.pop() + } + ), + availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), + transition: .immediate + ) + context.add(back + .position(CGPoint(x: sideInset + back.size.width / 2.0, y: contentSize.height + back.size.height / 2.0)) + ) + + let title = title.update( + component: Text(text: "Report Ad", font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor), + availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), + transition: .immediate + ) + if let subtitleText = component.title { + let subtitle = subtitle.update( + component: Text(text: subtitleText, font: Font.regular(13.0), color: theme.list.itemSecondaryTextColor), + availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), + transition: .immediate + ) + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0 - 8.0)) + ) + contentSize.height += title.size.height + context.add(subtitle + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + subtitle.size.height / 2.0 - 9.0)) + ) + contentSize.height += subtitle.size.height + contentSize.height += 24.0 + } else { + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) + ) + contentSize.height += title.size.height + contentSize.height += 24.0 + } + + var items: [AnyComponentWithIdentity] = [] + for item in component.items { + items.append(AnyComponentWithIdentity(id: item.title, component: AnyComponent(ListActionItemComponent( + theme: theme, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: item.title, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))), + ], alignment: .left, spacing: 2.0)), + accessory: !item.subItems.isEmpty ? .arrow : nil, + action: { _ in + component.action(item) + } + )))) + } + + let section = section.update( + component: ListSectionComponent( + theme: theme, + header: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "WHAT IS WRONG WITH THIS AD?".uppercased(), + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + footer: AnyComponent(MultilineTextComponent( + text: .markdown( + text: "Learn more about [Telegram Ad Policies and Guidelines]().", + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.freeTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.freeTextColor), + link: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.itemAccentColor), + linkAttribute: { _ in + return nil + } + ) + ), + maximumNumberOfLines: 0 + )), + items: items + ), + environment: {}, + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude), + transition: context.transition + ) + context.add(section + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + section.size.height / 2.0)) + ) + contentSize.height += section.size.height + contentSize.height += 54.0 + + return contentSize + } + } +} + private final class SheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext - let animatedEmojis: [String: TelegramMediaFile] + let pts: Int let openMore: () -> Void + let complete: () -> Void let dismiss: () -> Void + let update: (Transition) -> Void init( context: AccountContext, - animatedEmojis: [String: TelegramMediaFile], + pts: Int, openMore: @escaping () -> Void, - dismiss: @escaping () -> Void + complete: @escaping () -> Void, + dismiss: @escaping () -> Void, + update: @escaping (Transition) -> Void ) { self.context = context - self.animatedEmojis = animatedEmojis + self.pts = pts self.openMore = openMore + self.complete = complete self.dismiss = dismiss + self.update = update } static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { if lhs.context !== rhs.context { return false } + if lhs.pts != rhs.pts { + return false + } return true } final class State: ComponentState { - var cachedIconImage: (UIImage, PresentationTheme)? - var cachedChevronImage: (UIImage, PresentationTheme)? - - let playOnce = ActionSlot() - private var didPlayAnimation = false - - func playAnimationIfNeeded() { - guard !self.didPlayAnimation else { - return - } - self.didPlayAnimation = true - self.playOnce.invoke(Void()) - } + var pushedItem: SheetPageContent.Item? } func makeState() -> State { return State() } - + static var body: Body { - let iconBackground = Child(Image.self) - let icon = Child(BundleIconComponent.self) - - let title = Child(BalancedTextComponent.self) - let list = Child(List.self) - let actionButton = Child(SolidRoundedButtonComponent.self) - - let infoBackground = Child(RoundedRectangle.self) - let infoTitle = Child(MultilineTextComponent.self) - let infoText = Child(MultilineTextComponent.self) +// let title = Child(BalancedTextComponent.self) + let navigation = Child(NavigationStackComponent.self) return { context in - let environment = context.environment[EnvironmentType.self] let component = context.component let state = context.state - - let theme = environment.theme -// let strings = environment.strings - - let sideInset: CGFloat = 16.0 + environment.safeInsets.left - let textSideInset: CGFloat = 30.0 + environment.safeInsets.left - - let titleFont = Font.semibold(20.0) - let textFont = Font.regular(15.0) - - let textColor = theme.actionSheet.primaryTextColor - let secondaryTextColor = theme.actionSheet.secondaryTextColor - let linkColor = theme.actionSheet.controlAccentColor - - let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in - return (TelegramTextAttributes.URL, contents) - }) - - //TODO:localize - - let spacing: CGFloat = 16.0 - var contentSize = CGSize(width: context.availableSize.width, height: 32.0) - - let iconSize = CGSize(width: 90.0, height: 90.0) - let gradientImage: UIImage - - if let (current, currentTheme) = state.cachedIconImage, currentTheme === theme { - gradientImage = current - } else { - gradientImage = generateGradientFilledCircleImage(diameter: iconSize.width, colors: [ - UIColor(rgb: 0x4bbb45).cgColor, - UIColor(rgb: 0x9ad164).cgColor - ])! - context.state.cachedIconImage = (gradientImage, theme) - } - - let iconBackground = iconBackground.update( - component: Image(image: gradientImage), - availableSize: iconSize, - transition: .immediate - ) - context.add(iconBackground - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0)) - ) - - let icon = icon.update( - component: BundleIconComponent(name: "Chart/Monetization", tintColor: theme.list.itemCheckColors.foregroundColor), - availableSize: CGSize(width: 90, height: 90), - transition: .immediate - ) - - context.add(icon - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0)) - ) - contentSize.height += iconSize.height - contentSize.height += spacing + 5.0 - - let title = title.update( - component: BalancedTextComponent( - text: .plain(NSAttributedString(string: "Earn From Your Channel", font: titleFont, textColor: textColor)), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - lineSpacing: 0.1 - ), - availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), - transition: .immediate - ) - context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) - ) - contentSize.height += title.size.height - contentSize.height += spacing - + let update = component.update var items: [AnyComponentWithIdentity] = [] - items.append( - AnyComponentWithIdentity( - id: "ads", - component: AnyComponent(ParagraphComponent( - title: "Telegram Ads", - titleColor: textColor, - text: "Telegram can display ads in your channel.", - textColor: secondaryTextColor, - iconName: "Chart/Ads", - iconColor: linkColor - )) - ) - ) - items.append( - AnyComponentWithIdentity( - id: "split", - component: AnyComponent(ParagraphComponent( - title: "50:50 Revenue Split", - titleColor: textColor, - text: "You receive 50% of the ad revenue in TON.", - textColor: secondaryTextColor, - iconName: "Chart/Split", - iconColor: linkColor - )) - ) - ) - items.append( - AnyComponentWithIdentity( - id: "withdrawal", - component: AnyComponent(ParagraphComponent( - title: "Flexible Withdrawals", - titleColor: textColor, - text: "You can withdraw your TON any time.", - textColor: secondaryTextColor, - iconName: "Chart/Withdrawal", - iconColor: linkColor - )) - ) - ) - - let list = list.update( - component: List(items), - availableSize: CGSize(width: context.availableSize.width - sideInset, height: 10000.0), - transition: context.transition - ) - context.add(list - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + list.size.height / 2.0)) - ) - contentSize.height += list.size.height - contentSize.height += spacing - 9.0 - - let infoTitleString = "What's #TON?"//.replacingOccurrences(of: "#", with: "# ") - let infoTitleAttributedString = NSMutableAttributedString(string: infoTitleString, font: titleFont, textColor: textColor) - let infoTitle = infoTitle.update( - component: MultilineTextComponent( - text: .plain(infoTitleAttributedString), - horizontalAlignment: .natural, - maximumNumberOfLines: 0, - lineSpacing: 0.2 - ), - availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), - transition: .immediate - ) - - if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme { - state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme) - } - - let infoString = "TON is a blockchain platform and cryptocurrency that Telegram uses for its record scalability and ultra low commissions on transactions.\n[Learn More >]()" - let infoAttributedString = parseMarkdownIntoAttributedString(infoString, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString - if let range = infoAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { - infoAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: infoAttributedString.string)) - } - let infoText = infoText.update( - component: MultilineTextComponent( - text: .plain(infoAttributedString), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - lineSpacing: 0.2 - ), - availableSize: CGSize(width: context.availableSize.width - (textSideInset + sideInset - 2.0) * 2.0, height: context.availableSize.height), - transition: .immediate - ) - - let infoPadding: CGFloat = 17.0 - let infoSpacing: CGFloat = 12.0 - let totalInfoHeight = infoPadding + infoTitle.size.height + infoSpacing + infoText.size.height + infoPadding - - let infoBackground = infoBackground.update( - component: RoundedRectangle( - color: theme.list.blocksBackgroundColor, - cornerRadius: 10.0 - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: totalInfoHeight), - transition: .immediate - ) - context.add(infoBackground - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoBackground.size.height / 2.0)) - ) - contentSize.height += infoPadding - - context.add(infoTitle - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoTitle.size.height / 2.0)) - ) - contentSize.height += infoTitle.size.height - contentSize.height += infoSpacing - - context.add(infoText - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoText.size.height / 2.0)) - ) - contentSize.height += infoText.size.height - contentSize.height += infoPadding - contentSize.height += spacing - - let actionButton = actionButton.update( - component: SolidRoundedButtonComponent( - title: "Understood", - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: theme.list.itemCheckColors.fillColor, - backgroundColors: [], - foregroundColor: theme.list.itemCheckColors.foregroundColor - ), - font: .bold, - fontSize: 17.0, - height: 50.0, - cornerRadius: 10.0, - gloss: false, - iconName: nil, - animationName: nil, - iconPosition: .left, - action: { + items.append(AnyComponentWithIdentity(id: 0, component: AnyComponent( + SheetPageContent( + context: component.context, + title: nil, + items: [ + SheetPageContent.Item(title: "I don't like it", subItems: []), + SheetPageContent.Item(title: "I don't want to see ads", subItems: []), + SheetPageContent.Item(title: "Destination doesn't match the ad", subItems: []), + SheetPageContent.Item(title: "Word choice or style", subItems: []), + SheetPageContent.Item(title: "Shocking or sexual content", subItems: []), + SheetPageContent.Item(title: "Hate speech or threats", subItems: []), + SheetPageContent.Item(title: "Scam or misleading", subItems: []), + SheetPageContent.Item(title: "Illegal or questionable products", subItems: [ + SheetPageContent.Item(title: "Drugs, alcohol or tobacco", subItems: []), + SheetPageContent.Item(title: "Uncertified medicine or supplements", subItems: []), + SheetPageContent.Item(title: "Weapons or firearms", subItems: []), + SheetPageContent.Item(title: "Fake money", subItems: []), + SheetPageContent.Item(title: "Fake documents", subItems: []), + SheetPageContent.Item(title: "Malware, phishing, hacked accounts", subItems: []), + SheetPageContent.Item(title: "Human trafficking or exploitation", subItems: []), + SheetPageContent.Item(title: "Wild or restricted animals", subItems: []), + SheetPageContent.Item(title: "Gambling", subItems: []) + ]), + SheetPageContent.Item(title: "Copyright infringement", subItems: []), + SheetPageContent.Item(title: "Politics or religion", subItems: []), + SheetPageContent.Item(title: "Spam", subItems: []) + ], + action: { [weak state] item in + if !item.subItems.isEmpty { + state?.pushedItem = item + update(.spring(duration: 0.45)) + } else { + component.complete() + } + }, + pop: { component.dismiss() } - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + ) + ))) + if let pushedItem = context.state.pushedItem { + items.append(AnyComponentWithIdentity(id: 1, component: AnyComponent( + SheetPageContent( + context: component.context, + title: pushedItem.title, + items: pushedItem.subItems.map { + SheetPageContent.Item(title: $0.title, subItems: []) + }, + action: { item in + component.complete() + }, + pop: { [weak state] in + state?.pushedItem = nil + update(.spring(duration: 0.45)) + } + ) + ))) + } + + var contentSize = CGSize(width: context.availableSize.width, height: 0.0) + let navigation = navigation.update( + component: NavigationStackComponent(items: items), + availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), transition: context.transition ) - context.add(actionButton - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + actionButton.size.height / 2.0)) + context.add(navigation + .position(CGPoint(x: context.availableSize.width / 2.0, y: navigation.size.height / 2.0)) + .clipsToBounds(true) ) - contentSize.height += actionButton.size.height - contentSize.height += 22.0 + contentSize.height += navigation.size.height - contentSize.height += environment.safeInsets.bottom - - state.playAnimationIfNeeded() - return contentSize } } @@ -309,17 +324,17 @@ private final class SheetContainerComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext - let animatedEmojis: [String: TelegramMediaFile] let openMore: () -> Void + let complete: () -> Void init( context: AccountContext, - animatedEmojis: [String: TelegramMediaFile], - openMore: @escaping () -> Void + openMore: @escaping () -> Void, + complete: @escaping () -> Void ) { self.context = context - self.animatedEmojis = animatedEmojis self.openMore = openMore + self.complete = complete } static func ==(lhs: SheetContainerComponent, rhs: SheetContainerComponent) -> Bool { @@ -329,6 +344,14 @@ private final class SheetContainerComponent: CombinedComponent { return true } + final class State: ComponentState { + var pts: Int = 0 + } + + func makeState() -> State { + return State() + } + static var body: Body { let sheet = Child(SheetComponent.self) let animateOut = StoredActionSlot(Action.self) @@ -337,24 +360,29 @@ private final class SheetContainerComponent: CombinedComponent { return { context in let environment = context.environment[EnvironmentType.self] - + let state = context.state let controller = environment.controller let sheet = sheet.update( component: SheetComponent( content: AnyComponent(SheetContent( context: context.component.context, - animatedEmojis: context.component.animatedEmojis, + pts: state.pts, openMore: context.component.openMore, + complete: context.component.complete, dismiss: { animateOut.invoke(Action { _ in if let controller = controller() { controller.dismiss(completion: nil) } }) + }, + update: { [weak state] transition in + state?.pts += 1 + state?.updated(transition: transition) } )), - backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), + backgroundColor: .color(environment.theme.list.modalBlocksBackgroundColor), followContentSizeChanges: true, externalState: sheetExternalState, animateOut: animateOut @@ -411,26 +439,23 @@ private final class SheetContainerComponent: CombinedComponent { } -final class AdsReportScreen: ViewControllerComponentContainer { +public final class AdsReportScreen: ViewControllerComponentContainer { private let context: AccountContext - private let animatedEmojis: [String: TelegramMediaFile] - private var openMore: (() -> Void)? - init( - context: AccountContext, - animatedEmojis: [String: TelegramMediaFile], - openMore: @escaping () -> Void + public init( + context: AccountContext ) { self.context = context - self.animatedEmojis = animatedEmojis - self.openMore = openMore + var completeImpl: (() -> Void)? super.init( context: context, component: SheetContainerComponent( context: context, - animatedEmojis: animatedEmojis, - openMore: openMore + openMore: {}, + complete: { + completeImpl?() + } ), navigationBarAppearance: .none, statusBarStyle: .ignore, @@ -438,146 +463,259 @@ final class AdsReportScreen: ViewControllerComponentContainer { ) self.navigationPresentation = .flatModal + + completeImpl = { [weak self] in + guard let self else { + return + } + let navigationController = self.navigationController + self.dismissAnimated() + + Queue.mainQueue().after(0.4, { + //TODO:localize + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + (navigationController?.viewControllers.last as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: "We will review this ad to ensure it matches our [Ad Policies and Guidelines]().", cancel: nil, destructive: false), elevatedLayout: false, action: { _ in + return true + }), in: .current) + }) + } } required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func viewDidLoad() { + public override func viewDidLoad() { super.viewDidLoad() self.view.disablesInteractiveModalDismiss = true } - func dismissAnimated() { + public func dismissAnimated() { if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { view.dismissAnimated() } } } -private final class ParagraphComponent: CombinedComponent { - let title: String - let titleColor: UIColor - let text: String - let textColor: UIColor - let iconName: String - let iconColor: UIColor + + +//private final class NavigationContainer: UIView, UIGestureRecognizerDelegate { +// var requestUpdate: ((Transition) -> Void)? +// var requestPop: (() -> Void)? +// var transitionFraction: CGFloat = 0.0 +// +// private var panRecognizer: InteractiveTransitionGestureRecognizer? +// +// var isNavigationEnabled: Bool = false { +// didSet { +// self.panRecognizer?.isEnabled = self.isNavigationEnabled +// } +// } +// +// override init() { +// super.init() +// +// self.clipsToBounds = true +// +// let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in +// guard let strongSelf = self else { +// return [] +// } +// let _ = strongSelf +// return [.right] +// }) +// panRecognizer.delegate = self +// self.view.addGestureRecognizer(panRecognizer) +// self.panRecognizer = panRecognizer +// } +// +// func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { +// return false +// } +// +// func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { +// if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer { +// return false +// } +// if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { +// return true +// } +// return false +// } +// +// @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { +// switch recognizer.state { +// case .began: +// self.transitionFraction = 0.0 +// case .changed: +// let distanceFactor: CGFloat = recognizer.translation(in: self.view).x / self.bounds.width +// let transitionFraction = max(0.0, min(1.0, distanceFactor)) +// if self.transitionFraction != transitionFraction { +// self.transitionFraction = transitionFraction +// self.requestUpdate?(.immediate) +// } +// case .ended, .cancelled: +// let distanceFactor: CGFloat = recognizer.translation(in: self.view).x / self.bounds.width +// let transitionFraction = max(0.0, min(1.0, distanceFactor)) +// if transitionFraction > 0.2 { +// self.transitionFraction = 0.0 +// self.requestPop?() +// } else { +// self.transitionFraction = 0.0 +// self.requestUpdate?(.spring(duration: 0.45)) +// } +// default: +// break +// } +// } +//} + +final class NavigationStackComponent: Component { + public let items: [AnyComponentWithIdentity] public init( - title: String, - titleColor: UIColor, - text: String, - textColor: UIColor, - iconName: String, - iconColor: UIColor + items: [AnyComponentWithIdentity] ) { - self.title = title - self.titleColor = titleColor - self.text = text - self.textColor = textColor - self.iconName = iconName - self.iconColor = iconColor + self.items = items } - static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool { - if lhs.title != rhs.title { - return false - } - if lhs.titleColor != rhs.titleColor { - return false - } - if lhs.text != rhs.text { - return false - } - if lhs.textColor != rhs.textColor { - return false - } - if lhs.iconName != rhs.iconName { - return false - } - if lhs.iconColor != rhs.iconColor { + public static func ==(lhs: NavigationStackComponent, rhs: NavigationStackComponent) -> Bool { + if lhs.items != rhs.items { return false } return true } - - static var body: Body { - let title = Child(MultilineTextComponent.self) - let text = Child(MultilineTextComponent.self) - let icon = Child(BundleIconComponent.self) - return { context in - let component = context.component - - let leftInset: CGFloat = 64.0 - let rightInset: CGFloat = 32.0 - let textSideInset: CGFloat = leftInset + 8.0 - let spacing: CGFloat = 5.0 - - let textTopInset: CGFloat = 9.0 - - let title = title.update( - component: MultilineTextComponent( - text: .plain(NSAttributedString( - string: component.title, - font: Font.semibold(15.0), - textColor: component.titleColor, - paragraphAlignment: .natural - )), - horizontalAlignment: .center, - maximumNumberOfLines: 1 - ), - availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), - transition: .immediate - ) - - let textFont = Font.regular(15.0) - let boldTextFont = Font.semibold(15.0) - let textColor = component.textColor - let markdownAttributes = MarkdownAttributes( - body: MarkdownAttributeSet(font: textFont, textColor: textColor), - bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), - link: MarkdownAttributeSet(font: textFont, textColor: textColor), - linkAttribute: { _ in - return nil - } - ) - - let text = text.update( - component: MultilineTextComponent( - text: .markdown(text: component.text, attributes: markdownAttributes), - horizontalAlignment: .natural, - maximumNumberOfLines: 0, - lineSpacing: 0.2 - ), - availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height), - transition: .immediate - ) - - let icon = icon.update( - component: BundleIconComponent( - name: component.iconName, - tintColor: component.iconColor - ), - availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), - transition: .immediate - ) - - context.add(title - .position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0)) - ) - - context.add(text - .position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0)) - ) - - context.add(icon - .position(CGPoint(x: 47.0, y: textTopInset + 18.0)) - ) + private final class ItemView: UIView { + let contents = ComponentView() - return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 20.0) + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } } + + public final class View: UIView { + private var itemViews: [AnyHashable: ItemView] = [:] + + private var component: NavigationStackComponent? + + public override init(frame: CGRect) { + super.init(frame: CGRect()) + } + + required public init?(coder: NSCoder) { + preconditionFailure() + } + + func update(component: NavigationStackComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + var contentHeight: CGFloat = 0.0 + + var validItemIds: [AnyHashable] = [] + struct ReadyItem { + var index: Int + var itemId: AnyHashable + var itemView: ItemView + var itemTransition: Transition + var itemSize: CGSize + + init(index: Int, itemId: AnyHashable, itemView: ItemView, itemTransition: Transition, itemSize: CGSize) { + self.index = index + self.itemId = itemId + self.itemView = itemView + self.itemTransition = itemTransition + self.itemSize = itemSize + } + } + + var readyItems: [ReadyItem] = [] + for i in 0 ..< component.items.count { + let item = component.items[i] + let itemId = item.id + validItemIds.append(itemId) + + let itemView: ItemView + var itemTransition = transition + if let current = self.itemViews[itemId] { + itemView = current + } else { + itemTransition = itemTransition.withAnimation(.none) + itemView = ItemView() + itemView.clipsToBounds = true + self.itemViews[itemId] = itemView + itemView.contents.parentState = state + } + + let itemSize = itemView.contents.update( + transition: itemTransition, + component: item.component, + environment: {}, + containerSize: CGSize(width: availableSize.width, height: availableSize.height) + ) + + readyItems.append(ReadyItem( + index: i, + itemId: itemId, + itemView: itemView, + itemTransition: itemTransition, + itemSize: itemSize + )) + + if i == component.items.count - 1 { + contentHeight = itemSize.height + } + } + + for readyItem in readyItems.sorted(by: { $0.index < $1.index }) { + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: readyItem.itemSize) + if let itemComponentView = readyItem.itemView.contents.view { + var isAdded = false + if itemComponentView.superview == nil { + isAdded = true + self.addSubview(itemComponentView) + } + readyItem.itemTransition.setFrame(view: readyItem.itemView, frame: itemFrame) + readyItem.itemTransition.setFrame(view: itemComponentView, frame: CGRect(origin: CGPoint(), size: itemFrame.size)) + + if readyItem.index > 0 && isAdded { + transition.animatePosition(view: itemComponentView, from: CGPoint(x: itemFrame.width, y: 0.0), to: .zero, additive: true, completion: nil) + } + } + } + + var removedItemIds: [AnyHashable] = [] + for (id, _) in self.itemViews { + if !validItemIds.contains(id) { + removedItemIds.append(id) + } + } + for id in removedItemIds { + guard let itemView = self.itemViews[id] else { + continue + } + var position = itemView.center + position.x += itemView.bounds.width / 2.0 + transition.setPosition(view: itemView, position: position, completion: { _ in + itemView.removeFromSuperview() + self.itemViews.removeValue(forKey: id) + }) + } + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } } diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h index f935e3cafd..b3d2e2b03d 100644 --- a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h @@ -7,7 +7,7 @@ extern "C" { #endif -void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow); +void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow, bool keepColorsOrder); void combineYUVAPlanesIntoARGB(uint8_t *argb, uint8_t const *inY, uint8_t const *inU, uint8_t const *inV, uint8_t const *inA, int width, int height, int bytesPerRow); void scaleImagePlane(uint8_t *outPlane, int outWidth, int outHeight, int outBytesPerRow, uint8_t const *inPlane, int inWidth, int inHeight, int inBytesPerRow); diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m index 4d00b0e92a..3adaf579cf 100644 --- a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m @@ -3,9 +3,10 @@ #import #import -static uint8_t permuteMap[4] = { 3, 2, 1, 0}; +static uint8_t permuteMap[4] = { 3, 2, 1, 0 }; +static uint8_t invertedPermuteMap[4] = { 3, 0, 1, 2 }; -void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow) { +void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow, bool keepColorsOrder) { static vImage_ARGBToYpCbCr info; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -45,7 +46,7 @@ void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, destA.height = height; destA.rowBytes = width; - error = vImageConvert_ARGB8888To420Yp8_Cb8_Cr8(&src, &destYp, &destCb, &destCr, &info, permuteMap, kvImageDoNotTile); + error = vImageConvert_ARGB8888To420Yp8_Cb8_Cr8(&src, &destYp, &destCb, &destCr, &info, keepColorsOrder ? invertedPermuteMap : permuteMap, kvImageDoNotTile); if (error != kvImageNoError) { return; } diff --git a/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift b/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift index 352867087e..62234c883b 100644 --- a/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift +++ b/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift @@ -365,7 +365,8 @@ extension ImageARGB { aBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self), Int32(self.argbPlane.width), Int32(self.argbPlane.height), - Int32(self.argbPlane.bytesPerRow) + Int32(self.argbPlane.bytesPerRow), + false ) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index a6c634bea4..5ed3934282 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -153,7 +153,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { self.activateAction?() } - public typealias AsyncLayout = (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize, _ animationCache: AnimationCache, _ animationRenderer: MultiAnimationRenderer) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) + public typealias AsyncLayout = (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ titleBadge: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize, _ animationCache: AnimationCache, _ animationRenderer: MultiAnimationRenderer) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) public func makeProgress() -> Promise { let progress = Promise() @@ -178,7 +178,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { let makeActionButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.actionButton) let makeStatusLayout = ChatMessageDateAndStatusNode.asyncLayout(self.statusNode) - return { [weak self] presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, chatLocation, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize, animationCache, animationRenderer in + return { [weak self] presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, chatLocation, title, titleBadge, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize, animationCache, animationRenderer in let isPreview = presentationData.isPreview let fontSize: CGFloat if message.adAttribute != nil { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift index 7f70a401ef..7da7c1c32c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift @@ -52,7 +52,7 @@ public final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessag } let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/Sources/ChatMessageEventLogPreviousLinkContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/Sources/ChatMessageEventLogPreviousLinkContentNode.swift index 37502efe72..7588af15ed 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/Sources/ChatMessageEventLogPreviousLinkContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/Sources/ChatMessageEventLogPreviousLinkContentNode.swift @@ -47,7 +47,7 @@ public final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubble let text: String = item.message.text let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/Sources/ChatMessageEventLogPreviousMessageContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/Sources/ChatMessageEventLogPreviousMessageContentNode.swift index 73176c153f..79975870a3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/Sources/ChatMessageEventLogPreviousMessageContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/Sources/ChatMessageEventLogPreviousMessageContentNode.swift @@ -52,7 +52,7 @@ public final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBub } let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift index 1d9a30a9f1..b93e0ea81c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift @@ -80,7 +80,7 @@ public final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNod } } - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, .peer(id: item.message.id.peerId), title, nil, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, .peer(id: item.message.id.peerId), title, nil, nil, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift index 59a16518f5..c78beea3bc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift @@ -76,7 +76,7 @@ public final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContent } } - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, automaticDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, nil, mediaAndFlags, nil, nil, nil, false, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, automaticDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, nil, subtitle, text, nil, mediaAndFlags, nil, nil, nil, false, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 85eeb685db..3ad5078c2d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -236,6 +236,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent var subtitle: NSAttributedString? var text: String? var entities: [MessageTextEntity]? + var titleBadge: String? var mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? var badge: String? @@ -505,6 +506,8 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent } } + titleBadge = "what's this?" + if let buttonText = adAttribute.buttonText { actionTitle = buttonText.uppercased() } else if let author = item.message.author as? TelegramUser, author.botInfo != nil { @@ -532,7 +535,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent displayLine = true } - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, titleBadge, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index 5efd0709c8..b0ed7ff815 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -1517,6 +1517,8 @@ public extension EmojiPagerContentComponent { displaySearchWithPlaceholder = strings.EmojiSearch_SearchEmojiPlaceholder } else if [.profilePhoto, .groupPhoto].contains(subject) { displaySearchWithPlaceholder = strings.Common_Search + } else if case .stickerAlt = subject { + displaySearchWithPlaceholder = strings.Common_Search } } diff --git a/submodules/TelegramUI/Components/MediaEditor/BUILD b/submodules/TelegramUI/Components/MediaEditor/BUILD index 4f6545e82f..122c555860 100644 --- a/submodules/TelegramUI/Components/MediaEditor/BUILD +++ b/submodules/TelegramUI/Components/MediaEditor/BUILD @@ -71,6 +71,7 @@ swift_library( "//submodules/WallpaperBackgroundNode", "//submodules/ImageTransparency", "//submodules/FFMpegBinding", + "//submodules/TelegramUI/Components/AnimationCache/ImageDCT", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift index a0e9d03562..2a691e1ff7 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift @@ -152,17 +152,7 @@ final class MediaEditorComposer { if var compositedImage { let scale = self.outputDimensions.width / compositedImage.extent.width compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale)) - - if self.isFirst { - let path = NSTemporaryDirectory() + "test22.png" - if let cgImage = self.ciContext?.createCGImage(compositedImage, from: CGRect(origin: .zero, size: compositedImage.extent.size)) { - let image = UIImage(cgImage: cgImage) - let data = image.pngData() - try? data?.write(to: URL(fileURLWithPath: path)) - self.isFirst = false - } - } - + self.ciContext?.render(compositedImage, to: pixelBuffer) completion(pixelBuffer) } else { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift index 6928200359..0921a4703c 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift @@ -2,7 +2,7 @@ import Foundation import UIKit import CoreMedia import FFMpegBinding -import YuvConversion +import ImageDCT final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter { public static let registerFFMpegGlobals: Void = { @@ -10,9 +10,8 @@ final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter { return }() - var ffmpegWriter: FFMpegVideoWriter? + let ffmpegWriter = FFMpegVideoWriter() var pool: CVPixelBufferPool? - var secondPool: CVPixelBufferPool? func setup(configuration: MediaEditorVideoExport.Configuration, outputPath: String) { let _ = MediaEditorVideoFFMpegWriter.registerFFMpegGlobals @@ -26,8 +25,7 @@ final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter { let pixelBufferOptions: [String: Any] = [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA as NSNumber, kCVPixelBufferWidthKey as String: UInt32(width), - kCVPixelBufferHeightKey as String: UInt32(height)//, -// kCVPixelBufferIOSurfacePropertiesKey as String: [:] as NSDictionary + kCVPixelBufferHeightKey as String: UInt32(height) ] var pool: CVPixelBufferPool? @@ -38,25 +36,7 @@ final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter { } self.pool = pool - let secondPixelBufferOptions: [String: Any] = [ - kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8VideoRange_8A_TriPlanar as NSNumber, - kCVPixelBufferWidthKey as String: UInt32(width), - kCVPixelBufferHeightKey as String: UInt32(height)//, -// kCVPixelBufferIOSurfacePropertiesKey as String: [:] as NSDictionary - ] - - var secondPool: CVPixelBufferPool? - CVPixelBufferPoolCreate(nil, bufferOptions as CFDictionary, secondPixelBufferOptions as CFDictionary, &secondPool) - guard let secondPool else { - self.status = .failed - return - } - self.secondPool = secondPool - - let ffmpegWriter = FFMpegVideoWriter() - self.ffmpegWriter = ffmpegWriter - - if !ffmpegWriter.setup(withOutputPath: outputPath, width: width, height: height) { + if !self.ffmpegWriter.setup(withOutputPath: outputPath, width: width, height: height, bitrate: 200 * 1000, framerate: 30) { self.status = .failed } } @@ -83,10 +63,7 @@ final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter { } func finishWriting(completion: @escaping () -> Void) { - guard let ffmpegWriter = self.ffmpegWriter else { - return - } - ffmpegWriter.finalizeVideo() + self.ffmpegWriter.finalizeVideo() self.status = .completed completion() } @@ -113,75 +90,31 @@ final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter { return false } - var isFirst = true func appendPixelBuffer(_ buffer: CVPixelBuffer, at time: CMTime) -> Bool { - guard let ffmpegWriter = self.ffmpegWriter, let secondPool = self.secondPool else { - return false - } - let width = Int32(CVPixelBufferGetWidth(buffer)) let height = Int32(CVPixelBufferGetHeight(buffer)) let bytesPerRow = Int32(CVPixelBufferGetBytesPerRow(buffer)) - var convertedBuffer: CVPixelBuffer? - CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, secondPool, &convertedBuffer) - guard let convertedBuffer else { - return false - } + let frame = FFMpegAVFrame(pixelFormat: .YUVA, width: width, height: height) - CVPixelBufferLockBaseAddress(buffer, CVPixelBufferLockFlags(rawValue: 0)) + CVPixelBufferLockBaseAddress(buffer, CVPixelBufferLockFlags.readOnly) let src = CVPixelBufferGetBaseAddress(buffer) - CVPixelBufferLockBaseAddress(convertedBuffer, CVPixelBufferLockFlags(rawValue: 0)) - let dst = CVPixelBufferGetBaseAddress(convertedBuffer) - - encodeRGBAToYUVA(dst, src, width, height, bytesPerRow, false, false) - - CVPixelBufferUnlockBaseAddress(convertedBuffer, CVPixelBufferLockFlags(rawValue: 0)) - CVPixelBufferUnlockBaseAddress(buffer, CVPixelBufferLockFlags(rawValue: 0)) + splitRGBAIntoYUVAPlanes( + src, + frame.data[0], + frame.data[1], + frame.data[2], + frame.data[3], + width, + height, + bytesPerRow, + true + ) + + CVPixelBufferUnlockBaseAddress(buffer, CVPixelBufferLockFlags.readOnly) - if self.isFirst { - let path = NSTemporaryDirectory() + "test.png" - let image = self.imageFromCVPixelBuffer(convertedBuffer, orientation: .up) - let data = image?.pngData() - try? data?.write(to: URL(fileURLWithPath: path)) - self.isFirst = false - } - - return ffmpegWriter.encodeFrame(convertedBuffer) - } - - func imageFromCVPixelBuffer(_ pixelBuffer: CVPixelBuffer, orientation: UIImage.Orientation) -> UIImage? { - CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly) - - let width = CVPixelBufferGetWidth(pixelBuffer) - let height = CVPixelBufferGetHeight(pixelBuffer) - let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer) - let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer) - - let colorSpace = CGColorSpaceCreateDeviceRGB() - - guard let context = CGContext( - data: baseAddress, - width: width, - height: height, - bitsPerComponent: 8, - bytesPerRow: bytesPerRow, - space: colorSpace, - bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue - ) else { - CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) - return nil - } - - guard let cgImage = context.makeImage() else { - CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) - return nil - } - - CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) - - return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation) + return self.ffmpegWriter.encode(frame) } func markVideoAsFinished() { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 346339683d..b8d9df7a03 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -5875,14 +5875,25 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate ), sourceView: { [weak self] in if let self { - let size = CGSize(width: self.view.frame.width, height: self.view.frame.width) - return (self.view, CGRect(origin: CGPoint(x: (self.view.frame.width - size.width) / 2.0, y: (self.view.frame.height - size.height) / 2.0), size: size)) + let previewContainerFrame = self.node.previewContainerView.frame + let size = CGSize(width: previewContainerFrame.width, height: previewContainerFrame.width) + return (self.view, CGRect(origin: CGPoint(x: previewContainerFrame.midX - size.width / 2.0, y: previewContainerFrame.midY - size.height / 2.0), size: size)) } else { return nil } }, activateImmediately: true ) + peekController.appeared = { [weak self] in + if let self { + self.node.previewView.alpha = 0.0 + } + } + peekController.disappeared = { [weak self] in + if let self { + self.node.previewView.alpha = 1.0 + } + } self.present(peekController, in: .window(.root)) } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift index aca3995de1..7b01557735 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift @@ -49,6 +49,9 @@ private final class StickerPackListContextItemNode: ASDisplayNode, ContextMenuCu var i = 0 for (pack, topItem) in item.packs { + if pack.flags.contains(.isEmoji) { + continue + } let thumbSize = CGSize(width: 24.0, height: 24.0) let thumbnailResource = pack.thumbnail?.resource ?? topItem?.file.resource let thumbnailIconSource: ContextMenuActionItemIconSource? diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 4b35bbd191..e575f46b62 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -483,6 +483,7 @@ private enum PeerInfoContextSubject { case link(customLink: String?) case businessHours(String) case businessLocation(String) + case birthday } private enum PeerInfoSettingsSection { @@ -1212,7 +1213,11 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese if today.day == Int(birthday.day) && today.month == Int(birthday.month) { hasBirthdayToday = true } - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 400, context: context, label: hasBirthdayToday ? presentationData.strings.UserInfo_BirthdayToday : presentationData.strings.UserInfo_Birthday, text: stringForCompactBirthday(birthday, strings: presentationData.strings, showAge: true), textColor: .primary, leftIcon: hasBirthdayToday ? .birthday : nil, icon: hasBirthdayToday ? .premiumGift : nil, action: nil, longTapAction: nil, iconAction: { + items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 400, context: context, label: hasBirthdayToday ? presentationData.strings.UserInfo_BirthdayToday : presentationData.strings.UserInfo_Birthday, text: stringForCompactBirthday(birthday, strings: presentationData.strings, showAge: true), textColor: .primary, leftIcon: hasBirthdayToday ? .birthday : nil, icon: hasBirthdayToday ? .premiumGift : nil, action: hasBirthdayToday ? { _, _ in + interaction.openPremiumGift() + } : nil, longTapAction: { sourceNode in + interaction.openPeerInfoContextMenu(.birthday, sourceNode, nil) + }, iconAction: { interaction.openPremiumGift() }, contextAction: nil, requestLayout: { })) @@ -7920,6 +7925,29 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } let context = self.context switch subject { + case .birthday: + if let cachedData = data.cachedData as? CachedUserData, let birthday = cachedData.birthday { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let text = stringForCompactBirthday(birthday, strings: presentationData.strings) + + let actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in + UIPasteboard.general.string = text + + self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })] + let contextMenuController = makeContextMenuController(actions: actions) + controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in + if let controller = self?.controller, let sourceNode = sourceNode { + var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) + if let sourceRect = sourceRect { + rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) + } + return (sourceNode, rect, controller.displayNode, controller.view.bounds) + } else { + return nil + } + })) + } case .bio: var text: String? if let cachedData = data.cachedData as? CachedUserData { diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Business/Links.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Business/Links.imageset/Contents.json new file mode 100644 index 0000000000..d9849a9db2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Business/Links.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "linktochat_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Business/Links.imageset/linktochat_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Business/Links.imageset/linktochat_30.pdf new file mode 100644 index 0000000000..1722b09223 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Business/Links.imageset/linktochat_30.pdf @@ -0,0 +1,158 @@ +%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 4.334961 4.285645 cm +0.000000 0.000000 0.000000 scn +10.665000 20.049395 m +5.448359 20.049395 1.330000 16.218761 1.330000 11.623485 c +1.330000 8.994537 2.547439 6.838058 4.671453 5.283018 c +4.906846 5.110682 5.034761 4.842655 5.104862 4.646481 c +5.185174 4.421730 5.236406 4.157593 5.250420 3.876801 c +5.278440 3.315346 5.159962 2.626087 4.745953 1.991434 c +4.593297 1.757420 4.430534 1.547665 4.277983 1.366190 c +4.730005 1.417858 5.215943 1.538151 5.606034 1.756918 c +6.305136 2.148983 6.750002 2.537632 7.070339 2.831562 c +7.096000 2.855108 7.122358 2.879496 7.149155 2.904289 c +7.275768 3.021437 7.412188 3.147655 7.531287 3.237131 c +7.653394 3.328869 7.961880 3.547728 8.359556 3.456514 c +9.096143 3.287567 9.868434 3.197573 10.665000 3.197573 c +15.881641 3.197573 20.000000 7.028207 20.000000 11.623485 c +20.000000 11.990754 20.297731 12.288485 20.665001 12.288485 c +21.032270 12.288485 21.330002 11.990754 21.330002 11.623485 c +21.330002 6.177219 16.494055 1.867573 10.665000 1.867573 c +9.838774 1.867573 9.033659 1.953581 8.260066 2.116642 c +8.213211 2.076147 8.155871 2.023260 8.077589 1.951059 c +8.045362 1.921337 8.009587 1.888342 7.969534 1.851589 c +7.613387 1.524799 7.082547 1.060091 6.256593 0.596888 c +5.612106 0.235451 4.874246 0.083754 4.288342 0.030340 c +3.990697 0.003206 3.717034 0.000048 3.493284 0.012119 c +3.381668 0.018141 3.276548 0.028267 3.183294 0.042713 c +3.102665 0.055204 2.987490 0.077129 2.879746 0.122322 c +2.757599 0.173557 2.561313 0.283442 2.445159 0.514097 c +2.320866 0.760912 2.361047 0.997538 2.411787 1.141516 c +2.459289 1.276306 2.533860 1.385971 2.586927 1.457314 c +2.644485 1.534695 2.710716 1.611164 2.772628 1.680155 c +2.823253 1.736568 2.875850 1.793650 2.929860 1.852264 c +3.152941 2.094366 3.400128 2.362629 3.632015 2.718100 c +3.866793 3.078001 3.938666 3.478035 3.922073 3.810507 c +3.913780 3.976679 3.883965 4.110666 3.852423 4.198935 c +3.844490 4.221134 3.837532 4.237669 3.832081 4.249437 c +1.436229 6.025094 0.000000 8.548334 0.000000 11.623485 c +0.000000 17.069750 4.835946 21.379395 10.665000 21.379395 c +11.032269 21.379395 11.330000 21.081663 11.330000 20.714394 c +11.330000 20.347126 11.032269 20.049395 10.665000 20.049395 c +h +3.819499 4.273321 m +3.819511 4.273344 3.819916 4.272768 3.820682 4.271513 c +3.819868 4.272675 3.819487 4.273298 3.819499 4.273321 c +h +8.362708 2.195467 m +8.362536 2.194937 8.357435 2.191450 8.347900 2.186579 c +8.358110 2.193563 8.362881 2.195997 8.362708 2.195467 c +h +3.641995 0.646202 m +3.641986 0.646227 3.642429 0.646975 3.643422 0.648491 c +3.642514 0.646952 3.642005 0.646177 3.641995 0.646202 c +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 12.278564 11.809570 cm +0.000000 0.000000 0.000000 scn +8.503049 13.383927 m +9.624062 14.504941 11.441583 14.504941 12.562596 13.383927 c +13.683610 12.262915 13.683610 10.445393 12.562596 9.324379 c +10.895930 7.657713 l +9.774917 6.536699 7.957396 6.536699 6.836382 7.657713 c +6.687177 7.806918 6.558270 7.967865 6.449299 8.137315 c +6.250646 8.446222 5.839186 8.535600 5.530279 8.336946 c +5.221373 8.138292 5.131994 7.726833 5.330647 7.417926 c +5.490635 7.169146 5.679183 6.934008 5.895930 6.717260 c +7.536341 5.076850 10.195971 5.076850 11.836382 6.717260 c +13.503049 8.383927 l +15.143458 10.024338 15.143458 12.683969 13.503049 14.324379 c +11.862638 15.964790 9.203007 15.964790 7.562596 14.324379 c +5.895930 12.657713 l +5.636231 12.398014 5.636231 11.976959 5.895930 11.717260 c +6.155629 11.457562 6.576684 11.457562 6.836382 11.717260 c +8.503049 13.383927 l +h +6.230308 2.991029 m +5.109295 1.870015 3.291773 1.870015 2.170760 2.991029 c +1.049747 4.112042 1.049747 5.929564 2.170760 7.050577 c +3.837426 8.717245 l +4.958440 9.838259 6.775961 9.838259 7.896974 8.717245 c +8.046180 8.568039 8.175087 8.407093 8.284058 8.237642 c +8.482711 7.928736 8.894170 7.839358 9.203077 8.038012 c +9.511984 8.236666 9.601362 8.648125 9.402708 8.957031 c +9.242722 9.205812 9.054173 9.440950 8.837426 9.657698 c +7.197016 11.298107 4.537385 11.298107 2.896974 9.657698 c +1.230308 7.991030 l +-0.410103 6.350618 -0.410103 3.690988 1.230308 2.050577 c +2.870718 0.410166 5.530349 0.410166 7.170760 2.050577 c +8.837426 3.717243 l +9.097125 3.976942 9.097125 4.397997 8.837426 4.657696 c +8.577727 4.917395 8.156672 4.917395 7.896974 4.657696 c +6.230308 2.991029 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 4349 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.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 +0000004439 00000 n +0000004462 00000 n +0000004635 00000 n +0000004709 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +4768 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 018aebdd58..d094b5b42e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -36,7 +36,9 @@ import ChatControllerInteraction import ChatMessageItemCommon import ChatMessageItemView import ChatMessageBubbleItemNode - +import AdsInfoScreen +import AdsReportScreen + private struct MessageContextMenuData { let starStatus: Bool? let canReply: Bool @@ -475,61 +477,27 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState var actions: [ContextMenuItem] = [] - if adAttribute.sponsorInfo != nil || adAttribute.additionalInfo != nil { - actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfo, textColor: .primary, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.actionSheet.primaryTextColor) - }, iconSource: nil, action: { c, _ in - var subItems: [ContextMenuItem] = [] - - subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, textColor: .primary, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) - }, iconSource: nil, iconPosition: .left, action: { c, _ in - c.popItems() - }))) - - subItems.append(.separator) - - if let sponsorInfo = adAttribute.sponsorInfo { - subItems.append(.action(ContextMenuActionItem(text: sponsorInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in - return nil - }, iconSource: nil, action: { [weak controllerInteraction] c, _ in - c.dismiss(completion: { - UIPasteboard.general.string = sponsorInfo - - let content: UndoOverlayContent = .copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied) - controllerInteraction?.displayUndo(content) - }) - }))) - } - if let additionalInfo = adAttribute.additionalInfo { - subItems.append(.action(ContextMenuActionItem(text: additionalInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in - return nil - }, iconSource: nil, action: { [weak controllerInteraction] c, _ in - c.dismiss(completion: { - UIPasteboard.general.string = additionalInfo - - let content: UndoOverlayContent = .copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied) - controllerInteraction?.displayUndo(content) - }) - }))) - } - - c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + if "".isEmpty { + //TODO:localize + + actions.append(.action(ContextMenuActionItem(text: "About This Ad", textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { _, f in + f(.dismissWithoutContent) + controllerInteraction.navigationController()?.pushViewController(AdsInfoScreen(context: context)) }))) - actions.append(.separator) - } - - actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Info, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) - }, iconSource: nil, action: { _, f in - f(.dismissWithoutContent) - controllerInteraction.navigationController()?.pushViewController(AdInfoScreen(context: context)) - }))) - - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) - if !chatPresentationInterfaceState.isPremium && !premiumConfiguration.isPremiumDisabled { - actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Hide, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + + actions.append(.action(ContextMenuActionItem(text: "Report Ad", textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { _, f in + f(.dismissWithoutContent) + controllerInteraction.navigationController()?.pushViewController(AdsReportScreen(context: context)) + }))) + + actions.append(.separator) + + actions.append(.action(ContextMenuActionItem(text: "Remove Ad", textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) }, iconSource: nil, action: { c, _ in c.dismiss(completion: { var replaceImpl: ((ViewController) -> Void)? @@ -543,61 +511,131 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState controllerInteraction.navigationController()?.pushViewController(controller) }) }))) - } - - actions.append(.separator) - - if chatPresentationInterfaceState.copyProtectionEnabled { } else { - actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopy, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - var messageEntities: [MessageTextEntity]? - var restrictedText: String? - for attribute in message.attributes { - if let attribute = attribute as? TextEntitiesMessageAttribute { - messageEntities = attribute.entities + if adAttribute.sponsorInfo != nil || adAttribute.additionalInfo != nil { + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfo, textColor: .primary, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { c, _ in + var subItems: [ContextMenuItem] = [] + + subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, textColor: .primary, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, iconPosition: .left, action: { c, _ in + c.popItems() + }))) + + subItems.append(.separator) + + if let sponsorInfo = adAttribute.sponsorInfo { + subItems.append(.action(ContextMenuActionItem(text: sponsorInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return nil + }, iconSource: nil, action: { [weak controllerInteraction] c, _ in + c.dismiss(completion: { + UIPasteboard.general.string = sponsorInfo + + let content: UndoOverlayContent = .copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied) + controllerInteraction?.displayUndo(content) + }) + }))) } - if let attribute = attribute as? RestrictedContentMessageAttribute { - restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? "" + if let additionalInfo = adAttribute.additionalInfo { + subItems.append(.action(ContextMenuActionItem(text: additionalInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return nil + }, iconSource: nil, action: { [weak controllerInteraction] c, _ in + c.dismiss(completion: { + UIPasteboard.general.string = additionalInfo + + let content: UndoOverlayContent = .copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied) + controllerInteraction?.displayUndo(content) + }) + }))) } - } - - if let restrictedText = restrictedText { - storeMessageTextInPasteboard(restrictedText, entities: nil) - } else { - if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled, - let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty { - storeMessageTextInPasteboard(translation.text, entities: translation.entities) + + c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + }))) + actions.append(.separator) + } + + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Info, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { _, f in + f(.dismissWithoutContent) + controllerInteraction.navigationController()?.pushViewController(AdInfoScreen(context: context)) + }))) + + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + if !chatPresentationInterfaceState.isPremium && !premiumConfiguration.isPremiumDisabled { + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Hide, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { c, _ in + c.dismiss(completion: { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumDemoScreen(context: context, subject: .noAds, action: { + let controller = PremiumIntroScreen(context: context, source: .ads) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + controllerInteraction.navigationController()?.pushViewController(controller) + }) + }))) + } + + actions.append(.separator) + + if chatPresentationInterfaceState.copyProtectionEnabled { + } else { + actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopy, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + var messageEntities: [MessageTextEntity]? + var restrictedText: String? + for attribute in message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + messageEntities = attribute.entities + } + if let attribute = attribute as? RestrictedContentMessageAttribute { + restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? "" + } + } + + if let restrictedText = restrictedText { + storeMessageTextInPasteboard(restrictedText, entities: nil) } else { - storeMessageTextInPasteboard(message.text, entities: messageEntities) + if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled, + let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty { + storeMessageTextInPasteboard(translation.text, entities: translation.entities) + } else { + storeMessageTextInPasteboard(message.text, entities: messageEntities) + } } - } - - Queue.mainQueue().after(0.2, { - let content: UndoOverlayContent = .copy(text: chatPresentationInterfaceState.strings.Conversation_MessageCopied) - controllerInteraction.displayUndo(content) - }) - - f(.default) - }))) - } - - if let author = message.author, let addressName = author.addressName { - let link = "https://t.me/\(addressName)" - actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - UIPasteboard.general.string = link - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - Queue.mainQueue().after(0.2, { - controllerInteraction.displayUndo(.linkCopied(text: presentationData.strings.Conversation_LinkCopied)) - }) - - f(.default) - }))) + + Queue.mainQueue().after(0.2, { + let content: UndoOverlayContent = .copy(text: chatPresentationInterfaceState.strings.Conversation_MessageCopied) + controllerInteraction.displayUndo(content) + }) + + f(.default) + }))) + } + + if let author = message.author, let addressName = author.addressName { + let link = "https://t.me/\(addressName)" + actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + UIPasteboard.general.string = link + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + Queue.mainQueue().after(0.2, { + controllerInteraction.displayUndo(.linkCopied(text: presentationData.strings.Conversation_LinkCopied)) + }) + + f(.default) + }))) + } } return .single(ContextController.Items(content: .list(actions))) From b8382386c20616e1e8d08dd826fd6d198b683dfa Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 24 Mar 2024 17:58:23 +0400 Subject: [PATCH 2/4] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../Sources/AccountContext.swift | 1 + .../Sources/Node/ChatListNode.swift | 4 +- ...nfoItem.swift => ChatListNoticeItem.swift} | 79 +++++++++---------- .../Sources/DrawingEntitiesView.swift | 42 ---------- .../Sources/PremiumCoinComponent.swift | 3 +- .../Sources/SettingsController.swift | 24 ++++++ .../Sources/MediaEditorScreen.swift | 63 ++++++++++++--- .../ListItems/PeerInfoScreenCommentItem.swift | 15 +++- .../Sources/PeerInfoScreen.swift | 21 ++++- .../Sources/SharedAccountContext.swift | 4 + 11 files changed, 159 insertions(+), 99 deletions(-) rename submodules/ChatListUI/Sources/Node/{ChatListStorageInfoItem.swift => ChatListNoticeItem.swift} (92%) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 16c85b22b9..c170aa411b 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11669,3 +11669,5 @@ Sorry for the inconvenience."; "Business.Links" = "Links to Chat"; "Business.LinksInfo" = "Create links that start a chat with you, suggesting the first message."; + +"Settings.About.PrivacyHelp" = "You can add a few lines about yourself. Choose who can see your bio in [Settings]()."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 4d4967d035..5a2665746f 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -952,6 +952,7 @@ public protocol SharedAccountContext: AnyObject { func makeCreateGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String?, mode: CreateGroupMode, completion: ((PeerId, @escaping () -> Void) -> Void)?) -> ViewController func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController func makePrivacyAndSecurityController(context: AccountContext) -> ViewController + func makeBioPrivacyController(context: AccountContext, settings: Promise, present: @escaping (ViewController) -> Void) func makeBirthdayPrivacyController(context: AccountContext, settings: Promise, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void) func makeSetupTwoFactorAuthController(context: AccountContext) -> ViewController func makeStorageManagementController(context: AccountContext) -> ViewController diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index e38ed319c2..8bc2745f76 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -724,7 +724,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL hideChatListContacts(context: context) } : nil), directionHint: entry.directionHint) case let .Notice(presentationData, notice): - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in switch action { case .activate: switch notice { @@ -1060,7 +1060,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL hideChatListContacts(context: context) } : nil), directionHint: entry.directionHint) case let .Notice(presentationData, notice): - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in switch action { case .activate: switch notice { diff --git a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift similarity index 92% rename from submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift rename to submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift index c4e6bf4b68..2edde3089f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift @@ -12,7 +12,7 @@ import Markdown import AccountContext import MergedAvatarsNode -class ChatListStorageInfoItem: ListViewItem { +class ChatListNoticeItem: ListViewItem { enum Action { case activate case hide @@ -43,7 +43,7 @@ class ChatListStorageInfoItem: ListViewItem { func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { async { - let node = ChatListStorageInfoItemNode() + let node = ChatListNoticeItemNode() let (nodeLayout, apply) = node.asyncLayout()(self, params, false) @@ -62,8 +62,8 @@ class ChatListStorageInfoItem: ListViewItem { func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { Queue.mainQueue().async { - assert(node() is ChatListStorageInfoItemNode) - if let nodeValue = node() as? ChatListStorageInfoItemNode { + assert(node() is ChatListNoticeItemNode) + if let nodeValue = node() as? ChatListNoticeItemNode { let layout = nodeValue.asyncLayout() async { @@ -84,7 +84,7 @@ private let separatorHeight = 1.0 / UIScreen.main.scale private let titleFont = Font.semibold(15.0) private let textFont = Font.regular(15.0) -class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode { +final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode { private let contentContainer: ASDisplayNode private let titleNode: TextNode private let textNode: TextNode @@ -100,7 +100,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode { private var okButton: HighlightableButtonNode? private var cancelButton: HighlightableButtonNode? - private var item: ChatListStorageInfoItem? + private var item: ChatListNoticeItem? override var apparentHeight: CGFloat { didSet { @@ -145,11 +145,11 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode { override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { let layout = self.asyncLayout() - let (_, apply) = layout(item as! ChatListStorageInfoItem, params, nextItem == nil) + let (_, apply) = layout(item as! ChatListNoticeItem, params, nextItem == nil) apply() } - func asyncLayout() -> (_ item: ChatListStorageInfoItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) { + func asyncLayout() -> (_ item: ChatListNoticeItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) { let previousItem = self.item let makeTitleLayout = TextNode.asyncLayout(self.titleNode) @@ -321,9 +321,19 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode { if let image = strongSelf.arrowNode.image { strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - image.size.width + 8.0, y: floor((layout.size.height - image.size.height) / 2.0)), size: image.size) } + + var hasCloseButton = false + if case .xmasPremiumGift = item.notice { + hasCloseButton = true + } else if case .setupBirthday = item.notice { + hasCloseButton = true + } else if case .birthdayPremiumGift = item.notice { + hasCloseButton = true + } if let okButtonLayout, let cancelButtonLayout { strongSelf.arrowNode.isHidden = true + strongSelf.closeButton?.isHidden = true let okButton: HighlightableButtonNode if let current = strongSelf.okButton { @@ -372,7 +382,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode { okButtonText.frame = CGRect(origin: CGPoint(x: floor((okButtonFrame.width - okButtonLayout.0.size.width) * 0.5), y: floor((okButtonFrame.height - okButtonLayout.0.size.height) * 0.5)), size: okButtonLayout.0.size) cancelButtonText.frame = CGRect(origin: CGPoint(x: floor((cancelButtonFrame.width - cancelButtonLayout.0.size.width) * 0.5), y: floor((cancelButtonFrame.height - cancelButtonLayout.0.size.height) * 0.5)), size: cancelButtonLayout.0.size) } else { - strongSelf.arrowNode.isHidden = false + strongSelf.arrowNode.isHidden = hasCloseButton if let okButton = strongSelf.okButton { strongSelf.okButton = nil @@ -390,40 +400,29 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode { strongSelf.cancelButtonText = nil cancelButtonText.removeFromSupernode() } - } - - let arrowIsHidden = strongSelf.arrowNode.isHidden - var hasCloseButton = false - if case .xmasPremiumGift = item.notice { - hasCloseButton = true - } else if case .setupBirthday = item.notice { - hasCloseButton = true - } else if case .birthdayPremiumGift = item.notice { - hasCloseButton = true - } - - if hasCloseButton { - strongSelf.arrowNode.isHidden = true - let closeButton: HighlightableButtonNode - if let current = strongSelf.closeButton { - closeButton = current + if hasCloseButton { + let closeButton: HighlightableButtonNode + if let current = strongSelf.closeButton { + closeButton = current + } else { + closeButton = HighlightableButtonNode() + closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) + closeButton.addTarget(self, action: #selector(strongSelf.closePressed), forControlEvents: [.touchUpInside]) + strongSelf.contentContainer.addSubnode(closeButton) + strongSelf.closeButton = closeButton + } + + if themeUpdated { + closeButton.setImage(PresentationResourcesItemList.itemListCloseIconImage(item.theme), for: .normal) + } + + let closeButtonSize = closeButton.measure(CGSize(width: 100.0, height: 100.0)) + closeButton.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - closeButtonSize.width, y: floor((layout.size.height - closeButtonSize.height) / 2.0)), size: closeButtonSize) } else { - closeButton = HighlightableButtonNode() - closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) - closeButton.addTarget(self, action: #selector(strongSelf.closePressed), forControlEvents: [.touchUpInside]) - strongSelf.contentContainer.addSubnode(closeButton) - strongSelf.closeButton = closeButton + strongSelf.closeButton?.removeFromSupernode() + strongSelf.closeButton = nil } - - if themeUpdated { - closeButton.setImage(PresentationResourcesItemList.itemListCloseIconImage(item.theme), for: .normal) - } - - let closeButtonSize = closeButton.measure(CGSize(width: 100.0, height: 100.0)) - closeButton.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - closeButtonSize.width, y: floor((layout.size.height - closeButtonSize.height) / 2.0)), size: closeButtonSize) - } else { - strongSelf.arrowNode.isHidden = arrowIsHidden } strongSelf.contentSize = layout.contentSize diff --git a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift index 7d50682a93..8207fec372 100644 --- a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift +++ b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift @@ -97,9 +97,6 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { private let angleLayer = SimpleShapeLayer() private let bin = ComponentView() - private let stickerOverlayLayer = SimpleShapeLayer() - private let stickerFrameLayer = SimpleShapeLayer() - public var onInteractionUpdated: (Bool) -> Void = { _ in } public var edgePreviewUpdated: (Bool) -> Void = { _ in } @@ -145,13 +142,6 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { self.angleLayer.opacity = 0.0 self.angleLayer.lineDashPattern = [12, 12] as [NSNumber] - self.stickerOverlayLayer.fillColor = UIColor(rgb: 0x000000, alpha: 0.7).cgColor - - self.stickerFrameLayer.fillColor = UIColor.clear.cgColor - self.stickerFrameLayer.strokeColor = UIColor(rgb: 0xffffff, alpha: 0.55).cgColor - self.stickerFrameLayer.lineDashPattern = [24, 24] as [NSNumber] - self.stickerFrameLayer.lineCap = .round - self.addSubview(self.topEdgeView) self.addSubview(self.leftEdgeView) self.addSubview(self.rightEdgeView) @@ -160,25 +150,12 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { self.addSubview(self.xAxisView) self.addSubview(self.yAxisView) self.layer.addSublayer(self.angleLayer) - - if isStickerEditor { - self.layer.addSublayer(self.stickerOverlayLayer) - self.layer.addSublayer(self.stickerFrameLayer) - } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func addSubview(_ view: UIView) { - super.addSubview(view) - if self.stickerOverlayLayer.superlayer != nil, view is DrawingEntityView { - self.layer.addSublayer(self.stickerOverlayLayer) - self.layer.addSublayer(self.stickerFrameLayer) - } - } - public override func layoutSubviews() { super.layoutSubviews() @@ -214,25 +191,6 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { self.angleLayer.path = anglePath self.angleLayer.lineWidth = width self.angleLayer.bounds = CGRect(origin: .zero, size: CGSize(width: 3000.0, height: width)) - - let frameWidth = floor(self.bounds.width * 0.97) - let frameRect = CGRect(origin: CGPoint(x: floor((self.bounds.width - frameWidth) / 2.0), y: floor((self.bounds.height - frameWidth) / 2.0)), size: CGSize(width: frameWidth, height: frameWidth)) - - self.stickerOverlayLayer.frame = self.bounds - - let overlayOuterRect = UIBezierPath(rect: self.bounds) - let overlayInnerRect = UIBezierPath(cgPath: CGPath(roundedRect: frameRect, cornerWidth: frameWidth / 8.0, cornerHeight: frameWidth / 8.0, transform: nil)) - let overlayLineWidth: CGFloat = 2.0 * 2.2 - - overlayOuterRect.append(overlayInnerRect) - overlayOuterRect.usesEvenOddFillRule = true - - self.stickerOverlayLayer.path = overlayOuterRect.cgPath - self.stickerOverlayLayer.fillRule = .evenOdd - - self.stickerFrameLayer.frame = self.bounds - self.stickerFrameLayer.lineWidth = overlayLineWidth - self.stickerFrameLayer.path = CGPath(roundedRect: frameRect.insetBy(dx: -overlayLineWidth / 2.0, dy: -overlayLineWidth / 2.0), cornerWidth: frameWidth / 8.0 * 1.02, cornerHeight: frameWidth / 8.0 * 1.02, transform: nil) } public var entities: [DrawingEntity] { diff --git a/submodules/PremiumUI/Sources/PremiumCoinComponent.swift b/submodules/PremiumUI/Sources/PremiumCoinComponent.swift index f5681e1d6c..4fa5b3f522 100644 --- a/submodules/PremiumUI/Sources/PremiumCoinComponent.swift +++ b/submodules/PremiumUI/Sources/PremiumCoinComponent.swift @@ -345,7 +345,8 @@ class PremiumCoinComponent: Component { return } - if #available(iOS 17.0, *), let material = node.geometry?.materials.first { +// if #available(iOS 17.0, *), let material = node.geometry?.materials.first { + if let material = node.geometry?.materials.first { material.metalness.intensity = 0.3 } diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index 343112ac50..a14d5e81af 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -16,6 +16,30 @@ public func makePrivacyAndSecurityController(context: AccountContext) -> ViewCon return privacyAndSecurityController(context: context, focusOnItemTag: PrivacyAndSecurityEntryTag.autoArchive) } +public func makeBioPrivacyController(context: AccountContext, settings: Promise, present: @escaping (ViewController) -> Void) { + let signal = settings.get() + |> take(1) + |> deliverOnMainQueue + + let _ = signal.startStandalone(next: { info in + if let info = info { + present(selectivePrivacySettingsController(context: context, kind: .bio, current: info.bio, updated: { updated, _, _, _ in + let applySetting: Signal = settings.get() + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { value -> Signal in + if let value = value { + settings.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: updated, birthday: value.birthday, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + } + return .complete() + } + let _ = applySetting.startStandalone() + })) + } + }) +} + public func makeBirthdayPrivacyController(context: AccountContext, settings: Promise, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void) { let signal = settings.get() |> take(1) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index b8d9df7a03..faa08988d2 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -2146,7 +2146,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate private let gradientView: UIImageView private var gradientColorsDisposable: Disposable? - private let stickerTransparentView: UIImageView + private var stickerBackgroundView: UIImageView? + private var stickerOverlayLayer: SimpleShapeLayer? + private var stickerFrameLayer: SimpleShapeLayer? fileprivate let entitiesContainerView: UIView let entitiesView: DrawingEntitiesView @@ -2216,9 +2218,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } self.gradientView = UIImageView() - self.stickerTransparentView = UIImageView() - self.stickerTransparentView.clipsToBounds = true - + var isStickerEditor = false if case .stickerEditor = controller.mode { isStickerEditor = true @@ -2255,7 +2255,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if case .stickerEditor = controller.mode { let rowsCount = 40 - self.stickerTransparentView.image = generateImage(CGSize(width: rowsCount, height: rowsCount), opaque: true, scale: 1.0, rotatedContext: { size, context in + let stickerBackgroundView = UIImageView() + stickerBackgroundView.clipsToBounds = true + stickerBackgroundView.image = generateImage(CGSize(width: rowsCount, height: rowsCount), opaque: true, scale: 1.0, rotatedContext: { size, context in context.setFillColor(UIColor.black.cgColor) context.fill(CGRect(origin: .zero, size: size)) context.setFillColor(UIColor(rgb: 0x2b2b2d).cgColor) @@ -2269,10 +2271,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } context.fillPath() }) - self.stickerTransparentView.layer.magnificationFilter = .nearest - self.stickerTransparentView.layer.shouldRasterize = true - self.stickerTransparentView.layer.rasterizationScale = UIScreenScale - self.previewContainerView.addSubview(self.stickerTransparentView) + stickerBackgroundView.layer.magnificationFilter = .nearest + stickerBackgroundView.layer.shouldRasterize = true + stickerBackgroundView.layer.rasterizationScale = UIScreenScale + self.stickerBackgroundView = stickerBackgroundView + self.previewContainerView.addSubview(stickerBackgroundView) } else { self.previewContainerView.addSubview(self.gradientView) } @@ -2281,6 +2284,24 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.previewContainerView.addSubview(self.entitiesContainerView) self.entitiesContainerView.addSubview(self.entitiesView) self.entitiesView.addSubview(self.drawingView) + + if case .stickerEditor = controller.mode { + let stickerOverlayLayer = SimpleShapeLayer() + stickerOverlayLayer.fillColor = UIColor(rgb: 0x000000, alpha: 0.7).cgColor + stickerOverlayLayer.fillRule = .evenOdd + self.stickerOverlayLayer = stickerOverlayLayer + self.previewContainerView.layer.addSublayer(stickerOverlayLayer) + + let stickerFrameLayer = SimpleShapeLayer() + stickerFrameLayer.fillColor = UIColor.clear.cgColor + stickerFrameLayer.strokeColor = UIColor(rgb: 0xffffff, alpha: 0.55).cgColor + stickerFrameLayer.lineDashPattern = [12, 12] as [NSNumber] + stickerFrameLayer.lineCap = .round + + self.stickerFrameLayer = stickerFrameLayer + self.previewContainerView.layer.addSublayer(stickerFrameLayer) + } + self.previewContainerView.addSubview(self.selectionContainerView) self.subjectDisposable = ( @@ -4408,8 +4429,25 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate transition.setFrame(view: self.selectionContainerView, frame: CGRect(origin: .zero, size: previewFrame.size)) let stickerFrameWidth = floor(previewSize.width * 0.97) - transition.setFrame(view: self.stickerTransparentView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((previewSize.width - stickerFrameWidth) / 2.0), y: floorToScreenPixels((previewSize.height - stickerFrameWidth) / 2.0)), size: CGSize(width: stickerFrameWidth, height: stickerFrameWidth))) - self.stickerTransparentView.layer.cornerRadius = stickerFrameWidth / 8.0 + if let stickerBackgroundView = self.stickerBackgroundView, let stickerOverlayLayer = self.stickerOverlayLayer, let stickerFrameLayer = self.stickerFrameLayer { + stickerOverlayLayer.frame = CGRect(origin: .zero, size: previewSize) + + let stickerFrameRect = CGRect(origin: CGPoint(x: floor((previewSize.width - stickerFrameWidth) / 2.0), y: floor((previewSize.height - stickerFrameWidth) / 2.0)), size: CGSize(width: stickerFrameWidth, height: stickerFrameWidth)) + + let overlayOuterRect = UIBezierPath(rect: CGRect(origin: .zero, size: previewSize)) + let overlayInnerRect = UIBezierPath(cgPath: CGPath(roundedRect: stickerFrameRect, cornerWidth: stickerFrameWidth / 8.0, cornerHeight: stickerFrameWidth / 8.0, transform: nil)) + let overlayLineWidth: CGFloat = 2.0 - UIScreenPixel + overlayOuterRect.append(overlayInnerRect) + overlayOuterRect.usesEvenOddFillRule = true + stickerOverlayLayer.path = overlayOuterRect.cgPath + + stickerFrameLayer.frame = stickerOverlayLayer.frame + stickerFrameLayer.lineWidth = overlayLineWidth + stickerFrameLayer.path = CGPath(roundedRect: stickerFrameRect.insetBy(dx: -overlayLineWidth / 2.0, dy: -overlayLineWidth / 2.0), cornerWidth: stickerFrameWidth / 8.0 * 1.02, cornerHeight: stickerFrameWidth / 8.0 * 1.02, transform: nil) + + transition.setFrame(view: stickerBackgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((previewSize.width - stickerFrameWidth) / 2.0), y: floorToScreenPixels((previewSize.height - stickerFrameWidth) / 2.0)), size: CGSize(width: stickerFrameWidth, height: stickerFrameWidth))) + stickerBackgroundView.layer.cornerRadius = stickerFrameWidth / 8.0 + } self.interaction?.containerLayoutUpdated(layout: layout, transition: transition) @@ -5886,11 +5924,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate ) peekController.appeared = { [weak self] in if let self { + self.node.entitiesView.alpha = 0.0 self.node.previewView.alpha = 0.0 } } peekController.disappeared = { [weak self] in if let self { + self.node.entitiesView.alpha = 1.0 self.node.previewView.alpha = 1.0 } } @@ -5905,6 +5945,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } private func presentCreateStickerPack(file: TelegramMediaFile, completion: @escaping () -> Void) { + //TODO:localize let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) var dismissImpl: (() -> Void)? diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift index eae761c6c2..6f5f7ad96b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift @@ -32,6 +32,8 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode { private var item: PeerInfoScreenCommentItem? private var presentationData: PresentationData? + private var chevronImage: UIImage? + override init() { self.textNode = ImmediateTextNode() self.textNode.displaysAsynchronously = false @@ -66,6 +68,7 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode { return 10.0 } + let themeUpdated = self.presentationData?.theme !== presentationData.theme self.item = item self.presentationData = presentationData @@ -79,8 +82,16 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode { let attributedText = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: presentationData.theme.list.itemAccentColor), linkAttribute: { contents in return (TelegramTextAttributes.URL, contents) - })) - + })).mutableCopy() as! NSMutableAttributedString + if let range = attributedText.string.range(of: ">") { + if themeUpdated || self.chevronImage == nil { + self.chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: presentationData.theme.list.itemAccentColor) + } + if let chevronImage = self.chevronImage { + attributedText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedText.string)) + } + } + self.textNode.attributedText = attributedText self.activateArea.accessibilityLabel = attributedText.string diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index bdd42569db..09b05e1cf4 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -587,6 +587,7 @@ private final class PeerInfoInteraction { let openEditing: () -> Void let updateBirthdate: (TelegramBirthday??) -> Void let updateIsEditingBirthdate: (Bool) -> Void + let openBioPrivacy: () -> Void let openBirthdatePrivacy: () -> Void let openPremiumGift: () -> Void let editingOpenPersonalChannel: () -> Void @@ -647,6 +648,7 @@ private final class PeerInfoInteraction { openEditing: @escaping () -> Void, updateBirthdate: @escaping (TelegramBirthday??) -> Void, updateIsEditingBirthdate: @escaping (Bool) -> Void, + openBioPrivacy: @escaping () -> Void, openBirthdatePrivacy: @escaping () -> Void, openPremiumGift: @escaping () -> Void, editingOpenPersonalChannel: @escaping () -> Void @@ -706,6 +708,7 @@ private final class PeerInfoInteraction { self.openEditing = openEditing self.updateBirthdate = updateBirthdate self.updateIsEditingBirthdate = updateIsEditingBirthdate + self.openBioPrivacy = openBioPrivacy self.openBirthdatePrivacy = openBirthdatePrivacy self.openPremiumGift = openPremiumGift self.editingOpenPersonalChannel = editingOpenPersonalChannel @@ -1030,7 +1033,9 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat }, action: { interaction.dismissInput() }, maxLength: Int(data.globalSettings?.userLimits.maxAboutLength ?? 70))) - items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: presentationData.strings.Settings_About_Help)) + items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: presentationData.strings.Settings_About_PrivacyHelp, linkAction: { _ in + interaction.openBioPrivacy() + })) } @@ -2683,6 +2688,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } }, + openBioPrivacy: { [weak self] in + if let self { + self.openBioPrivacy() + } + }, openBirthdatePrivacy: { [weak self] in if let self { self.openBirthdatePrivacy() @@ -7577,6 +7587,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }) } + private func openBioPrivacy() { + guard let _ = self.data?.globalSettings?.privacySettings else { + return + } + self.context.sharedContext.makeBioPrivacyController(context: self.context, settings: self.privacySettings, present: { [weak self] c in + self?.controller?.push(c) + }) + } + private func openBirthdatePrivacy() { guard let _ = self.data?.globalSettings?.privacySettings else { return diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 855cdfba53..1604a7bb3d 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1845,6 +1845,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func makePrivacyAndSecurityController(context: AccountContext) -> ViewController { return SettingsUI.makePrivacyAndSecurityController(context: context) } + + public func makeBioPrivacyController(context: AccountContext, settings: Promise, present: @escaping (ViewController) -> Void) { + SettingsUI.makeBioPrivacyController(context: context, settings: settings, present: present) + } public func makeBirthdayPrivacyController(context: AccountContext, settings: Promise, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void) { SettingsUI.makeBirthdayPrivacyController(context: context, settings: settings, openedFromBirthdayScreen: openedFromBirthdayScreen, present: present) From 75070e2aa77049758009c4d0264b051fedc9339c Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 24 Mar 2024 19:01:41 +0400 Subject: [PATCH 3/4] Various fixes --- .../Sources/InstantPageTextItem.swift | 4 ++-- .../PremiumUI/Resources/darkerTexture.jpg | Bin 9624 -> 9475 bytes .../PremiumUI/Resources/lighterTexture.jpg | Bin 9199 -> 9545 bytes 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift index 6fa1ee6ec3..c01951175b 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift @@ -875,8 +875,8 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo for line in textItem.lines { let lineFrame = frameForLine(line, boundingWidth: boundingWidth, alignment: alignment) for imageItem in line.imageItems { - if case let .image(image) = media[imageItem.id] { - let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: .image(image), url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false) + if let media = media[imageItem.id] { + let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: media, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false) additionalItems.append(item) if item.frame.minY < topInset { diff --git a/submodules/PremiumUI/Resources/darkerTexture.jpg b/submodules/PremiumUI/Resources/darkerTexture.jpg index 75d2fa9fa661a086727502e3dde35bd781432ed0..f7a4c4360c8ae0c544e98f228a5d009d7b89228f 100644 GIT binary patch delta 2351 zcmeH|T})GF7{_U8(Nb0o&_WmE+U}G>X`wAuph^OTBBzuJrF;~D@o`##2%;7x%aUR# zb0B8rv|!7qD@qjv8McmTW9Hh)LNR3JOA&Pg5;Du&qT8<5oia_9Wf$&Z*^STDo0IqX zJ^%kddER5nZ5N05m;GxY@AQOx8kQvB@hRLCK~jo<({ue48nfYGqG)JMH(=pzw2WXu z0LHJE7&qfwu{)cq|hrTH_1M|sHr#F!^baChswUsH203vdf+mwrELgN;phTFS8_Rj~z zbq9^X`4&>T+$;bSw>4^t5tR^DHh^H~qqLqB35*ySABX`t=N@Q1g6=E5z#uQ!34=gMuzd>H;7Qs>kWYHzMoNKfSB(BPks;A< z9H5R=1|rR)>WTA4&%*X6FdYU@KeKQ;G^=;~i*OfQS8*tZXkh4iAS~pWry}|g=4~RJ zGi0DJK|mkBUUF2u$-7XPQ;Da@Bm8v2BhY?=3r2(o{!RKouFZO`iY*DK4hktGAW7v`(}xS zzI4kvR>%Nj(o2mqU=P%(pjmjiT0{=Ql30NAcgK)nGoijkSzf#g8i>{TbWLB<)xY%1 z5R*~<2mOeYD;#D7L(;V>H|c!9G-2gBNQ2{D2#F6&hZ-0larLRa3S2;J22w=dj9V<6 zc@=)gi$uS73&KUDi$8}C;(96x_k>Eo!$aG_$Z2~P611q9DGovhcY_3YlW%HFhh+UIhoy_Jbxv8~KDo3SK{t3*> z6J#B&ZD~|Csllu?8ZsgQs)q#srvKpa`or~2)kprqF*HZxfY)IdEYb4p*SSceoKTBS zss*R&luiHa+mTFe!;yk9sFxLZNVW$6x_cf{WYer6_Up>#~dRxXGS00;N(^*Yr;Ug#Nrer$% zI(m5aQn+h{%c`*^1Yi0ZM?ZvlY9u|1OF!A2OTIz6e?SdPRFGM==evKWl#y%(e6dE6 zr!v}CCW^>6YJ;8P{6n(7koC03Rg<`aBMOSX?0&smY>Oo<+6uaCv*@ek-@W_j8@$ka zgN|`~dvia@W*X~itWNfbxOCs_VMiy%osj{IR+)0x-dMt1XYF3?%Jk2=>+uVB+sjEd zF|GiF+)I7=#K8uaTJ?x*Ez(TL``&NuEOw3Ku!D7CmOh7F%6!2GAo?&iZI|2* zdqRc*QpaFyOk~8C(Q}Gpu*LrE7m<51VjXock>LTK?p(Iqc6x<7rX2ijeX8HROb;1m z@M5UMVjh|@5!#Umxtqp%XB4-OTk`K^@>KZ&k{JYqKSrF_Sroc ls~*WliWg?k+nwRdw@?|NtX-xkPz}EtT delta 2421 zcmeHIUrZBs9H*57T9F`>LeVJHqtKROIZ;c)mR2ZoNA2oS{uBYJUN=PMTr7xYrf?Rb zsI0wGv<+;6rBrAfw8Ts_S!#4CB`#2kgKZ{~Ndhb;x)+%*-Ie&VWe>j3@6#{2@9+Eh zci+2<$c@sd&_ZY_s>&4gZ7d|t-ItcTR}6{7xu)^6RA7gPM`2U-!!*#e+yVpe9B4Y4 z@I4RVzHr3*=u2V@*KBiBCyj>{*7Q+OamAhwi?4P{DisW^SQBd!yJ4t#`$Z6WJ}mF! z#QVWyT2O^+HhcA<8Ap#paX+JN)Ie}SAKmOww8yr6#|@Sl0xZg+k;_rgy9+BMx*EnB zPmkV8Co&L;bC_m7<)g1Y#MYn!7Nu6i#S!wrRwELJA`xV z^%s}IY5OsJlUWh#7VtVokfQ?fek#d<7St9vj1QO+yOoSd8Ro>8YR0bOORy}Q zn@%5^zhlh@G8XcmyNaB*IlI)dRz63surehrBuDv`G`_#aZj>66Kmy(!EemW9=VlV; ze4{!*kaGEUTba}Mtd*1vNrj!tqoJo1x&21RLjKKhSbVJORfU-0N4UUiK8FXJ>gWWG z;dA4YwE+dbZZSKDVbWsU3I=?eLA<_Scx{ob2CX9fhSHzuFgW=p_gEq~>me7w9s!`w zO??b5Lwv6qBn(!{JdD&6eDCb(>pJ|zd=&W>+E8-fGA!fDH*y;6&+B;o@$S&f#v}oJ z00w)xnduO}y_!*f+hM3AJv`&b)NGYitn7NFN+=-hV^Ppl7&NEx$k1aj;7dQzb=p=4 zmWCEXFXMEc%qZ8U_)ULCgo_>#5!xuv6WW`|EzzO_@S(`q@Do66o{eKh0y`p7JOz~a z4;%GiqyE<$#r*G$^2{aPp~gXi+9ck=f4Yy2r0e7{C!imLd1pxJ3> zbg~wm_-upr)JJSc2;~Wc|CH$&OKGDqBR5grRrss1QB+EVxtRTletGom=YW*wJ)hu8 zE}G7I8+ms+z7)MKDbH3NM5le?KGvZFHwD_>OCxC8=$S#B*Q<;h>cv+qu7!IC%kZs$ zH0vPxhqOGaG+R=eFjMU%?NiUrN-f9JfF+maXc8(0ltu1o^hfG!>Z6GZ zttPG1#e%ick#cm}SwxCW+150kom@0=K{Vh}H>!z>fVv7R?mbehJXs_)Voy(~kg>Xp zzei0~dmGg~wkH*8rheGcvA)iltIo7*c2rpmS_{ofxy! zR?Q|$W_gR%;>b`wgo+qjV3iO@&8Ae6trk$(60J^`!ra!lk7UV`?gh4FS(eTJ_K%m` zu@G zdf#yq0zwlQtt2JPA0_65<^wChFFPEseMlT-*C&0Ce6jT2BXJhP%G zGd!JXbo!UTJl_-e|pCoU^ zzhIEnEnQs!F}QJr}Tx zYjAE=@^(B*(ugqmC?3v3U@P86sw&hSfP&bdmlTNR3j;#z(ARa!Vao=R7%A}fZ8B%V z2t_5%STZFUSw_&4YZt8U_VC6PHlePqs6{N)c?B^6S*SCd*dTk$qW?_b5vqN#LnPFZ zlJyY9c}Q|D`5lWIm_i0=t7%VC78^4qCAriBZ!mtP3mAo4VJUs(;tKs5IZ(*nwcIJO z6pDnL(tUyBEcP?6KlA$k>UI8qdflC!agcI1;LSj(8TnFFVfgdG7RwZvOy-{BY{xc} z6D%KHj^yUFyw;4B+Hi~2f?Hm;;I@}?Q5mX`%Kqv2&MYmkC<+Ve3zRZxLA_ilRpeVX zOO=*gHfe!Ofuiy{nZ;_8xj)ZZ2bA*sdUThiUaBmx*rW=pyiTfA?y8g4E9G*FQm&A1 zDnQ*VE_k|)6-3U62G! z+SI>Xc;NWn+7c6=-zdTTle~c@npvZ63UotDW2f1}T&1gKIcH22rUroo&+1<*@uioE zGt>jMBa#up>+K#hl2&H+-$;XXkp`iIy-&i^n%8)w>MMFfug4KJg!uLbq3Kmb!lR6u zCiSqrj@2zHh;B3rw3A+%i(WCp6cT&@vX*D2wYl-~P+bC#p;p!h?h!gt+S zRZNCf>g+W;2mKMVHtQILR`KavZG4Mwy_npqF*Rfh2vW)36A^vLM~8PVTPh3>&5Sf? vJuK|^Z{2ggti(S?(`KfQ=2e#Ick}}wRmFvp(~@Df2QyjIE4R5zUg-WEEijCH delta 2027 zcmeH{T})GF9LD=`4x`0NBebZ|rF3tBLXWn`g4nQ(R++q|?cnV2RV6`aS&MTc5vR$b z{TM7`UaXv=STY8|7OiuHvV>9T7X|>SY_-d*xM6}jLc7u*!<&A_9z%4@8t%Zh&TF1BZE#kbh!h9>0&XIa{Mmgvzfdj)(1<@;ykM%UBt#(U9F-)IA(urDBy`$F!92YuY|VIRFJ z5M~|5Dm&f$`l6nt^IT_*e*ShAw1>_bI<4+yqm0Zn^Hq|Ji{dYs7Xm>IR;PDl zcmz$wXq!VU-YRNq+&_XMy}^j%j7KnyC|Vsq5-ek8`!<2CcXZShuuq969at@=GbA1w zXvGxUvafSY&G~w#YASNeTvNL>Nw_=fxZ#ebqUgg}p>%A4#UOXen#p(cC1~YkGhcDC z;!li^Uc?ZT0r`dy!&rl;F3^@ZS@GnOH&IN^=^X{lM-Y{jJ6*>t6NvswDQ!L8n^dk# z>P=K02zTAp`6WM>lOT)t) Date: Mon, 25 Mar 2024 01:09:45 +0400 Subject: [PATCH 4/4] Various fixes --- .../Sources/PeekControllerNode.swift | 17 +- .../FFMpegBinding/Sources/FFMpegVideoWriter.m | 6 + .../Sources/Items/ItemListTextItem.swift | 24 +- .../Sources/ChannelStatsController.swift | 4 +- .../PublicHeaders/ImageDCT/YuvConversion.h | 2 +- .../ImageDCT/Sources/YuvConversion.m | 7 +- .../AnimationCache/Sources/ImageData.swift | 1 + .../Sources/MediaEditorComposer.swift | 3 +- .../MediaEditorVideoFFMpegWriter.swift | 1 + .../Sources/MediaEditorScreen.swift | 277 +++++++++++------- .../ListItems/PeerInfoScreenCommentItem.swift | 6 +- .../Sources/PeerInfoScreen.swift | 14 +- 12 files changed, 225 insertions(+), 137 deletions(-) diff --git a/submodules/ContextUI/Sources/PeekControllerNode.swift b/submodules/ContextUI/Sources/PeekControllerNode.swift index 632d501ce1..4a28d0e110 100644 --- a/submodules/ContextUI/Sources/PeekControllerNode.swift +++ b/submodules/ContextUI/Sources/PeekControllerNode.swift @@ -266,23 +266,19 @@ final class PeekControllerNode: ViewControllerTracingNode { } func animateIn(from rect: CGRect) { - if let appeared = self.controller?.appeared { - appeared() - } else { - self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - } - self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.blurView.layer.animateAlpha(from: 0.0, to: self.blurView.alpha, duration: 0.3) let offset = CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y) self.containerNode.layer.animateSpring(from: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true) - if rect.width > 10.0 { + if let appeared = self.controller?.appeared { + appeared() let scale = rect.width / self.contentNode.frame.width self.containerNode.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0) } else { self.containerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0) + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) } if let topAccessoryNode = self.topAccessoryNode { @@ -322,12 +318,8 @@ final class PeekControllerNode: ViewControllerTracingNode { outCompletion() completion() }) + if let _ = self.controller?.disappeared { - } else { - self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - } - - if rect.width > 10.0 { let scale = rect.width / self.contentNode.frame.width self.containerNode.layer.animateScale(from: 1.0, to: scale, duration: 0.25, removeOnCompletion: false, completion: { _ in scaleCompleted = true @@ -338,6 +330,7 @@ final class PeekControllerNode: ViewControllerTracingNode { scaleCompleted = true outCompletion() }) + self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } if !self.actionsStackNode.alpha.isZero { diff --git a/submodules/FFMpegBinding/Sources/FFMpegVideoWriter.m b/submodules/FFMpegBinding/Sources/FFMpegVideoWriter.m index 03114fdc99..81ccc78103 100755 --- a/submodules/FFMpegBinding/Sources/FFMpegVideoWriter.m +++ b/submodules/FFMpegBinding/Sources/FFMpegVideoWriter.m @@ -47,6 +47,9 @@ _codecContext->codec_id = AV_CODEC_ID_VP9; _codecContext->codec_type = AVMEDIA_TYPE_VIDEO; _codecContext->pix_fmt = AV_PIX_FMT_YUVA420P; + _codecContext->color_range = AVCOL_RANGE_MPEG; + _codecContext->color_primaries = AVCOL_PRI_BT709; + _codecContext->colorspace = AVCOL_SPC_BT709; _codecContext->width = width; _codecContext->height = height; _codecContext->time_base = (AVRational){1, framerate}; @@ -96,6 +99,9 @@ AVFrame *frameImpl = (AVFrame *)[frame impl]; frameImpl->pts = self.framePts; + frameImpl->color_range = AVCOL_RANGE_MPEG; + frameImpl->color_primaries = AVCOL_PRI_BT709; + frameImpl->colorspace = AVCOL_SPC_BT709; int sendRet = avcodec_send_frame(_codecContext, frameImpl); if (sendRet < 0) { diff --git a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift index c338e765eb..2c30236c89 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift @@ -81,6 +81,8 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { private var item: ItemListTextItem? + private var chevronImage: UIImage? + public var tag: ItemListItemTag? { return self.item?.tag } @@ -117,6 +119,8 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { public func asyncLayout() -> (_ item: ItemListTextItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.textNode) + let currentChevronImage = self.chevronImage + let currentItem = self.item return { item, params, neighbors in let leftInset: CGFloat = 15.0 @@ -127,6 +131,12 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { let largeTitleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize)) let titleBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize) + var themeUpdated = false + var chevronImage = currentChevronImage + if currentItem?.presentationData.theme !== item.presentationData.theme { + themeUpdated = true + } + let attributedText: NSAttributedString switch item.text { case let .plain(text): @@ -134,9 +144,18 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { case let .large(text): attributedText = NSAttributedString(string: text, font: largeTitleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) case let .markdown(text): - attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: item.presentationData.theme.list.freeTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in + let mutableAttributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: item.presentationData.theme.list.freeTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in return (TelegramTextAttributes.URL, contents) - })) + })).mutableCopy() as! NSMutableAttributedString + if let _ = text.range(of: ">]"), let range = mutableAttributedText.string.range(of: ">") { + if themeUpdated || currentChevronImage == nil { + chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: item.presentationData.theme.list.itemAccentColor) + } + if let chevronImage { + mutableAttributedText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: mutableAttributedText.string)) + } + } + attributedText = mutableAttributedText } let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0 - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -158,6 +177,7 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { return (layout, { [weak self] in if let strongSelf = self { strongSelf.item = item + strongSelf.chevronImage = chevronImage strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height)) strongSelf.activateArea.accessibilityLabel = attributedText.string diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index fc3ba23e44..3e4b28a8a3 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -1344,7 +1344,7 @@ private func monetizationEntries( var entries: [StatsEntry] = [] //TODO:localize - entries.append(.adsHeader(presentationData.theme, "Telegram shares 50% of the revenue from ads displayed in your channel. [Learn More]()")) + entries.append(.adsHeader(presentationData.theme, "Telegram shares 50% of the revenue from ads displayed in your channel. [Learn More >]()")) entries.append(.adsImpressionsTitle(presentationData.theme, "AD IMPRESSIONS (BY HOURS)")) if !stats.topHoursGraph.isEmpty { @@ -1361,7 +1361,7 @@ private func monetizationEntries( entries.append(.adsBalanceTitle(presentationData.theme, "AVAILABLE BALANCE")) entries.append(.adsBalance(presentationData.theme, data, false, diamond, state.monetizationAddress)) - entries.append(.adsBalanceInfo(presentationData.theme, "We will transfer your balance to the TON wallet address you specify. [Learn More]()")) + entries.append(.adsBalanceInfo(presentationData.theme, "We will transfer your balance to the TON wallet address you specify. [Learn More >]()")) entries.append(.adsTransactionsTitle(presentationData.theme, "TRANSACTION HISTORY")) diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h index b3d2e2b03d..7710863f27 100644 --- a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h @@ -7,7 +7,7 @@ extern "C" { #endif -void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow, bool keepColorsOrder); +void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow, bool restrictedRange, bool keepColorsOrder); void combineYUVAPlanesIntoARGB(uint8_t *argb, uint8_t const *inY, uint8_t const *inU, uint8_t const *inV, uint8_t const *inA, int width, int height, int bytesPerRow); void scaleImagePlane(uint8_t *outPlane, int outWidth, int outHeight, int outBytesPerRow, uint8_t const *inPlane, int inWidth, int inHeight, int inBytesPerRow); diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m index 3adaf579cf..b8558b556c 100644 --- a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m @@ -6,12 +6,15 @@ static uint8_t permuteMap[4] = { 3, 2, 1, 0 }; static uint8_t invertedPermuteMap[4] = { 3, 0, 1, 2 }; -void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow, bool keepColorsOrder) { +void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow, bool restrictedRange, bool keepColorsOrder) { static vImage_ARGBToYpCbCr info; + static vImage_ARGBToYpCbCr restrictedInfo; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ vImage_YpCbCrPixelRange pixelRange = (vImage_YpCbCrPixelRange){ 0, 128, 255, 255, 255, 1, 255, 0 }; + vImage_YpCbCrPixelRange restrictedPixelRange = (vImage_YpCbCrPixelRange){ 16, 128, 235, 240, 255, 0, 255, 0 }; vImageConvert_ARGBToYpCbCr_GenerateConversion(kvImage_ARGBToYpCbCrMatrix_ITU_R_709_2, &pixelRange, &info, kvImageARGB8888, kvImage420Yp8_Cb8_Cr8, 0); + vImageConvert_ARGBToYpCbCr_GenerateConversion(kvImage_ARGBToYpCbCrMatrix_ITU_R_709_2, &restrictedPixelRange, &restrictedInfo, kvImageARGB8888, kvImage420Yp8_Cb8_Cr8, 0); }); vImage_Error error = kvImageNoError; @@ -46,7 +49,7 @@ void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, destA.height = height; destA.rowBytes = width; - error = vImageConvert_ARGB8888To420Yp8_Cb8_Cr8(&src, &destYp, &destCb, &destCr, &info, keepColorsOrder ? invertedPermuteMap : permuteMap, kvImageDoNotTile); + error = vImageConvert_ARGB8888To420Yp8_Cb8_Cr8(&src, &destYp, &destCb, &destCr, restrictedRange ? &restrictedInfo : &info, keepColorsOrder ? invertedPermuteMap : permuteMap, kvImageDoNotTile); if (error != kvImageNoError) { return; } diff --git a/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift b/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift index 62234c883b..66c853510f 100644 --- a/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift +++ b/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift @@ -366,6 +366,7 @@ extension ImageARGB { Int32(self.argbPlane.width), Int32(self.argbPlane.height), Int32(self.argbPlane.bytesPerRow), + false, false ) } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift index 2a691e1ff7..569fed2ea2 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift @@ -152,7 +152,7 @@ final class MediaEditorComposer { if var compositedImage { let scale = self.outputDimensions.width / compositedImage.extent.width compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale)) - + self.ciContext?.render(compositedImage, to: pixelBuffer) completion(pixelBuffer) } else { @@ -164,7 +164,6 @@ final class MediaEditorComposer { } completion(nil) } - private var isFirst = true private var cachedTexture: MTLTexture? func textureForImage(_ image: UIImage) -> MTLTexture? { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift index 0921a4703c..c9b703df92 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift @@ -109,6 +109,7 @@ final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter { width, height, bytesPerRow, + true, true ) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index faa08988d2..88dbe55ed6 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -4596,11 +4596,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate init( media: MediaResult?, - mediaAreas: [MediaArea], - caption: NSAttributedString, - options: MediaEditorResultPrivacy, - stickers: [TelegramMediaFile], - randomId: Int64 + mediaAreas: [MediaArea] = [], + caption: NSAttributedString = NSAttributedString(), + options: MediaEditorResultPrivacy = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false), + stickers: [TelegramMediaFile] = [], + randomId: Int64 = 0 ) { self.media = media self.mediaAreas = mediaAreas @@ -5755,24 +5755,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate let values = mediaEditor.values.withUpdatedQualityPreset(.sticker) makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: image, dimensions: storyDimensions, outputDimensions: CGSize(width: 512, height: 512), values: values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in if let self, let resultImage { -// let dimensions = CGSize(width: 512, height: 512) -// let scaledImage = generateImage(dimensions, contextGenerator: { size, context in -// context.clear(CGRect(origin: CGPoint(), size: size)) -// -// context.addPath(CGPath(roundedRect: CGRect(origin: .zero, size: size), cornerWidth: size.width / 8.0, cornerHeight: size.width / 8.0, transform: nil)) -// context.clip() -// -// let scaledSize = resultImage.size.aspectFilled(size) -// context.draw(resultImage.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - scaledSize.width) / 2.0), y: floor((size.height - scaledSize.height) / 2.0)), size: scaledSize)) -// }, opaque: false, scale: 1.0)! - self.presentStickerPreview(image: resultImage) } }) } } - + private weak var resultController: PeekController? func presentStickerPreview(image: UIImage) { guard let mediaEditor = self.node.mediaEditor else { return @@ -5783,7 +5772,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate var isVideo = false if mediaEditor.resultIsVideo { isVideo = true - self.performSave(toStickerResource: resource) } else { Queue.concurrentDefaultQueue().async { if let data = try? WebP.convert(toWebP: image, quality: 97.0) { @@ -5804,26 +5792,29 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate guard let self else { return } - if self.videoExport != nil { - return - } - f(.default) - self.completion(MediaEditorScreen.Result( - media: .sticker(file: file), - mediaAreas: [], - caption: NSAttributedString(), - options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false), - stickers: [], - randomId: 0 - ), { [weak self] finished in - self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in - self?.dismiss() - Queue.mainQueue().justDispatch { - finished() - } + if isVideo { + self.uploadSticker(file, action: .send) + } else { + self.resultController?.disappeared = nil + self.completion(MediaEditorScreen.Result( + media: .sticker(file: file), + mediaAreas: [], + caption: NSAttributedString(), + options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false), + stickers: [], + randomId: 0 + ), { [weak self] finished in + self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in + self?.dismiss() + Queue.mainQueue().justDispatch { + finished() + } + }) }) - }) + } + + f(.default) }))) menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) @@ -5899,6 +5890,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } } + Queue.mainQueue().justDispatch { + self.node.entitiesView.selectEntity(nil) + } + let peekController = PeekController( presentationData: presentationData, content: StickerPreviewPeekContent( @@ -5934,6 +5929,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.node.previewView.alpha = 1.0 } } + self.resultController = peekController self.present(peekController, in: .window(.root)) } @@ -5942,6 +5938,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate case createStickerPack(title: String) case addToStickerPack(pack: StickerPackReference, title: String) case upload + case send } private func presentCreateStickerPack(file: TelegramMediaFile, completion: @escaping () -> Void) { @@ -6014,69 +6011,118 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate let context = self.context let dimensions = PixelDimensions(width: 512, height: 512) let mimeType = file.mimeType + let isVideo = file.mimeType == "video/webm" self.updateEditProgress(0.0, cancel: { [weak self] in self?.stickerUploadDisposable.set(nil) }) + enum PrepareStickerStatus { + case progress(Float) + case complete(TelegramMediaResource) + case failed + } + let resourceSignal: Signal + if isVideo { + self.performSave(toStickerResource: file.resource) + resourceSignal = self.videoExportPromise.get() + |> castError(UploadStickerError.self) + |> filter { $0 != nil } + |> take(1) + |> mapToSignal { videoExport -> Signal in + guard let videoExport else { + return .complete() + } + return videoExport.status + |> castError(UploadStickerError.self) + |> mapToSignal { status -> Signal in + switch status { + case .unknown: + return .single(.progress(0.0)) + case let .progress(progress): + return .single(.progress(progress)) + case .completed: + return .single(.complete(file.resource)) + |> delay(0.05, queue: Queue.mainQueue()) + case .failed: + return .single(.failed) + } + } + } + } else { + resourceSignal = .single(.complete(file.resource)) + } + let signal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> castError(UploadStickerError.self) |> mapToSignal { peer -> Signal in guard let peer else { return .complete() } - return context.engine.stickers.uploadSticker(peer: peer._asPeer(), resource: file.resource, alt: "", dimensions: dimensions, mimeType: mimeType) - |> mapToSignal { status -> Signal in - switch status { - case .progress: - return .single(status) - case let .complete(resource, _): - let file = stickerFile(resource: resource, size: file.size ?? 0, dimensions: dimensions, isVideo: file.mimeType == "video/webm") - switch action { - case .addToFavorites: - return context.engine.stickers.toggleStickerSaved(file: file, saved: true) - |> `catch` { _ -> Signal in - return .fail(.generic) - } - |> map { _ in - return status - } - case let .createStickerPack(title): - let sticker = ImportSticker( - resource: resource, - emojis: ["😀😂"], - dimensions: dimensions, - mimeType: mimeType, - keywords: "" - ) - return context.engine.stickers.createStickerSet(title: title, shortName: "", stickers: [sticker], thumbnail: nil, type: .stickers(content: .image), software: nil) - |> `catch` { _ -> Signal in - return .fail(.generic) - } - |> mapToSignal { innerStatus in - if case .complete = innerStatus { + return resourceSignal + |> mapToSignal { result -> Signal in + switch result { + case .failed: + return .fail(.generic) + case let .progress(progress): + return .single(.progress(progress * 0.5)) + case let .complete(resource): + return context.engine.stickers.uploadSticker(peer: peer._asPeer(), resource: resource, alt: "", dimensions: dimensions, mimeType: mimeType) + |> mapToSignal { status -> Signal in + switch status { + case let .progress(progress): + return .single(.progress(isVideo ? 0.5 + progress * 0.5 : progress)) + case let .complete(resource, _): + let file = stickerFile(resource: resource, size: file.size ?? 0, dimensions: dimensions, isVideo: isVideo) + switch action { + case .send: + return .single(status) + case .addToFavorites: + return context.engine.stickers.toggleStickerSaved(file: file, saved: true) + |> `catch` { _ -> Signal in + return .fail(.generic) + } + |> map { _ in + return status + } + case let .createStickerPack(title): + let sticker = ImportSticker( + resource: resource, + emojis: ["😀😂"], + dimensions: dimensions, + mimeType: mimeType, + keywords: "" + ) + return context.engine.stickers.createStickerSet(title: title, shortName: "", stickers: [sticker], thumbnail: nil, type: .stickers(content: .image), software: nil) + |> `catch` { _ -> Signal in + return .fail(.generic) + } + |> mapToSignal { innerStatus in + if case .complete = innerStatus { + return .single(status) + } else { + return .complete() + } + } + case let .addToStickerPack(pack, _): + let sticker = ImportSticker( + resource: resource, + emojis: ["😀😂"], + dimensions: dimensions, + mimeType: mimeType, + keywords: "" + ) + return context.engine.stickers.addStickerToStickerSet(packReference: pack, sticker: sticker) + |> `catch` { _ -> Signal in + return .fail(.generic) + } + |> map { _ in + return status + } + case .upload: return .single(status) - } else { - return .complete() } } - case let .addToStickerPack(pack, _): - let sticker = ImportSticker( - resource: resource, - emojis: ["😀😂"], - dimensions: dimensions, - mimeType: mimeType, - keywords: "" - ) - return context.engine.stickers.addStickerToStickerSet(packReference: pack, sticker: sticker) - |> `catch` { _ -> Signal in - return .fail(.generic) - } - |> map { _ in - return status - } - case .upload: - return .single(status) } } } @@ -6090,26 +6136,23 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate switch status { case let .progress(progress): self.updateEditProgress(progress, cancel: { [weak self] in + self?.videoExport?.cancel() + self?.videoExport = nil + self?.exportDisposable.set(nil) self?.stickerUploadDisposable.set(nil) }) case let .complete(resource, _): let navigationController = self.navigationController as? NavigationController let result: MediaEditorScreen.Result - if case .upload = action { - let file = stickerFile(resource: resource, size: resource.size ?? 0, dimensions: dimensions, isVideo: file.mimeType == "video/webm") - result = MediaEditorScreen.Result( - media: .sticker(file: file), - mediaAreas: [], - caption: NSAttributedString(), - options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false), - stickers: [], - randomId: 0 - ) - } else { + switch action { + case .upload, .send: + let file = stickerFile(resource: resource, size: resource.size ?? 0, dimensions: dimensions, isVideo: isVideo) + result = MediaEditorScreen.Result(media: .sticker(file: file)) + default: result = MediaEditorScreen.Result() } - + self.completion(result, { [weak self] finished in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in guard let self else { @@ -6147,7 +6190,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate })) } - private var videoExport: MediaEditorVideoExport? + private var videoExport: MediaEditorVideoExport? { + didSet { + self.videoExportPromise.set(.single(self.videoExport)) + } + } + private var videoExportPromise = Promise(nil) private var exportDisposable = MetaDisposable() fileprivate var isSavingAvailable = false @@ -6178,7 +6226,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities) let isSticker = toStickerResource != nil - if !isSticker { self.previousSavedValues = mediaEditor.values self.isSavingAvailable = false @@ -6207,8 +6254,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } if mediaEditor.resultIsVideo { - mediaEditor.maybePauseVideo() - self.node.entitiesView.pause() + if !isSticker { + mediaEditor.maybePauseVideo() + self.node.entitiesView.pause() + } let exportSubject: Signal switch subject { @@ -6293,8 +6342,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate switch status { case .completed: self.videoExport = nil - if let toStickerResource, let data = try? Data(contentsOf: URL(fileURLWithPath: outputPath)) { - self.context.account.postbox.mediaBox.storeResourceData(toStickerResource.id, data: data) + if let toStickerResource { + if let data = try? Data(contentsOf: URL(fileURLWithPath: outputPath)) { + self.context.account.postbox.mediaBox.storeResourceData(toStickerResource.id, data: data, synchronous: true) + } } else { saveToPhotos(outputPath, true) self.node.presentSaveTooltip() @@ -6337,16 +6388,18 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } fileprivate func cancelVideoExport() { - if let videoExport = self.videoExport { - self.previousSavedValues = nil - - videoExport.cancel() - self.videoExport = nil - self.exportDisposable.set(nil) - - self.node.mediaEditor?.play() - self.node.entitiesView.play() + guard let videoExport = self.videoExport else { + return } + videoExport.cancel() + + self.videoExport = nil + self.exportDisposable.set(nil) + + self.previousSavedValues = nil + + self.node.mediaEditor?.play() + self.node.entitiesView.play() } public func updateEditProgress(_ progress: Float, cancel: @escaping () -> Void) { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift index 6f5f7ad96b..59374afed5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift @@ -80,10 +80,12 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode { let textFont = Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize) let textColor = presentationData.theme.list.freeTextColor - let attributedText = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: presentationData.theme.list.itemAccentColor), linkAttribute: { contents in + var text = item.text + text = text.replacingOccurrences(of: " >]", with: "\u{00A0}>]") + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: presentationData.theme.list.itemAccentColor), linkAttribute: { contents in return (TelegramTextAttributes.URL, contents) })).mutableCopy() as! NSMutableAttributedString - if let range = attributedText.string.range(of: ">") { + if let _ = item.text.range(of: ">]"), let range = attributedText.string.range(of: ">") { if themeUpdated || self.chevronImage == nil { self.chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: presentationData.theme.list.itemAccentColor) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 09b05e1cf4..b7133f907c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -2678,8 +2678,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, updateIsEditingBirthdate: { [weak self] value in if let self { - if value, let data = self.data?.cachedData as? CachedUserData, data.birthday == nil { - self.state = self.state.withUpdatingBirthDate(TelegramBirthday(day: 1, month: 1, year: nil)) + if value { + if let data = self.data?.cachedData as? CachedUserData { + if data.birthday == nil { + self.state = self.state.withUpdatingBirthDate(TelegramBirthday(day: 1, month: 1, year: nil)) + } else { + self.state = self.state.withUpdatingBirthDate(nil) + } + } + } else { + if self.state.updatingBirthDate != .some(nil) { + self.state = self.state.withUpdatingBirthDate(nil) + } } self.state = self.state.withIsEditingBirthDate(value)