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.Editor.TooltipPremiumCaptionEntities" = "Subscribe to [Telegram Premium]() to add links and formatting in captions to your stories.";
|
||||||
|
|
||||||
"Story.Context.TooltipPremiumSaveStories" = "Subscribe to [Telegram Premium]() to save other people's unprotected stories to your Gallery.";
|
"Story.Context.TooltipPremiumSaveStories" = "Subscribe to [Telegram Premium]() to save other people's unprotected stories to your Gallery.";
|
||||||
|
|
||||||
|
"Story.Privacy.GroupTooLarge" = "Group Too Large";
|
||||||
|
"Story.Privacy.GroupParticipantsLimit" = "You can select groups that are up to 200 members.";
|
||||||
|
@ -651,7 +651,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
|
|
||||||
if let selectionView = entityView.makeSelectionView() {
|
if let selectionView = entityView.makeSelectionView() {
|
||||||
selectionView.tapped = { [weak self, weak entityView] in
|
selectionView.tapped = { [weak self, weak entityView] in
|
||||||
if let self, let entityView = entityView {
|
if let self, let entityView {
|
||||||
let entityViews = self.subviews.filter { $0 is DrawingEntityView }
|
let entityViews = self.subviews.filter { $0 is DrawingEntityView }
|
||||||
self.requestedMenuForEntityView(entityView, entityViews.last === entityView)
|
self.requestedMenuForEntityView(entityView, entityViews.last === entityView)
|
||||||
}
|
}
|
||||||
|
@ -2978,8 +2978,6 @@ public final class DrawingToolsInteraction {
|
|||||||
private var isActive = false
|
private var isActive = false
|
||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: ContainerViewLayout?
|
||||||
|
|
||||||
private let startTimestamp = CACurrentMediaTime()
|
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
drawingView: DrawingView,
|
drawingView: DrawingView,
|
||||||
|
@ -58,13 +58,9 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
|||||||
self.textView.delegate = self
|
self.textView.delegate = self
|
||||||
self.addSubview(self.textView)
|
self.addSubview(self.textView)
|
||||||
|
|
||||||
self.emojiViewProvider = { [weak self] emoji in
|
self.emojiViewProvider = { emoji in
|
||||||
guard let strongSelf = self else {
|
|
||||||
return UIView()
|
|
||||||
}
|
|
||||||
|
|
||||||
let pointSize: CGFloat = 128.0
|
let pointSize: CGFloat = 128.0
|
||||||
return EmojiTextAttachmentView(context: context, userLocation: .other, emoji: emoji, file: emoji.file, cache: strongSelf.context.animationCache, renderer: strongSelf.context.animationRenderer, placeholderColor: UIColor.white.withAlphaComponent(0.12), pointSize: CGSize(width: pointSize, height: pointSize))
|
return EmojiTextAttachmentView(context: context, userLocation: .other, emoji: emoji, file: emoji.file, cache: context.animationCache, renderer: context.animationRenderer, placeholderColor: UIColor.white.withAlphaComponent(0.12), pointSize: CGSize(width: pointSize, height: pointSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.textView.onPaste = { [weak self] in
|
self.textView.onPaste = { [weak self] in
|
||||||
@ -285,9 +281,10 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
self._isEditing = false
|
self._isEditing = false
|
||||||
self.textView.resignFirstResponder()
|
|
||||||
self.textView.inputView = nil
|
self.textView.inputView = nil
|
||||||
self.textView.inputAccessoryView = nil
|
self.textView.inputAccessoryView = nil
|
||||||
|
self.textView.reloadInputViews()
|
||||||
|
self.textView.resignFirstResponder()
|
||||||
|
|
||||||
self.textView.isEditable = false
|
self.textView.isEditable = false
|
||||||
self.textView.isSelectable = false
|
self.textView.isSelectable = false
|
||||||
@ -656,8 +653,8 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
|||||||
self.updateEditingPosition(animated: animated)
|
self.updateEditingPosition(animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.textView.onLayoutUpdate = {
|
self.textView.onLayoutUpdate = { [weak self] in
|
||||||
self.updateEntities()
|
self?.updateEntities()
|
||||||
}
|
}
|
||||||
|
|
||||||
super.update(animated: animated)
|
super.update(animated: animated)
|
||||||
@ -1023,15 +1020,20 @@ private class DrawingTextLayoutManager: NSLayoutManager {
|
|||||||
private func prepare() {
|
private func prepare() {
|
||||||
self.path = nil
|
self.path = nil
|
||||||
self.rectArray.removeAll()
|
self.rectArray.removeAll()
|
||||||
|
|
||||||
self.enumerateLineFragments(forGlyphRange: NSRange(location: 0, length: ((self.textStorage?.string ?? "") as NSString).length)) { rect, usedRect, textContainer, glyphRange, _ in
|
self.enumerateLineFragments(forGlyphRange: NSRange(location: 0, length: ((self.textStorage?.string ?? "") as NSString).length)) { rect, usedRect, textContainer, glyphRange, _ in
|
||||||
var ignoreRange = false
|
var ignoreRange = false
|
||||||
let charecterRange = self.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
|
let characterRange = self.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
|
||||||
let substring = ((self.textStorage?.string ?? "") as NSString).substring(with: charecterRange)
|
let substring = ((self.textStorage?.string ?? "") as NSString).substring(with: characterRange)
|
||||||
if substring.trimmingCharacters(in: .newlines).isEmpty {
|
if substring.trimmingCharacters(in: .newlines).isEmpty {
|
||||||
ignoreRange = true
|
ignoreRange = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var usedRect = usedRect
|
||||||
|
if substring.hasSuffix(" ") {
|
||||||
|
usedRect.size.width -= floorToScreenPixels(usedRect.height * 0.145)
|
||||||
|
}
|
||||||
|
|
||||||
if !ignoreRange {
|
if !ignoreRange {
|
||||||
let newRect = CGRect(origin: CGPoint(x: usedRect.minX - self.frameWidthInset, y: usedRect.minY), size: CGSize(width: usedRect.width + self.frameWidthInset * 2.0, height: usedRect.height))
|
let newRect = CGRect(origin: CGPoint(x: usedRect.minX - self.frameWidthInset, y: usedRect.minY), size: CGSize(width: usedRect.width + self.frameWidthInset * 2.0, height: usedRect.height))
|
||||||
self.rectArray.append(newRect)
|
self.rectArray.append(newRect)
|
||||||
|
@ -579,12 +579,13 @@ final class TextSettingsComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let presentFontPicker = component.presentFontPicker
|
||||||
let font = font.update(
|
let font = font.update(
|
||||||
component: TextFontComponent(
|
component: TextFontComponent(
|
||||||
selectedValue: component.font,
|
selectedValue: component.font,
|
||||||
tag: component.fontTag,
|
tag: component.fontTag,
|
||||||
tapped: {
|
tapped: {
|
||||||
component.presentFontPicker()
|
presentFontPicker()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: fontAvailableWidth, height: 30.0),
|
availableSize: CGSize(width: fontAvailableWidth, height: 30.0),
|
||||||
|
@ -53,10 +53,10 @@ enum MessageContentToUpload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, message: Message) -> MessageContentToUpload {
|
func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, message: Message) -> MessageContentToUpload {
|
||||||
return messageContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: false, peerId: message.id.peerId, messageId: message.id, attributes: message.attributes, text: message.text, media: message.media)
|
return messageContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: false, forceNoBigParts: false, peerId: message.id.peerId, messageId: message.id, attributes: message.attributes, text: message.text, media: message.media)
|
||||||
}
|
}
|
||||||
|
|
||||||
func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, peerId: PeerId, messageId: MessageId?, attributes: [MessageAttribute], text: String, media: [Media]) -> MessageContentToUpload {
|
func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, forceNoBigParts: Bool, peerId: PeerId, messageId: MessageId?, attributes: [MessageAttribute], text: String, media: [Media]) -> MessageContentToUpload {
|
||||||
var contextResult: OutgoingChatContextResultMessageAttribute?
|
var contextResult: OutgoingChatContextResultMessageAttribute?
|
||||||
var autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?
|
var autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?
|
||||||
var autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?
|
var autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?
|
||||||
@ -96,14 +96,14 @@ func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Po
|
|||||||
return .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaStory(userId: inputUser, id: media.storyId.id), ""), reuploadInfo: nil, cacheReferenceKey: nil))
|
return .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaStory(userId: inputUser, id: media.storyId.id), ""), reuploadInfo: nil, cacheReferenceKey: nil))
|
||||||
}
|
}
|
||||||
|> castError(PendingMessageUploadError.self), .text)
|
|> castError(PendingMessageUploadError.self), .text)
|
||||||
} else if let media = media.first, let mediaResult = mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, peerId: peerId, media: media, text: text, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, messageId: messageId, attributes: attributes) {
|
} else if let media = media.first, let mediaResult = mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, forceNoBigParts: forceNoBigParts, peerId: peerId, media: media, text: text, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, messageId: messageId, attributes: attributes) {
|
||||||
return .signal(mediaResult, .media)
|
return .signal(mediaResult, .media)
|
||||||
} else {
|
} else {
|
||||||
return .signal(.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil, cacheReferenceKey: nil))), .text)
|
return .signal(.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil, cacheReferenceKey: nil))), .text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, peerId: PeerId, media: Media, text: String, autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?, messageId: MessageId?, attributes: [MessageAttribute]) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? {
|
func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, forceNoBigParts: Bool, peerId: PeerId, media: Media, text: String, autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?, messageId: MessageId?, attributes: [MessageAttribute]) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? {
|
||||||
if let image = media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) {
|
if let image = media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) {
|
||||||
if peerId.namespace == Namespaces.Peer.SecretChat, let resource = largest.resource as? SecretFileMediaResource {
|
if peerId.namespace == Namespaces.Peer.SecretChat, let resource = largest.resource as? SecretFileMediaResource {
|
||||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(.inputEncryptedFile(id: resource.fileId, accessHash: resource.accessHash), resource.decryptedSize, resource.key), reuploadInfo: nil, cacheReferenceKey: nil)))
|
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(.inputEncryptedFile(id: resource.fileId, accessHash: resource.accessHash), resource.decryptedSize, resource.key), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||||
@ -123,7 +123,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: true, isGrouped: isGrouped, passFetchProgress: false, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file)
|
return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: true, isGrouped: isGrouped, passFetchProgress: false, forceNoBigParts: false, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file)
|
||||||
} else {
|
} else {
|
||||||
if forceReupload {
|
if forceReupload {
|
||||||
let mediaReference: AnyMediaReference
|
let mediaReference: AnyMediaReference
|
||||||
@ -157,7 +157,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
|
|||||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: emojiSearchQuery), text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: emojiSearchQuery), text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file)
|
return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, forceNoBigParts: forceNoBigParts, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file)
|
||||||
}
|
}
|
||||||
} else if let contact = media as? TelegramMediaContact {
|
} else if let contact = media as? TelegramMediaContact {
|
||||||
let input = Api.InputMedia.inputMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName, vcard: contact.vCardData ?? "")
|
let input = Api.InputMedia.inputMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName, vcard: contact.vCardData ?? "")
|
||||||
@ -619,8 +619,8 @@ private enum UploadedMediaFileAndThumbnail {
|
|||||||
case done(TelegramMediaFile, UploadedMediaThumbnailResult)
|
case done(TelegramMediaFile, UploadedMediaThumbnailResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func uploadedThumbnail(network: Network, postbox: Postbox, resourceReference: MediaResourceReference) -> Signal<Api.InputFile?, PendingMessageUploadError> {
|
private func uploadedThumbnail(network: Network, postbox: Postbox, resourceReference: MediaResourceReference, forceNoBigParts: Bool = false) -> Signal<Api.InputFile?, PendingMessageUploadError> {
|
||||||
return multipartUpload(network: network, postbox: postbox, source: .resource(resourceReference), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|
return multipartUpload(network: network, postbox: postbox, source: .resource(resourceReference), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: forceNoBigParts)
|
||||||
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
||||||
|> mapToSignal { result -> Signal<Api.InputFile?, PendingMessageUploadError> in
|
|> mapToSignal { result -> Signal<Api.InputFile?, PendingMessageUploadError> in
|
||||||
switch result {
|
switch result {
|
||||||
@ -656,7 +656,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA
|
|||||||
return .file
|
return .file
|
||||||
}
|
}
|
||||||
|
|
||||||
private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], file: TelegramMediaFile) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
|
private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, forceNoBigParts: Bool, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], file: TelegramMediaFile) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
|
||||||
return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource, forceRefresh: forceReupload)
|
return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource, forceRefresh: forceReupload)
|
||||||
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||||
var referenceKey: CachedSentMediaReferenceKey?
|
var referenceKey: CachedSentMediaReferenceKey?
|
||||||
@ -781,7 +781,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
|||||||
fileReference = .standalone(media: media)
|
fileReference = .standalone(media: media)
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploadedThumbnail(network: network, postbox: postbox, resourceReference: fileReference.resourceReference(smallestThumbnail.resource))
|
return uploadedThumbnail(network: network, postbox: postbox, resourceReference: fileReference.resourceReference(smallestThumbnail.resource), forceNoBigParts: forceNoBigParts)
|
||||||
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
||||||
|> map { result in
|
|> map { result in
|
||||||
if let result = result {
|
if let result = result {
|
||||||
|
@ -59,7 +59,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
|
|||||||
case let .update(media):
|
case let .update(media):
|
||||||
let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in
|
let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in
|
||||||
let augmentedMedia = augmentMediaWithReference(media)
|
let augmentedMedia = augmentMediaWithReference(media)
|
||||||
return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: [])
|
return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, forceNoBigParts: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: [])
|
||||||
}
|
}
|
||||||
if let uploadSignal = generateUploadSignal(forceReupload) {
|
if let uploadSignal = generateUploadSignal(forceReupload) {
|
||||||
uploadedMedia = .single(.progress(0.027))
|
uploadedMedia = .single(.progress(0.027))
|
||||||
|
@ -688,6 +688,7 @@ private func uploadedStoryContent(postbox: Postbox, network: Network, media: Med
|
|||||||
forceReupload: true,
|
forceReupload: true,
|
||||||
isGrouped: false,
|
isGrouped: false,
|
||||||
passFetchProgress: passFetchProgress,
|
passFetchProgress: passFetchProgress,
|
||||||
|
forceNoBigParts: true,
|
||||||
peerId: accountPeerId,
|
peerId: accountPeerId,
|
||||||
messageId: nil,
|
messageId: nil,
|
||||||
attributes: attributes,
|
attributes: attributes,
|
||||||
|
@ -1651,9 +1651,15 @@ public class CameraScreen: ViewController {
|
|||||||
guard let camera = self.camera else {
|
guard let camera = self.camera else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let location = gestureRecognizer.location(in: self.mainPreviewView)
|
|
||||||
let point = self.mainPreviewView.cameraPoint(for: location)
|
let location = gestureRecognizer.location(in: gestureRecognizer.view)
|
||||||
camera.focus(at: point, autoFocus: false)
|
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) {
|
@objc private func handleDoubleTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||||
|
@ -42,6 +42,7 @@ final class MediaEditorComposer {
|
|||||||
private let values: MediaEditorValues
|
private let values: MediaEditorValues
|
||||||
private let dimensions: CGSize
|
private let dimensions: CGSize
|
||||||
private let outputDimensions: CGSize
|
private let outputDimensions: CGSize
|
||||||
|
private let textScale: CGFloat
|
||||||
|
|
||||||
private let ciContext: CIContext?
|
private let ciContext: CIContext?
|
||||||
private var textureCache: CVMetalTextureCache?
|
private var textureCache: CVMetalTextureCache?
|
||||||
@ -53,10 +54,11 @@ final class MediaEditorComposer {
|
|||||||
private let drawingImage: CIImage?
|
private let drawingImage: CIImage?
|
||||||
private var entities: [MediaEditorComposerEntity]
|
private var entities: [MediaEditorComposerEntity]
|
||||||
|
|
||||||
init(account: Account, values: MediaEditorValues, dimensions: CGSize, outputDimensions: CGSize) {
|
init(account: Account, values: MediaEditorValues, dimensions: CGSize, outputDimensions: CGSize, textScale: CGFloat) {
|
||||||
self.values = values
|
self.values = values
|
||||||
self.dimensions = dimensions
|
self.dimensions = dimensions
|
||||||
self.outputDimensions = outputDimensions
|
self.outputDimensions = outputDimensions
|
||||||
|
self.textScale = textScale
|
||||||
|
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
self.colorSpace = colorSpace
|
self.colorSpace = colorSpace
|
||||||
@ -77,7 +79,7 @@ final class MediaEditorComposer {
|
|||||||
|
|
||||||
var entities: [MediaEditorComposerEntity] = []
|
var entities: [MediaEditorComposerEntity] = []
|
||||||
for entity in values.entities {
|
for entity in values.entities {
|
||||||
entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, entity: entity.entity, colorSpace: colorSpace))
|
entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, textScale: textScale, entity: entity.entity, colorSpace: colorSpace))
|
||||||
}
|
}
|
||||||
self.entities = entities
|
self.entities = entities
|
||||||
|
|
||||||
@ -155,7 +157,7 @@ final class MediaEditorComposer {
|
|||||||
CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer)
|
CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer)
|
||||||
|
|
||||||
if let pixelBuffer, let context = self.ciContext {
|
if let pixelBuffer, let context = self.ciContext {
|
||||||
makeEditorImageFrameComposition(context: context, inputImage: image, gradientImage: self.gradientImage, drawingImage: self.drawingImage, dimensions: self.dimensions, values: self.values, entities: self.entities, time: time, completion: { compositedImage in
|
makeEditorImageFrameComposition(context: context, inputImage: image, gradientImage: self.gradientImage, drawingImage: self.drawingImage, dimensions: self.dimensions, outputDimensions: self.outputDimensions, values: self.values, entities: self.entities, time: time, completion: { compositedImage in
|
||||||
if var compositedImage {
|
if var compositedImage {
|
||||||
let scale = self.outputDimensions.width / self.dimensions.width
|
let scale = self.outputDimensions.width / self.dimensions.width
|
||||||
compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale))
|
compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale))
|
||||||
@ -176,11 +178,11 @@ final class MediaEditorComposer {
|
|||||||
guard let context = self.ciContext else {
|
guard let context = self.ciContext else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
makeEditorImageFrameComposition(context: context, inputImage: inputImage, gradientImage: self.gradientImage, drawingImage: self.drawingImage, dimensions: self.dimensions, values: self.values, entities: self.entities, time: time, completion: completion)
|
makeEditorImageFrameComposition(context: context, inputImage: inputImage, gradientImage: self.gradientImage, drawingImage: self.drawingImage, dimensions: self.dimensions, outputDimensions: self.outputDimensions, values: self.values, entities: self.entities, time: time, textScale: self.textScale, completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeEditorImageComposition(context: CIContext, account: Account, inputImage: UIImage, dimensions: CGSize, values: MediaEditorValues, time: CMTime, completion: @escaping (UIImage?) -> Void) {
|
public func makeEditorImageComposition(context: CIContext, account: Account, inputImage: UIImage, dimensions: CGSize, values: MediaEditorValues, time: CMTime, textScale: CGFloat, completion: @escaping (UIImage?) -> Void) {
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
let inputImage = CIImage(image: inputImage, options: [.colorSpace: colorSpace])!
|
let inputImage = CIImage(image: inputImage, options: [.colorSpace: colorSpace])!
|
||||||
let gradientImage: CIImage
|
let gradientImage: CIImage
|
||||||
@ -197,10 +199,10 @@ public func makeEditorImageComposition(context: CIContext, account: Account, inp
|
|||||||
|
|
||||||
var entities: [MediaEditorComposerEntity] = []
|
var entities: [MediaEditorComposerEntity] = []
|
||||||
for entity in values.entities {
|
for entity in values.entities {
|
||||||
entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, entity: entity.entity, colorSpace: colorSpace))
|
entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, textScale: textScale, entity: entity.entity, colorSpace: colorSpace))
|
||||||
}
|
}
|
||||||
|
|
||||||
makeEditorImageFrameComposition(context: context, inputImage: inputImage, gradientImage: gradientImage, drawingImage: drawingImage, dimensions: dimensions, values: values, entities: entities, time: time, completion: { ciImage in
|
makeEditorImageFrameComposition(context: context, inputImage: inputImage, gradientImage: gradientImage, drawingImage: drawingImage, dimensions: dimensions, outputDimensions: dimensions, values: values, entities: entities, time: time, textScale: textScale, completion: { ciImage in
|
||||||
if let ciImage {
|
if let ciImage {
|
||||||
if let cgImage = context.createCGImage(ciImage, from: CGRect(origin: .zero, size: ciImage.extent.size)) {
|
if let cgImage = context.createCGImage(ciImage, from: CGRect(origin: .zero, size: ciImage.extent.size)) {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
@ -213,7 +215,7 @@ public func makeEditorImageComposition(context: CIContext, account: Account, inp
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeEditorImageFrameComposition(context: CIContext, inputImage: CIImage, gradientImage: CIImage, drawingImage: CIImage?, dimensions: CGSize, values: MediaEditorValues, entities: [MediaEditorComposerEntity], time: CMTime, completion: @escaping (CIImage?) -> Void) {
|
private func makeEditorImageFrameComposition(context: CIContext, inputImage: CIImage, gradientImage: CIImage, drawingImage: CIImage?, dimensions: CGSize, outputDimensions: CGSize, values: MediaEditorValues, entities: [MediaEditorComposerEntity], time: CMTime, textScale: CGFloat = 1.0, completion: @escaping (CIImage?) -> Void) {
|
||||||
var resultImage = CIImage(color: .black).cropped(to: CGRect(origin: .zero, size: dimensions)).transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0))
|
var resultImage = CIImage(color: .black).cropped(to: CGRect(origin: .zero, size: dimensions)).transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0))
|
||||||
resultImage = gradientImage.composited(over: resultImage)
|
resultImage = gradientImage.composited(over: resultImage)
|
||||||
|
|
||||||
@ -267,8 +269,10 @@ private func makeEditorImageFrameComposition(context: CIContext, inputImage: CII
|
|||||||
image = image.transformed(by: resetTransform)
|
image = image.transformed(by: resetTransform)
|
||||||
|
|
||||||
var baseScale: CGFloat = 1.0
|
var baseScale: CGFloat = 1.0
|
||||||
if let entityBaseScale = entity.baseScale {
|
if let scale = entity.baseScale {
|
||||||
baseScale = entityBaseScale
|
baseScale = scale
|
||||||
|
} else if let _ = entity.baseDrawingSize {
|
||||||
|
// baseScale = textScale
|
||||||
} else if let baseSize = entity.baseSize {
|
} else if let baseSize = entity.baseSize {
|
||||||
baseScale = baseSize.width / image.extent.width
|
baseScale = baseSize.width / image.extent.width
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,11 @@ import TelegramAnimatedStickerNode
|
|||||||
import YuvConversion
|
import YuvConversion
|
||||||
import StickerResources
|
import StickerResources
|
||||||
|
|
||||||
private func prerenderTextTransformations(entity: DrawingTextEntity, image: UIImage, colorSpace: CGColorSpace) -> MediaEditorComposerStaticEntity {
|
private func prerenderTextTransformations(entity: DrawingTextEntity, image: UIImage, textScale: CGFloat, colorSpace: CGColorSpace) -> MediaEditorComposerStaticEntity {
|
||||||
let imageSize = image.size
|
let imageSize = image.size
|
||||||
|
|
||||||
let angle = -entity.rotation
|
let angle = -entity.rotation
|
||||||
let scale = entity.scale
|
let scale = entity.scale * 0.5 * textScale
|
||||||
|
|
||||||
let rotatedSize = CGSize(
|
let rotatedSize = CGSize(
|
||||||
width: abs(imageSize.width * cos(angle)) + abs(imageSize.height * sin(angle)),
|
width: abs(imageSize.width * cos(angle)) + abs(imageSize.height * sin(angle)),
|
||||||
@ -43,10 +43,10 @@ private func prerenderTextTransformations(entity: DrawingTextEntity, image: UIIm
|
|||||||
}
|
}
|
||||||
}, scale: 1.0)!
|
}, scale: 1.0)!
|
||||||
|
|
||||||
return MediaEditorComposerStaticEntity(image: CIImage(image: newImage, options: [.colorSpace: colorSpace])!, position: entity.position, scale: 1.0, rotation: 0.0, baseSize: nil, baseScale: 1.0, mirrored: false)
|
return MediaEditorComposerStaticEntity(image: CIImage(image: newImage, options: [.colorSpace: colorSpace])!, position: entity.position, scale: 1.0, rotation: 0.0, baseSize: nil, baseDrawingSize: CGSize(width: 1080, height: 1920), mirrored: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func composerEntitiesForDrawingEntity(account: Account, entity: DrawingEntity, colorSpace: CGColorSpace, tintColor: UIColor? = nil) -> [MediaEditorComposerEntity] {
|
func composerEntitiesForDrawingEntity(account: Account, textScale: CGFloat, entity: DrawingEntity, colorSpace: CGColorSpace, tintColor: UIColor? = nil) -> [MediaEditorComposerEntity] {
|
||||||
if let entity = entity as? DrawingStickerEntity {
|
if let entity = entity as? DrawingStickerEntity {
|
||||||
let content: MediaEditorComposerStickerEntity.Content
|
let content: MediaEditorComposerStickerEntity.Content
|
||||||
switch entity.content {
|
switch entity.content {
|
||||||
@ -62,19 +62,18 @@ func composerEntitiesForDrawingEntity(account: Account, entity: DrawingEntity, c
|
|||||||
return [MediaEditorComposerStickerEntity(account: account, content: content, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: entity.mirrored, colorSpace: colorSpace, tintColor: tintColor, isStatic: entity.isExplicitlyStatic)]
|
return [MediaEditorComposerStickerEntity(account: account, content: content, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: entity.mirrored, colorSpace: colorSpace, tintColor: tintColor, isStatic: entity.isExplicitlyStatic)]
|
||||||
} else if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) {
|
} else if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) {
|
||||||
if let entity = entity as? DrawingBubbleEntity {
|
if let entity = entity as? DrawingBubbleEntity {
|
||||||
return [MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, baseScale: nil, mirrored: false)]
|
return [MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, mirrored: false)]
|
||||||
} else if let entity = entity as? DrawingSimpleShapeEntity {
|
} else if let entity = entity as? DrawingSimpleShapeEntity {
|
||||||
return [MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, baseScale: nil, mirrored: false)]
|
return [MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, mirrored: false)]
|
||||||
} else if let entity = entity as? DrawingVectorEntity {
|
} else if let entity = entity as? DrawingVectorEntity {
|
||||||
return [MediaEditorComposerStaticEntity(image: image, position: CGPoint(x: entity.drawingSize.width * 0.5, y: entity.drawingSize.height * 0.5), scale: 1.0, rotation: 0.0, baseSize: entity.drawingSize, baseScale: nil, mirrored: false)]
|
return [MediaEditorComposerStaticEntity(image: image, position: CGPoint(x: entity.drawingSize.width * 0.5, y: entity.drawingSize.height * 0.5), scale: 1.0, rotation: 0.0, baseSize: entity.drawingSize, mirrored: false)]
|
||||||
} else if let entity = entity as? DrawingTextEntity {
|
} else if let entity = entity as? DrawingTextEntity {
|
||||||
var entities: [MediaEditorComposerEntity] = []
|
var entities: [MediaEditorComposerEntity] = []
|
||||||
// entities.append(prerenderTextTransformations(entity: entity, image: renderImage, colorSpace: colorSpace))
|
entities.append(prerenderTextTransformations(entity: entity, image: renderImage, textScale: textScale, colorSpace: colorSpace))
|
||||||
|
|
||||||
entities.append(MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: nil, baseScale: 0.5, mirrored: false))
|
|
||||||
if let renderSubEntities = entity.renderSubEntities {
|
if let renderSubEntities = entity.renderSubEntities {
|
||||||
for subEntity in renderSubEntities {
|
for subEntity in renderSubEntities {
|
||||||
entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, entity: subEntity, colorSpace: colorSpace, tintColor: entity.color.toUIColor()))
|
entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, textScale: textScale, entity: subEntity, colorSpace: colorSpace, tintColor: entity.color.toUIColor()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return entities
|
return entities
|
||||||
@ -90,15 +89,26 @@ private class MediaEditorComposerStaticEntity: MediaEditorComposerEntity {
|
|||||||
let rotation: CGFloat
|
let rotation: CGFloat
|
||||||
let baseSize: CGSize?
|
let baseSize: CGSize?
|
||||||
let baseScale: CGFloat?
|
let baseScale: CGFloat?
|
||||||
|
let baseDrawingSize: CGSize?
|
||||||
let mirrored: Bool
|
let mirrored: Bool
|
||||||
|
|
||||||
init(image: CIImage, position: CGPoint, scale: CGFloat, rotation: CGFloat, baseSize: CGSize?, baseScale: CGFloat?, mirrored: Bool) {
|
init(
|
||||||
|
image: CIImage,
|
||||||
|
position: CGPoint,
|
||||||
|
scale: CGFloat,
|
||||||
|
rotation: CGFloat,
|
||||||
|
baseSize: CGSize?,
|
||||||
|
baseScale: CGFloat? = nil,
|
||||||
|
baseDrawingSize: CGSize? = nil,
|
||||||
|
mirrored: Bool
|
||||||
|
) {
|
||||||
self.image = image
|
self.image = image
|
||||||
self.position = position
|
self.position = position
|
||||||
self.scale = scale
|
self.scale = scale
|
||||||
self.rotation = rotation
|
self.rotation = rotation
|
||||||
self.baseSize = baseSize
|
self.baseSize = baseSize
|
||||||
self.baseScale = baseScale
|
self.baseScale = baseScale
|
||||||
|
self.baseDrawingSize = baseDrawingSize
|
||||||
self.mirrored = mirrored
|
self.mirrored = mirrored
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +137,7 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity {
|
|||||||
let rotation: CGFloat
|
let rotation: CGFloat
|
||||||
let baseSize: CGSize?
|
let baseSize: CGSize?
|
||||||
let baseScale: CGFloat? = nil
|
let baseScale: CGFloat? = nil
|
||||||
|
let baseDrawingSize: CGSize? = nil
|
||||||
let mirrored: Bool
|
let mirrored: Bool
|
||||||
let colorSpace: CGColorSpace
|
let colorSpace: CGColorSpace
|
||||||
let tintColor: UIColor?
|
let tintColor: UIColor?
|
||||||
@ -475,6 +486,7 @@ protocol MediaEditorComposerEntity {
|
|||||||
var rotation: CGFloat { get }
|
var rotation: CGFloat { get }
|
||||||
var baseSize: CGSize? { get }
|
var baseSize: CGSize? { get }
|
||||||
var baseScale: CGFloat? { get }
|
var baseScale: CGFloat? { get }
|
||||||
|
var baseDrawingSize: CGSize? { get }
|
||||||
var mirrored: Bool { get }
|
var mirrored: Bool { get }
|
||||||
|
|
||||||
func image(for time: CMTime, frameRate: Float, context: CIContext, completion: @escaping (CIImage?) -> Void)
|
func image(for time: CMTime, frameRate: Float, context: CIContext, completion: @escaping (CIImage?) -> Void)
|
||||||
|
@ -261,6 +261,7 @@ public final class MediaEditorVideoExport {
|
|||||||
private let account: Account
|
private let account: Account
|
||||||
private let subject: Subject
|
private let subject: Subject
|
||||||
private let configuration: Configuration
|
private let configuration: Configuration
|
||||||
|
private let textScale: CGFloat
|
||||||
private let outputPath: String
|
private let outputPath: String
|
||||||
|
|
||||||
private var reader: AVAssetReader?
|
private var reader: AVAssetReader?
|
||||||
@ -295,11 +296,12 @@ public final class MediaEditorVideoExport {
|
|||||||
|
|
||||||
private let semaphore = DispatchSemaphore(value: 0)
|
private let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
|
||||||
public init(account: Account, subject: Subject, configuration: Configuration, outputPath: String) {
|
public init(account: Account, subject: Subject, configuration: Configuration, outputPath: String, textScale: CGFloat = 1.0) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.outputPath = outputPath
|
self.outputPath = outputPath
|
||||||
|
self.textScale = textScale
|
||||||
|
|
||||||
if FileManager.default.fileExists(atPath: outputPath) {
|
if FileManager.default.fileExists(atPath: outputPath) {
|
||||||
try? FileManager.default.removeItem(atPath: outputPath)
|
try? FileManager.default.removeItem(atPath: outputPath)
|
||||||
@ -354,7 +356,7 @@ public final class MediaEditorVideoExport {
|
|||||||
guard self.composer == nil else {
|
guard self.composer == nil else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.composer = MediaEditorComposer(account: self.account, values: self.configuration.values, dimensions: self.configuration.composerDimensions, outputDimensions: self.configuration.dimensions)
|
self.composer = MediaEditorComposer(account: self.account, values: self.configuration.values, dimensions: self.configuration.composerDimensions, outputDimensions: self.configuration.dimensions, textScale: self.textScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupWithAsset(_ asset: AVAsset, additionalAsset: AVAsset?) {
|
private func setupWithAsset(_ asset: AVAsset, additionalAsset: AVAsset?) {
|
||||||
|
@ -3320,7 +3320,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openEditCategory(privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, pin: Bool, completion: @escaping (EngineStoryPrivacy) -> Void) {
|
private func openEditCategory(privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, pin: Bool, completion: @escaping (EngineStoryPrivacy) -> Void) {
|
||||||
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .contacts(privacy.base), initialPeerIds: Set(privacy.additionallyIncludePeers))
|
let subject: ShareWithPeersScreen.StateContext.Subject
|
||||||
|
if privacy.base == .nobody {
|
||||||
|
subject = .chats
|
||||||
|
} else {
|
||||||
|
subject = .contacts(privacy.base)
|
||||||
|
}
|
||||||
|
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: subject, initialPeerIds: Set(privacy.additionallyIncludePeers))
|
||||||
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -3561,7 +3567,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
|
|
||||||
if let resultImage = mediaEditor.resultImage {
|
if let resultImage = mediaEditor.resultImage {
|
||||||
mediaEditor.seek(0.0, andPlay: false)
|
mediaEditor.seek(0.0, andPlay: false)
|
||||||
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: resultImage, dimensions: storyDimensions, values: values, time: .zero, completion: { resultImage in
|
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: resultImage, dimensions: storyDimensions, values: values, time: .zero, textScale: 2.0, completion: { resultImage in
|
||||||
guard let resultImage else {
|
guard let resultImage else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -3844,7 +3850,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
inputImage = UIImage()
|
inputImage = UIImage()
|
||||||
}
|
}
|
||||||
|
|
||||||
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: inputImage, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
|
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: inputImage, dimensions: storyDimensions, values: mediaEditor.values, time: firstFrameTime, textScale: 2.0, completion: { [weak self] coverImage in
|
||||||
if let self {
|
if let self {
|
||||||
Logger.shared.log("MediaEditor", "Completed with video \(videoResult)")
|
Logger.shared.log("MediaEditor", "Completed with video \(videoResult)")
|
||||||
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in
|
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in
|
||||||
@ -3867,7 +3873,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
if let image = mediaEditor.resultImage {
|
if let image = mediaEditor.resultImage {
|
||||||
self.saveDraft(id: randomId)
|
self.saveDraft(id: randomId)
|
||||||
|
|
||||||
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] resultImage in
|
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in
|
||||||
if let self, let resultImage {
|
if let self, let resultImage {
|
||||||
Logger.shared.log("MediaEditor", "Completed with image \(resultImage)")
|
Logger.shared.log("MediaEditor", "Completed with image \(resultImage)")
|
||||||
self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), caption, self.state.privacy, stickers, { [weak self] finished in
|
self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), caption, self.state.privacy, stickers, { [weak self] finished in
|
||||||
@ -3995,7 +4001,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, duration: duration, forceFullHd: true, frameRate: 60.0)
|
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, duration: duration, forceFullHd: true, frameRate: 60.0)
|
||||||
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).mp4"
|
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).mp4"
|
||||||
let videoExport = MediaEditorVideoExport(account: self.context.account, subject: exportSubject, configuration: configuration, outputPath: outputPath)
|
let videoExport = MediaEditorVideoExport(account: self.context.account, subject: exportSubject, configuration: configuration, outputPath: outputPath, textScale: 2.0)
|
||||||
self.videoExport = videoExport
|
self.videoExport = videoExport
|
||||||
|
|
||||||
videoExport.start()
|
videoExport.start()
|
||||||
@ -4028,7 +4034,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
} else {
|
} else {
|
||||||
if let image = mediaEditor.resultImage {
|
if let image = mediaEditor.resultImage {
|
||||||
Queue.concurrentDefaultQueue().async {
|
Queue.concurrentDefaultQueue().async {
|
||||||
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { resultImage in
|
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, textScale: 2.0, completion: { resultImage in
|
||||||
if let data = resultImage?.jpegData(compressionQuality: 0.8) {
|
if let data = resultImage?.jpegData(compressionQuality: 0.8) {
|
||||||
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).jpg"
|
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).jpg"
|
||||||
try? data.write(to: URL(fileURLWithPath: outputPath))
|
try? data.write(to: URL(fileURLWithPath: outputPath))
|
||||||
|
@ -9,15 +9,21 @@ final class SectionHeaderComponent: Component {
|
|||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let style: ShareWithPeersScreenComponent.Style
|
let style: ShareWithPeersScreenComponent.Style
|
||||||
let title: String
|
let title: String
|
||||||
|
let actionTitle: String?
|
||||||
|
let action: (() -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
style: ShareWithPeersScreenComponent.Style,
|
style: ShareWithPeersScreenComponent.Style,
|
||||||
title: String
|
title: String,
|
||||||
|
actionTitle: String?,
|
||||||
|
action: (() -> Void)?
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.style = style
|
self.style = style
|
||||||
self.title = title
|
self.title = title
|
||||||
|
self.actionTitle = actionTitle
|
||||||
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: SectionHeaderComponent, rhs: SectionHeaderComponent) -> Bool {
|
static func ==(lhs: SectionHeaderComponent, rhs: SectionHeaderComponent) -> Bool {
|
||||||
@ -30,12 +36,16 @@ final class SectionHeaderComponent: Component {
|
|||||||
if lhs.title != rhs.title {
|
if lhs.title != rhs.title {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.actionTitle != rhs.actionTitle {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
final class View: UIView {
|
final class View: UIView {
|
||||||
private let title = ComponentView<Empty>()
|
private let title = ComponentView<Empty>()
|
||||||
private let backgroundView: BlurredBackgroundView
|
private let backgroundView: BlurredBackgroundView
|
||||||
|
private let action = ComponentView<Empty>()
|
||||||
|
|
||||||
private var component: SectionHeaderComponent?
|
private var component: SectionHeaderComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
@ -95,6 +105,32 @@ final class SectionHeaderComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let actionTitle = component.actionTitle {
|
||||||
|
let actionSize = self.action.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
Button(content: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: actionTitle, font: Font.regular(13.0), textColor: component.theme.list.itemSecondaryTextColor))
|
||||||
|
)), action: { [weak self] in
|
||||||
|
if let self, let component = self.component {
|
||||||
|
component.action?()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
|
||||||
|
)
|
||||||
|
if let view = self.action.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
self.addSubview(view)
|
||||||
|
}
|
||||||
|
let actionFrame = CGRect(origin: CGPoint(x: availableSize.width - leftInset - titleSize.width, y: floor((height - titleSize.height) / 2.0)), size: actionSize)
|
||||||
|
view.frame = actionFrame
|
||||||
|
}
|
||||||
|
} else if self.action.view?.superview != nil {
|
||||||
|
self.action.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
let size = CGSize(width: availableSize.width, height: height)
|
let size = CGSize(width: availableSize.width, height: height)
|
||||||
|
|
||||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
@ -307,6 +307,10 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
|
|
||||||
private var savedSelectedPeers: [EnginePeer.Id] = []
|
private var savedSelectedPeers: [EnginePeer.Id] = []
|
||||||
private var selectedPeers: [EnginePeer.Id] = []
|
private var selectedPeers: [EnginePeer.Id] = []
|
||||||
|
private var selectedGroups: [EnginePeer.Id] = []
|
||||||
|
|
||||||
|
private var peersMap: [EnginePeer.Id: EnginePeer] = [:]
|
||||||
|
|
||||||
private var selectedCategories = Set<CategoryId>()
|
private var selectedCategories = Set<CategoryId>()
|
||||||
private var selectedOptions = Set<OptionId>()
|
private var selectedOptions = Set<OptionId>()
|
||||||
|
|
||||||
@ -557,6 +561,119 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
controller.present(tooltipScreen, in: .window(.root))
|
controller.present(tooltipScreen, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func toggleGroupPeer(_ peer: EnginePeer) {
|
||||||
|
guard let component = self.component, let environment = self.environment, let controller = self.environment?.controller() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupTooLarge = false
|
||||||
|
if case let .legacyGroup(group) = peer, group.participantCount > 200 {
|
||||||
|
groupTooLarge = true
|
||||||
|
} else if let stateValue = self.effectiveStateValue, let count = stateValue.participants[peer.id], count > 200 {
|
||||||
|
groupTooLarge = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupTooLarge {
|
||||||
|
let alertController = textAlertController(
|
||||||
|
context: component.context,
|
||||||
|
forceTheme: defaultDarkColorPresentationTheme,
|
||||||
|
title: environment.strings.Story_Privacy_GroupTooLarge,
|
||||||
|
text: environment.strings.Story_Privacy_GroupParticipantsLimit,
|
||||||
|
actions: [
|
||||||
|
TextAlertAction(type: .defaultAction, title: "OK", action: {})
|
||||||
|
],
|
||||||
|
actionLayout: .vertical
|
||||||
|
)
|
||||||
|
controller.present(alertController, in: .window(.root))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var append = false
|
||||||
|
if let index = self.selectedGroups.firstIndex(of: peer.id) {
|
||||||
|
self.selectedGroups.remove(at: index)
|
||||||
|
} else {
|
||||||
|
self.selectedGroups.append(peer.id)
|
||||||
|
append = true
|
||||||
|
}
|
||||||
|
let context = component.context
|
||||||
|
if peer.id.namespace == Namespaces.Peer.CloudGroup {
|
||||||
|
let _ = (context.engine.data.subscribe(
|
||||||
|
TelegramEngine.EngineData.Item.Peer.LegacyGroupParticipants(id: peer.id)
|
||||||
|
)
|
||||||
|
|> mapToSignal { participants -> Signal<[EnginePeer], NoError> in
|
||||||
|
if case let .known(participants) = participants {
|
||||||
|
return context.engine.data.get(
|
||||||
|
EngineDataMap(participants.map { TelegramEngine.EngineData.Item.Peer.Peer(id: $0.peerId) })
|
||||||
|
)
|
||||||
|
|> map { peers in
|
||||||
|
var result: [EnginePeer] = []
|
||||||
|
for participant in participants {
|
||||||
|
if let peer = peers[participant.peerId], let peer, peer.id != context.account.peerId {
|
||||||
|
result.append(peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var peerIds = Set<EnginePeer.Id>()
|
||||||
|
for peer in peers {
|
||||||
|
self.peersMap[peer.id] = peer
|
||||||
|
peerIds.insert(peer.id)
|
||||||
|
}
|
||||||
|
if append {
|
||||||
|
self.selectedPeers.append(contentsOf: peers.map { $0.id })
|
||||||
|
} else {
|
||||||
|
self.selectedPeers = self.selectedPeers.filter { !peerIds.contains($0) }
|
||||||
|
}
|
||||||
|
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
|
||||||
|
self.state?.updated(transition: transition)
|
||||||
|
})
|
||||||
|
} else if peer.id.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
let participants: Signal<[EnginePeer], NoError> = Signal { subscriber in
|
||||||
|
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peer.id, requestUpdate: true, updated: { list in
|
||||||
|
var peers: [EnginePeer] = []
|
||||||
|
for item in list.list {
|
||||||
|
peers.append(EnginePeer(item.peer))
|
||||||
|
}
|
||||||
|
if !peers.isEmpty {
|
||||||
|
subscriber.putNext(peers)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return disposable
|
||||||
|
}
|
||||||
|
|> take(1)
|
||||||
|
|
||||||
|
let _ = (participants
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var peerIds = Set<EnginePeer.Id>()
|
||||||
|
for peer in peers {
|
||||||
|
self.peersMap[peer.id] = peer
|
||||||
|
peerIds.insert(peer.id)
|
||||||
|
}
|
||||||
|
if append {
|
||||||
|
self.selectedPeers.append(contentsOf: peers.map { $0.id })
|
||||||
|
} else {
|
||||||
|
self.selectedPeers = self.selectedPeers.filter { !peerIds.contains($0) }
|
||||||
|
}
|
||||||
|
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
|
||||||
|
self.state?.updated(transition: transition)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateScrolling(transition: Transition) {
|
private func updateScrolling(transition: Transition) {
|
||||||
guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else {
|
guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else {
|
||||||
return
|
return
|
||||||
@ -665,14 +782,22 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
component: AnyComponent(SectionHeaderComponent(
|
component: AnyComponent(SectionHeaderComponent(
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
style: itemLayout.style,
|
style: itemLayout.style,
|
||||||
title: sectionTitle
|
title: sectionTitle,
|
||||||
|
actionTitle: (section.id == 1 && !self.selectedPeers.isEmpty) ? environment.strings.Contacts_DeselectAll : nil,
|
||||||
|
action: { [weak self] in
|
||||||
|
if let self {
|
||||||
|
self.selectedPeers = []
|
||||||
|
self.selectedGroups = []
|
||||||
|
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
|
||||||
|
self.state?.updated(transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: sectionHeaderFrame.size
|
containerSize: sectionHeaderFrame.size
|
||||||
)
|
)
|
||||||
if let sectionHeaderView = sectionHeader.view {
|
if let sectionHeaderView = sectionHeader.view {
|
||||||
if sectionHeaderView.superview == nil {
|
if sectionHeaderView.superview == nil {
|
||||||
sectionHeaderView.isUserInteractionEnabled = false
|
|
||||||
self.scrollContentClippingView.addSubview(sectionHeaderView)
|
self.scrollContentClippingView.addSubview(sectionHeaderView)
|
||||||
}
|
}
|
||||||
if minSectionHeader == nil {
|
if minSectionHeader == nil {
|
||||||
@ -736,7 +861,8 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
self.selectedCategories.removeAll()
|
self.selectedCategories.removeAll()
|
||||||
self.selectedCategories.insert(categoryId)
|
self.selectedCategories.insert(categoryId)
|
||||||
|
|
||||||
if self.selectedPeers.isEmpty && categoryId == .selectedContacts {
|
let closeFriends = self.component?.stateContext.stateValue?.closeFriendsPeers ?? []
|
||||||
|
if categoryId == .selectedContacts && self.selectedPeers.isEmpty {
|
||||||
component.editCategory(
|
component.editCategory(
|
||||||
EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
|
EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
|
||||||
self.selectedOptions.contains(.screenshot),
|
self.selectedOptions.contains(.screenshot),
|
||||||
@ -744,6 +870,14 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
)
|
)
|
||||||
controller.dismissAllTooltips()
|
controller.dismissAllTooltips()
|
||||||
controller.dismiss()
|
controller.dismiss()
|
||||||
|
} else if categoryId == .closeFriends && closeFriends.isEmpty {
|
||||||
|
component.editCategory(
|
||||||
|
EngineStoryPrivacy(base: .closeFriends, additionallyIncludePeers: []),
|
||||||
|
self.selectedOptions.contains(.screenshot),
|
||||||
|
self.selectedOptions.contains(.pin)
|
||||||
|
)
|
||||||
|
controller.dismissAllTooltips()
|
||||||
|
controller.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring)))
|
||||||
@ -811,6 +945,20 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
self.visibleItems[itemId] = visibleItem
|
self.visibleItems[itemId] = visibleItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let subtitle: String?
|
||||||
|
if case let .legacyGroup(group) = peer {
|
||||||
|
subtitle = environment.strings.Conversation_StatusMembers(Int32(group.participantCount))
|
||||||
|
} else if case .channel = peer {
|
||||||
|
if let count = stateValue.participants[peer.id] {
|
||||||
|
subtitle = environment.strings.Conversation_StatusMembers(Int32(count))
|
||||||
|
} else {
|
||||||
|
subtitle = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subtitle = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let isSelected = self.selectedPeers.contains(peer.id) || self.selectedGroups.contains(peer.id)
|
||||||
let _ = visibleItem.update(
|
let _ = visibleItem.update(
|
||||||
transition: itemTransition,
|
transition: itemTransition,
|
||||||
component: AnyComponent(PeerListItemComponent(
|
component: AnyComponent(PeerListItemComponent(
|
||||||
@ -821,19 +969,23 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
sideInset: itemLayout.sideInset,
|
sideInset: itemLayout.sideInset,
|
||||||
title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
|
title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
|
||||||
peer: peer,
|
peer: peer,
|
||||||
subtitle: nil,
|
subtitle: subtitle,
|
||||||
subtitleAccessory: .none,
|
subtitleAccessory: .none,
|
||||||
presence: stateValue.presences[peer.id],
|
presence: stateValue.presences[peer.id],
|
||||||
selectionState: .editing(isSelected: self.selectedPeers.contains(peer.id), isTinted: false),
|
selectionState: .editing(isSelected: isSelected, isTinted: false),
|
||||||
hasNext: true,
|
hasNext: true,
|
||||||
action: { [weak self] peer in
|
action: { [weak self] peer in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let index = self.selectedPeers.firstIndex(of: peer.id) {
|
if peer.id.isGroupOrChannel {
|
||||||
self.selectedPeers.remove(at: index)
|
self.toggleGroupPeer(peer)
|
||||||
} else {
|
} 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))
|
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
|
||||||
@ -1244,9 +1396,20 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
|
|
||||||
var tokens: [TokenListTextField.Token] = []
|
var tokens: [TokenListTextField.Token] = []
|
||||||
for peerId in self.selectedPeers {
|
for peerId in self.selectedPeers {
|
||||||
guard let stateValue = self.defaultStateValue, let peer = stateValue.peers.first(where: { $0.id == peerId }) else {
|
guard let stateValue = self.defaultStateValue else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
var peer: EnginePeer?
|
||||||
|
if let peerValue = self.peersMap[peerId] {
|
||||||
|
peer = peerValue
|
||||||
|
} else if let peerValue = stateValue.peers.first(where: { $0.id == peerId }) {
|
||||||
|
peer = peerValue
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let peer else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
tokens.append(TokenListTextField.Token(
|
tokens.append(TokenListTextField.Token(
|
||||||
id: AnyHashable(peerId),
|
id: AnyHashable(peerId),
|
||||||
title: peer.compactDisplayTitle,
|
title: peer.compactDisplayTitle,
|
||||||
@ -1440,7 +1603,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
actionButtonTitle = environment.strings.Story_Privacy_PostStory
|
actionButtonTitle = environment.strings.Story_Privacy_PostStory
|
||||||
}
|
}
|
||||||
case .chats:
|
case .chats:
|
||||||
title = ""
|
title = environment.strings.Story_Privacy_CategorySelectedContacts
|
||||||
case let .contacts(category):
|
case let .contacts(category):
|
||||||
switch category {
|
switch category {
|
||||||
case .closeFriends:
|
case .closeFriends:
|
||||||
@ -1507,6 +1670,13 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
|
|
||||||
transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(x: containerSideInset, y: navigationHeight), size: CGSize(width: containerWidth, height: UIScreenPixel)))
|
transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(x: containerSideInset, y: navigationHeight), size: CGSize(width: containerWidth, height: UIScreenPixel)))
|
||||||
|
|
||||||
|
let badge: Int
|
||||||
|
if case .stories = component.stateContext.subject {
|
||||||
|
badge = 0
|
||||||
|
} else {
|
||||||
|
badge = self.selectedPeers.count
|
||||||
|
}
|
||||||
|
|
||||||
let actionButtonSize = self.actionButton.update(
|
let actionButtonSize = self.actionButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(ButtonComponent(
|
component: AnyComponent(ButtonComponent(
|
||||||
@ -1519,7 +1689,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
id: actionButtonTitle,
|
id: actionButtonTitle,
|
||||||
component: AnyComponent(ButtonTextContentComponent(
|
component: AnyComponent(ButtonTextContentComponent(
|
||||||
text: actionButtonTitle,
|
text: actionButtonTitle,
|
||||||
badge: 0,
|
badge: badge,
|
||||||
textColor: environment.theme.list.itemCheckColors.foregroundColor,
|
textColor: environment.theme.list.itemCheckColors.foregroundColor,
|
||||||
badgeBackground: environment.theme.list.itemCheckColors.foregroundColor,
|
badgeBackground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||||
badgeForeground: environment.theme.list.itemCheckColors.fillColor
|
badgeForeground: environment.theme.list.itemCheckColors.fillColor
|
||||||
@ -1755,15 +1925,18 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
public final class State {
|
public final class State {
|
||||||
let peers: [EnginePeer]
|
let peers: [EnginePeer]
|
||||||
let presences: [EnginePeer.Id: EnginePeer.Presence]
|
let presences: [EnginePeer.Id: EnginePeer.Presence]
|
||||||
|
let participants: [EnginePeer.Id: Int]
|
||||||
let closeFriendsPeers: [EnginePeer]
|
let closeFriendsPeers: [EnginePeer]
|
||||||
|
|
||||||
fileprivate init(
|
fileprivate init(
|
||||||
peers: [EnginePeer],
|
peers: [EnginePeer],
|
||||||
presences: [EnginePeer.Id: EnginePeer.Presence],
|
presences: [EnginePeer.Id: EnginePeer.Presence],
|
||||||
|
participants: [EnginePeer.Id: Int],
|
||||||
closeFriendsPeers: [EnginePeer]
|
closeFriendsPeers: [EnginePeer]
|
||||||
) {
|
) {
|
||||||
self.peers = peers
|
self.peers = peers
|
||||||
self.presences = presences
|
self.presences = presences
|
||||||
|
self.participants = participants
|
||||||
self.closeFriendsPeers = closeFriendsPeers
|
self.closeFriendsPeers = closeFriendsPeers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1820,6 +1993,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
let state = State(
|
let state = State(
|
||||||
peers: peers.compactMap { $0 },
|
peers: peers.compactMap { $0 },
|
||||||
presences: [:],
|
presences: [:],
|
||||||
|
participants: [:],
|
||||||
closeFriendsPeers: closeFriends
|
closeFriendsPeers: closeFriends
|
||||||
)
|
)
|
||||||
self.stateValue = state
|
self.stateValue = state
|
||||||
@ -1828,16 +2002,36 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
self.readySubject.set(true)
|
self.readySubject.set(true)
|
||||||
})
|
})
|
||||||
case .chats:
|
case .chats:
|
||||||
self.stateDisposable = (context.engine.messages.chatList(group: .root, count: 200)
|
self.stateDisposable = (combineLatest(
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] chatList in
|
context.engine.messages.chatList(group: .root, count: 200) |> take(1),
|
||||||
|
context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.List(includePresences: true))
|
||||||
|
)
|
||||||
|
|> mapToSignal { chatList, contacts -> Signal<(EngineChatList, EngineContactList, [EnginePeer.Id: Optional<Int>]), NoError> in
|
||||||
|
return context.engine.data.get(
|
||||||
|
EngineDataMap(chatList.items.map(\.renderedPeer.peerId).map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init))
|
||||||
|
)
|
||||||
|
|> map { participantCountMap -> (EngineChatList, EngineContactList, [EnginePeer.Id: Optional<Int>]) in
|
||||||
|
return (chatList, contacts, participantCountMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] chatList, contacts, participantCounts in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var participants: [EnginePeer.Id: Int] = [:]
|
||||||
|
for (key, value) in participantCounts {
|
||||||
|
if let value {
|
||||||
|
participants[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingIds = Set<EnginePeer.Id>()
|
||||||
var selectedPeers: [EnginePeer] = []
|
var selectedPeers: [EnginePeer] = []
|
||||||
for item in chatList.items.reversed() {
|
for item in chatList.items.reversed() {
|
||||||
if self.initialPeerIds.contains(item.renderedPeer.peerId), let peer = item.renderedPeer.peer {
|
if self.initialPeerIds.contains(item.renderedPeer.peerId), let peer = item.renderedPeer.peer {
|
||||||
selectedPeers.append(peer)
|
selectedPeers.append(peer)
|
||||||
|
existingIds.insert(peer.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1847,12 +2041,39 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var peers: [EnginePeer] = []
|
var peers: [EnginePeer] = []
|
||||||
peers = chatList.items.filter { !self.initialPeerIds.contains($0.renderedPeer.peerId) && $0.renderedPeer.peerId != context.account.peerId }.reversed().compactMap { $0.renderedPeer.peer }
|
peers = chatList.items.filter { peer in
|
||||||
|
if let peer = peer.renderedPeer.peer {
|
||||||
|
if self.initialPeerIds.contains(peer.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if peer.id == context.account.peerId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if peer.isService {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if case let .channel(channel) = peer {
|
||||||
|
if channel.isForum {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if case .broadcast = channel.info {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}.reversed().compactMap { $0.renderedPeer.peer }
|
||||||
|
for peer in peers {
|
||||||
|
existingIds.insert(peer.id)
|
||||||
|
}
|
||||||
peers.insert(contentsOf: selectedPeers, at: 0)
|
peers.insert(contentsOf: selectedPeers, at: 0)
|
||||||
|
|
||||||
let state = State(
|
let state = State(
|
||||||
peers: peers,
|
peers: peers,
|
||||||
presences: presences,
|
presences: presences,
|
||||||
|
participants: participants,
|
||||||
closeFriendsPeers: []
|
closeFriendsPeers: []
|
||||||
)
|
)
|
||||||
self.stateValue = state
|
self.stateValue = state
|
||||||
@ -1908,6 +2129,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
let state = State(
|
let state = State(
|
||||||
peers: peers,
|
peers: peers,
|
||||||
presences: contactList.presences,
|
presences: contactList.presences,
|
||||||
|
participants: [:],
|
||||||
closeFriendsPeers: []
|
closeFriendsPeers: []
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1946,11 +2168,20 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
} else {
|
} else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
} else if case let .channel(channel) = peer {
|
||||||
|
if channel.isForum {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if case .broadcast = channel.info {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
presences: [:],
|
presences: [:],
|
||||||
|
participants: [:],
|
||||||
closeFriendsPeers: []
|
closeFriendsPeers: []
|
||||||
)
|
)
|
||||||
self.stateValue = state
|
self.stateValue = state
|
||||||
|
@ -450,7 +450,7 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate {
|
|||||||
|
|
||||||
let previousContentHeight = self.scrollNode.view.contentSize.height
|
let previousContentHeight = self.scrollNode.view.contentSize.height
|
||||||
let contentHeight = currentOffset.y + 29.0 + verticalInset
|
let contentHeight = currentOffset.y + 29.0 + verticalInset
|
||||||
let nodeHeight = min(contentHeight, 110.0)
|
let nodeHeight = min(contentHeight, 140.0)
|
||||||
|
|
||||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
|
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
|
||||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: nodeHeight)))
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: nodeHeight)))
|
||||||
|
@ -13054,7 +13054,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if !value || concealed || botApp.flags.contains(.notActivated) {
|
if !value || concealed || botApp.flags.contains(.notActivated) {
|
||||||
let context = self.context
|
let context = self.context
|
||||||
|
|
||||||
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: self.updatedPresentationData, peer: botPeer, requestWriteAccess: !botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in
|
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: self.updatedPresentationData, peer: botPeer, requestWriteAccess: botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in
|
||||||
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).start()
|
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).start()
|
||||||
openBotApp(allowWrite)
|
openBotApp(allowWrite)
|
||||||
}, showMore: { [weak self] in
|
}, showMore: { [weak self] in
|
||||||
|
Loading…
x
Reference in New Issue
Block a user