Merge commit 'b7c18980f64fadd1b1292d62f9b1ded9bb2ce486'

This commit is contained in:
Isaac 2024-03-25 14:37:13 +04:00
commit 6b227b8aa4
57 changed files with 1464 additions and 1044 deletions

View File

@ -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.";
@ -11666,3 +11669,5 @@ Sorry for the inconvenience.";
"Business.Links" = "Links to Chat";
"Business.LinksInfo" = "Create links that start a chat with you, suggesting the first message.";
"Settings.About.PrivacyHelp" = "You can add a few lines about yourself. Choose who can see your bio in [Settings]().";

View File

@ -952,6 +952,7 @@ public protocol SharedAccountContext: AnyObject {
func makeCreateGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String?, mode: CreateGroupMode, completion: ((PeerId, @escaping () -> Void) -> Void)?) -> ViewController
func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController
func makePrivacyAndSecurityController(context: AccountContext) -> ViewController
func makeBioPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, present: @escaping (ViewController) -> Void)
func makeBirthdayPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void)
func makeSetupTwoFactorAuthController(context: AccountContext) -> ViewController
func makeStorageManagementController(context: AccountContext) -> ViewController

View File

@ -724,7 +724,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
hideChatListContacts(context: context)
} : nil), directionHint: entry.directionHint)
case let .Notice(presentationData, notice):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
switch action {
case .activate:
switch notice {
@ -1060,7 +1060,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
hideChatListContacts(context: context)
} : nil), directionHint: entry.directionHint)
case let .Notice(presentationData, notice):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
switch action {
case .activate:
switch notice {

View File

@ -12,7 +12,7 @@ import Markdown
import AccountContext
import MergedAvatarsNode
class ChatListStorageInfoItem: ListViewItem {
class ChatListNoticeItem: ListViewItem {
enum Action {
case activate
case hide
@ -43,7 +43,7 @@ class ChatListStorageInfoItem: ListViewItem {
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ChatListStorageInfoItemNode()
let node = ChatListNoticeItemNode()
let (nodeLayout, apply) = node.asyncLayout()(self, params, false)
@ -62,8 +62,8 @@ class ChatListStorageInfoItem: ListViewItem {
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
assert(node() is ChatListStorageInfoItemNode)
if let nodeValue = node() as? ChatListStorageInfoItemNode {
assert(node() is ChatListNoticeItemNode)
if let nodeValue = node() as? ChatListNoticeItemNode {
let layout = nodeValue.asyncLayout()
async {
@ -84,7 +84,7 @@ private let separatorHeight = 1.0 / UIScreen.main.scale
private let titleFont = Font.semibold(15.0)
private let textFont = Font.regular(15.0)
class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
private let contentContainer: ASDisplayNode
private let titleNode: TextNode
private let textNode: TextNode
@ -100,7 +100,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
private var okButton: HighlightableButtonNode?
private var cancelButton: HighlightableButtonNode?
private var item: ChatListStorageInfoItem?
private var item: ChatListNoticeItem?
override var apparentHeight: CGFloat {
didSet {
@ -145,11 +145,11 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
let layout = self.asyncLayout()
let (_, apply) = layout(item as! ChatListStorageInfoItem, params, nextItem == nil)
let (_, apply) = layout(item as! ChatListNoticeItem, params, nextItem == nil)
apply()
}
func asyncLayout() -> (_ item: ChatListStorageInfoItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) {
func asyncLayout() -> (_ item: ChatListNoticeItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) {
let previousItem = self.item
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
@ -321,9 +321,19 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
if let image = strongSelf.arrowNode.image {
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - image.size.width + 8.0, y: floor((layout.size.height - image.size.height) / 2.0)), size: image.size)
}
var hasCloseButton = false
if case .xmasPremiumGift = item.notice {
hasCloseButton = true
} else if case .setupBirthday = item.notice {
hasCloseButton = true
} else if case .birthdayPremiumGift = item.notice {
hasCloseButton = true
}
if let okButtonLayout, let cancelButtonLayout {
strongSelf.arrowNode.isHidden = true
strongSelf.closeButton?.isHidden = true
let okButton: HighlightableButtonNode
if let current = strongSelf.okButton {
@ -372,7 +382,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
okButtonText.frame = CGRect(origin: CGPoint(x: floor((okButtonFrame.width - okButtonLayout.0.size.width) * 0.5), y: floor((okButtonFrame.height - okButtonLayout.0.size.height) * 0.5)), size: okButtonLayout.0.size)
cancelButtonText.frame = CGRect(origin: CGPoint(x: floor((cancelButtonFrame.width - cancelButtonLayout.0.size.width) * 0.5), y: floor((cancelButtonFrame.height - cancelButtonLayout.0.size.height) * 0.5)), size: cancelButtonLayout.0.size)
} else {
strongSelf.arrowNode.isHidden = false
strongSelf.arrowNode.isHidden = hasCloseButton
if let okButton = strongSelf.okButton {
strongSelf.okButton = nil
@ -390,40 +400,29 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
strongSelf.cancelButtonText = nil
cancelButtonText.removeFromSupernode()
}
}
let arrowIsHidden = strongSelf.arrowNode.isHidden
var hasCloseButton = false
if case .xmasPremiumGift = item.notice {
hasCloseButton = true
} else if case .setupBirthday = item.notice {
hasCloseButton = true
} else if case .birthdayPremiumGift = item.notice {
hasCloseButton = true
}
if hasCloseButton {
strongSelf.arrowNode.isHidden = true
let closeButton: HighlightableButtonNode
if let current = strongSelf.closeButton {
closeButton = current
if hasCloseButton {
let closeButton: HighlightableButtonNode
if let current = strongSelf.closeButton {
closeButton = current
} else {
closeButton = HighlightableButtonNode()
closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
closeButton.addTarget(self, action: #selector(strongSelf.closePressed), forControlEvents: [.touchUpInside])
strongSelf.contentContainer.addSubnode(closeButton)
strongSelf.closeButton = closeButton
}
if themeUpdated {
closeButton.setImage(PresentationResourcesItemList.itemListCloseIconImage(item.theme), for: .normal)
}
let closeButtonSize = closeButton.measure(CGSize(width: 100.0, height: 100.0))
closeButton.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - closeButtonSize.width, y: floor((layout.size.height - closeButtonSize.height) / 2.0)), size: closeButtonSize)
} else {
closeButton = HighlightableButtonNode()
closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
closeButton.addTarget(self, action: #selector(strongSelf.closePressed), forControlEvents: [.touchUpInside])
strongSelf.contentContainer.addSubnode(closeButton)
strongSelf.closeButton = closeButton
strongSelf.closeButton?.removeFromSupernode()
strongSelf.closeButton = nil
}
if themeUpdated {
closeButton.setImage(PresentationResourcesItemList.itemListCloseIconImage(item.theme), for: .normal)
}
let closeButtonSize = closeButton.measure(CGSize(width: 100.0, height: 100.0))
closeButton.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - closeButtonSize.width, y: floor((layout.size.height - closeButtonSize.height) / 2.0)), size: closeButtonSize)
} else {
strongSelf.arrowNode.isHidden = arrowIsHidden
}
strongSelf.contentSize = layout.contentSize

View File

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

View File

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

View File

@ -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)
}
@ -267,8 +271,15 @@ final class PeekControllerNode: ViewControllerTracingNode {
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 let appeared = self.controller?.appeared {
appeared()
let scale = rect.width / self.contentNode.frame.width
self.containerNode.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0)
} else {
self.containerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0)
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
if let topAccessoryNode = self.topAccessoryNode {
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 +304,35 @@ 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 {
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()
})
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
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)

View File

@ -97,9 +97,6 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
private let angleLayer = SimpleShapeLayer()
private let bin = ComponentView<Empty>()
private let stickerOverlayLayer = SimpleShapeLayer()
private let stickerFrameLayer = SimpleShapeLayer()
public var onInteractionUpdated: (Bool) -> Void = { _ in }
public var edgePreviewUpdated: (Bool) -> Void = { _ in }
@ -145,13 +142,6 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
self.angleLayer.opacity = 0.0
self.angleLayer.lineDashPattern = [12, 12] as [NSNumber]
self.stickerOverlayLayer.fillColor = UIColor(rgb: 0x000000, alpha: 0.7).cgColor
self.stickerFrameLayer.fillColor = UIColor.clear.cgColor
self.stickerFrameLayer.strokeColor = UIColor(rgb: 0xffffff, alpha: 0.55).cgColor
self.stickerFrameLayer.lineDashPattern = [24, 24] as [NSNumber]
self.stickerFrameLayer.lineCap = .round
self.addSubview(self.topEdgeView)
self.addSubview(self.leftEdgeView)
self.addSubview(self.rightEdgeView)
@ -160,25 +150,12 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
self.addSubview(self.xAxisView)
self.addSubview(self.yAxisView)
self.layer.addSublayer(self.angleLayer)
if isStickerEditor {
self.layer.addSublayer(self.stickerOverlayLayer)
self.layer.addSublayer(self.stickerFrameLayer)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func addSubview(_ view: UIView) {
super.addSubview(view)
if self.stickerOverlayLayer.superlayer != nil, view is DrawingEntityView {
self.layer.addSublayer(self.stickerOverlayLayer)
self.layer.addSublayer(self.stickerFrameLayer)
}
}
public override func layoutSubviews() {
super.layoutSubviews()
@ -214,25 +191,6 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
self.angleLayer.path = anglePath
self.angleLayer.lineWidth = width
self.angleLayer.bounds = CGRect(origin: .zero, size: CGSize(width: 3000.0, height: width))
let frameWidth = floor(self.bounds.width * 0.97)
let frameRect = CGRect(origin: CGPoint(x: floor((self.bounds.width - frameWidth) / 2.0), y: floor((self.bounds.height - frameWidth) / 2.0)), size: CGSize(width: frameWidth, height: frameWidth))
self.stickerOverlayLayer.frame = self.bounds
let overlayOuterRect = UIBezierPath(rect: self.bounds)
let overlayInnerRect = UIBezierPath(cgPath: CGPath(roundedRect: frameRect, cornerWidth: frameWidth / 8.0, cornerHeight: frameWidth / 8.0, transform: nil))
let overlayLineWidth: CGFloat = 2.0 * 2.2
overlayOuterRect.append(overlayInnerRect)
overlayOuterRect.usesEvenOddFillRule = true
self.stickerOverlayLayer.path = overlayOuterRect.cgPath
self.stickerOverlayLayer.fillRule = .evenOdd
self.stickerFrameLayer.frame = self.bounds
self.stickerFrameLayer.lineWidth = overlayLineWidth
self.stickerFrameLayer.path = CGPath(roundedRect: frameRect.insetBy(dx: -overlayLineWidth / 2.0, dy: -overlayLineWidth / 2.0), cornerWidth: frameWidth / 8.0 * 1.02, cornerHeight: frameWidth / 8.0 * 1.02, transform: nil)
}
public var entities: [DrawingEntity] {

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
#import <FFMpegBinding/FFMpegVideoWriter.h>
#import <FFMpegBinding/FrameConverter.h>
#import <FFMpegBinding/FFMpegAVFrame.h>
#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;
@ -47,13 +47,14 @@
_codecContext->codec_id = AV_CODEC_ID_VP9;
_codecContext->codec_type = AVMEDIA_TYPE_VIDEO;
_codecContext->pix_fmt = AV_PIX_FMT_YUVA420P;
_codecContext->color_range = AVCOL_RANGE_MPEG;
_codecContext->color_primaries = AVCOL_PRI_BT709;
_codecContext->colorspace = AVCOL_SPC_BT709;
_codecContext->width = width;
_codecContext->height = height;
_codecContext->time_base = (AVRational){1, 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 +89,22 @@
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;
frameImpl->color_range = AVCOL_RANGE_MPEG;
frameImpl->color_primaries = AVCOL_PRI_BT709;
frameImpl->colorspace = AVCOL_SPC_BT709;
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 +131,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);

View File

@ -875,8 +875,8 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
for line in textItem.lines {
let lineFrame = frameForLine(line, boundingWidth: boundingWidth, alignment: alignment)
for imageItem in line.imageItems {
if case let .image(image) = media[imageItem.id] {
let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: .image(image), url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false)
if let media = media[imageItem.id] {
let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: media, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false)
additionalItems.append(item)
if item.frame.minY < topInset {

View File

@ -81,6 +81,8 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
private var item: ItemListTextItem?
private var chevronImage: UIImage?
public var tag: ItemListItemTag? {
return self.item?.tag
}
@ -117,6 +119,8 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
public func asyncLayout() -> (_ item: ItemListTextItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.textNode)
let currentChevronImage = self.chevronImage
let currentItem = self.item
return { item, params, neighbors in
let leftInset: CGFloat = 15.0
@ -127,6 +131,12 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
let largeTitleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize))
let titleBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize)
var themeUpdated = false
var chevronImage = currentChevronImage
if currentItem?.presentationData.theme !== item.presentationData.theme {
themeUpdated = true
}
let attributedText: NSAttributedString
switch item.text {
case let .plain(text):
@ -134,9 +144,18 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
case let .large(text):
attributedText = NSAttributedString(string: text, font: largeTitleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
case let .markdown(text):
attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: item.presentationData.theme.list.freeTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in
let mutableAttributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: item.presentationData.theme.list.freeTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}))
})).mutableCopy() as! NSMutableAttributedString
if let _ = text.range(of: ">]"), let range = mutableAttributedText.string.range(of: ">") {
if themeUpdated || currentChevronImage == nil {
chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: item.presentationData.theme.list.itemAccentColor)
}
if let chevronImage {
mutableAttributedText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: mutableAttributedText.string))
}
}
attributedText = mutableAttributedText
}
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0 - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -158,6 +177,7 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.item = item
strongSelf.chevronImage = chevronImage
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = attributedText.string

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

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

View File

@ -9,6 +9,7 @@ import MultilineTextComponent
import BlurredBackgroundComponent
import Markdown
import TelegramPresentationData
import ScrollComponent
private final class LimitComponent: CombinedComponent {
let title: String

View File

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

View File

@ -345,7 +345,8 @@ class PremiumCoinComponent: Component {
return
}
if #available(iOS 17.0, *), let material = node.geometry?.materials.first {
// if #available(iOS 17.0, *), let material = node.geometry?.materials.first {
if let material = node.geometry?.materials.first {
material.metalness.intensity = 0.3
}

View File

@ -20,6 +20,7 @@ import ConfettiEffect
import TextFormat
import UniversalMediaPlayer
import InstantPageCache
import ScrollComponent
extension PremiumGiftSource {
var identifier: String? {

View File

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

View File

@ -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<ChildEnvironment: Equatable>: 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<CGPoint>) -> Void
let resetScroll: ActionSlot<Void>
public init(
content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>,
externalState: ExternalState? = nil,
contentInsets: UIEdgeInsets,
contentOffsetUpdated: @escaping (_ top: CGFloat, _ bottom: CGFloat) -> Void,
contentOffsetWillCommit: @escaping (UnsafeMutablePointer<CGPoint>) -> Void,
resetScroll: ActionSlot<Void> = 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<ChildEnvironment>?
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<CGPoint>) {
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<ChildEnvironment>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ChildEnvironment>, 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<ChildEnvironment>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -12,6 +12,7 @@ import TelegramPresentationData
import BundleIconComponent
import AvatarNode
import AvatarStoryIndicatorComponent
import ScrollComponent
private final class AvatarComponent: Component {
let context: AccountContext

View File

@ -16,6 +16,30 @@ public func makePrivacyAndSecurityController(context: AccountContext) -> ViewCon
return privacyAndSecurityController(context: context, focusOnItemTag: PrivacyAndSecurityEntryTag.autoArchive)
}
public func makeBioPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, present: @escaping (ViewController) -> Void) {
let signal = settings.get()
|> take(1)
|> deliverOnMainQueue
let _ = signal.startStandalone(next: { info in
if let info = info {
present(selectivePrivacySettingsController(context: context, kind: .bio, current: info.bio, updated: { updated, _, _, _ in
let applySetting: Signal<Void, NoError> = settings.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in
if let value = value {
settings.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: updated, birthday: value.birthday, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout)))
}
return .complete()
}
let _ = applySetting.startStandalone()
}))
}
})
}
public func makeBirthdayPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void) {
let signal = settings.get()
|> take(1)

View File

@ -1344,7 +1344,7 @@ private func monetizationEntries(
var entries: [StatsEntry] = []
//TODO:localize
entries.append(.adsHeader(presentationData.theme, "Telegram shares 50% of the revenue from ads displayed in your channel. [Learn More]()"))
entries.append(.adsHeader(presentationData.theme, "Telegram shares 50% of the revenue from ads displayed in your channel. [Learn More >]()"))
entries.append(.adsImpressionsTitle(presentationData.theme, "AD IMPRESSIONS (BY HOURS)"))
if !stats.topHoursGraph.isEmpty {
@ -1361,7 +1361,7 @@ private func monetizationEntries(
entries.append(.adsBalanceTitle(presentationData.theme, "AVAILABLE BALANCE"))
entries.append(.adsBalance(presentationData.theme, data, false, diamond, state.monetizationAddress))
entries.append(.adsBalanceInfo(presentationData.theme, "We will transfer your balance to the TON wallet address you specify. [Learn More]()"))
entries.append(.adsBalanceInfo(presentationData.theme, "We will transfer your balance to the TON wallet address you specify. [Learn More >]()"))
entries.append(.adsTransactionsTitle(presentationData.theme, "TRANSACTION HISTORY"))

View File

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

View File

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

View File

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

View File

@ -443,6 +443,8 @@ swift_library(
"//submodules/TelegramUI/Components/Chat/ChatEmptyNode",
"//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem",
"//submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController",
"//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": [],

View File

@ -28,7 +28,6 @@ swift_library(
"//submodules/AppBundle",
"//submodules/TelegramStringFormatting",
"//submodules/PresentationDataUtils",
"//submodules/Components/SheetComponent",
],
visibility = [
"//visibility:public",

View File

@ -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<Empty>.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(

View File

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

View File

@ -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 restrictedRange, bool keepColorsOrder);
void combineYUVAPlanesIntoARGB(uint8_t *argb, uint8_t const *inY, uint8_t const *inU, uint8_t const *inV, uint8_t const *inA, int width, int height, int bytesPerRow);
void scaleImagePlane(uint8_t *outPlane, int outWidth, int outHeight, int outBytesPerRow, uint8_t const *inPlane, int inWidth, int inHeight, int inBytesPerRow);

View File

@ -3,14 +3,18 @@
#import <Foundation/Foundation.h>
#import <Accelerate/Accelerate.h>
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 restrictedRange, bool keepColorsOrder) {
static vImage_ARGBToYpCbCr info;
static vImage_ARGBToYpCbCr restrictedInfo;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
vImage_YpCbCrPixelRange pixelRange = (vImage_YpCbCrPixelRange){ 0, 128, 255, 255, 255, 1, 255, 0 };
vImage_YpCbCrPixelRange restrictedPixelRange = (vImage_YpCbCrPixelRange){ 16, 128, 235, 240, 255, 0, 255, 0 };
vImageConvert_ARGBToYpCbCr_GenerateConversion(kvImage_ARGBToYpCbCrMatrix_ITU_R_709_2, &pixelRange, &info, kvImageARGB8888, kvImage420Yp8_Cb8_Cr8, 0);
vImageConvert_ARGBToYpCbCr_GenerateConversion(kvImage_ARGBToYpCbCrMatrix_ITU_R_709_2, &restrictedPixelRange, &restrictedInfo, kvImageARGB8888, kvImage420Yp8_Cb8_Cr8, 0);
});
vImage_Error error = kvImageNoError;
@ -45,7 +49,7 @@ void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU,
destA.height = height;
destA.rowBytes = width;
error = vImageConvert_ARGB8888To420Yp8_Cb8_Cr8(&src, &destYp, &destCb, &destCr, &info, permuteMap, kvImageDoNotTile);
error = vImageConvert_ARGB8888To420Yp8_Cb8_Cr8(&src, &destYp, &destCb, &destCr, restrictedRange ? &restrictedInfo : &info, keepColorsOrder ? invertedPermuteMap : permuteMap, kvImageDoNotTile);
if (error != kvImageNoError) {
return;
}

View File

@ -365,7 +365,9 @@ 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,
false
)
}
}

View File

@ -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<Bool> {
let progress = Promise<Bool>()
@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -71,6 +71,7 @@ swift_library(
"//submodules/WallpaperBackgroundNode",
"//submodules/ImageTransparency",
"//submodules/FFMpegBinding",
"//submodules/TelegramUI/Components/AnimationCache/ImageDCT",
],
visibility = [
"//visibility:public",

View File

@ -152,16 +152,6 @@ 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)
@ -174,7 +164,6 @@ final class MediaEditorComposer {
}
completion(nil)
}
private var isFirst = true
private var cachedTexture: MTLTexture?
func textureForImage(_ image: UIImage) -> MTLTexture? {

View File

@ -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,32 @@ 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,
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() {

View File

@ -2146,7 +2146,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
private let gradientView: UIImageView
private var gradientColorsDisposable: Disposable?
private let stickerTransparentView: UIImageView
private var stickerBackgroundView: UIImageView?
private var stickerOverlayLayer: SimpleShapeLayer?
private var stickerFrameLayer: SimpleShapeLayer?
fileprivate let entitiesContainerView: UIView
let entitiesView: DrawingEntitiesView
@ -2216,9 +2218,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
self.gradientView = UIImageView()
self.stickerTransparentView = UIImageView()
self.stickerTransparentView.clipsToBounds = true
var isStickerEditor = false
if case .stickerEditor = controller.mode {
isStickerEditor = true
@ -2255,7 +2255,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if case .stickerEditor = controller.mode {
let rowsCount = 40
self.stickerTransparentView.image = generateImage(CGSize(width: rowsCount, height: rowsCount), opaque: true, scale: 1.0, rotatedContext: { size, context in
let stickerBackgroundView = UIImageView()
stickerBackgroundView.clipsToBounds = true
stickerBackgroundView.image = generateImage(CGSize(width: rowsCount, height: rowsCount), opaque: true, scale: 1.0, rotatedContext: { size, context in
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor(rgb: 0x2b2b2d).cgColor)
@ -2269,10 +2271,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
context.fillPath()
})
self.stickerTransparentView.layer.magnificationFilter = .nearest
self.stickerTransparentView.layer.shouldRasterize = true
self.stickerTransparentView.layer.rasterizationScale = UIScreenScale
self.previewContainerView.addSubview(self.stickerTransparentView)
stickerBackgroundView.layer.magnificationFilter = .nearest
stickerBackgroundView.layer.shouldRasterize = true
stickerBackgroundView.layer.rasterizationScale = UIScreenScale
self.stickerBackgroundView = stickerBackgroundView
self.previewContainerView.addSubview(stickerBackgroundView)
} else {
self.previewContainerView.addSubview(self.gradientView)
}
@ -2281,6 +2284,24 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.previewContainerView.addSubview(self.entitiesContainerView)
self.entitiesContainerView.addSubview(self.entitiesView)
self.entitiesView.addSubview(self.drawingView)
if case .stickerEditor = controller.mode {
let stickerOverlayLayer = SimpleShapeLayer()
stickerOverlayLayer.fillColor = UIColor(rgb: 0x000000, alpha: 0.7).cgColor
stickerOverlayLayer.fillRule = .evenOdd
self.stickerOverlayLayer = stickerOverlayLayer
self.previewContainerView.layer.addSublayer(stickerOverlayLayer)
let stickerFrameLayer = SimpleShapeLayer()
stickerFrameLayer.fillColor = UIColor.clear.cgColor
stickerFrameLayer.strokeColor = UIColor(rgb: 0xffffff, alpha: 0.55).cgColor
stickerFrameLayer.lineDashPattern = [12, 12] as [NSNumber]
stickerFrameLayer.lineCap = .round
self.stickerFrameLayer = stickerFrameLayer
self.previewContainerView.layer.addSublayer(stickerFrameLayer)
}
self.previewContainerView.addSubview(self.selectionContainerView)
self.subjectDisposable = (
@ -4408,8 +4429,25 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
transition.setFrame(view: self.selectionContainerView, frame: CGRect(origin: .zero, size: previewFrame.size))
let stickerFrameWidth = floor(previewSize.width * 0.97)
transition.setFrame(view: self.stickerTransparentView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((previewSize.width - stickerFrameWidth) / 2.0), y: floorToScreenPixels((previewSize.height - stickerFrameWidth) / 2.0)), size: CGSize(width: stickerFrameWidth, height: stickerFrameWidth)))
self.stickerTransparentView.layer.cornerRadius = stickerFrameWidth / 8.0
if let stickerBackgroundView = self.stickerBackgroundView, let stickerOverlayLayer = self.stickerOverlayLayer, let stickerFrameLayer = self.stickerFrameLayer {
stickerOverlayLayer.frame = CGRect(origin: .zero, size: previewSize)
let stickerFrameRect = CGRect(origin: CGPoint(x: floor((previewSize.width - stickerFrameWidth) / 2.0), y: floor((previewSize.height - stickerFrameWidth) / 2.0)), size: CGSize(width: stickerFrameWidth, height: stickerFrameWidth))
let overlayOuterRect = UIBezierPath(rect: CGRect(origin: .zero, size: previewSize))
let overlayInnerRect = UIBezierPath(cgPath: CGPath(roundedRect: stickerFrameRect, cornerWidth: stickerFrameWidth / 8.0, cornerHeight: stickerFrameWidth / 8.0, transform: nil))
let overlayLineWidth: CGFloat = 2.0 - UIScreenPixel
overlayOuterRect.append(overlayInnerRect)
overlayOuterRect.usesEvenOddFillRule = true
stickerOverlayLayer.path = overlayOuterRect.cgPath
stickerFrameLayer.frame = stickerOverlayLayer.frame
stickerFrameLayer.lineWidth = overlayLineWidth
stickerFrameLayer.path = CGPath(roundedRect: stickerFrameRect.insetBy(dx: -overlayLineWidth / 2.0, dy: -overlayLineWidth / 2.0), cornerWidth: stickerFrameWidth / 8.0 * 1.02, cornerHeight: stickerFrameWidth / 8.0 * 1.02, transform: nil)
transition.setFrame(view: stickerBackgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((previewSize.width - stickerFrameWidth) / 2.0), y: floorToScreenPixels((previewSize.height - stickerFrameWidth) / 2.0)), size: CGSize(width: stickerFrameWidth, height: stickerFrameWidth)))
stickerBackgroundView.layer.cornerRadius = stickerFrameWidth / 8.0
}
self.interaction?.containerLayoutUpdated(layout: layout, transition: transition)
@ -4558,11 +4596,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
init(
media: MediaResult?,
mediaAreas: [MediaArea],
caption: NSAttributedString,
options: MediaEditorResultPrivacy,
stickers: [TelegramMediaFile],
randomId: Int64
mediaAreas: [MediaArea] = [],
caption: NSAttributedString = NSAttributedString(),
options: MediaEditorResultPrivacy = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
stickers: [TelegramMediaFile] = [],
randomId: Int64 = 0
) {
self.media = media
self.mediaAreas = mediaAreas
@ -5717,24 +5755,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let values = mediaEditor.values.withUpdatedQualityPreset(.sticker)
makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: image, dimensions: storyDimensions, outputDimensions: CGSize(width: 512, height: 512), values: values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in
if let self, let resultImage {
// let dimensions = CGSize(width: 512, height: 512)
// let scaledImage = generateImage(dimensions, contextGenerator: { size, context in
// context.clear(CGRect(origin: CGPoint(), size: size))
//
// context.addPath(CGPath(roundedRect: CGRect(origin: .zero, size: size), cornerWidth: size.width / 8.0, cornerHeight: size.width / 8.0, transform: nil))
// context.clip()
//
// let scaledSize = resultImage.size.aspectFilled(size)
// context.draw(resultImage.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - scaledSize.width) / 2.0), y: floor((size.height - scaledSize.height) / 2.0)), size: scaledSize))
// }, opaque: false, scale: 1.0)!
self.presentStickerPreview(image: resultImage)
}
})
}
}
private weak var resultController: PeekController?
func presentStickerPreview(image: UIImage) {
guard let mediaEditor = self.node.mediaEditor else {
return
@ -5745,7 +5772,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
var isVideo = false
if mediaEditor.resultIsVideo {
isVideo = true
self.performSave(toStickerResource: resource)
} else {
Queue.concurrentDefaultQueue().async {
if let data = try? WebP.convert(toWebP: image, quality: 97.0) {
@ -5766,26 +5792,29 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
guard let self else {
return
}
if self.videoExport != nil {
return
}
f(.default)
self.completion(MediaEditorScreen.Result(
media: .sticker(file: file),
mediaAreas: [],
caption: NSAttributedString(),
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
stickers: [],
randomId: 0
), { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss()
Queue.mainQueue().justDispatch {
finished()
}
if isVideo {
self.uploadSticker(file, action: .send)
} else {
self.resultController?.disappeared = nil
self.completion(MediaEditorScreen.Result(
media: .sticker(file: file),
mediaAreas: [],
caption: NSAttributedString(),
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
stickers: [],
randomId: 0
), { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss()
Queue.mainQueue().justDispatch {
finished()
}
})
})
})
}
f(.default)
})))
menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
@ -5861,6 +5890,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
}
Queue.mainQueue().justDispatch {
self.node.entitiesView.selectEntity(nil)
}
let peekController = PeekController(
presentationData: presentationData,
content: StickerPreviewPeekContent(
@ -5875,14 +5908,28 @@ 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.entitiesView.alpha = 0.0
self.node.previewView.alpha = 0.0
}
}
peekController.disappeared = { [weak self] in
if let self {
self.node.entitiesView.alpha = 1.0
self.node.previewView.alpha = 1.0
}
}
self.resultController = peekController
self.present(peekController, in: .window(.root))
}
@ -5891,9 +5938,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
case createStickerPack(title: String)
case addToStickerPack(pack: StickerPackReference, title: String)
case upload
case send
}
private func presentCreateStickerPack(file: TelegramMediaFile, completion: @escaping () -> Void) {
//TODO:localize
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
var dismissImpl: (() -> Void)?
@ -5962,69 +6011,118 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let context = self.context
let dimensions = PixelDimensions(width: 512, height: 512)
let mimeType = file.mimeType
let isVideo = file.mimeType == "video/webm"
self.updateEditProgress(0.0, cancel: { [weak self] in
self?.stickerUploadDisposable.set(nil)
})
enum PrepareStickerStatus {
case progress(Float)
case complete(TelegramMediaResource)
case failed
}
let resourceSignal: Signal<PrepareStickerStatus, UploadStickerError>
if isVideo {
self.performSave(toStickerResource: file.resource)
resourceSignal = self.videoExportPromise.get()
|> castError(UploadStickerError.self)
|> filter { $0 != nil }
|> take(1)
|> mapToSignal { videoExport -> Signal<PrepareStickerStatus, UploadStickerError> in
guard let videoExport else {
return .complete()
}
return videoExport.status
|> castError(UploadStickerError.self)
|> mapToSignal { status -> Signal<PrepareStickerStatus, UploadStickerError> in
switch status {
case .unknown:
return .single(.progress(0.0))
case let .progress(progress):
return .single(.progress(progress))
case .completed:
return .single(.complete(file.resource))
|> delay(0.05, queue: Queue.mainQueue())
case .failed:
return .single(.failed)
}
}
}
} else {
resourceSignal = .single(.complete(file.resource))
}
let signal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> castError(UploadStickerError.self)
|> mapToSignal { peer -> Signal<UploadStickerStatus, UploadStickerError> in
guard let peer else {
return .complete()
}
return context.engine.stickers.uploadSticker(peer: peer._asPeer(), resource: file.resource, alt: "", dimensions: dimensions, mimeType: mimeType)
|> mapToSignal { status -> Signal<UploadStickerStatus, UploadStickerError> in
switch status {
case .progress:
return .single(status)
case let .complete(resource, _):
let file = stickerFile(resource: resource, size: file.size ?? 0, dimensions: dimensions, isVideo: file.mimeType == "video/webm")
switch action {
case .addToFavorites:
return context.engine.stickers.toggleStickerSaved(file: file, saved: true)
|> `catch` { _ -> Signal<SavedStickerResult, UploadStickerError> in
return .fail(.generic)
}
|> map { _ in
return status
}
case let .createStickerPack(title):
let sticker = ImportSticker(
resource: resource,
emojis: ["😀😂"],
dimensions: dimensions,
mimeType: mimeType,
keywords: ""
)
return context.engine.stickers.createStickerSet(title: title, shortName: "", stickers: [sticker], thumbnail: nil, type: .stickers(content: .image), software: nil)
|> `catch` { _ -> Signal<CreateStickerSetStatus, UploadStickerError> in
return .fail(.generic)
}
|> mapToSignal { innerStatus in
if case .complete = innerStatus {
return resourceSignal
|> mapToSignal { result -> Signal<UploadStickerStatus, UploadStickerError> in
switch result {
case .failed:
return .fail(.generic)
case let .progress(progress):
return .single(.progress(progress * 0.5))
case let .complete(resource):
return context.engine.stickers.uploadSticker(peer: peer._asPeer(), resource: resource, alt: "", dimensions: dimensions, mimeType: mimeType)
|> mapToSignal { status -> Signal<UploadStickerStatus, UploadStickerError> in
switch status {
case let .progress(progress):
return .single(.progress(isVideo ? 0.5 + progress * 0.5 : progress))
case let .complete(resource, _):
let file = stickerFile(resource: resource, size: file.size ?? 0, dimensions: dimensions, isVideo: isVideo)
switch action {
case .send:
return .single(status)
case .addToFavorites:
return context.engine.stickers.toggleStickerSaved(file: file, saved: true)
|> `catch` { _ -> Signal<SavedStickerResult, UploadStickerError> in
return .fail(.generic)
}
|> map { _ in
return status
}
case let .createStickerPack(title):
let sticker = ImportSticker(
resource: resource,
emojis: ["😀😂"],
dimensions: dimensions,
mimeType: mimeType,
keywords: ""
)
return context.engine.stickers.createStickerSet(title: title, shortName: "", stickers: [sticker], thumbnail: nil, type: .stickers(content: .image), software: nil)
|> `catch` { _ -> Signal<CreateStickerSetStatus, UploadStickerError> in
return .fail(.generic)
}
|> mapToSignal { innerStatus in
if case .complete = innerStatus {
return .single(status)
} else {
return .complete()
}
}
case let .addToStickerPack(pack, _):
let sticker = ImportSticker(
resource: resource,
emojis: ["😀😂"],
dimensions: dimensions,
mimeType: mimeType,
keywords: ""
)
return context.engine.stickers.addStickerToStickerSet(packReference: pack, sticker: sticker)
|> `catch` { _ -> Signal<Bool, UploadStickerError> in
return .fail(.generic)
}
|> map { _ in
return status
}
case .upload:
return .single(status)
} else {
return .complete()
}
}
case let .addToStickerPack(pack, _):
let sticker = ImportSticker(
resource: resource,
emojis: ["😀😂"],
dimensions: dimensions,
mimeType: mimeType,
keywords: ""
)
return context.engine.stickers.addStickerToStickerSet(packReference: pack, sticker: sticker)
|> `catch` { _ -> Signal<Bool, UploadStickerError> in
return .fail(.generic)
}
|> map { _ in
return status
}
case .upload:
return .single(status)
}
}
}
@ -6038,26 +6136,23 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
switch status {
case let .progress(progress):
self.updateEditProgress(progress, cancel: { [weak self] in
self?.videoExport?.cancel()
self?.videoExport = nil
self?.exportDisposable.set(nil)
self?.stickerUploadDisposable.set(nil)
})
case let .complete(resource, _):
let navigationController = self.navigationController as? NavigationController
let result: MediaEditorScreen.Result
if case .upload = action {
let file = stickerFile(resource: resource, size: resource.size ?? 0, dimensions: dimensions, isVideo: file.mimeType == "video/webm")
result = MediaEditorScreen.Result(
media: .sticker(file: file),
mediaAreas: [],
caption: NSAttributedString(),
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
stickers: [],
randomId: 0
)
} else {
switch action {
case .upload, .send:
let file = stickerFile(resource: resource, size: resource.size ?? 0, dimensions: dimensions, isVideo: isVideo)
result = MediaEditorScreen.Result(media: .sticker(file: file))
default:
result = MediaEditorScreen.Result()
}
self.completion(result, { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
guard let self else {
@ -6095,7 +6190,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}))
}
private var videoExport: MediaEditorVideoExport?
private var videoExport: MediaEditorVideoExport? {
didSet {
self.videoExportPromise.set(.single(self.videoExport))
}
}
private var videoExportPromise = Promise<MediaEditorVideoExport?>(nil)
private var exportDisposable = MetaDisposable()
fileprivate var isSavingAvailable = false
@ -6126,7 +6226,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
let isSticker = toStickerResource != nil
if !isSticker {
self.previousSavedValues = mediaEditor.values
self.isSavingAvailable = false
@ -6155,8 +6254,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
if mediaEditor.resultIsVideo {
mediaEditor.maybePauseVideo()
self.node.entitiesView.pause()
if !isSticker {
mediaEditor.maybePauseVideo()
self.node.entitiesView.pause()
}
let exportSubject: Signal<MediaEditorVideoExport.Subject, NoError>
switch subject {
@ -6241,8 +6342,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
switch status {
case .completed:
self.videoExport = nil
if let toStickerResource, let data = try? Data(contentsOf: URL(fileURLWithPath: outputPath)) {
self.context.account.postbox.mediaBox.storeResourceData(toStickerResource.id, data: data)
if let toStickerResource {
if let data = try? Data(contentsOf: URL(fileURLWithPath: outputPath)) {
self.context.account.postbox.mediaBox.storeResourceData(toStickerResource.id, data: data, synchronous: true)
}
} else {
saveToPhotos(outputPath, true)
self.node.presentSaveTooltip()
@ -6285,16 +6388,18 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
fileprivate func cancelVideoExport() {
if let videoExport = self.videoExport {
self.previousSavedValues = nil
videoExport.cancel()
self.videoExport = nil
self.exportDisposable.set(nil)
self.node.mediaEditor?.play()
self.node.entitiesView.play()
guard let videoExport = self.videoExport else {
return
}
videoExport.cancel()
self.videoExport = nil
self.exportDisposable.set(nil)
self.previousSavedValues = nil
self.node.mediaEditor?.play()
self.node.entitiesView.play()
}
public func updateEditProgress(_ progress: Float, cancel: @escaping () -> Void) {

View File

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

View File

@ -32,6 +32,8 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode {
private var item: PeerInfoScreenCommentItem?
private var presentationData: PresentationData?
private var chevronImage: UIImage?
override init() {
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
@ -66,6 +68,7 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode {
return 10.0
}
let themeUpdated = self.presentationData?.theme !== presentationData.theme
self.item = item
self.presentationData = presentationData
@ -77,10 +80,20 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode {
let textFont = Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize)
let textColor = presentationData.theme.list.freeTextColor
let attributedText = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: presentationData.theme.list.itemAccentColor), linkAttribute: { contents in
var text = item.text
text = text.replacingOccurrences(of: " >]", with: "\u{00A0}>]")
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: presentationData.theme.list.itemAccentColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}))
})).mutableCopy() as! NSMutableAttributedString
if let _ = item.text.range(of: ">]"), let range = attributedText.string.range(of: ">") {
if themeUpdated || self.chevronImage == nil {
self.chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: presentationData.theme.list.itemAccentColor)
}
if let chevronImage = self.chevronImage {
attributedText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedText.string))
}
}
self.textNode.attributedText = attributedText
self.activateArea.accessibilityLabel = attributedText.string

View File

@ -484,6 +484,7 @@ private enum PeerInfoContextSubject {
case link(customLink: String?)
case businessHours(String)
case businessLocation(String)
case birthday
}
private enum PeerInfoSettingsSection {
@ -586,6 +587,7 @@ private final class PeerInfoInteraction {
let openEditing: () -> Void
let updateBirthdate: (TelegramBirthday??) -> Void
let updateIsEditingBirthdate: (Bool) -> Void
let openBioPrivacy: () -> Void
let openBirthdatePrivacy: () -> Void
let openPremiumGift: () -> Void
let editingOpenPersonalChannel: () -> Void
@ -646,6 +648,7 @@ private final class PeerInfoInteraction {
openEditing: @escaping () -> Void,
updateBirthdate: @escaping (TelegramBirthday??) -> Void,
updateIsEditingBirthdate: @escaping (Bool) -> Void,
openBioPrivacy: @escaping () -> Void,
openBirthdatePrivacy: @escaping () -> Void,
openPremiumGift: @escaping () -> Void,
editingOpenPersonalChannel: @escaping () -> Void
@ -705,6 +708,7 @@ private final class PeerInfoInteraction {
self.openEditing = openEditing
self.updateBirthdate = updateBirthdate
self.updateIsEditingBirthdate = updateIsEditingBirthdate
self.openBioPrivacy = openBioPrivacy
self.openBirthdatePrivacy = openBirthdatePrivacy
self.openPremiumGift = openPremiumGift
self.editingOpenPersonalChannel = editingOpenPersonalChannel
@ -1029,7 +1033,9 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
}, action: {
interaction.dismissInput()
}, maxLength: Int(data.globalSettings?.userLimits.maxAboutLength ?? 70)))
items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: presentationData.strings.Settings_About_Help))
items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: presentationData.strings.Settings_About_PrivacyHelp, linkAction: { _ in
interaction.openBioPrivacy()
}))
}
@ -1244,7 +1250,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: {
}))
@ -2677,8 +2687,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
},
updateIsEditingBirthdate: { [weak self] value in
if let self {
if value, let data = self.data?.cachedData as? CachedUserData, data.birthday == nil {
self.state = self.state.withUpdatingBirthDate(TelegramBirthday(day: 1, month: 1, year: nil))
if value {
if let data = self.data?.cachedData as? CachedUserData {
if data.birthday == nil {
self.state = self.state.withUpdatingBirthDate(TelegramBirthday(day: 1, month: 1, year: nil))
} else {
self.state = self.state.withUpdatingBirthDate(nil)
}
}
} else {
if self.state.updatingBirthDate != .some(nil) {
self.state = self.state.withUpdatingBirthDate(nil)
}
}
self.state = self.state.withIsEditingBirthDate(value)
@ -2687,6 +2707,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
}
},
openBioPrivacy: { [weak self] in
if let self {
self.openBioPrivacy()
}
},
openBirthdatePrivacy: { [weak self] in
if let self {
self.openBirthdatePrivacy()
@ -7581,6 +7606,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
})
}
private func openBioPrivacy() {
guard let _ = self.data?.globalSettings?.privacySettings else {
return
}
self.context.sharedContext.makeBioPrivacyController(context: self.context, settings: self.privacySettings, present: { [weak self] c in
self?.controller?.push(c)
})
}
private func openBirthdatePrivacy() {
guard let _ = self.data?.globalSettings?.privacySettings else {
return
@ -7996,6 +8030,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 {

View File

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

View File

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

View File

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

View File

@ -1845,6 +1845,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public func makePrivacyAndSecurityController(context: AccountContext) -> ViewController {
return SettingsUI.makePrivacyAndSecurityController(context: context)
}
public func makeBioPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, present: @escaping (ViewController) -> Void) {
SettingsUI.makeBioPrivacyController(context: context, settings: settings, present: present)
}
public func makeBirthdayPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void) {
SettingsUI.makeBirthdayPrivacyController(context: context, settings: settings, openedFromBirthdayScreen: openedFromBirthdayScreen, present: present)