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