Various fixes

This commit is contained in:
Ilya Laktyushin 2023-07-25 22:37:45 +02:00
parent f4fc72b762
commit ab6b0140cd
17 changed files with 379 additions and 77 deletions

View File

@ -9728,3 +9728,6 @@ Sorry for the inconvenience.";
"Story.Editor.TooltipPremiumCaptionEntities" = "Subscribe to [Telegram Premium]() to add links and formatting in captions to your stories."; "Story.Editor.TooltipPremiumCaptionEntities" = "Subscribe to [Telegram Premium]() to add links and formatting in captions to your stories.";
"Story.Context.TooltipPremiumSaveStories" = "Subscribe to [Telegram Premium]() to save other people's unprotected stories to your Gallery."; "Story.Context.TooltipPremiumSaveStories" = "Subscribe to [Telegram Premium]() to save other people's unprotected stories to your Gallery.";
"Story.Privacy.GroupTooLarge" = "Group Too Large";
"Story.Privacy.GroupParticipantsLimit" = "You can select groups that are up to 200 members.";

View File

@ -651,7 +651,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
if let selectionView = entityView.makeSelectionView() { if let selectionView = entityView.makeSelectionView() {
selectionView.tapped = { [weak self, weak entityView] in selectionView.tapped = { [weak self, weak entityView] in
if let self, let entityView = entityView { if let self, let entityView {
let entityViews = self.subviews.filter { $0 is DrawingEntityView } let entityViews = self.subviews.filter { $0 is DrawingEntityView }
self.requestedMenuForEntityView(entityView, entityViews.last === entityView) self.requestedMenuForEntityView(entityView, entityViews.last === entityView)
} }

View File

@ -2978,8 +2978,6 @@ public final class DrawingToolsInteraction {
private var isActive = false private var isActive = false
private var validLayout: ContainerViewLayout? private var validLayout: ContainerViewLayout?
private let startTimestamp = CACurrentMediaTime()
public init( public init(
context: AccountContext, context: AccountContext,
drawingView: DrawingView, drawingView: DrawingView,

View File

@ -58,13 +58,9 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
self.textView.delegate = self self.textView.delegate = self
self.addSubview(self.textView) self.addSubview(self.textView)
self.emojiViewProvider = { [weak self] emoji in self.emojiViewProvider = { emoji in
guard let strongSelf = self else {
return UIView()
}
let pointSize: CGFloat = 128.0 let pointSize: CGFloat = 128.0
return EmojiTextAttachmentView(context: context, userLocation: .other, emoji: emoji, file: emoji.file, cache: strongSelf.context.animationCache, renderer: strongSelf.context.animationRenderer, placeholderColor: UIColor.white.withAlphaComponent(0.12), pointSize: CGSize(width: pointSize, height: pointSize)) return EmojiTextAttachmentView(context: context, userLocation: .other, emoji: emoji, file: emoji.file, cache: context.animationCache, renderer: context.animationRenderer, placeholderColor: UIColor.white.withAlphaComponent(0.12), pointSize: CGSize(width: pointSize, height: pointSize))
} }
self.textView.onPaste = { [weak self] in self.textView.onPaste = { [weak self] in
@ -285,9 +281,10 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
} }
self._isEditing = false self._isEditing = false
self.textView.resignFirstResponder()
self.textView.inputView = nil self.textView.inputView = nil
self.textView.inputAccessoryView = nil self.textView.inputAccessoryView = nil
self.textView.reloadInputViews()
self.textView.resignFirstResponder()
self.textView.isEditable = false self.textView.isEditable = false
self.textView.isSelectable = false self.textView.isSelectable = false
@ -656,8 +653,8 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
self.updateEditingPosition(animated: animated) self.updateEditingPosition(animated: animated)
} }
self.textView.onLayoutUpdate = { self.textView.onLayoutUpdate = { [weak self] in
self.updateEntities() self?.updateEntities()
} }
super.update(animated: animated) super.update(animated: animated)
@ -1026,12 +1023,17 @@ private class DrawingTextLayoutManager: NSLayoutManager {
self.enumerateLineFragments(forGlyphRange: NSRange(location: 0, length: ((self.textStorage?.string ?? "") as NSString).length)) { rect, usedRect, textContainer, glyphRange, _ in self.enumerateLineFragments(forGlyphRange: NSRange(location: 0, length: ((self.textStorage?.string ?? "") as NSString).length)) { rect, usedRect, textContainer, glyphRange, _ in
var ignoreRange = false var ignoreRange = false
let charecterRange = self.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil) let characterRange = self.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
let substring = ((self.textStorage?.string ?? "") as NSString).substring(with: charecterRange) let substring = ((self.textStorage?.string ?? "") as NSString).substring(with: characterRange)
if substring.trimmingCharacters(in: .newlines).isEmpty { if substring.trimmingCharacters(in: .newlines).isEmpty {
ignoreRange = true ignoreRange = true
} }
var usedRect = usedRect
if substring.hasSuffix(" ") {
usedRect.size.width -= floorToScreenPixels(usedRect.height * 0.145)
}
if !ignoreRange { if !ignoreRange {
let newRect = CGRect(origin: CGPoint(x: usedRect.minX - self.frameWidthInset, y: usedRect.minY), size: CGSize(width: usedRect.width + self.frameWidthInset * 2.0, height: usedRect.height)) let newRect = CGRect(origin: CGPoint(x: usedRect.minX - self.frameWidthInset, y: usedRect.minY), size: CGSize(width: usedRect.width + self.frameWidthInset * 2.0, height: usedRect.height))
self.rectArray.append(newRect) self.rectArray.append(newRect)

View File

@ -579,12 +579,13 @@ final class TextSettingsComponent: CombinedComponent {
) )
} }
let presentFontPicker = component.presentFontPicker
let font = font.update( let font = font.update(
component: TextFontComponent( component: TextFontComponent(
selectedValue: component.font, selectedValue: component.font,
tag: component.fontTag, tag: component.fontTag,
tapped: { tapped: {
component.presentFontPicker() presentFontPicker()
} }
), ),
availableSize: CGSize(width: fontAvailableWidth, height: 30.0), availableSize: CGSize(width: fontAvailableWidth, height: 30.0),

View File

@ -53,10 +53,10 @@ enum MessageContentToUpload {
} }
func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, message: Message) -> MessageContentToUpload { func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, message: Message) -> MessageContentToUpload {
return messageContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: false, peerId: message.id.peerId, messageId: message.id, attributes: message.attributes, text: message.text, media: message.media) return messageContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: false, forceNoBigParts: false, peerId: message.id.peerId, messageId: message.id, attributes: message.attributes, text: message.text, media: message.media)
} }
func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, peerId: PeerId, messageId: MessageId?, attributes: [MessageAttribute], text: String, media: [Media]) -> MessageContentToUpload { func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, forceNoBigParts: Bool, peerId: PeerId, messageId: MessageId?, attributes: [MessageAttribute], text: String, media: [Media]) -> MessageContentToUpload {
var contextResult: OutgoingChatContextResultMessageAttribute? var contextResult: OutgoingChatContextResultMessageAttribute?
var autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute? var autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?
var autoclearMessageAttribute: AutoclearTimeoutMessageAttribute? var autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?
@ -96,14 +96,14 @@ func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Po
return .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaStory(userId: inputUser, id: media.storyId.id), ""), reuploadInfo: nil, cacheReferenceKey: nil)) return .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaStory(userId: inputUser, id: media.storyId.id), ""), reuploadInfo: nil, cacheReferenceKey: nil))
} }
|> castError(PendingMessageUploadError.self), .text) |> castError(PendingMessageUploadError.self), .text)
} else if let media = media.first, let mediaResult = mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, peerId: peerId, media: media, text: text, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, messageId: messageId, attributes: attributes) { } else if let media = media.first, let mediaResult = mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, forceNoBigParts: forceNoBigParts, peerId: peerId, media: media, text: text, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, messageId: messageId, attributes: attributes) {
return .signal(mediaResult, .media) return .signal(mediaResult, .media)
} else { } else {
return .signal(.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil, cacheReferenceKey: nil))), .text) return .signal(.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil, cacheReferenceKey: nil))), .text)
} }
} }
func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, peerId: PeerId, media: Media, text: String, autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?, messageId: MessageId?, attributes: [MessageAttribute]) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? { func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, forceNoBigParts: Bool, peerId: PeerId, media: Media, text: String, autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?, messageId: MessageId?, attributes: [MessageAttribute]) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? {
if let image = media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { if let image = media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) {
if peerId.namespace == Namespaces.Peer.SecretChat, let resource = largest.resource as? SecretFileMediaResource { if peerId.namespace == Namespaces.Peer.SecretChat, let resource = largest.resource as? SecretFileMediaResource {
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(.inputEncryptedFile(id: resource.fileId, accessHash: resource.accessHash), resource.decryptedSize, resource.key), reuploadInfo: nil, cacheReferenceKey: nil))) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(.inputEncryptedFile(id: resource.fileId, accessHash: resource.accessHash), resource.decryptedSize, resource.key), reuploadInfo: nil, cacheReferenceKey: nil)))
@ -123,7 +123,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
} }
} }
} }
return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: true, isGrouped: isGrouped, passFetchProgress: false, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file) return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: true, isGrouped: isGrouped, passFetchProgress: false, forceNoBigParts: false, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file)
} else { } else {
if forceReupload { if forceReupload {
let mediaReference: AnyMediaReference let mediaReference: AnyMediaReference
@ -157,7 +157,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: emojiSearchQuery), text), reuploadInfo: nil, cacheReferenceKey: nil))) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: emojiSearchQuery), text), reuploadInfo: nil, cacheReferenceKey: nil)))
} }
} else { } else {
return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file) return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, forceNoBigParts: forceNoBigParts, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file)
} }
} else if let contact = media as? TelegramMediaContact { } else if let contact = media as? TelegramMediaContact {
let input = Api.InputMedia.inputMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName, vcard: contact.vCardData ?? "") let input = Api.InputMedia.inputMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName, vcard: contact.vCardData ?? "")
@ -619,8 +619,8 @@ private enum UploadedMediaFileAndThumbnail {
case done(TelegramMediaFile, UploadedMediaThumbnailResult) case done(TelegramMediaFile, UploadedMediaThumbnailResult)
} }
private func uploadedThumbnail(network: Network, postbox: Postbox, resourceReference: MediaResourceReference) -> Signal<Api.InputFile?, PendingMessageUploadError> { private func uploadedThumbnail(network: Network, postbox: Postbox, resourceReference: MediaResourceReference, forceNoBigParts: Bool = false) -> Signal<Api.InputFile?, PendingMessageUploadError> {
return multipartUpload(network: network, postbox: postbox, source: .resource(resourceReference), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false) return multipartUpload(network: network, postbox: postbox, source: .resource(resourceReference), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: forceNoBigParts)
|> mapError { _ -> PendingMessageUploadError in return .generic } |> mapError { _ -> PendingMessageUploadError in return .generic }
|> mapToSignal { result -> Signal<Api.InputFile?, PendingMessageUploadError> in |> mapToSignal { result -> Signal<Api.InputFile?, PendingMessageUploadError> in
switch result { switch result {
@ -656,7 +656,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA
return .file return .file
} }
private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], file: TelegramMediaFile) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> { private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, forceNoBigParts: Bool, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], file: TelegramMediaFile) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource, forceRefresh: forceReupload) return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource, forceRefresh: forceReupload)
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
var referenceKey: CachedSentMediaReferenceKey? var referenceKey: CachedSentMediaReferenceKey?
@ -781,7 +781,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
fileReference = .standalone(media: media) fileReference = .standalone(media: media)
} }
return uploadedThumbnail(network: network, postbox: postbox, resourceReference: fileReference.resourceReference(smallestThumbnail.resource)) return uploadedThumbnail(network: network, postbox: postbox, resourceReference: fileReference.resourceReference(smallestThumbnail.resource), forceNoBigParts: forceNoBigParts)
|> mapError { _ -> PendingMessageUploadError in return .generic } |> mapError { _ -> PendingMessageUploadError in return .generic }
|> map { result in |> map { result in
if let result = result { if let result = result {

View File

@ -59,7 +59,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
case let .update(media): case let .update(media):
let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in
let augmentedMedia = augmentMediaWithReference(media) let augmentedMedia = augmentMediaWithReference(media)
return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: []) return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, forceNoBigParts: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: [])
} }
if let uploadSignal = generateUploadSignal(forceReupload) { if let uploadSignal = generateUploadSignal(forceReupload) {
uploadedMedia = .single(.progress(0.027)) uploadedMedia = .single(.progress(0.027))

View File

@ -688,6 +688,7 @@ private func uploadedStoryContent(postbox: Postbox, network: Network, media: Med
forceReupload: true, forceReupload: true,
isGrouped: false, isGrouped: false,
passFetchProgress: passFetchProgress, passFetchProgress: passFetchProgress,
forceNoBigParts: true,
peerId: accountPeerId, peerId: accountPeerId,
messageId: nil, messageId: nil,
attributes: attributes, attributes: attributes,

View File

@ -1651,10 +1651,16 @@ public class CameraScreen: ViewController {
guard let camera = self.camera else { guard let camera = self.camera else {
return return
} }
let location = gestureRecognizer.location(in: gestureRecognizer.view)
if self.cameraState.isDualCameraEnabled && self.additionalPreviewContainerView.frame.contains(location) {
self.toggleCameraPositionAction.invoke(Void())
} else {
let location = gestureRecognizer.location(in: self.mainPreviewView) let location = gestureRecognizer.location(in: self.mainPreviewView)
let point = self.mainPreviewView.cameraPoint(for: location) let point = self.mainPreviewView.cameraPoint(for: location)
camera.focus(at: point, autoFocus: false) camera.focus(at: point, autoFocus: false)
} }
}
@objc private func handleDoubleTap(_ gestureRecognizer: UITapGestureRecognizer) { @objc private func handleDoubleTap(_ gestureRecognizer: UITapGestureRecognizer) {
self.toggleCameraPositionAction.invoke(Void()) self.toggleCameraPositionAction.invoke(Void())

View File

@ -42,6 +42,7 @@ final class MediaEditorComposer {
private let values: MediaEditorValues private let values: MediaEditorValues
private let dimensions: CGSize private let dimensions: CGSize
private let outputDimensions: CGSize private let outputDimensions: CGSize
private let textScale: CGFloat
private let ciContext: CIContext? private let ciContext: CIContext?
private var textureCache: CVMetalTextureCache? private var textureCache: CVMetalTextureCache?
@ -53,10 +54,11 @@ final class MediaEditorComposer {
private let drawingImage: CIImage? private let drawingImage: CIImage?
private var entities: [MediaEditorComposerEntity] private var entities: [MediaEditorComposerEntity]
init(account: Account, values: MediaEditorValues, dimensions: CGSize, outputDimensions: CGSize) { init(account: Account, values: MediaEditorValues, dimensions: CGSize, outputDimensions: CGSize, textScale: CGFloat) {
self.values = values self.values = values
self.dimensions = dimensions self.dimensions = dimensions
self.outputDimensions = outputDimensions self.outputDimensions = outputDimensions
self.textScale = textScale
let colorSpace = CGColorSpaceCreateDeviceRGB() let colorSpace = CGColorSpaceCreateDeviceRGB()
self.colorSpace = colorSpace self.colorSpace = colorSpace
@ -77,7 +79,7 @@ final class MediaEditorComposer {
var entities: [MediaEditorComposerEntity] = [] var entities: [MediaEditorComposerEntity] = []
for entity in values.entities { for entity in values.entities {
entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, entity: entity.entity, colorSpace: colorSpace)) entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, textScale: textScale, entity: entity.entity, colorSpace: colorSpace))
} }
self.entities = entities self.entities = entities
@ -155,7 +157,7 @@ final class MediaEditorComposer {
CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer) CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer)
if let pixelBuffer, let context = self.ciContext { if let pixelBuffer, let context = self.ciContext {
makeEditorImageFrameComposition(context: context, inputImage: image, gradientImage: self.gradientImage, drawingImage: self.drawingImage, dimensions: self.dimensions, values: self.values, entities: self.entities, time: time, completion: { compositedImage in makeEditorImageFrameComposition(context: context, inputImage: image, gradientImage: self.gradientImage, drawingImage: self.drawingImage, dimensions: self.dimensions, outputDimensions: self.outputDimensions, values: self.values, entities: self.entities, time: time, completion: { compositedImage in
if var compositedImage { if var compositedImage {
let scale = self.outputDimensions.width / self.dimensions.width let scale = self.outputDimensions.width / self.dimensions.width
compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale)) compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale))
@ -176,11 +178,11 @@ final class MediaEditorComposer {
guard let context = self.ciContext else { guard let context = self.ciContext else {
return return
} }
makeEditorImageFrameComposition(context: context, inputImage: inputImage, gradientImage: self.gradientImage, drawingImage: self.drawingImage, dimensions: self.dimensions, values: self.values, entities: self.entities, time: time, completion: completion) makeEditorImageFrameComposition(context: context, inputImage: inputImage, gradientImage: self.gradientImage, drawingImage: self.drawingImage, dimensions: self.dimensions, outputDimensions: self.outputDimensions, values: self.values, entities: self.entities, time: time, textScale: self.textScale, completion: completion)
} }
} }
public func makeEditorImageComposition(context: CIContext, account: Account, inputImage: UIImage, dimensions: CGSize, values: MediaEditorValues, time: CMTime, completion: @escaping (UIImage?) -> Void) { public func makeEditorImageComposition(context: CIContext, account: Account, inputImage: UIImage, dimensions: CGSize, values: MediaEditorValues, time: CMTime, textScale: CGFloat, completion: @escaping (UIImage?) -> Void) {
let colorSpace = CGColorSpaceCreateDeviceRGB() let colorSpace = CGColorSpaceCreateDeviceRGB()
let inputImage = CIImage(image: inputImage, options: [.colorSpace: colorSpace])! let inputImage = CIImage(image: inputImage, options: [.colorSpace: colorSpace])!
let gradientImage: CIImage let gradientImage: CIImage
@ -197,10 +199,10 @@ public func makeEditorImageComposition(context: CIContext, account: Account, inp
var entities: [MediaEditorComposerEntity] = [] var entities: [MediaEditorComposerEntity] = []
for entity in values.entities { for entity in values.entities {
entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, entity: entity.entity, colorSpace: colorSpace)) entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, textScale: textScale, entity: entity.entity, colorSpace: colorSpace))
} }
makeEditorImageFrameComposition(context: context, inputImage: inputImage, gradientImage: gradientImage, drawingImage: drawingImage, dimensions: dimensions, values: values, entities: entities, time: time, completion: { ciImage in makeEditorImageFrameComposition(context: context, inputImage: inputImage, gradientImage: gradientImage, drawingImage: drawingImage, dimensions: dimensions, outputDimensions: dimensions, values: values, entities: entities, time: time, textScale: textScale, completion: { ciImage in
if let ciImage { if let ciImage {
if let cgImage = context.createCGImage(ciImage, from: CGRect(origin: .zero, size: ciImage.extent.size)) { if let cgImage = context.createCGImage(ciImage, from: CGRect(origin: .zero, size: ciImage.extent.size)) {
Queue.mainQueue().async { Queue.mainQueue().async {
@ -213,7 +215,7 @@ public func makeEditorImageComposition(context: CIContext, account: Account, inp
}) })
} }
private func makeEditorImageFrameComposition(context: CIContext, inputImage: CIImage, gradientImage: CIImage, drawingImage: CIImage?, dimensions: CGSize, values: MediaEditorValues, entities: [MediaEditorComposerEntity], time: CMTime, completion: @escaping (CIImage?) -> Void) { private func makeEditorImageFrameComposition(context: CIContext, inputImage: CIImage, gradientImage: CIImage, drawingImage: CIImage?, dimensions: CGSize, outputDimensions: CGSize, values: MediaEditorValues, entities: [MediaEditorComposerEntity], time: CMTime, textScale: CGFloat = 1.0, completion: @escaping (CIImage?) -> Void) {
var resultImage = CIImage(color: .black).cropped(to: CGRect(origin: .zero, size: dimensions)).transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0)) var resultImage = CIImage(color: .black).cropped(to: CGRect(origin: .zero, size: dimensions)).transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0))
resultImage = gradientImage.composited(over: resultImage) resultImage = gradientImage.composited(over: resultImage)
@ -267,8 +269,10 @@ private func makeEditorImageFrameComposition(context: CIContext, inputImage: CII
image = image.transformed(by: resetTransform) image = image.transformed(by: resetTransform)
var baseScale: CGFloat = 1.0 var baseScale: CGFloat = 1.0
if let entityBaseScale = entity.baseScale { if let scale = entity.baseScale {
baseScale = entityBaseScale baseScale = scale
} else if let _ = entity.baseDrawingSize {
// baseScale = textScale
} else if let baseSize = entity.baseSize { } else if let baseSize = entity.baseSize {
baseScale = baseSize.width / image.extent.width baseScale = baseSize.width / image.extent.width
} }

View File

@ -12,11 +12,11 @@ import TelegramAnimatedStickerNode
import YuvConversion import YuvConversion
import StickerResources import StickerResources
private func prerenderTextTransformations(entity: DrawingTextEntity, image: UIImage, colorSpace: CGColorSpace) -> MediaEditorComposerStaticEntity { private func prerenderTextTransformations(entity: DrawingTextEntity, image: UIImage, textScale: CGFloat, colorSpace: CGColorSpace) -> MediaEditorComposerStaticEntity {
let imageSize = image.size let imageSize = image.size
let angle = -entity.rotation let angle = -entity.rotation
let scale = entity.scale let scale = entity.scale * 0.5 * textScale
let rotatedSize = CGSize( let rotatedSize = CGSize(
width: abs(imageSize.width * cos(angle)) + abs(imageSize.height * sin(angle)), width: abs(imageSize.width * cos(angle)) + abs(imageSize.height * sin(angle)),
@ -43,10 +43,10 @@ private func prerenderTextTransformations(entity: DrawingTextEntity, image: UIIm
} }
}, scale: 1.0)! }, scale: 1.0)!
return MediaEditorComposerStaticEntity(image: CIImage(image: newImage, options: [.colorSpace: colorSpace])!, position: entity.position, scale: 1.0, rotation: 0.0, baseSize: nil, baseScale: 1.0, mirrored: false) return MediaEditorComposerStaticEntity(image: CIImage(image: newImage, options: [.colorSpace: colorSpace])!, position: entity.position, scale: 1.0, rotation: 0.0, baseSize: nil, baseDrawingSize: CGSize(width: 1080, height: 1920), mirrored: false)
} }
func composerEntitiesForDrawingEntity(account: Account, entity: DrawingEntity, colorSpace: CGColorSpace, tintColor: UIColor? = nil) -> [MediaEditorComposerEntity] { func composerEntitiesForDrawingEntity(account: Account, textScale: CGFloat, entity: DrawingEntity, colorSpace: CGColorSpace, tintColor: UIColor? = nil) -> [MediaEditorComposerEntity] {
if let entity = entity as? DrawingStickerEntity { if let entity = entity as? DrawingStickerEntity {
let content: MediaEditorComposerStickerEntity.Content let content: MediaEditorComposerStickerEntity.Content
switch entity.content { switch entity.content {
@ -62,19 +62,18 @@ func composerEntitiesForDrawingEntity(account: Account, entity: DrawingEntity, c
return [MediaEditorComposerStickerEntity(account: account, content: content, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: entity.mirrored, colorSpace: colorSpace, tintColor: tintColor, isStatic: entity.isExplicitlyStatic)] return [MediaEditorComposerStickerEntity(account: account, content: content, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: entity.mirrored, colorSpace: colorSpace, tintColor: tintColor, isStatic: entity.isExplicitlyStatic)]
} else if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) { } else if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) {
if let entity = entity as? DrawingBubbleEntity { if let entity = entity as? DrawingBubbleEntity {
return [MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, baseScale: nil, mirrored: false)] return [MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, mirrored: false)]
} else if let entity = entity as? DrawingSimpleShapeEntity { } else if let entity = entity as? DrawingSimpleShapeEntity {
return [MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, baseScale: nil, mirrored: false)] return [MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, mirrored: false)]
} else if let entity = entity as? DrawingVectorEntity { } else if let entity = entity as? DrawingVectorEntity {
return [MediaEditorComposerStaticEntity(image: image, position: CGPoint(x: entity.drawingSize.width * 0.5, y: entity.drawingSize.height * 0.5), scale: 1.0, rotation: 0.0, baseSize: entity.drawingSize, baseScale: nil, mirrored: false)] return [MediaEditorComposerStaticEntity(image: image, position: CGPoint(x: entity.drawingSize.width * 0.5, y: entity.drawingSize.height * 0.5), scale: 1.0, rotation: 0.0, baseSize: entity.drawingSize, mirrored: false)]
} else if let entity = entity as? DrawingTextEntity { } else if let entity = entity as? DrawingTextEntity {
var entities: [MediaEditorComposerEntity] = [] var entities: [MediaEditorComposerEntity] = []
// entities.append(prerenderTextTransformations(entity: entity, image: renderImage, colorSpace: colorSpace)) entities.append(prerenderTextTransformations(entity: entity, image: renderImage, textScale: textScale, colorSpace: colorSpace))
entities.append(MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: nil, baseScale: 0.5, mirrored: false))
if let renderSubEntities = entity.renderSubEntities { if let renderSubEntities = entity.renderSubEntities {
for subEntity in renderSubEntities { for subEntity in renderSubEntities {
entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, entity: subEntity, colorSpace: colorSpace, tintColor: entity.color.toUIColor())) entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, textScale: textScale, entity: subEntity, colorSpace: colorSpace, tintColor: entity.color.toUIColor()))
} }
} }
return entities return entities
@ -90,15 +89,26 @@ private class MediaEditorComposerStaticEntity: MediaEditorComposerEntity {
let rotation: CGFloat let rotation: CGFloat
let baseSize: CGSize? let baseSize: CGSize?
let baseScale: CGFloat? let baseScale: CGFloat?
let baseDrawingSize: CGSize?
let mirrored: Bool let mirrored: Bool
init(image: CIImage, position: CGPoint, scale: CGFloat, rotation: CGFloat, baseSize: CGSize?, baseScale: CGFloat?, mirrored: Bool) { init(
image: CIImage,
position: CGPoint,
scale: CGFloat,
rotation: CGFloat,
baseSize: CGSize?,
baseScale: CGFloat? = nil,
baseDrawingSize: CGSize? = nil,
mirrored: Bool
) {
self.image = image self.image = image
self.position = position self.position = position
self.scale = scale self.scale = scale
self.rotation = rotation self.rotation = rotation
self.baseSize = baseSize self.baseSize = baseSize
self.baseScale = baseScale self.baseScale = baseScale
self.baseDrawingSize = baseDrawingSize
self.mirrored = mirrored self.mirrored = mirrored
} }
@ -127,6 +137,7 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity {
let rotation: CGFloat let rotation: CGFloat
let baseSize: CGSize? let baseSize: CGSize?
let baseScale: CGFloat? = nil let baseScale: CGFloat? = nil
let baseDrawingSize: CGSize? = nil
let mirrored: Bool let mirrored: Bool
let colorSpace: CGColorSpace let colorSpace: CGColorSpace
let tintColor: UIColor? let tintColor: UIColor?
@ -475,6 +486,7 @@ protocol MediaEditorComposerEntity {
var rotation: CGFloat { get } var rotation: CGFloat { get }
var baseSize: CGSize? { get } var baseSize: CGSize? { get }
var baseScale: CGFloat? { get } var baseScale: CGFloat? { get }
var baseDrawingSize: CGSize? { get }
var mirrored: Bool { get } var mirrored: Bool { get }
func image(for time: CMTime, frameRate: Float, context: CIContext, completion: @escaping (CIImage?) -> Void) func image(for time: CMTime, frameRate: Float, context: CIContext, completion: @escaping (CIImage?) -> Void)

View File

@ -261,6 +261,7 @@ public final class MediaEditorVideoExport {
private let account: Account private let account: Account
private let subject: Subject private let subject: Subject
private let configuration: Configuration private let configuration: Configuration
private let textScale: CGFloat
private let outputPath: String private let outputPath: String
private var reader: AVAssetReader? private var reader: AVAssetReader?
@ -295,11 +296,12 @@ public final class MediaEditorVideoExport {
private let semaphore = DispatchSemaphore(value: 0) private let semaphore = DispatchSemaphore(value: 0)
public init(account: Account, subject: Subject, configuration: Configuration, outputPath: String) { public init(account: Account, subject: Subject, configuration: Configuration, outputPath: String, textScale: CGFloat = 1.0) {
self.account = account self.account = account
self.subject = subject self.subject = subject
self.configuration = configuration self.configuration = configuration
self.outputPath = outputPath self.outputPath = outputPath
self.textScale = textScale
if FileManager.default.fileExists(atPath: outputPath) { if FileManager.default.fileExists(atPath: outputPath) {
try? FileManager.default.removeItem(atPath: outputPath) try? FileManager.default.removeItem(atPath: outputPath)
@ -354,7 +356,7 @@ public final class MediaEditorVideoExport {
guard self.composer == nil else { guard self.composer == nil else {
return return
} }
self.composer = MediaEditorComposer(account: self.account, values: self.configuration.values, dimensions: self.configuration.composerDimensions, outputDimensions: self.configuration.dimensions) self.composer = MediaEditorComposer(account: self.account, values: self.configuration.values, dimensions: self.configuration.composerDimensions, outputDimensions: self.configuration.dimensions, textScale: self.textScale)
} }
private func setupWithAsset(_ asset: AVAsset, additionalAsset: AVAsset?) { private func setupWithAsset(_ asset: AVAsset, additionalAsset: AVAsset?) {

View File

@ -3320,7 +3320,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
private func openEditCategory(privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, pin: Bool, completion: @escaping (EngineStoryPrivacy) -> Void) { private func openEditCategory(privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, pin: Bool, completion: @escaping (EngineStoryPrivacy) -> Void) {
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .contacts(privacy.base), initialPeerIds: Set(privacy.additionallyIncludePeers)) let subject: ShareWithPeersScreen.StateContext.Subject
if privacy.base == .nobody {
subject = .chats
} else {
subject = .contacts(privacy.base)
}
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: subject, initialPeerIds: Set(privacy.additionallyIncludePeers))
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else { guard let self else {
return return
@ -3561,7 +3567,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if let resultImage = mediaEditor.resultImage { if let resultImage = mediaEditor.resultImage {
mediaEditor.seek(0.0, andPlay: false) mediaEditor.seek(0.0, andPlay: false)
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: resultImage, dimensions: storyDimensions, values: values, time: .zero, completion: { resultImage in makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: resultImage, dimensions: storyDimensions, values: values, time: .zero, textScale: 2.0, completion: { resultImage in
guard let resultImage else { guard let resultImage else {
return return
} }
@ -3844,7 +3850,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
inputImage = UIImage() inputImage = UIImage()
} }
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: inputImage, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: inputImage, dimensions: storyDimensions, values: mediaEditor.values, time: firstFrameTime, textScale: 2.0, completion: { [weak self] coverImage in
if let self { if let self {
Logger.shared.log("MediaEditor", "Completed with video \(videoResult)") Logger.shared.log("MediaEditor", "Completed with video \(videoResult)")
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in
@ -3867,7 +3873,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if let image = mediaEditor.resultImage { if let image = mediaEditor.resultImage {
self.saveDraft(id: randomId) self.saveDraft(id: randomId)
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] resultImage in makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in
if let self, let resultImage { if let self, let resultImage {
Logger.shared.log("MediaEditor", "Completed with image \(resultImage)") Logger.shared.log("MediaEditor", "Completed with image \(resultImage)")
self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), caption, self.state.privacy, stickers, { [weak self] finished in self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), caption, self.state.privacy, stickers, { [weak self] finished in
@ -3995,7 +4001,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, duration: duration, forceFullHd: true, frameRate: 60.0) let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, duration: duration, forceFullHd: true, frameRate: 60.0)
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).mp4" let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).mp4"
let videoExport = MediaEditorVideoExport(account: self.context.account, subject: exportSubject, configuration: configuration, outputPath: outputPath) let videoExport = MediaEditorVideoExport(account: self.context.account, subject: exportSubject, configuration: configuration, outputPath: outputPath, textScale: 2.0)
self.videoExport = videoExport self.videoExport = videoExport
videoExport.start() videoExport.start()
@ -4028,7 +4034,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} else { } else {
if let image = mediaEditor.resultImage { if let image = mediaEditor.resultImage {
Queue.concurrentDefaultQueue().async { Queue.concurrentDefaultQueue().async {
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { resultImage in makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, textScale: 2.0, completion: { resultImage in
if let data = resultImage?.jpegData(compressionQuality: 0.8) { if let data = resultImage?.jpegData(compressionQuality: 0.8) {
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).jpg" let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).jpg"
try? data.write(to: URL(fileURLWithPath: outputPath)) try? data.write(to: URL(fileURLWithPath: outputPath))

View File

@ -9,15 +9,21 @@ final class SectionHeaderComponent: Component {
let theme: PresentationTheme let theme: PresentationTheme
let style: ShareWithPeersScreenComponent.Style let style: ShareWithPeersScreenComponent.Style
let title: String let title: String
let actionTitle: String?
let action: (() -> Void)?
init( init(
theme: PresentationTheme, theme: PresentationTheme,
style: ShareWithPeersScreenComponent.Style, style: ShareWithPeersScreenComponent.Style,
title: String title: String,
actionTitle: String?,
action: (() -> Void)?
) { ) {
self.theme = theme self.theme = theme
self.style = style self.style = style
self.title = title self.title = title
self.actionTitle = actionTitle
self.action = action
} }
static func ==(lhs: SectionHeaderComponent, rhs: SectionHeaderComponent) -> Bool { static func ==(lhs: SectionHeaderComponent, rhs: SectionHeaderComponent) -> Bool {
@ -30,12 +36,16 @@ final class SectionHeaderComponent: Component {
if lhs.title != rhs.title { if lhs.title != rhs.title {
return false return false
} }
if lhs.actionTitle != rhs.actionTitle {
return false
}
return true return true
} }
final class View: UIView { final class View: UIView {
private let title = ComponentView<Empty>() private let title = ComponentView<Empty>()
private let backgroundView: BlurredBackgroundView private let backgroundView: BlurredBackgroundView
private let action = ComponentView<Empty>()
private var component: SectionHeaderComponent? private var component: SectionHeaderComponent?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
@ -95,6 +105,32 @@ final class SectionHeaderComponent: Component {
} }
} }
if let actionTitle = component.actionTitle {
let actionSize = self.action.update(
transition: .immediate,
component: AnyComponent(
Button(content: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: actionTitle, font: Font.regular(13.0), textColor: component.theme.list.itemSecondaryTextColor))
)), action: { [weak self] in
if let self, let component = self.component {
component.action?()
}
})
),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
if let view = self.action.view {
if view.superview == nil {
self.addSubview(view)
}
let actionFrame = CGRect(origin: CGPoint(x: availableSize.width - leftInset - titleSize.width, y: floor((height - titleSize.height) / 2.0)), size: actionSize)
view.frame = actionFrame
}
} else if self.action.view?.superview != nil {
self.action.view?.removeFromSuperview()
}
let size = CGSize(width: availableSize.width, height: height) let size = CGSize(width: availableSize.width, height: height)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size)) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))

View File

@ -307,6 +307,10 @@ final class ShareWithPeersScreenComponent: Component {
private var savedSelectedPeers: [EnginePeer.Id] = [] private var savedSelectedPeers: [EnginePeer.Id] = []
private var selectedPeers: [EnginePeer.Id] = [] private var selectedPeers: [EnginePeer.Id] = []
private var selectedGroups: [EnginePeer.Id] = []
private var peersMap: [EnginePeer.Id: EnginePeer] = [:]
private var selectedCategories = Set<CategoryId>() private var selectedCategories = Set<CategoryId>()
private var selectedOptions = Set<OptionId>() private var selectedOptions = Set<OptionId>()
@ -557,6 +561,119 @@ final class ShareWithPeersScreenComponent: Component {
controller.present(tooltipScreen, in: .window(.root)) controller.present(tooltipScreen, in: .window(.root))
} }
private func toggleGroupPeer(_ peer: EnginePeer) {
guard let component = self.component, let environment = self.environment, let controller = self.environment?.controller() else {
return
}
var groupTooLarge = false
if case let .legacyGroup(group) = peer, group.participantCount > 200 {
groupTooLarge = true
} else if let stateValue = self.effectiveStateValue, let count = stateValue.participants[peer.id], count > 200 {
groupTooLarge = true
}
if groupTooLarge {
let alertController = textAlertController(
context: component.context,
forceTheme: defaultDarkColorPresentationTheme,
title: environment.strings.Story_Privacy_GroupTooLarge,
text: environment.strings.Story_Privacy_GroupParticipantsLimit,
actions: [
TextAlertAction(type: .defaultAction, title: "OK", action: {})
],
actionLayout: .vertical
)
controller.present(alertController, in: .window(.root))
return
}
var append = false
if let index = self.selectedGroups.firstIndex(of: peer.id) {
self.selectedGroups.remove(at: index)
} else {
self.selectedGroups.append(peer.id)
append = true
}
let context = component.context
if peer.id.namespace == Namespaces.Peer.CloudGroup {
let _ = (context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.LegacyGroupParticipants(id: peer.id)
)
|> mapToSignal { participants -> Signal<[EnginePeer], NoError> in
if case let .known(participants) = participants {
return context.engine.data.get(
EngineDataMap(participants.map { TelegramEngine.EngineData.Item.Peer.Peer(id: $0.peerId) })
)
|> map { peers in
var result: [EnginePeer] = []
for participant in participants {
if let peer = peers[participant.peerId], let peer, peer.id != context.account.peerId {
result.append(peer)
}
}
return result
}
} else {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
return .complete()
}
}
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] peers in
guard let self else {
return
}
var peerIds = Set<EnginePeer.Id>()
for peer in peers {
self.peersMap[peer.id] = peer
peerIds.insert(peer.id)
}
if append {
self.selectedPeers.append(contentsOf: peers.map { $0.id })
} else {
self.selectedPeers = self.selectedPeers.filter { !peerIds.contains($0) }
}
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
self.state?.updated(transition: transition)
})
} else if peer.id.namespace == Namespaces.Peer.CloudChannel {
let participants: Signal<[EnginePeer], NoError> = Signal { subscriber in
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peer.id, requestUpdate: true, updated: { list in
var peers: [EnginePeer] = []
for item in list.list {
peers.append(EnginePeer(item.peer))
}
if !peers.isEmpty {
subscriber.putNext(peers)
subscriber.putCompletion()
}
})
return disposable
}
|> take(1)
let _ = (participants
|> deliverOnMainQueue).start(next: { [weak self] peers in
guard let self else {
return
}
var peerIds = Set<EnginePeer.Id>()
for peer in peers {
self.peersMap[peer.id] = peer
peerIds.insert(peer.id)
}
if append {
self.selectedPeers.append(contentsOf: peers.map { $0.id })
} else {
self.selectedPeers = self.selectedPeers.filter { !peerIds.contains($0) }
}
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
self.state?.updated(transition: transition)
})
}
}
private func updateScrolling(transition: Transition) { private func updateScrolling(transition: Transition) {
guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else { guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else {
return return
@ -665,14 +782,22 @@ final class ShareWithPeersScreenComponent: Component {
component: AnyComponent(SectionHeaderComponent( component: AnyComponent(SectionHeaderComponent(
theme: environment.theme, theme: environment.theme,
style: itemLayout.style, style: itemLayout.style,
title: sectionTitle title: sectionTitle,
actionTitle: (section.id == 1 && !self.selectedPeers.isEmpty) ? environment.strings.Contacts_DeselectAll : nil,
action: { [weak self] in
if let self {
self.selectedPeers = []
self.selectedGroups = []
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
self.state?.updated(transition: transition)
}
}
)), )),
environment: {}, environment: {},
containerSize: sectionHeaderFrame.size containerSize: sectionHeaderFrame.size
) )
if let sectionHeaderView = sectionHeader.view { if let sectionHeaderView = sectionHeader.view {
if sectionHeaderView.superview == nil { if sectionHeaderView.superview == nil {
sectionHeaderView.isUserInteractionEnabled = false
self.scrollContentClippingView.addSubview(sectionHeaderView) self.scrollContentClippingView.addSubview(sectionHeaderView)
} }
if minSectionHeader == nil { if minSectionHeader == nil {
@ -736,7 +861,8 @@ final class ShareWithPeersScreenComponent: Component {
self.selectedCategories.removeAll() self.selectedCategories.removeAll()
self.selectedCategories.insert(categoryId) self.selectedCategories.insert(categoryId)
if self.selectedPeers.isEmpty && categoryId == .selectedContacts { let closeFriends = self.component?.stateContext.stateValue?.closeFriendsPeers ?? []
if categoryId == .selectedContacts && self.selectedPeers.isEmpty {
component.editCategory( component.editCategory(
EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []), EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
self.selectedOptions.contains(.screenshot), self.selectedOptions.contains(.screenshot),
@ -744,6 +870,14 @@ final class ShareWithPeersScreenComponent: Component {
) )
controller.dismissAllTooltips() controller.dismissAllTooltips()
controller.dismiss() controller.dismiss()
} else if categoryId == .closeFriends && closeFriends.isEmpty {
component.editCategory(
EngineStoryPrivacy(base: .closeFriends, additionallyIncludePeers: []),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin)
)
controller.dismissAllTooltips()
controller.dismiss()
} }
} }
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring))) self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring)))
@ -811,6 +945,20 @@ final class ShareWithPeersScreenComponent: Component {
self.visibleItems[itemId] = visibleItem self.visibleItems[itemId] = visibleItem
} }
let subtitle: String?
if case let .legacyGroup(group) = peer {
subtitle = environment.strings.Conversation_StatusMembers(Int32(group.participantCount))
} else if case .channel = peer {
if let count = stateValue.participants[peer.id] {
subtitle = environment.strings.Conversation_StatusMembers(Int32(count))
} else {
subtitle = nil
}
} else {
subtitle = nil
}
let isSelected = self.selectedPeers.contains(peer.id) || self.selectedGroups.contains(peer.id)
let _ = visibleItem.update( let _ = visibleItem.update(
transition: itemTransition, transition: itemTransition,
component: AnyComponent(PeerListItemComponent( component: AnyComponent(PeerListItemComponent(
@ -821,20 +969,24 @@ final class ShareWithPeersScreenComponent: Component {
sideInset: itemLayout.sideInset, sideInset: itemLayout.sideInset,
title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
peer: peer, peer: peer,
subtitle: nil, subtitle: subtitle,
subtitleAccessory: .none, subtitleAccessory: .none,
presence: stateValue.presences[peer.id], presence: stateValue.presences[peer.id],
selectionState: .editing(isSelected: self.selectedPeers.contains(peer.id), isTinted: false), selectionState: .editing(isSelected: isSelected, isTinted: false),
hasNext: true, hasNext: true,
action: { [weak self] peer in action: { [weak self] peer in
guard let self else { guard let self else {
return return
} }
if peer.id.isGroupOrChannel {
self.toggleGroupPeer(peer)
} else {
if let index = self.selectedPeers.firstIndex(of: peer.id) { if let index = self.selectedPeers.firstIndex(of: peer.id) {
self.selectedPeers.remove(at: index) self.selectedPeers.remove(at: index)
} else { } else {
self.selectedPeers.append(peer.id) self.selectedPeers.append(peer.id)
} }
}
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring)) let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
self.state?.updated(transition: transition) self.state?.updated(transition: transition)
@ -1244,9 +1396,20 @@ final class ShareWithPeersScreenComponent: Component {
var tokens: [TokenListTextField.Token] = [] var tokens: [TokenListTextField.Token] = []
for peerId in self.selectedPeers { for peerId in self.selectedPeers {
guard let stateValue = self.defaultStateValue, let peer = stateValue.peers.first(where: { $0.id == peerId }) else { guard let stateValue = self.defaultStateValue else {
continue continue
} }
var peer: EnginePeer?
if let peerValue = self.peersMap[peerId] {
peer = peerValue
} else if let peerValue = stateValue.peers.first(where: { $0.id == peerId }) {
peer = peerValue
}
guard let peer else {
continue
}
tokens.append(TokenListTextField.Token( tokens.append(TokenListTextField.Token(
id: AnyHashable(peerId), id: AnyHashable(peerId),
title: peer.compactDisplayTitle, title: peer.compactDisplayTitle,
@ -1440,7 +1603,7 @@ final class ShareWithPeersScreenComponent: Component {
actionButtonTitle = environment.strings.Story_Privacy_PostStory actionButtonTitle = environment.strings.Story_Privacy_PostStory
} }
case .chats: case .chats:
title = "" title = environment.strings.Story_Privacy_CategorySelectedContacts
case let .contacts(category): case let .contacts(category):
switch category { switch category {
case .closeFriends: case .closeFriends:
@ -1507,6 +1670,13 @@ final class ShareWithPeersScreenComponent: Component {
transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(x: containerSideInset, y: navigationHeight), size: CGSize(width: containerWidth, height: UIScreenPixel))) transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(x: containerSideInset, y: navigationHeight), size: CGSize(width: containerWidth, height: UIScreenPixel)))
let badge: Int
if case .stories = component.stateContext.subject {
badge = 0
} else {
badge = self.selectedPeers.count
}
let actionButtonSize = self.actionButton.update( let actionButtonSize = self.actionButton.update(
transition: transition, transition: transition,
component: AnyComponent(ButtonComponent( component: AnyComponent(ButtonComponent(
@ -1519,7 +1689,7 @@ final class ShareWithPeersScreenComponent: Component {
id: actionButtonTitle, id: actionButtonTitle,
component: AnyComponent(ButtonTextContentComponent( component: AnyComponent(ButtonTextContentComponent(
text: actionButtonTitle, text: actionButtonTitle,
badge: 0, badge: badge,
textColor: environment.theme.list.itemCheckColors.foregroundColor, textColor: environment.theme.list.itemCheckColors.foregroundColor,
badgeBackground: environment.theme.list.itemCheckColors.foregroundColor, badgeBackground: environment.theme.list.itemCheckColors.foregroundColor,
badgeForeground: environment.theme.list.itemCheckColors.fillColor badgeForeground: environment.theme.list.itemCheckColors.fillColor
@ -1755,15 +1925,18 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
public final class State { public final class State {
let peers: [EnginePeer] let peers: [EnginePeer]
let presences: [EnginePeer.Id: EnginePeer.Presence] let presences: [EnginePeer.Id: EnginePeer.Presence]
let participants: [EnginePeer.Id: Int]
let closeFriendsPeers: [EnginePeer] let closeFriendsPeers: [EnginePeer]
fileprivate init( fileprivate init(
peers: [EnginePeer], peers: [EnginePeer],
presences: [EnginePeer.Id: EnginePeer.Presence], presences: [EnginePeer.Id: EnginePeer.Presence],
participants: [EnginePeer.Id: Int],
closeFriendsPeers: [EnginePeer] closeFriendsPeers: [EnginePeer]
) { ) {
self.peers = peers self.peers = peers
self.presences = presences self.presences = presences
self.participants = participants
self.closeFriendsPeers = closeFriendsPeers self.closeFriendsPeers = closeFriendsPeers
} }
} }
@ -1820,6 +1993,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
let state = State( let state = State(
peers: peers.compactMap { $0 }, peers: peers.compactMap { $0 },
presences: [:], presences: [:],
participants: [:],
closeFriendsPeers: closeFriends closeFriendsPeers: closeFriends
) )
self.stateValue = state self.stateValue = state
@ -1828,16 +2002,36 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
self.readySubject.set(true) self.readySubject.set(true)
}) })
case .chats: case .chats:
self.stateDisposable = (context.engine.messages.chatList(group: .root, count: 200) self.stateDisposable = (combineLatest(
|> deliverOnMainQueue).start(next: { [weak self] chatList in context.engine.messages.chatList(group: .root, count: 200) |> take(1),
context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.List(includePresences: true))
)
|> mapToSignal { chatList, contacts -> Signal<(EngineChatList, EngineContactList, [EnginePeer.Id: Optional<Int>]), NoError> in
return context.engine.data.get(
EngineDataMap(chatList.items.map(\.renderedPeer.peerId).map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init))
)
|> map { participantCountMap -> (EngineChatList, EngineContactList, [EnginePeer.Id: Optional<Int>]) in
return (chatList, contacts, participantCountMap)
}
}
|> deliverOnMainQueue).start(next: { [weak self] chatList, contacts, participantCounts in
guard let self else { guard let self else {
return return
} }
var participants: [EnginePeer.Id: Int] = [:]
for (key, value) in participantCounts {
if let value {
participants[key] = value
}
}
var existingIds = Set<EnginePeer.Id>()
var selectedPeers: [EnginePeer] = [] var selectedPeers: [EnginePeer] = []
for item in chatList.items.reversed() { for item in chatList.items.reversed() {
if self.initialPeerIds.contains(item.renderedPeer.peerId), let peer = item.renderedPeer.peer { if self.initialPeerIds.contains(item.renderedPeer.peerId), let peer = item.renderedPeer.peer {
selectedPeers.append(peer) selectedPeers.append(peer)
existingIds.insert(peer.id)
} }
} }
@ -1847,12 +2041,39 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
} }
var peers: [EnginePeer] = [] var peers: [EnginePeer] = []
peers = chatList.items.filter { !self.initialPeerIds.contains($0.renderedPeer.peerId) && $0.renderedPeer.peerId != context.account.peerId }.reversed().compactMap { $0.renderedPeer.peer } peers = chatList.items.filter { peer in
if let peer = peer.renderedPeer.peer {
if self.initialPeerIds.contains(peer.id) {
return false
}
if peer.id == context.account.peerId {
return false
}
if peer.isService {
return false
}
if case let .channel(channel) = peer {
if channel.isForum {
return false
}
if case .broadcast = channel.info {
return false
}
}
return true
} else {
return false
}
}.reversed().compactMap { $0.renderedPeer.peer }
for peer in peers {
existingIds.insert(peer.id)
}
peers.insert(contentsOf: selectedPeers, at: 0) peers.insert(contentsOf: selectedPeers, at: 0)
let state = State( let state = State(
peers: peers, peers: peers,
presences: presences, presences: presences,
participants: participants,
closeFriendsPeers: [] closeFriendsPeers: []
) )
self.stateValue = state self.stateValue = state
@ -1908,6 +2129,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
let state = State( let state = State(
peers: peers, peers: peers,
presences: contactList.presences, presences: contactList.presences,
participants: [:],
closeFriendsPeers: [] closeFriendsPeers: []
) )
@ -1946,11 +2168,20 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
} else { } else {
return true return true
} }
} else { } else if case let .channel(channel) = peer {
if channel.isForum {
return false return false
} }
if case .broadcast = channel.info {
return false
}
return true
} else {
return true
}
}, },
presences: [:], presences: [:],
participants: [:],
closeFriendsPeers: [] closeFriendsPeers: []
) )
self.stateValue = state self.stateValue = state

View File

@ -450,7 +450,7 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate {
let previousContentHeight = self.scrollNode.view.contentSize.height let previousContentHeight = self.scrollNode.view.contentSize.height
let contentHeight = currentOffset.y + 29.0 + verticalInset let contentHeight = currentOffset.y + 29.0 + verticalInset
let nodeHeight = min(contentHeight, 110.0) let nodeHeight = min(contentHeight, 140.0)
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: nodeHeight))) transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: nodeHeight)))

View File

@ -13054,7 +13054,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if !value || concealed || botApp.flags.contains(.notActivated) { if !value || concealed || botApp.flags.contains(.notActivated) {
let context = self.context let context = self.context
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: self.updatedPresentationData, peer: botPeer, requestWriteAccess: !botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: self.updatedPresentationData, peer: botPeer, requestWriteAccess: botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).start() let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).start()
openBotApp(allowWrite) openBotApp(allowWrite)
}, showMore: { [weak self] in }, showMore: { [weak self] in