mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various fixes
This commit is contained in:
parent
f4fc72b762
commit
ab6b0140cd
@ -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.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.";
|
||||
|
@ -651,7 +651,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
||||
|
||||
if let selectionView = entityView.makeSelectionView() {
|
||||
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 }
|
||||
self.requestedMenuForEntityView(entityView, entityViews.last === entityView)
|
||||
}
|
||||
|
@ -2978,8 +2978,6 @@ public final class DrawingToolsInteraction {
|
||||
private var isActive = false
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private let startTimestamp = CACurrentMediaTime()
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
drawingView: DrawingView,
|
||||
|
@ -58,13 +58,9 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
||||
self.textView.delegate = self
|
||||
self.addSubview(self.textView)
|
||||
|
||||
self.emojiViewProvider = { [weak self] emoji in
|
||||
guard let strongSelf = self else {
|
||||
return UIView()
|
||||
}
|
||||
|
||||
self.emojiViewProvider = { emoji in
|
||||
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
|
||||
@ -285,9 +281,10 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
||||
}
|
||||
|
||||
self._isEditing = false
|
||||
self.textView.resignFirstResponder()
|
||||
self.textView.inputView = nil
|
||||
self.textView.inputAccessoryView = nil
|
||||
self.textView.reloadInputViews()
|
||||
self.textView.resignFirstResponder()
|
||||
|
||||
self.textView.isEditable = false
|
||||
self.textView.isSelectable = false
|
||||
@ -656,8 +653,8 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
||||
self.updateEditingPosition(animated: animated)
|
||||
}
|
||||
|
||||
self.textView.onLayoutUpdate = {
|
||||
self.updateEntities()
|
||||
self.textView.onLayoutUpdate = { [weak self] in
|
||||
self?.updateEntities()
|
||||
}
|
||||
|
||||
super.update(animated: animated)
|
||||
@ -1023,15 +1020,20 @@ private class DrawingTextLayoutManager: NSLayoutManager {
|
||||
private func prepare() {
|
||||
self.path = nil
|
||||
self.rectArray.removeAll()
|
||||
|
||||
|
||||
self.enumerateLineFragments(forGlyphRange: NSRange(location: 0, length: ((self.textStorage?.string ?? "") as NSString).length)) { rect, usedRect, textContainer, glyphRange, _ in
|
||||
var ignoreRange = false
|
||||
let charecterRange = self.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
|
||||
let substring = ((self.textStorage?.string ?? "") as NSString).substring(with: charecterRange)
|
||||
let characterRange = self.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
|
||||
let substring = ((self.textStorage?.string ?? "") as NSString).substring(with: characterRange)
|
||||
if substring.trimmingCharacters(in: .newlines).isEmpty {
|
||||
ignoreRange = true
|
||||
}
|
||||
|
||||
var usedRect = usedRect
|
||||
if substring.hasSuffix(" ") {
|
||||
usedRect.size.width -= floorToScreenPixels(usedRect.height * 0.145)
|
||||
}
|
||||
|
||||
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))
|
||||
self.rectArray.append(newRect)
|
||||
|
@ -579,12 +579,13 @@ final class TextSettingsComponent: CombinedComponent {
|
||||
)
|
||||
}
|
||||
|
||||
let presentFontPicker = component.presentFontPicker
|
||||
let font = font.update(
|
||||
component: TextFontComponent(
|
||||
selectedValue: component.font,
|
||||
tag: component.fontTag,
|
||||
tapped: {
|
||||
component.presentFontPicker()
|
||||
presentFontPicker()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: fontAvailableWidth, height: 30.0),
|
||||
|
@ -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 {
|
||||
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 autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?
|
||||
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))
|
||||
}
|
||||
|> 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)
|
||||
} else {
|
||||
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 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)))
|
||||
@ -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 {
|
||||
if forceReupload {
|
||||
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)))
|
||||
}
|
||||
} 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
private func uploadedThumbnail(network: Network, postbox: Postbox, resourceReference: MediaResourceReference) -> 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)
|
||||
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: forceNoBigParts)
|
||||
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
||||
|> mapToSignal { result -> Signal<Api.InputFile?, PendingMessageUploadError> in
|
||||
switch result {
|
||||
@ -656,7 +656,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA
|
||||
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)
|
||||
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
var referenceKey: CachedSentMediaReferenceKey?
|
||||
@ -781,7 +781,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
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 }
|
||||
|> map { result in
|
||||
if let result = result {
|
||||
|
@ -59,7 +59,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
|
||||
case let .update(media):
|
||||
let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in
|
||||
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) {
|
||||
uploadedMedia = .single(.progress(0.027))
|
||||
|
@ -688,6 +688,7 @@ private func uploadedStoryContent(postbox: Postbox, network: Network, media: Med
|
||||
forceReupload: true,
|
||||
isGrouped: false,
|
||||
passFetchProgress: passFetchProgress,
|
||||
forceNoBigParts: true,
|
||||
peerId: accountPeerId,
|
||||
messageId: nil,
|
||||
attributes: attributes,
|
||||
|
@ -1651,9 +1651,15 @@ public class CameraScreen: ViewController {
|
||||
guard let camera = self.camera else {
|
||||
return
|
||||
}
|
||||
let location = gestureRecognizer.location(in: self.mainPreviewView)
|
||||
let point = self.mainPreviewView.cameraPoint(for: location)
|
||||
camera.focus(at: point, autoFocus: false)
|
||||
|
||||
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 point = self.mainPreviewView.cameraPoint(for: location)
|
||||
camera.focus(at: point, autoFocus: false)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func handleDoubleTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||
|
@ -42,6 +42,7 @@ final class MediaEditorComposer {
|
||||
private let values: MediaEditorValues
|
||||
private let dimensions: CGSize
|
||||
private let outputDimensions: CGSize
|
||||
private let textScale: CGFloat
|
||||
|
||||
private let ciContext: CIContext?
|
||||
private var textureCache: CVMetalTextureCache?
|
||||
@ -53,10 +54,11 @@ final class MediaEditorComposer {
|
||||
private let drawingImage: CIImage?
|
||||
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.dimensions = dimensions
|
||||
self.outputDimensions = outputDimensions
|
||||
self.textScale = textScale
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
self.colorSpace = colorSpace
|
||||
@ -77,7 +79,7 @@ final class MediaEditorComposer {
|
||||
|
||||
var entities: [MediaEditorComposerEntity] = []
|
||||
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
|
||||
|
||||
@ -155,7 +157,7 @@ final class MediaEditorComposer {
|
||||
CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer)
|
||||
|
||||
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 {
|
||||
let scale = self.outputDimensions.width / self.dimensions.width
|
||||
compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale))
|
||||
@ -176,11 +178,11 @@ final class MediaEditorComposer {
|
||||
guard let context = self.ciContext else {
|
||||
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 inputImage = CIImage(image: inputImage, options: [.colorSpace: colorSpace])!
|
||||
let gradientImage: CIImage
|
||||
@ -197,10 +199,10 @@ public func makeEditorImageComposition(context: CIContext, account: Account, inp
|
||||
|
||||
var entities: [MediaEditorComposerEntity] = []
|
||||
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 cgImage = context.createCGImage(ciImage, from: CGRect(origin: .zero, size: ciImage.extent.size)) {
|
||||
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))
|
||||
resultImage = gradientImage.composited(over: resultImage)
|
||||
|
||||
@ -267,8 +269,10 @@ private func makeEditorImageFrameComposition(context: CIContext, inputImage: CII
|
||||
image = image.transformed(by: resetTransform)
|
||||
|
||||
var baseScale: CGFloat = 1.0
|
||||
if let entityBaseScale = entity.baseScale {
|
||||
baseScale = entityBaseScale
|
||||
if let scale = entity.baseScale {
|
||||
baseScale = scale
|
||||
} else if let _ = entity.baseDrawingSize {
|
||||
// baseScale = textScale
|
||||
} else if let baseSize = entity.baseSize {
|
||||
baseScale = baseSize.width / image.extent.width
|
||||
}
|
||||
|
@ -12,11 +12,11 @@ import TelegramAnimatedStickerNode
|
||||
import YuvConversion
|
||||
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 angle = -entity.rotation
|
||||
let scale = entity.scale
|
||||
let scale = entity.scale * 0.5 * textScale
|
||||
|
||||
let rotatedSize = CGSize(
|
||||
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)!
|
||||
|
||||
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 {
|
||||
let content: MediaEditorComposerStickerEntity.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)]
|
||||
} else if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
@ -90,15 +89,26 @@ private class MediaEditorComposerStaticEntity: MediaEditorComposerEntity {
|
||||
let rotation: CGFloat
|
||||
let baseSize: CGSize?
|
||||
let baseScale: CGFloat?
|
||||
let baseDrawingSize: CGSize?
|
||||
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.position = position
|
||||
self.scale = scale
|
||||
self.rotation = rotation
|
||||
self.baseSize = baseSize
|
||||
self.baseScale = baseScale
|
||||
self.baseDrawingSize = baseDrawingSize
|
||||
self.mirrored = mirrored
|
||||
}
|
||||
|
||||
@ -127,6 +137,7 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity {
|
||||
let rotation: CGFloat
|
||||
let baseSize: CGSize?
|
||||
let baseScale: CGFloat? = nil
|
||||
let baseDrawingSize: CGSize? = nil
|
||||
let mirrored: Bool
|
||||
let colorSpace: CGColorSpace
|
||||
let tintColor: UIColor?
|
||||
@ -475,6 +486,7 @@ protocol MediaEditorComposerEntity {
|
||||
var rotation: CGFloat { get }
|
||||
var baseSize: CGSize? { get }
|
||||
var baseScale: CGFloat? { get }
|
||||
var baseDrawingSize: CGSize? { get }
|
||||
var mirrored: Bool { get }
|
||||
|
||||
func image(for time: CMTime, frameRate: Float, context: CIContext, completion: @escaping (CIImage?) -> Void)
|
||||
|
@ -261,6 +261,7 @@ public final class MediaEditorVideoExport {
|
||||
private let account: Account
|
||||
private let subject: Subject
|
||||
private let configuration: Configuration
|
||||
private let textScale: CGFloat
|
||||
private let outputPath: String
|
||||
|
||||
private var reader: AVAssetReader?
|
||||
@ -295,11 +296,12 @@ public final class MediaEditorVideoExport {
|
||||
|
||||
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.subject = subject
|
||||
self.configuration = configuration
|
||||
self.outputPath = outputPath
|
||||
self.textScale = textScale
|
||||
|
||||
if FileManager.default.fileExists(atPath: outputPath) {
|
||||
try? FileManager.default.removeItem(atPath: outputPath)
|
||||
@ -354,7 +356,7 @@ public final class MediaEditorVideoExport {
|
||||
guard self.composer == nil else {
|
||||
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?) {
|
||||
|
@ -3320,7 +3320,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
|
||||
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
|
||||
guard let self else {
|
||||
return
|
||||
@ -3561,7 +3567,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
|
||||
if let resultImage = mediaEditor.resultImage {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -3844,7 +3850,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
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 {
|
||||
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
|
||||
@ -3867,7 +3873,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
if let image = mediaEditor.resultImage {
|
||||
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 {
|
||||
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
|
||||
@ -3995,7 +4001,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, duration: duration, forceFullHd: true, frameRate: 60.0)
|
||||
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
|
||||
|
||||
videoExport.start()
|
||||
@ -4028,7 +4034,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
} else {
|
||||
if let image = mediaEditor.resultImage {
|
||||
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) {
|
||||
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).jpg"
|
||||
try? data.write(to: URL(fileURLWithPath: outputPath))
|
||||
|
@ -9,15 +9,21 @@ final class SectionHeaderComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let style: ShareWithPeersScreenComponent.Style
|
||||
let title: String
|
||||
let actionTitle: String?
|
||||
let action: (() -> Void)?
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
style: ShareWithPeersScreenComponent.Style,
|
||||
title: String
|
||||
title: String,
|
||||
actionTitle: String?,
|
||||
action: (() -> Void)?
|
||||
) {
|
||||
self.theme = theme
|
||||
self.style = style
|
||||
self.title = title
|
||||
self.actionTitle = actionTitle
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: SectionHeaderComponent, rhs: SectionHeaderComponent) -> Bool {
|
||||
@ -30,12 +36,16 @@ final class SectionHeaderComponent: Component {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.actionTitle != rhs.actionTitle {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let title = ComponentView<Empty>()
|
||||
private let backgroundView: BlurredBackgroundView
|
||||
private let action = ComponentView<Empty>()
|
||||
|
||||
private var component: SectionHeaderComponent?
|
||||
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)
|
||||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
@ -307,6 +307,10 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
|
||||
private var savedSelectedPeers: [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 selectedOptions = Set<OptionId>()
|
||||
|
||||
@ -557,6 +561,119 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
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) {
|
||||
guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else {
|
||||
return
|
||||
@ -665,14 +782,22 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
component: AnyComponent(SectionHeaderComponent(
|
||||
theme: environment.theme,
|
||||
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: {},
|
||||
containerSize: sectionHeaderFrame.size
|
||||
)
|
||||
if let sectionHeaderView = sectionHeader.view {
|
||||
if sectionHeaderView.superview == nil {
|
||||
sectionHeaderView.isUserInteractionEnabled = false
|
||||
self.scrollContentClippingView.addSubview(sectionHeaderView)
|
||||
}
|
||||
if minSectionHeader == nil {
|
||||
@ -736,7 +861,8 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
self.selectedCategories.removeAll()
|
||||
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(
|
||||
EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
|
||||
self.selectedOptions.contains(.screenshot),
|
||||
@ -744,6 +870,14 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
)
|
||||
controller.dismissAllTooltips()
|
||||
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)))
|
||||
@ -811,6 +945,20 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
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(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(PeerListItemComponent(
|
||||
@ -821,19 +969,23 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
sideInset: itemLayout.sideInset,
|
||||
title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
|
||||
peer: peer,
|
||||
subtitle: nil,
|
||||
subtitle: subtitle,
|
||||
subtitleAccessory: .none,
|
||||
presence: stateValue.presences[peer.id],
|
||||
selectionState: .editing(isSelected: self.selectedPeers.contains(peer.id), isTinted: false),
|
||||
selectionState: .editing(isSelected: isSelected, isTinted: false),
|
||||
hasNext: true,
|
||||
action: { [weak self] peer in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let index = self.selectedPeers.firstIndex(of: peer.id) {
|
||||
self.selectedPeers.remove(at: index)
|
||||
if peer.id.isGroupOrChannel {
|
||||
self.toggleGroupPeer(peer)
|
||||
} else {
|
||||
self.selectedPeers.append(peer.id)
|
||||
if let index = self.selectedPeers.firstIndex(of: peer.id) {
|
||||
self.selectedPeers.remove(at: index)
|
||||
} else {
|
||||
self.selectedPeers.append(peer.id)
|
||||
}
|
||||
}
|
||||
|
||||
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
|
||||
@ -1244,9 +1396,20 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
|
||||
var tokens: [TokenListTextField.Token] = []
|
||||
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
|
||||
}
|
||||
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(
|
||||
id: AnyHashable(peerId),
|
||||
title: peer.compactDisplayTitle,
|
||||
@ -1440,7 +1603,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
actionButtonTitle = environment.strings.Story_Privacy_PostStory
|
||||
}
|
||||
case .chats:
|
||||
title = ""
|
||||
title = environment.strings.Story_Privacy_CategorySelectedContacts
|
||||
case let .contacts(category):
|
||||
switch category {
|
||||
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)))
|
||||
|
||||
let badge: Int
|
||||
if case .stories = component.stateContext.subject {
|
||||
badge = 0
|
||||
} else {
|
||||
badge = self.selectedPeers.count
|
||||
}
|
||||
|
||||
let actionButtonSize = self.actionButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
@ -1519,7 +1689,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
id: actionButtonTitle,
|
||||
component: AnyComponent(ButtonTextContentComponent(
|
||||
text: actionButtonTitle,
|
||||
badge: 0,
|
||||
badge: badge,
|
||||
textColor: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
badgeBackground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
badgeForeground: environment.theme.list.itemCheckColors.fillColor
|
||||
@ -1755,15 +1925,18 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
public final class State {
|
||||
let peers: [EnginePeer]
|
||||
let presences: [EnginePeer.Id: EnginePeer.Presence]
|
||||
let participants: [EnginePeer.Id: Int]
|
||||
let closeFriendsPeers: [EnginePeer]
|
||||
|
||||
fileprivate init(
|
||||
peers: [EnginePeer],
|
||||
presences: [EnginePeer.Id: EnginePeer.Presence],
|
||||
participants: [EnginePeer.Id: Int],
|
||||
closeFriendsPeers: [EnginePeer]
|
||||
) {
|
||||
self.peers = peers
|
||||
self.presences = presences
|
||||
self.participants = participants
|
||||
self.closeFriendsPeers = closeFriendsPeers
|
||||
}
|
||||
}
|
||||
@ -1820,6 +1993,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
let state = State(
|
||||
peers: peers.compactMap { $0 },
|
||||
presences: [:],
|
||||
participants: [:],
|
||||
closeFriendsPeers: closeFriends
|
||||
)
|
||||
self.stateValue = state
|
||||
@ -1828,16 +2002,36 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
self.readySubject.set(true)
|
||||
})
|
||||
case .chats:
|
||||
self.stateDisposable = (context.engine.messages.chatList(group: .root, count: 200)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] chatList in
|
||||
self.stateDisposable = (combineLatest(
|
||||
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 {
|
||||
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] = []
|
||||
for item in chatList.items.reversed() {
|
||||
if self.initialPeerIds.contains(item.renderedPeer.peerId), let peer = item.renderedPeer.peer {
|
||||
selectedPeers.append(peer)
|
||||
existingIds.insert(peer.id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1847,12 +2041,39 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
let state = State(
|
||||
peers: peers,
|
||||
presences: presences,
|
||||
participants: participants,
|
||||
closeFriendsPeers: []
|
||||
)
|
||||
self.stateValue = state
|
||||
@ -1908,6 +2129,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
let state = State(
|
||||
peers: peers,
|
||||
presences: contactList.presences,
|
||||
participants: [:],
|
||||
closeFriendsPeers: []
|
||||
)
|
||||
|
||||
@ -1946,11 +2168,20 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} else if case let .channel(channel) = peer {
|
||||
if channel.isForum {
|
||||
return false
|
||||
}
|
||||
if case .broadcast = channel.info {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
},
|
||||
presences: [:],
|
||||
participants: [:],
|
||||
closeFriendsPeers: []
|
||||
)
|
||||
self.stateValue = state
|
||||
|
@ -450,7 +450,7 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate {
|
||||
|
||||
let previousContentHeight = self.scrollNode.view.contentSize.height
|
||||
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.scrollNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: nodeHeight)))
|
||||
|
@ -13054,7 +13054,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if !value || concealed || botApp.flags.contains(.notActivated) {
|
||||
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()
|
||||
openBotApp(allowWrite)
|
||||
}, showMore: { [weak self] in
|
||||
|
Loading…
x
Reference in New Issue
Block a user