Various fixes

This commit is contained in:
Ilya Laktyushin 2024-03-25 01:09:45 +04:00
parent 75070e2aa7
commit b7c18980f6
12 changed files with 225 additions and 137 deletions

View File

@ -266,23 +266,19 @@ final class PeekControllerNode: ViewControllerTracingNode {
} }
func animateIn(from rect: CGRect) { func animateIn(from rect: CGRect) {
if let appeared = self.controller?.appeared {
appeared()
} else {
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.blurView.layer.animateAlpha(from: 0.0, to: self.blurView.alpha, duration: 0.3) self.blurView.layer.animateAlpha(from: 0.0, to: self.blurView.alpha, duration: 0.3)
let offset = CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y) 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: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true)
if rect.width > 10.0 { if let appeared = self.controller?.appeared {
appeared()
let scale = rect.width / self.contentNode.frame.width 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) 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 { } 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.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 { if let topAccessoryNode = self.topAccessoryNode {
@ -322,12 +318,8 @@ final class PeekControllerNode: ViewControllerTracingNode {
outCompletion() outCompletion()
completion() completion()
}) })
if let _ = self.controller?.disappeared { if let _ = self.controller?.disappeared {
} else {
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
if rect.width > 10.0 {
let scale = rect.width / self.contentNode.frame.width let scale = rect.width / self.contentNode.frame.width
self.containerNode.layer.animateScale(from: 1.0, to: scale, duration: 0.25, removeOnCompletion: false, completion: { _ in self.containerNode.layer.animateScale(from: 1.0, to: scale, duration: 0.25, removeOnCompletion: false, completion: { _ in
scaleCompleted = true scaleCompleted = true
@ -338,6 +330,7 @@ final class PeekControllerNode: ViewControllerTracingNode {
scaleCompleted = true scaleCompleted = true
outCompletion() outCompletion()
}) })
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
} }
if !self.actionsStackNode.alpha.isZero { if !self.actionsStackNode.alpha.isZero {

View File

@ -47,6 +47,9 @@
_codecContext->codec_id = AV_CODEC_ID_VP9; _codecContext->codec_id = AV_CODEC_ID_VP9;
_codecContext->codec_type = AVMEDIA_TYPE_VIDEO; _codecContext->codec_type = AVMEDIA_TYPE_VIDEO;
_codecContext->pix_fmt = AV_PIX_FMT_YUVA420P; _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->width = width;
_codecContext->height = height; _codecContext->height = height;
_codecContext->time_base = (AVRational){1, framerate}; _codecContext->time_base = (AVRational){1, framerate};
@ -96,6 +99,9 @@
AVFrame *frameImpl = (AVFrame *)[frame impl]; AVFrame *frameImpl = (AVFrame *)[frame impl];
frameImpl->pts = self.framePts; frameImpl->pts = self.framePts;
frameImpl->color_range = AVCOL_RANGE_MPEG;
frameImpl->color_primaries = AVCOL_PRI_BT709;
frameImpl->colorspace = AVCOL_SPC_BT709;
int sendRet = avcodec_send_frame(_codecContext, frameImpl); int sendRet = avcodec_send_frame(_codecContext, frameImpl);
if (sendRet < 0) { if (sendRet < 0) {

View File

@ -81,6 +81,8 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
private var item: ItemListTextItem? private var item: ItemListTextItem?
private var chevronImage: UIImage?
public var tag: ItemListItemTag? { public var tag: ItemListItemTag? {
return self.item?.tag return self.item?.tag
} }
@ -117,6 +119,8 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
public func asyncLayout() -> (_ item: ItemListTextItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { public func asyncLayout() -> (_ item: ItemListTextItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.textNode) let makeTitleLayout = TextNode.asyncLayout(self.textNode)
let currentChevronImage = self.chevronImage
let currentItem = self.item
return { item, params, neighbors in return { item, params, neighbors in
let leftInset: CGFloat = 15.0 let leftInset: CGFloat = 15.0
@ -127,6 +131,12 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
let largeTitleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize)) let largeTitleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize))
let titleBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize) 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 let attributedText: NSAttributedString
switch item.text { switch item.text {
case let .plain(text): case let .plain(text):
@ -134,9 +144,18 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
case let .large(text): case let .large(text):
attributedText = NSAttributedString(string: text, font: largeTitleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) attributedText = NSAttributedString(string: text, font: largeTitleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
case let .markdown(text): 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) 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())) 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 return (layout, { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item 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.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 strongSelf.activateArea.accessibilityLabel = attributedText.string

View File

@ -1344,7 +1344,7 @@ private func monetizationEntries(
var entries: [StatsEntry] = [] var entries: [StatsEntry] = []
//TODO:localize //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)")) entries.append(.adsImpressionsTitle(presentationData.theme, "AD IMPRESSIONS (BY HOURS)"))
if !stats.topHoursGraph.isEmpty { if !stats.topHoursGraph.isEmpty {
@ -1361,7 +1361,7 @@ private func monetizationEntries(
entries.append(.adsBalanceTitle(presentationData.theme, "AVAILABLE BALANCE")) entries.append(.adsBalanceTitle(presentationData.theme, "AVAILABLE BALANCE"))
entries.append(.adsBalance(presentationData.theme, data, false, diamond, state.monetizationAddress)) 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")) entries.append(.adsTransactionsTitle(presentationData.theme, "TRANSACTION HISTORY"))

View File

@ -7,7 +7,7 @@
extern "C" { extern "C" {
#endif #endif
void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow, bool keepColorsOrder); void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow, bool restrictedRange, bool keepColorsOrder);
void combineYUVAPlanesIntoARGB(uint8_t *argb, uint8_t const *inY, uint8_t const *inU, uint8_t const *inV, uint8_t const *inA, int width, int height, int bytesPerRow); void 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); void scaleImagePlane(uint8_t *outPlane, int outWidth, int outHeight, int outBytesPerRow, uint8_t const *inPlane, int inWidth, int inHeight, int inBytesPerRow);

View File

@ -6,12 +6,15 @@
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 }; static uint8_t invertedPermuteMap[4] = { 3, 0, 1, 2 };
void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow, bool keepColorsOrder) { void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow, bool restrictedRange, bool keepColorsOrder) {
static vImage_ARGBToYpCbCr info; static vImage_ARGBToYpCbCr info;
static vImage_ARGBToYpCbCr restrictedInfo;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
vImage_YpCbCrPixelRange pixelRange = (vImage_YpCbCrPixelRange){ 0, 128, 255, 255, 255, 1, 255, 0 }; 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, &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; vImage_Error error = kvImageNoError;
@ -46,7 +49,7 @@ void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU,
destA.height = height; destA.height = height;
destA.rowBytes = width; destA.rowBytes = width;
error = vImageConvert_ARGB8888To420Yp8_Cb8_Cr8(&src, &destYp, &destCb, &destCr, &info, keepColorsOrder ? invertedPermuteMap : permuteMap, kvImageDoNotTile); error = vImageConvert_ARGB8888To420Yp8_Cb8_Cr8(&src, &destYp, &destCb, &destCr, restrictedRange ? &restrictedInfo : &info, keepColorsOrder ? invertedPermuteMap : permuteMap, kvImageDoNotTile);
if (error != kvImageNoError) { if (error != kvImageNoError) {
return; return;
} }

View File

@ -366,6 +366,7 @@ extension ImageARGB {
Int32(self.argbPlane.width), Int32(self.argbPlane.width),
Int32(self.argbPlane.height), Int32(self.argbPlane.height),
Int32(self.argbPlane.bytesPerRow), Int32(self.argbPlane.bytesPerRow),
false,
false false
) )
} }

View File

@ -152,7 +152,7 @@ final class MediaEditorComposer {
if var compositedImage { if var compositedImage {
let scale = self.outputDimensions.width / compositedImage.extent.width let scale = self.outputDimensions.width / compositedImage.extent.width
compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale)) compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale))
self.ciContext?.render(compositedImage, to: pixelBuffer) self.ciContext?.render(compositedImage, to: pixelBuffer)
completion(pixelBuffer) completion(pixelBuffer)
} else { } else {
@ -164,7 +164,6 @@ final class MediaEditorComposer {
} }
completion(nil) completion(nil)
} }
private var isFirst = true
private var cachedTexture: MTLTexture? private var cachedTexture: MTLTexture?
func textureForImage(_ image: UIImage) -> MTLTexture? { func textureForImage(_ image: UIImage) -> MTLTexture? {

View File

@ -109,6 +109,7 @@ final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter {
width, width,
height, height,
bytesPerRow, bytesPerRow,
true,
true true
) )

View File

@ -4596,11 +4596,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
init( init(
media: MediaResult?, media: MediaResult?,
mediaAreas: [MediaArea], mediaAreas: [MediaArea] = [],
caption: NSAttributedString, caption: NSAttributedString = NSAttributedString(),
options: MediaEditorResultPrivacy, options: MediaEditorResultPrivacy = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
stickers: [TelegramMediaFile], stickers: [TelegramMediaFile] = [],
randomId: Int64 randomId: Int64 = 0
) { ) {
self.media = media self.media = media
self.mediaAreas = mediaAreas self.mediaAreas = mediaAreas
@ -5755,24 +5755,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let values = mediaEditor.values.withUpdatedQualityPreset(.sticker) 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 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 { 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) self.presentStickerPreview(image: resultImage)
} }
}) })
} }
} }
private weak var resultController: PeekController?
func presentStickerPreview(image: UIImage) { func presentStickerPreview(image: UIImage) {
guard let mediaEditor = self.node.mediaEditor else { guard let mediaEditor = self.node.mediaEditor else {
return return
@ -5783,7 +5772,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
var isVideo = false var isVideo = false
if mediaEditor.resultIsVideo { if mediaEditor.resultIsVideo {
isVideo = true isVideo = true
self.performSave(toStickerResource: resource)
} else { } else {
Queue.concurrentDefaultQueue().async { Queue.concurrentDefaultQueue().async {
if let data = try? WebP.convert(toWebP: image, quality: 97.0) { if let data = try? WebP.convert(toWebP: image, quality: 97.0) {
@ -5804,26 +5792,29 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
guard let self else { guard let self else {
return return
} }
if self.videoExport != nil {
return
}
f(.default)
self.completion(MediaEditorScreen.Result( if isVideo {
media: .sticker(file: file), self.uploadSticker(file, action: .send)
mediaAreas: [], } else {
caption: NSAttributedString(), self.resultController?.disappeared = nil
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false), self.completion(MediaEditorScreen.Result(
stickers: [], media: .sticker(file: file),
randomId: 0 mediaAreas: [],
), { [weak self] finished in caption: NSAttributedString(),
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
self?.dismiss() stickers: [],
Queue.mainQueue().justDispatch { randomId: 0
finished() ), { [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 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) f(.default)
@ -5899,6 +5890,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
} }
Queue.mainQueue().justDispatch {
self.node.entitiesView.selectEntity(nil)
}
let peekController = PeekController( let peekController = PeekController(
presentationData: presentationData, presentationData: presentationData,
content: StickerPreviewPeekContent( content: StickerPreviewPeekContent(
@ -5934,6 +5929,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.node.previewView.alpha = 1.0 self.node.previewView.alpha = 1.0
} }
} }
self.resultController = peekController
self.present(peekController, in: .window(.root)) self.present(peekController, in: .window(.root))
} }
@ -5942,6 +5938,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
case createStickerPack(title: String) case createStickerPack(title: String)
case addToStickerPack(pack: StickerPackReference, title: String) case addToStickerPack(pack: StickerPackReference, title: String)
case upload case upload
case send
} }
private func presentCreateStickerPack(file: TelegramMediaFile, completion: @escaping () -> Void) { private func presentCreateStickerPack(file: TelegramMediaFile, completion: @escaping () -> Void) {
@ -6014,69 +6011,118 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let context = self.context let context = self.context
let dimensions = PixelDimensions(width: 512, height: 512) let dimensions = PixelDimensions(width: 512, height: 512)
let mimeType = file.mimeType let mimeType = file.mimeType
let isVideo = file.mimeType == "video/webm"
self.updateEditProgress(0.0, cancel: { [weak self] in self.updateEditProgress(0.0, cancel: { [weak self] in
self?.stickerUploadDisposable.set(nil) 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)) let signal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> castError(UploadStickerError.self) |> castError(UploadStickerError.self)
|> mapToSignal { peer -> Signal<UploadStickerStatus, UploadStickerError> in |> mapToSignal { peer -> Signal<UploadStickerStatus, UploadStickerError> in
guard let peer else { guard let peer else {
return .complete() return .complete()
} }
return context.engine.stickers.uploadSticker(peer: peer._asPeer(), resource: file.resource, alt: "", dimensions: dimensions, mimeType: mimeType) return resourceSignal
|> mapToSignal { status -> Signal<UploadStickerStatus, UploadStickerError> in |> mapToSignal { result -> Signal<UploadStickerStatus, UploadStickerError> in
switch status { switch result {
case .progress: case .failed:
return .single(status) return .fail(.generic)
case let .complete(resource, _): case let .progress(progress):
let file = stickerFile(resource: resource, size: file.size ?? 0, dimensions: dimensions, isVideo: file.mimeType == "video/webm") return .single(.progress(progress * 0.5))
switch action { case let .complete(resource):
case .addToFavorites: return context.engine.stickers.uploadSticker(peer: peer._asPeer(), resource: resource, alt: "", dimensions: dimensions, mimeType: mimeType)
return context.engine.stickers.toggleStickerSaved(file: file, saved: true) |> mapToSignal { status -> Signal<UploadStickerStatus, UploadStickerError> in
|> `catch` { _ -> Signal<SavedStickerResult, UploadStickerError> in switch status {
return .fail(.generic) case let .progress(progress):
} return .single(.progress(isVideo ? 0.5 + progress * 0.5 : progress))
|> map { _ in case let .complete(resource, _):
return status let file = stickerFile(resource: resource, size: file.size ?? 0, dimensions: dimensions, isVideo: isVideo)
} switch action {
case let .createStickerPack(title): case .send:
let sticker = ImportSticker( return .single(status)
resource: resource, case .addToFavorites:
emojis: ["😀😂"], return context.engine.stickers.toggleStickerSaved(file: file, saved: true)
dimensions: dimensions, |> `catch` { _ -> Signal<SavedStickerResult, UploadStickerError> in
mimeType: mimeType, return .fail(.generic)
keywords: "" }
) |> map { _ in
return context.engine.stickers.createStickerSet(title: title, shortName: "", stickers: [sticker], thumbnail: nil, type: .stickers(content: .image), software: nil) return status
|> `catch` { _ -> Signal<CreateStickerSetStatus, UploadStickerError> in }
return .fail(.generic) case let .createStickerPack(title):
} let sticker = ImportSticker(
|> mapToSignal { innerStatus in resource: resource,
if case .complete = innerStatus { 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) 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)
} }
} }
} }
@ -6090,26 +6136,23 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
switch status { switch status {
case let .progress(progress): case let .progress(progress):
self.updateEditProgress(progress, cancel: { [weak self] in self.updateEditProgress(progress, cancel: { [weak self] in
self?.videoExport?.cancel()
self?.videoExport = nil
self?.exportDisposable.set(nil)
self?.stickerUploadDisposable.set(nil) self?.stickerUploadDisposable.set(nil)
}) })
case let .complete(resource, _): case let .complete(resource, _):
let navigationController = self.navigationController as? NavigationController let navigationController = self.navigationController as? NavigationController
let result: MediaEditorScreen.Result let result: MediaEditorScreen.Result
if case .upload = action { switch action {
let file = stickerFile(resource: resource, size: resource.size ?? 0, dimensions: dimensions, isVideo: file.mimeType == "video/webm") case .upload, .send:
result = MediaEditorScreen.Result( let file = stickerFile(resource: resource, size: resource.size ?? 0, dimensions: dimensions, isVideo: isVideo)
media: .sticker(file: file), result = MediaEditorScreen.Result(media: .sticker(file: file))
mediaAreas: [], default:
caption: NSAttributedString(),
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
stickers: [],
randomId: 0
)
} else {
result = MediaEditorScreen.Result() result = MediaEditorScreen.Result()
} }
self.completion(result, { [weak self] finished in self.completion(result, { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
guard let self else { guard let self else {
@ -6147,7 +6190,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
})) }))
} }
private var videoExport: MediaEditorVideoExport? private var videoExport: MediaEditorVideoExport? {
didSet {
self.videoExportPromise.set(.single(self.videoExport))
}
}
private var videoExportPromise = Promise<MediaEditorVideoExport?>(nil)
private var exportDisposable = MetaDisposable() private var exportDisposable = MetaDisposable()
fileprivate var isSavingAvailable = false fileprivate var isSavingAvailable = false
@ -6178,7 +6226,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities) mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
let isSticker = toStickerResource != nil let isSticker = toStickerResource != nil
if !isSticker { if !isSticker {
self.previousSavedValues = mediaEditor.values self.previousSavedValues = mediaEditor.values
self.isSavingAvailable = false self.isSavingAvailable = false
@ -6207,8 +6254,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
if mediaEditor.resultIsVideo { if mediaEditor.resultIsVideo {
mediaEditor.maybePauseVideo() if !isSticker {
self.node.entitiesView.pause() mediaEditor.maybePauseVideo()
self.node.entitiesView.pause()
}
let exportSubject: Signal<MediaEditorVideoExport.Subject, NoError> let exportSubject: Signal<MediaEditorVideoExport.Subject, NoError>
switch subject { switch subject {
@ -6293,8 +6342,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
switch status { switch status {
case .completed: case .completed:
self.videoExport = nil self.videoExport = nil
if let toStickerResource, let data = try? Data(contentsOf: URL(fileURLWithPath: outputPath)) { if let toStickerResource {
self.context.account.postbox.mediaBox.storeResourceData(toStickerResource.id, data: data) if let data = try? Data(contentsOf: URL(fileURLWithPath: outputPath)) {
self.context.account.postbox.mediaBox.storeResourceData(toStickerResource.id, data: data, synchronous: true)
}
} else { } else {
saveToPhotos(outputPath, true) saveToPhotos(outputPath, true)
self.node.presentSaveTooltip() self.node.presentSaveTooltip()
@ -6337,16 +6388,18 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
fileprivate func cancelVideoExport() { fileprivate func cancelVideoExport() {
if let videoExport = self.videoExport { guard let videoExport = self.videoExport else {
self.previousSavedValues = nil return
videoExport.cancel()
self.videoExport = nil
self.exportDisposable.set(nil)
self.node.mediaEditor?.play()
self.node.entitiesView.play()
} }
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) { public func updateEditProgress(_ progress: Float, cancel: @escaping () -> Void) {

View File

@ -80,10 +80,12 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode {
let textFont = Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize) let textFont = Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize)
let textColor = presentationData.theme.list.freeTextColor 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) return (TelegramTextAttributes.URL, contents)
})).mutableCopy() as! NSMutableAttributedString })).mutableCopy() as! NSMutableAttributedString
if let range = attributedText.string.range(of: ">") { if let _ = item.text.range(of: ">]"), let range = attributedText.string.range(of: ">") {
if themeUpdated || self.chevronImage == nil { if themeUpdated || self.chevronImage == nil {
self.chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: presentationData.theme.list.itemAccentColor) self.chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: presentationData.theme.list.itemAccentColor)
} }

View File

@ -2678,8 +2678,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, },
updateIsEditingBirthdate: { [weak self] value in updateIsEditingBirthdate: { [weak self] value in
if let self { if let self {
if value, let data = self.data?.cachedData as? CachedUserData, data.birthday == nil { if value {
self.state = self.state.withUpdatingBirthDate(TelegramBirthday(day: 1, month: 1, year: nil)) 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) self.state = self.state.withIsEditingBirthDate(value)