diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 04c8b603b0..34ab9dbbf4 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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."; diff --git a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift index 7d98f514e2..0dd5519c5f 100644 --- a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift +++ b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift @@ -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) } diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index b23a9610c6..7c92b04869 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -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, diff --git a/submodules/DrawingUI/Sources/DrawingTextEntity.swift b/submodules/DrawingUI/Sources/DrawingTextEntity.swift index 08d69a7d98..9f0af4cb24 100644 --- a/submodules/DrawingUI/Sources/DrawingTextEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingTextEntity.swift @@ -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) diff --git a/submodules/DrawingUI/Sources/TextSettingsComponent.swift b/submodules/DrawingUI/Sources/TextSettingsComponent.swift index 1c803491b3..5702b3b372 100644 --- a/submodules/DrawingUI/Sources/TextSettingsComponent.swift +++ b/submodules/DrawingUI/Sources/TextSettingsComponent.swift @@ -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), diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 37b9aa36ad..f7590d6e0e 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -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? { +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? { 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 { - 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 { + 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 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 { +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 { return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource, forceRefresh: forceReupload) |> mapToSignal { result -> Signal 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 { diff --git a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift index 3ddeca6417..3d3fd6be4a 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift @@ -59,7 +59,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, case let .update(media): let generateUploadSignal: (Bool) -> Signal? = { 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)) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 13c7c2fcbd..2ecea0edd8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -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, diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index e9c159fffb..7fdde156fc 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -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) { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift index 3fb2979f19..a66568f0fb 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift index 2367c67702..c394b77b3d 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift @@ -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) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift index 80324382c1..b1123a0ebc 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift @@ -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?) { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index fe59c5f1b0..8f92f92060 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -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)) diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/SectionHeaderComponent.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/SectionHeaderComponent.swift index 53589d0d8a..8d31a033d1 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/SectionHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/SectionHeaderComponent.swift @@ -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() private let backgroundView: BlurredBackgroundView + private let action = ComponentView() 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)) diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 534fd87bf8..ba6de028c5 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -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() private var selectedOptions = Set() @@ -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() + 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() + 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]), 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]) 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() 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 diff --git a/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift b/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift index 81bca64a7e..99190021a3 100644 --- a/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift +++ b/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift @@ -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))) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 45e313eb82..ac285db1e5 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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