From 6c1376169c5aede8fd199806c6bbacbc2cea82db Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 11 Mar 2026 14:37:23 +0100 Subject: [PATCH] Update API --- .../Sources/Node/ChatListItem.swift | 2 +- .../GalleryUI/Sources/GalleryController.swift | 77 ++++-- .../Items/UniversalVideoGalleryItem.swift | 94 +++++++- .../Sources/LegacyMediaPickers.swift | 81 ++++++- submodules/TelegramApi/Sources/Api0.swift | 19 +- submodules/TelegramApi/Sources/Api11.swift | 72 ++++-- submodules/TelegramApi/Sources/Api16.swift | 38 ++- submodules/TelegramApi/Sources/Api20.swift | 167 ++++++++++--- submodules/TelegramApi/Sources/Api28.swift | 41 ++++ submodules/TelegramApi/Sources/Api29.swift | 22 +- submodules/TelegramApi/Sources/Api32.swift | 44 ++++ submodules/TelegramApi/Sources/Api40.swift | 74 +++++- .../Sources/ApiUtils/TelegramMediaImage.swift | 3 + .../Sources/ApiUtils/TelegramMediaPoll.swift | 13 +- .../PendingMessageUploadedContent.swift | 226 ++++++++++++++---- .../SyncCore/SyncCore_TelegramMediaFile.swift | 6 +- .../SyncCore/SyncCore_TelegramMediaPoll.swift | 12 +- .../TelegramEngine/Messages/Polls.swift | 4 +- .../TelegramEngine/Messages/Stories.swift | 8 +- .../Messages/StoryListContext.swift | 4 +- .../TelegramEngine/Messages/Summarize.swift | 2 +- .../TelegramEngine/Payments/StarGifts.swift | 25 ++ .../ChatMessageInteractiveMediaNode.swift | 151 ++++++++---- .../Sources/ChatMessageItemView.swift | 3 +- .../ChatMessageMediaBubbleContentNode.swift | 4 + .../ChatMessagePollBubbleContentNode.swift | 41 +++- .../Sources/CheckComponent.swift | 11 + .../Components/Gifts/GiftStoreScreen/BUILD | 1 + .../Sources/GiftStoreScreen.swift | 56 +++++ .../Sources/StarsFilterComponent.swift | 143 +++++++++++ 30 files changed, 1199 insertions(+), 245 deletions(-) create mode 100644 submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/StarsFilterComponent.swift diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 2b42d5c2e8..8300e4270d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -964,7 +964,7 @@ private final class ChatListMediaPreviewNode: ASDisplayNode { if file.isInstantVideo { isRound = true } - if file.isVideo && !file.isAnimated && !file.isLivePhoto { + if file.isVideo && !file.isAnimated { self.playIcon.isHidden = false } else { self.playIcon.isHidden = true diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index a1060351eb..b4711502aa 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -232,22 +232,67 @@ public func galleryItemForEntry( return nil } - if let _ = media as? TelegramMediaImage { - return ChatImageGalleryItem( - context: context, - presentationData: presentationData, - message: message, - mediaIndex: entry.mediaIndex, - location: location, - translateToLanguage: translateToLanguage, - peerIsCopyProtected: peerIsCopyProtected, - isSecret: isSecret, - displayInfoOnTop: displayInfoOnTop, - performAction: performAction, - openActionOptions: openActionOptions, - sendSticker: sendSticker, - present: present - ) + if let image = media as? TelegramMediaImage { + if let file = image.video { + let captureProtected = message.isCopyProtected() || message.containsSecretMedia || message.minAutoremoveOrClearTimeout == viewOnceTimeout || message.paidContent != nil || peerIsCopyProtected + + var originData = GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp) + if Namespaces.Message.allNonRegular.contains(message.id.namespace) { + originData = GalleryItemOriginData(title: nil, timestamp: nil) + } + + let content = NativeVideoContent(id: .message(message.stableId, file.fileId), userLocation: .peer(message.id.peerId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, loopVideo: loopVideos, tempFilePath: tempFilePath, captureProtected: captureProtected, storeAfterDownload: generateStoreAfterDownload?(message, file)) + + var entities: [MessageTextEntity] = [] + for attribute in message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + entities = attribute.entities + break + } + } + var text = galleryMessageCaptionText(message) + if let translateToLanguage, !text.isEmpty { + for attribute in message.attributes { + if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { + text = attribute.text + entities = attribute.entities + break + } + } + } + let caption = galleryCaptionStringWithAppliedEntities(context: context, text: text, entities: entities, message: message) + return UniversalVideoGalleryItem( + context: context, + presentationData: presentationData, + content: content, + originData: originData, + indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, + contentInfo: .message(message, entry.mediaIndex), + caption: caption, + peerIsCopyProtected: peerIsCopyProtected, + playbackRate: playbackRate, + performAction: performAction, + openActionOptions: openActionOptions, + storeMediaPlaybackState: storeMediaPlaybackState, + present: present + ) + } else { + return ChatImageGalleryItem( + context: context, + presentationData: presentationData, + message: message, + mediaIndex: entry.mediaIndex, + location: location, + translateToLanguage: translateToLanguage, + peerIsCopyProtected: peerIsCopyProtected, + isSecret: isSecret, + displayInfoOnTop: displayInfoOnTop, + performAction: performAction, + openActionOptions: openActionOptions, + sendSticker: sendSticker, + present: present + ) + } } else if let file = media as? TelegramMediaFile { if file.isVideo { let content: UniversalVideoContent diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index be576383cd..a714c74319 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -37,6 +37,7 @@ import ToastComponent import MultilineTextComponent import BundleIconComponent import VideoPlaybackControlsComponent +import PhotoResources public enum UniversalVideoGalleryItemContentInfo { case message(Message, Int?) @@ -873,6 +874,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private var moreBarButtonRate: Double = 1.0 private var moreBarButtonRateTimestamp: Double? + private var imageNode: TransformImageNode? private var videoNode: UniversalVideoNode? private var videoNodeUserInteractionEnabled: Bool = false private var videoFramePreview: FramePreview? @@ -881,6 +883,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private let statusButtonNode: HighlightableButtonNode private let statusNode: RadialStatusNode private var statusNodeShouldBeHidden = true + private var livePhotoIconNode: ASImageNode? private let playbackControls = ComponentView() @@ -949,6 +952,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private var activeEdgeRateIndicator: ComponentView? private var isAnimatingOut: Bool = false + private var isLivePhoto = false + private var didAutoplayLivePhotoOnce = false init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.context = context @@ -1358,6 +1363,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } self.dismissOnOrientationChange = item.landscape + self.isLivePhoto = false + self.didAutoplayLivePhotoOnce = false var hasLinkedStickers = false if let content = item.content as? NativeVideoContent { @@ -1376,7 +1383,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if let content = item.content as? NativeVideoContent { isAnimated = content.fileReference.media.isAnimated self.videoFramePreview = MediaPlayerFramePreview(postbox: item.context.account.postbox, userLocation: content.userLocation, userContentType: .video, fileReference: content.fileReference) - if content.fileReference.media.isLivePhoto { + if case let .message(message, _) = item.contentInfo, let _ = message.media.first(where: { $0 is TelegramMediaImage }) { + self.isLivePhoto = true disablePlayerControls = true isAnimated = false } @@ -1506,7 +1514,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if let item = strongSelf.item, let _ = item.content as? PlatformVideoContent { strongSelf.videoNode?.play() } else { - strongSelf.videoNode?.playOnceWithSound(playAndRecord: false, seek: seek, actionAtEnd: isAnimated ? .loop : strongSelf.actionAtEnd) + if let videoNode = strongSelf.videoNode, strongSelf.playLivePhotoAutoplayIfNeeded(videoNode: videoNode) { + } else { + strongSelf.videoNode?.playOnceWithSound(playAndRecord: false, seek: seek, actionAtEnd: isAnimated ? .loop : strongSelf.actionAtEnd) + } } Queue.mainQueue().after(0.1) { @@ -1810,6 +1821,22 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.zoomableContent = (videoSize, videoNode) + + if case let .message(message, _) = item.contentInfo, let content = item.content as? NativeVideoContent, let image = message.media.first(where: { $0 is TelegramMediaImage }), let imageReference = content.fileReference.abstract.withUpdatedMedia(image).concrete(TelegramMediaImage.self) { + let imageNode = TransformImageNode() + imageNode.alpha = 0.0 + imageNode.isUserInteractionEnabled = false + imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: content.userLocation, photoReference: imageReference, synchronousLoad: true, highQuality: true)) + imageNode.frame = CGRect(origin: .zero, size: videoSize) + let imageLayout = imageNode.asyncLayout() + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: videoSize, boundingSize: videoSize, intrinsicInsets: UIEdgeInsets(), resizeMode: .aspectFill, emptyColor: .clear, custom: nil) + let apply = imageLayout(arguments) + apply() + + videoNode.addSubnode(imageNode) + self.imageNode = imageNode + } + var hasSettingsButton = false var barButtonItems: [UIBarButtonItem] = [] @@ -1891,7 +1918,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } - if let snapshotView = videoNode?.view.snapshotView(afterScreenUpdates: false) { + if strongSelf.isLivePhoto { + if let imageNode = strongSelf.imageNode { + imageNode.alpha = 1.0 + imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + } + } else if let snapshotView = videoNode?.view.snapshotView(afterScreenUpdates: false) { videoNode?.view.addSubview(snapshotView) snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() @@ -1903,7 +1935,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if strongSelf.actionAtEnd == .stop && strongSelf.isCentral == true { strongSelf.isPlayingPromise.set(false) strongSelf.isPlaying = false - if !item.isSecret { + if !item.isSecret && !strongSelf.isLivePhoto { strongSelf.updateControlsVisibility(true) } } @@ -1980,6 +2012,25 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { controller.dismissAndNavigateToMessageContext(message: message) } : nil))) } + + if self.isLivePhoto { + let livePhotoIconNode: ASImageNode + if let current = self.livePhotoIconNode { + livePhotoIconNode = current + } else { + livePhotoIconNode = ASImageNode() + livePhotoIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LiveOn"), color: .white) + self.addSubnode(livePhotoIconNode) + self.livePhotoIconNode = livePhotoIconNode + } + + if let icon = livePhotoIconNode.image { + livePhotoIconNode.frame = CGRect(origin: CGPoint(x: 8.0, y: 8.0 + 169.0), size: icon.size) + } + } else if let livePhotoIconNode = self.livePhotoIconNode { + self.livePhotoIconNode = nil + livePhotoIconNode.removeFromSupernode() + } } override func controlsVisibilityUpdated(isVisible: Bool, animated: Bool) { @@ -2046,6 +2097,16 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { return false } + private func playLivePhotoAutoplayIfNeeded(videoNode: UniversalVideoNode) -> Bool { + guard self.isLivePhoto, !self.didAutoplayLivePhotoOnce else { + return false + } + self.didAutoplayLivePhotoOnce = true + videoNode.alpha = 1.0 + videoNode.continuePlayingWithoutSound(actionAtEnd: .stop) + return true + } + override func centralityUpdated(isCentral: Bool) { super.centralityUpdated(isCentral: isCentral) @@ -2070,7 +2131,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { videoNode.play() } else if self.shouldAutoplayOnCentrality() { self.initiallyActivated = true - videoNode.playOnceWithSound(playAndRecord: false, actionAtEnd: self.actionAtEnd) + if self.playLivePhotoAutoplayIfNeeded(videoNode: videoNode) { + } else { + videoNode.playOnceWithSound(playAndRecord: false, actionAtEnd: self.actionAtEnd) + } videoNode.setBaseRate(self.playbackRate ?? 1.0) } @@ -2193,7 +2257,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } else { self.hideStatusNodeUntilCentrality = false self.statusButtonNode.isHidden = self.hideStatusNodeUntilCentrality || self.statusNodeShouldBeHidden - videoNode.playOnceWithSound(playAndRecord: false, seek: seek, actionAtEnd: self.actionAtEnd) + if self.playLivePhotoAutoplayIfNeeded(videoNode: videoNode) { + } else { + videoNode.playOnceWithSound(playAndRecord: false, seek: seek, actionAtEnd: self.actionAtEnd) + } Queue.mainQueue().after(1.0, { if let item = self.item, item.isSecret, !self.isPlaying { @@ -2229,6 +2296,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } private var actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd { + if self.isLivePhoto { + return .stop + } if let item = self.item { if !item.isSecret, let content = item.content as? NativeVideoContent, content.duration <= 30 { return .loop @@ -2546,13 +2616,15 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } } else if let interactiveMediaNode = node.0 as? GalleryItemTransitionNode, interactiveMediaNode.isAvailableForGalleryTransition(), videoNode.hasAttachedContext { + self.imageNode?.alpha = 0.0 + copyView.removeFromSuperview() let previousFrame = videoNode.frame let previousSuperview = videoNode.view.superview addToTransitionSurface(videoNode.view) videoNode.view.superview?.bringSubviewToFront(videoNode.view) - + if let previousSuperview = previousSuperview { videoNode.frame = previousSuperview.convert(previousFrame, to: videoNode.view.superview) transformedSuperFrame = transformedSuperFrame.offsetBy(dx: videoNode.position.x - previousFrame.center.x, dy: videoNode.position.y - previousFrame.center.y) @@ -3906,7 +3978,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } override func hasActiveEdgeAction(edge: ActiveEdge) -> Bool { - if case .right = edge { + if case .middle = edge { + return self.isLivePhoto + } else if case .right = edge { if let playerStatusValue = self.playerStatusValue, case .playing = playerStatusValue.status { return true } else { @@ -3921,7 +3995,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { guard let videoNode = self.videoNode else { return } - if let edge, case .right = edge { + if let edge, case .middle = edge { + print() + } else if let edge, case .right = edge { let effectiveRate: Double if let current = self.activeEdgeRateState { effectiveRate = min(4.0, current.initialRate + 1.0) diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index 7b181df7f1..f9c61ee078 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -131,7 +131,7 @@ private enum LegacyAssetVideoData { } private enum LegacyAssetItem { - case image(data: LegacyAssetImageData, thumbnail: UIImage?, caption: NSAttributedString?, stickers: [FileMediaReference]) + case image(data: LegacyAssetImageData, thumbnail: UIImage?, caption: NSAttributedString?, stickers: [FileMediaReference], video: LegacyAssetVideoData?) case file(data: LegacyAssetImageData, thumbnail: UIImage?, mimeType: String, name: String, caption: NSAttributedString?) case video(data: LegacyAssetVideoData, thumbnail: UIImage?, cover: UIImage?, adjustments: TGVideoEditAdjustments?, caption: NSAttributedString?, asFile: Bool, asAnimation: Bool, stickers: [FileMediaReference], isLivePhoto: Bool) } @@ -186,7 +186,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: 4.0), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: false, asAnimation: true, stickers: stickers, isLivePhoto: false), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) } } else { - result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, forceHd: forceHd, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) + result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers, video: nil), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, forceHd: forceHd, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) } return result } else if (dict["type"] as! NSString) == "cloudPhoto" { @@ -209,7 +209,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .asset(asset.backingAsset), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: nil, spoiler: nil, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) } else { let forceHd = (dict["hd"] as? NSNumber)?.boolValue ?? false - result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .asset(asset.backingAsset), thumbnail: thumbnail, caption: caption, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, forceHd: forceHd, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) + result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .asset(asset.backingAsset), thumbnail: thumbnail, caption: caption, stickers: [], video: nil), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, forceHd: forceHd, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) } return result } else if (dict["type"] as! NSString) == "file" { @@ -269,15 +269,35 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str if let value = dict["livePhoto"] as? Bool { isLivePhoto = value } - + let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.path if let url = url, let previewImage = dict["previewImage"] as? UIImage { let dimensions = previewImage.pixelSize() let duration = (dict["duration"]! as AnyObject).doubleValue! - var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers, isLivePhoto: isLivePhoto), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) - return result + if isLivePhoto, let cover { + var result: [AnyHashable: Any] = [:] + result["item" as NSString] = LegacyAssetItemWrapper( + item: .image( + data: .image(cover), + thumbnail: thumbnail, + caption: caption, + stickers: stickers, + video: .tempFile(path: url, dimensions: dimensions, duration: duration) + ), + timer: (dict["timer"] as? NSNumber)?.intValue, + spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, + price: price, + groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, + uniqueId: uniqueId + ) + + return result + } else { + var result: [AnyHashable: Any] = [:] + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers, isLivePhoto: isLivePhoto), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) + return result + } } } return nil @@ -391,7 +411,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A outer: for item in (anyValues as! NSArray) { if let item = (item as? NSDictionary)?.object(forKey: "item") as? LegacyAssetItemWrapper { switch item.item { - case let .image(data, thumbnail, caption, stickers): + case let .image(data, thumbnail, caption, stickers, video): var representations: [TelegramMediaImageRepresentation] = [] if let thumbnail = thumbnail { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) @@ -436,7 +456,48 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A imageFlags.insert(.hasStickers) } - let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: imageFlags) + var videoFile: TelegramMediaFile? + if let video, case let .tempFile(path, dimensions, duration) = video { + let finalDimensions: CGSize = dimensions + let finalDuration: Double = duration + + //let defaultPreset = TGMediaVideoConversionPreset(rawValue: UInt32(UserDefaults.standard.integer(forKey: "TG_preferredVideoPreset_v0"))) + let preset: TGMediaVideoConversionPreset = TGMediaVideoConversionPresetCompressedMedium +// if let selectedPreset = adjustments?.preset { +// preset = selectedPreset +// } else if preset == TGMediaVideoConversionPresetCompressedDefault && defaultPreset != TGMediaVideoConversionPresetCompressedDefault { +// preset = defaultPreset +// } +// if !asAnimation { +// finalDimensions = TGMediaVideoConverter.dimensions(for: finalDimensions, adjustments: adjustments, preset: TGMediaVideoConversionPresetCompressedMedium) +// } + + let resourceAdjustments: VideoMediaResourceAdjustments? = nil +// if let adjustments = adjustments { +// if adjustments.trimApplied() { +// finalDuration = adjustments.trimEndValue - adjustments.trimStartValue +// } +// +// if let dict = adjustments.dictionary(), let data = try? NSKeyedArchiver.archivedData(withRootObject: dict, requiringSecureCoding: false) { +// let adjustmentsData = MemoryBuffer(data: data) +// let digest = MemoryBuffer(data: adjustmentsData.md5Digest()) +// resourceAdjustments = VideoMediaResourceAdjustments(data: adjustmentsData, digest: digest, isStory: false) +// } +// } + + let resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), path: path, adjustments: resourceAdjustments) + let estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: true) + + var fileAttributes: [TelegramMediaFileAttribute] = [] + fileAttributes.append(.Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)) + if estimatedSize > 10 * 1024 * 1024 { + fileAttributes.append(.hintFileIsLarge) + } + videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], videoCover: nil, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: []) + imageFlags.insert(.isLivePhoto) + } + + let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: imageFlags, video: videoFile) if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) } @@ -840,7 +901,6 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A } let defaultPreset = TGMediaVideoConversionPreset(rawValue: UInt32(UserDefaults.standard.integer(forKey: "TG_preferredVideoPreset_v0"))) - var preset: TGMediaVideoConversionPreset = TGMediaVideoConversionPresetCompressedMedium if let selectedPreset = adjustments?.preset { preset = selectedPreset @@ -850,7 +910,6 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A if asAnimation { preset = TGMediaVideoConversionPresetAnimation } - if !asAnimation { finalDimensions = TGMediaVideoConverter.dimensions(for: finalDimensions, adjustments: adjustments, preset: TGMediaVideoConversionPresetCompressedMedium) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 733fd5418f..a5f464968b 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -415,9 +415,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-104578748] = { return Api.InputMedia.parse_inputMediaGeoPoint($0) } dict[1080028941] = { return Api.InputMedia.parse_inputMediaInvoice($0) } dict[-1005571194] = { return Api.InputMedia.parse_inputMediaPaidMedia($0) } - dict[-1279654347] = { return Api.InputMedia.parse_inputMediaPhoto($0) } + dict[-475053004] = { return Api.InputMedia.parse_inputMediaPhoto($0) } dict[-440664550] = { return Api.InputMedia.parse_inputMediaPhotoExternal($0) } - dict[261416433] = { return Api.InputMedia.parse_inputMediaPoll($0) } + dict[-1229194966] = { return Api.InputMedia.parse_inputMediaPoll($0) } dict[-207018934] = { return Api.InputMedia.parse_inputMediaStakeDice($0) } dict[-1979852936] = { return Api.InputMedia.parse_inputMediaStory($0) } dict[-1614454818] = { return Api.InputMedia.parse_inputMediaTodo($0) } @@ -681,7 +681,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-156940077] = { return Api.MessageMedia.parse_messageMediaInvoice($0) } dict[-1467669359] = { return Api.MessageMedia.parse_messageMediaPaidMedia($0) } dict[-501814429] = { return Api.MessageMedia.parse_messageMediaPhoto($0) } - dict[1272375192] = { return Api.MessageMedia.parse_messageMediaPoll($0) } + dict[2000637542] = { return Api.MessageMedia.parse_messageMediaPoll($0) } dict[1758159491] = { return Api.MessageMedia.parse_messageMediaStory($0) } dict[-1974226924] = { return Api.MessageMedia.parse_messageMediaToDo($0) } dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) } @@ -814,9 +814,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-96535659] = { return Api.PhotoSize.parse_photoSizeProgressive($0) } dict[-525288402] = { return Api.PhotoSize.parse_photoStrippedSize($0) } dict[1484026161] = { return Api.Poll.parse_poll($0) } - dict[-15277366] = { return Api.PollAnswer.parse_pollAnswer($0) } - dict[997055186] = { return Api.PollAnswerVoters.parse_pollAnswerVoters($0) } - dict[2061444128] = { return Api.PollResults.parse_pollResults($0) } + dict[-237102592] = { return Api.PollAnswer.parse_inputPollAnswer($0) } + dict[-779361553] = { return Api.PollAnswer.parse_pollAnswer($0) } + dict[910500618] = { return Api.PollAnswerVoters.parse_pollAnswerVoters($0) } + dict[-1166298786] = { return Api.PollResults.parse_pollResults($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[512535275] = { return Api.PostAddress.parse_postAddress($0) } dict[-419066241] = { return Api.PostInteractionCounters.parse_postInteractionCountersMessage($0) } @@ -1160,6 +1161,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1442983757] = { return Api.Update.parse_updateLangPack($0) } dict[1180041828] = { return Api.Update.parse_updateLangPackTooLong($0) } dict[1448076945] = { return Api.Update.parse_updateLoginToken($0) } + dict[1216408986] = { return Api.Update.parse_updateManagedBot($0) } dict[-710666460] = { return Api.Update.parse_updateMessageExtendedMedia($0) } dict[1318109142] = { return Api.Update.parse_updateMessageID($0) } dict[-1398708869] = { return Api.Update.parse_updateMessagePoll($0) } @@ -1250,7 +1252,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-117904610] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) } dict[829899656] = { return Api.User.parse_user($0) } dict[-742634630] = { return Api.User.parse_userEmpty($0) } - dict[-1607745218] = { return Api.UserFull.parse_userFull($0) } + dict[-1577103027] = { return Api.UserFull.parse_userFull($0) } dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) } dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) } @@ -1348,6 +1350,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1284008785] = { return Api.auth.SentCodeType.parse_sentCodeTypeSmsPhrase($0) } dict[-1542017919] = { return Api.auth.SentCodeType.parse_sentCodeTypeSmsWord($0) } dict[-391678544] = { return Api.bots.BotInfo.parse_botInfo($0) } + dict[1012971041] = { return Api.bots.ExportedBotToken.parse_exportedBotToken($0) } dict[428978491] = { return Api.bots.PopularAppBots.parse_popularAppBots($0) } dict[212278628] = { return Api.bots.PreviewInfo.parse_previewInfo($0) } dict[-309659827] = { return Api.channels.AdminLogResults.parse_adminLogResults($0) } @@ -2477,6 +2480,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.bots.BotInfo: _1.serialize(buffer, boxed) + case let _1 as Api.bots.ExportedBotToken: + _1.serialize(buffer, boxed) case let _1 as Api.bots.PopularAppBots: _1.serialize(buffer, boxed) case let _1 as Api.bots.PreviewInfo: diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index cb09eaa874..0bb65be76c 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -141,13 +141,15 @@ public extension Api { public var flags: Int32 public var id: Api.InputPhoto public var ttlSeconds: Int32? - public init(flags: Int32, id: Api.InputPhoto, ttlSeconds: Int32?) { + public var video: Api.InputDocument? + public init(flags: Int32, id: Api.InputPhoto, ttlSeconds: Int32?, video: Api.InputDocument?) { self.flags = flags self.id = id self.ttlSeconds = ttlSeconds + self.video = video } public func descriptionFields() -> (String, [(String, Any)]) { - return ("inputMediaPhoto", [("flags", self.flags as Any), ("id", self.id as Any), ("ttlSeconds", self.ttlSeconds as Any)]) + return ("inputMediaPhoto", [("flags", self.flags as Any), ("id", self.id as Any), ("ttlSeconds", self.ttlSeconds as Any), ("video", self.video as Any)]) } } public class Cons_inputMediaPhotoExternal: TypeConstructorDescription { @@ -167,17 +169,21 @@ public extension Api { public var flags: Int32 public var poll: Api.Poll public var correctAnswers: [Buffer]? + public var attachedMedia: Api.InputMedia? public var solution: String? public var solutionEntities: [Api.MessageEntity]? - public init(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?) { + public var solutionMedia: Api.InputMedia? + public init(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, attachedMedia: Api.InputMedia?, solution: String?, solutionEntities: [Api.MessageEntity]?, solutionMedia: Api.InputMedia?) { self.flags = flags self.poll = poll self.correctAnswers = correctAnswers + self.attachedMedia = attachedMedia self.solution = solution self.solutionEntities = solutionEntities + self.solutionMedia = solutionMedia } public func descriptionFields() -> (String, [(String, Any)]) { - return ("inputMediaPoll", [("flags", self.flags as Any), ("poll", self.poll as Any), ("correctAnswers", self.correctAnswers as Any), ("solution", self.solution as Any), ("solutionEntities", self.solutionEntities as Any)]) + return ("inputMediaPoll", [("flags", self.flags as Any), ("poll", self.poll as Any), ("correctAnswers", self.correctAnswers as Any), ("attachedMedia", self.attachedMedia as Any), ("solution", self.solution as Any), ("solutionEntities", self.solutionEntities as Any), ("solutionMedia", self.solutionMedia as Any)]) } } public class Cons_inputMediaStakeDice: TypeConstructorDescription { @@ -431,13 +437,16 @@ public extension Api { break case .inputMediaPhoto(let _data): if boxed { - buffer.appendInt32(-1279654347) + buffer.appendInt32(-475053004) } serializeInt32(_data.flags, buffer: buffer, boxed: false) _data.id.serialize(buffer, true) if Int(_data.flags) & Int(1 << 0) != 0 { serializeInt32(_data.ttlSeconds!, buffer: buffer, boxed: false) } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.video!.serialize(buffer, true) + } break case .inputMediaPhotoExternal(let _data): if boxed { @@ -451,7 +460,7 @@ public extension Api { break case .inputMediaPoll(let _data): if boxed { - buffer.appendInt32(261416433) + buffer.appendInt32(-1229194966) } serializeInt32(_data.flags, buffer: buffer, boxed: false) _data.poll.serialize(buffer, true) @@ -462,6 +471,9 @@ public extension Api { serializeBytes(item, buffer: buffer, boxed: false) } } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.attachedMedia!.serialize(buffer, true) + } if Int(_data.flags) & Int(1 << 1) != 0 { serializeString(_data.solution!, buffer: buffer, boxed: false) } @@ -472,6 +484,9 @@ public extension Api { item.serialize(buffer, true) } } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.solutionMedia!.serialize(buffer, true) + } break case .inputMediaStakeDice(let _data): if boxed { @@ -590,11 +605,11 @@ public extension Api { case .inputMediaPaidMedia(let _data): return ("inputMediaPaidMedia", [("flags", _data.flags as Any), ("starsAmount", _data.starsAmount as Any), ("extendedMedia", _data.extendedMedia as Any), ("payload", _data.payload as Any)]) case .inputMediaPhoto(let _data): - return ("inputMediaPhoto", [("flags", _data.flags as Any), ("id", _data.id as Any), ("ttlSeconds", _data.ttlSeconds as Any)]) + return ("inputMediaPhoto", [("flags", _data.flags as Any), ("id", _data.id as Any), ("ttlSeconds", _data.ttlSeconds as Any), ("video", _data.video as Any)]) case .inputMediaPhotoExternal(let _data): return ("inputMediaPhotoExternal", [("flags", _data.flags as Any), ("url", _data.url as Any), ("ttlSeconds", _data.ttlSeconds as Any)]) case .inputMediaPoll(let _data): - return ("inputMediaPoll", [("flags", _data.flags as Any), ("poll", _data.poll as Any), ("correctAnswers", _data.correctAnswers as Any), ("solution", _data.solution as Any), ("solutionEntities", _data.solutionEntities as Any)]) + return ("inputMediaPoll", [("flags", _data.flags as Any), ("poll", _data.poll as Any), ("correctAnswers", _data.correctAnswers as Any), ("attachedMedia", _data.attachedMedia as Any), ("solution", _data.solution as Any), ("solutionEntities", _data.solutionEntities as Any), ("solutionMedia", _data.solutionMedia as Any)]) case .inputMediaStakeDice(let _data): return ("inputMediaStakeDice", [("gameHash", _data.gameHash as Any), ("tonAmount", _data.tonAmount as Any), ("clientSeed", _data.clientSeed as Any)]) case .inputMediaStory(let _data): @@ -861,11 +876,18 @@ public extension Api { if Int(_1!) & Int(1 << 0) != 0 { _3 = reader.readInt32() } + var _4: Api.InputDocument? + if Int(_1!) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InputDocument + } + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputMedia.inputMediaPhoto(Cons_inputMediaPhoto(flags: _1!, id: _2!, ttlSeconds: _3)) + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputMedia.inputMediaPhoto(Cons_inputMediaPhoto(flags: _1!, id: _2!, ttlSeconds: _3, video: _4)) } else { return nil @@ -903,23 +925,37 @@ public extension Api { _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) } } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = parseString(reader) + var _4: Api.InputMedia? + if Int(_1!) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InputMedia + } } - var _5: [Api.MessageEntity]? + var _5: String? + if Int(_1!) & Int(1 << 1) != 0 { + _5 = parseString(reader) + } + var _6: [Api.MessageEntity]? if Int(_1!) & Int(1 << 1) != 0 { if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + } + var _7: Api.InputMedia? + if Int(_1!) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.InputMedia } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputMedia.inputMediaPoll(Cons_inputMediaPoll(flags: _1!, poll: _2!, correctAnswers: _3, solution: _4, solutionEntities: _5)) + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.InputMedia.inputMediaPoll(Cons_inputMediaPoll(flags: _1!, poll: _2!, correctAnswers: _3, attachedMedia: _4, solution: _5, solutionEntities: _6, solutionMedia: _7)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api16.swift b/submodules/TelegramApi/Sources/Api16.swift index 521788dd10..a078ddc990 100644 --- a/submodules/TelegramApi/Sources/Api16.swift +++ b/submodules/TelegramApi/Sources/Api16.swift @@ -1298,14 +1298,18 @@ public extension Api { } } public class Cons_messageMediaPoll: TypeConstructorDescription { + public var flags: Int32 public var poll: Api.Poll public var results: Api.PollResults - public init(poll: Api.Poll, results: Api.PollResults) { + public var attachedMedia: Api.MessageMedia? + public init(flags: Int32, poll: Api.Poll, results: Api.PollResults, attachedMedia: Api.MessageMedia?) { + self.flags = flags self.poll = poll self.results = results + self.attachedMedia = attachedMedia } public func descriptionFields() -> (String, [(String, Any)]) { - return ("messageMediaPoll", [("poll", self.poll as Any), ("results", self.results as Any)]) + return ("messageMediaPoll", [("flags", self.flags as Any), ("poll", self.poll as Any), ("results", self.results as Any), ("attachedMedia", self.attachedMedia as Any)]) } } public class Cons_messageMediaStory: TypeConstructorDescription { @@ -1581,10 +1585,14 @@ public extension Api { break case .messageMediaPoll(let _data): if boxed { - buffer.appendInt32(1272375192) + buffer.appendInt32(2000637542) } + serializeInt32(_data.flags, buffer: buffer, boxed: false) _data.poll.serialize(buffer, true) _data.results.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.attachedMedia!.serialize(buffer, true) + } break case .messageMediaStory(let _data): if boxed { @@ -1671,7 +1679,7 @@ public extension Api { case .messageMediaPhoto(let _data): return ("messageMediaPhoto", [("flags", _data.flags as Any), ("photo", _data.photo as Any), ("ttlSeconds", _data.ttlSeconds as Any), ("video", _data.video as Any)]) case .messageMediaPoll(let _data): - return ("messageMediaPoll", [("poll", _data.poll as Any), ("results", _data.results as Any)]) + return ("messageMediaPoll", [("flags", _data.flags as Any), ("poll", _data.poll as Any), ("results", _data.results as Any), ("attachedMedia", _data.attachedMedia as Any)]) case .messageMediaStory(let _data): return ("messageMediaStory", [("flags", _data.flags as Any), ("peer", _data.peer as Any), ("id", _data.id as Any), ("story", _data.story as Any)]) case .messageMediaToDo(let _data): @@ -2021,18 +2029,28 @@ public extension Api { } } public static func parse_messageMediaPoll(_ reader: BufferReader) -> MessageMedia? { - var _1: Api.Poll? + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Poll? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Poll + _2 = Api.parse(reader, signature: signature) as? Api.Poll } - var _2: Api.PollResults? + var _3: Api.PollResults? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PollResults + _3 = Api.parse(reader, signature: signature) as? Api.PollResults + } + var _4: Api.MessageMedia? + if Int(_1!) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageMedia.messageMediaPoll(Cons_messageMediaPoll(poll: _1!, results: _2!)) + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageMedia.messageMediaPoll(Cons_messageMediaPoll(flags: _1!, poll: _2!, results: _3!, attachedMedia: _4)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api20.swift b/submodules/TelegramApi/Sources/Api20.swift index 9b42f0d00f..ffff2ac0c1 100644 --- a/submodules/TelegramApi/Sources/Api20.swift +++ b/submodules/TelegramApi/Sources/Api20.swift @@ -776,50 +776,123 @@ public extension Api { } } public extension Api { - enum PollAnswer: TypeConstructorDescription { - public class Cons_pollAnswer: TypeConstructorDescription { + indirect enum PollAnswer: TypeConstructorDescription { + public class Cons_inputPollAnswer: TypeConstructorDescription { + public var flags: Int32 public var text: Api.TextWithEntities public var option: Buffer - public init(text: Api.TextWithEntities, option: Buffer) { + public var media: Api.InputMedia? + public init(flags: Int32, text: Api.TextWithEntities, option: Buffer, media: Api.InputMedia?) { + self.flags = flags self.text = text self.option = option + self.media = media } public func descriptionFields() -> (String, [(String, Any)]) { - return ("pollAnswer", [("text", self.text as Any), ("option", self.option as Any)]) + return ("inputPollAnswer", [("flags", self.flags as Any), ("text", self.text as Any), ("option", self.option as Any), ("media", self.media as Any)]) } } + public class Cons_pollAnswer: TypeConstructorDescription { + public var flags: Int32 + public var text: Api.TextWithEntities + public var option: Buffer + public var media: Api.MessageMedia? + public init(flags: Int32, text: Api.TextWithEntities, option: Buffer, media: Api.MessageMedia?) { + self.flags = flags + self.text = text + self.option = option + self.media = media + } + public func descriptionFields() -> (String, [(String, Any)]) { + return ("pollAnswer", [("flags", self.flags as Any), ("text", self.text as Any), ("option", self.option as Any), ("media", self.media as Any)]) + } + } + case inputPollAnswer(Cons_inputPollAnswer) case pollAnswer(Cons_pollAnswer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pollAnswer(let _data): + case .inputPollAnswer(let _data): if boxed { - buffer.appendInt32(-15277366) + buffer.appendInt32(-237102592) } + serializeInt32(_data.flags, buffer: buffer, boxed: false) _data.text.serialize(buffer, true) serializeBytes(_data.option, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.media!.serialize(buffer, true) + } + break + case .pollAnswer(let _data): + if boxed { + buffer.appendInt32(-779361553) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.text.serialize(buffer, true) + serializeBytes(_data.option, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.media!.serialize(buffer, true) + } break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { + case .inputPollAnswer(let _data): + return ("inputPollAnswer", [("flags", _data.flags as Any), ("text", _data.text as Any), ("option", _data.option as Any), ("media", _data.media as Any)]) case .pollAnswer(let _data): - return ("pollAnswer", [("text", _data.text as Any), ("option", _data.option as Any)]) + return ("pollAnswer", [("flags", _data.flags as Any), ("text", _data.text as Any), ("option", _data.option as Any), ("media", _data.media as Any)]) } } - public static func parse_pollAnswer(_ reader: BufferReader) -> PollAnswer? { - var _1: Api.TextWithEntities? + public static func parse_inputPollAnswer(_ reader: BufferReader) -> PollAnswer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.TextWithEntities? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + var _3: Buffer? + _3 = parseBytes(reader) + var _4: Api.InputMedia? + if Int(_1!) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InputMedia + } } - var _2: Buffer? - _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PollAnswer.pollAnswer(Cons_pollAnswer(text: _1!, option: _2!)) + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PollAnswer.inputPollAnswer(Cons_inputPollAnswer(flags: _1!, text: _2!, option: _3!, media: _4)) + } + else { + return nil + } + } + public static func parse_pollAnswer(_ reader: BufferReader) -> PollAnswer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.TextWithEntities? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + var _3: Buffer? + _3 = parseBytes(reader) + var _4: Api.MessageMedia? + if Int(_1!) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PollAnswer.pollAnswer(Cons_pollAnswer(flags: _1!, text: _2!, option: _3!, media: _4)) } else { return nil @@ -832,14 +905,16 @@ public extension Api { public class Cons_pollAnswerVoters: TypeConstructorDescription { public var flags: Int32 public var option: Buffer - public var voters: Int32 - public init(flags: Int32, option: Buffer, voters: Int32) { + public var voters: Int32? + public var recentVoters: [Api.Peer]? + public init(flags: Int32, option: Buffer, voters: Int32?, recentVoters: [Api.Peer]?) { self.flags = flags self.option = option self.voters = voters + self.recentVoters = recentVoters } public func descriptionFields() -> (String, [(String, Any)]) { - return ("pollAnswerVoters", [("flags", self.flags as Any), ("option", self.option as Any), ("voters", self.voters as Any)]) + return ("pollAnswerVoters", [("flags", self.flags as Any), ("option", self.option as Any), ("voters", self.voters as Any), ("recentVoters", self.recentVoters as Any)]) } } case pollAnswerVoters(Cons_pollAnswerVoters) @@ -848,11 +923,20 @@ public extension Api { switch self { case .pollAnswerVoters(let _data): if boxed { - buffer.appendInt32(997055186) + buffer.appendInt32(910500618) } serializeInt32(_data.flags, buffer: buffer, boxed: false) serializeBytes(_data.option, buffer: buffer, boxed: false) - serializeInt32(_data.voters, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.voters!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.recentVoters!.count)) + for item in _data.recentVoters! { + item.serialize(buffer, true) + } + } break } } @@ -860,7 +944,7 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { case .pollAnswerVoters(let _data): - return ("pollAnswerVoters", [("flags", _data.flags as Any), ("option", _data.option as Any), ("voters", _data.voters as Any)]) + return ("pollAnswerVoters", [("flags", _data.flags as Any), ("option", _data.option as Any), ("voters", _data.voters as Any), ("recentVoters", _data.recentVoters as Any)]) } } @@ -870,12 +954,21 @@ public extension Api { var _2: Buffer? _2 = parseBytes(reader) var _3: Int32? - _3 = reader.readInt32() + if Int(_1!) & Int(1 << 2) != 0 { + _3 = reader.readInt32() + } + var _4: [Api.Peer]? + if Int(_1!) & Int(1 << 2) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PollAnswerVoters.pollAnswerVoters(Cons_pollAnswerVoters(flags: _1!, option: _2!, voters: _3!)) + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PollAnswerVoters.pollAnswerVoters(Cons_pollAnswerVoters(flags: _1!, option: _2!, voters: _3, recentVoters: _4)) } else { return nil @@ -884,7 +977,7 @@ public extension Api { } } public extension Api { - enum PollResults: TypeConstructorDescription { + indirect enum PollResults: TypeConstructorDescription { public class Cons_pollResults: TypeConstructorDescription { public var flags: Int32 public var results: [Api.PollAnswerVoters]? @@ -892,16 +985,18 @@ public extension Api { public var recentVoters: [Api.Peer]? public var solution: String? public var solutionEntities: [Api.MessageEntity]? - public init(flags: Int32, results: [Api.PollAnswerVoters]?, totalVoters: Int32?, recentVoters: [Api.Peer]?, solution: String?, solutionEntities: [Api.MessageEntity]?) { + public var solutionMedia: Api.MessageMedia? + public init(flags: Int32, results: [Api.PollAnswerVoters]?, totalVoters: Int32?, recentVoters: [Api.Peer]?, solution: String?, solutionEntities: [Api.MessageEntity]?, solutionMedia: Api.MessageMedia?) { self.flags = flags self.results = results self.totalVoters = totalVoters self.recentVoters = recentVoters self.solution = solution self.solutionEntities = solutionEntities + self.solutionMedia = solutionMedia } public func descriptionFields() -> (String, [(String, Any)]) { - return ("pollResults", [("flags", self.flags as Any), ("results", self.results as Any), ("totalVoters", self.totalVoters as Any), ("recentVoters", self.recentVoters as Any), ("solution", self.solution as Any), ("solutionEntities", self.solutionEntities as Any)]) + return ("pollResults", [("flags", self.flags as Any), ("results", self.results as Any), ("totalVoters", self.totalVoters as Any), ("recentVoters", self.recentVoters as Any), ("solution", self.solution as Any), ("solutionEntities", self.solutionEntities as Any), ("solutionMedia", self.solutionMedia as Any)]) } } case pollResults(Cons_pollResults) @@ -910,7 +1005,7 @@ public extension Api { switch self { case .pollResults(let _data): if boxed { - buffer.appendInt32(2061444128) + buffer.appendInt32(-1166298786) } serializeInt32(_data.flags, buffer: buffer, boxed: false) if Int(_data.flags) & Int(1 << 1) != 0 { @@ -940,6 +1035,9 @@ public extension Api { item.serialize(buffer, true) } } + if Int(_data.flags) & Int(1 << 5) != 0 { + _data.solutionMedia!.serialize(buffer, true) + } break } } @@ -947,7 +1045,7 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { case .pollResults(let _data): - return ("pollResults", [("flags", _data.flags as Any), ("results", _data.results as Any), ("totalVoters", _data.totalVoters as Any), ("recentVoters", _data.recentVoters as Any), ("solution", _data.solution as Any), ("solutionEntities", _data.solutionEntities as Any)]) + return ("pollResults", [("flags", _data.flags as Any), ("results", _data.results as Any), ("totalVoters", _data.totalVoters as Any), ("recentVoters", _data.recentVoters as Any), ("solution", _data.solution as Any), ("solutionEntities", _data.solutionEntities as Any), ("solutionMedia", _data.solutionMedia as Any)]) } } @@ -980,14 +1078,21 @@ public extension Api { _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } + var _7: Api.MessageMedia? + if Int(_1!) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PollResults.pollResults(Cons_pollResults(flags: _1!, results: _2, totalVoters: _3, recentVoters: _4, solution: _5, solutionEntities: _6)) + let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PollResults.pollResults(Cons_pollResults(flags: _1!, results: _2, totalVoters: _3, recentVoters: _4, solution: _5, solutionEntities: _6, solutionMedia: _7)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index 62f9f730d5..638a02cf09 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -1020,6 +1020,19 @@ public extension Api { return ("updateLangPackTooLong", [("langCode", self.langCode as Any)]) } } + public class Cons_updateManagedBot: TypeConstructorDescription { + public var userId: Int64 + public var botId: Int64 + public var qts: Int32 + public init(userId: Int64, botId: Int64, qts: Int32) { + self.userId = userId + self.botId = botId + self.qts = qts + } + public func descriptionFields() -> (String, [(String, Any)]) { + return ("updateManagedBot", [("userId", self.userId as Any), ("botId", self.botId as Any), ("qts", self.qts as Any)]) + } + } public class Cons_updateMessageExtendedMedia: TypeConstructorDescription { public var peer: Api.Peer public var msgId: Int32 @@ -1937,6 +1950,7 @@ public extension Api { case updateLangPack(Cons_updateLangPack) case updateLangPackTooLong(Cons_updateLangPackTooLong) case updateLoginToken + case updateManagedBot(Cons_updateManagedBot) case updateMessageExtendedMedia(Cons_updateMessageExtendedMedia) case updateMessageID(Cons_updateMessageID) case updateMessagePoll(Cons_updateMessagePoll) @@ -2763,6 +2777,14 @@ public extension Api { buffer.appendInt32(1448076945) } break + case .updateManagedBot(let _data): + if boxed { + buffer.appendInt32(1216408986) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeInt64(_data.botId, buffer: buffer, boxed: false) + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break case .updateMessageExtendedMedia(let _data): if boxed { buffer.appendInt32(-710666460) @@ -3591,6 +3613,8 @@ public extension Api { return ("updateLangPackTooLong", [("langCode", _data.langCode as Any)]) case .updateLoginToken: return ("updateLoginToken", []) + case .updateManagedBot(let _data): + return ("updateManagedBot", [("userId", _data.userId as Any), ("botId", _data.botId as Any), ("qts", _data.qts as Any)]) case .updateMessageExtendedMedia(let _data): return ("updateMessageExtendedMedia", [("peer", _data.peer as Any), ("msgId", _data.msgId as Any), ("extendedMedia", _data.extendedMedia as Any)]) case .updateMessageID(let _data): @@ -5269,6 +5293,23 @@ public extension Api { public static func parse_updateLoginToken(_ reader: BufferReader) -> Update? { return Api.Update.updateLoginToken } + public static func parse_updateManagedBot(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateManagedBot(Cons_updateManagedBot(userId: _1!, botId: _2!, qts: _3!)) + } + else { + return nil + } + } public static func parse_updateMessageExtendedMedia(_ reader: BufferReader) -> Update? { var _1: Api.Peer? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 6ebaed06a5..96ae5e4a25 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -1071,7 +1071,8 @@ public extension Api { public var mainTab: Api.ProfileTab? public var savedMusic: Api.Document? public var note: Api.TextWithEntities? - public init(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, theme: Api.ChatTheme?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?, starsRating: Api.StarsRating?, starsMyPendingRating: Api.StarsRating?, starsMyPendingRatingDate: Int32?, mainTab: Api.ProfileTab?, savedMusic: Api.Document?, note: Api.TextWithEntities?) { + public var botManagerId: Int64? + public init(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, theme: Api.ChatTheme?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?, starsRating: Api.StarsRating?, starsMyPendingRating: Api.StarsRating?, starsMyPendingRatingDate: Int32?, mainTab: Api.ProfileTab?, savedMusic: Api.Document?, note: Api.TextWithEntities?, botManagerId: Int64?) { self.flags = flags self.flags2 = flags2 self.id = id @@ -1111,9 +1112,10 @@ public extension Api { self.mainTab = mainTab self.savedMusic = savedMusic self.note = note + self.botManagerId = botManagerId } public func descriptionFields() -> (String, [(String, Any)]) { - return ("userFull", [("flags", self.flags as Any), ("flags2", self.flags2 as Any), ("id", self.id as Any), ("about", self.about as Any), ("settings", self.settings as Any), ("personalPhoto", self.personalPhoto as Any), ("profilePhoto", self.profilePhoto as Any), ("fallbackPhoto", self.fallbackPhoto as Any), ("notifySettings", self.notifySettings as Any), ("botInfo", self.botInfo as Any), ("pinnedMsgId", self.pinnedMsgId as Any), ("commonChatsCount", self.commonChatsCount as Any), ("folderId", self.folderId as Any), ("ttlPeriod", self.ttlPeriod as Any), ("theme", self.theme as Any), ("privateForwardName", self.privateForwardName as Any), ("botGroupAdminRights", self.botGroupAdminRights as Any), ("botBroadcastAdminRights", self.botBroadcastAdminRights as Any), ("wallpaper", self.wallpaper as Any), ("stories", self.stories as Any), ("businessWorkHours", self.businessWorkHours as Any), ("businessLocation", self.businessLocation as Any), ("businessGreetingMessage", self.businessGreetingMessage as Any), ("businessAwayMessage", self.businessAwayMessage as Any), ("businessIntro", self.businessIntro as Any), ("birthday", self.birthday as Any), ("personalChannelId", self.personalChannelId as Any), ("personalChannelMessage", self.personalChannelMessage as Any), ("stargiftsCount", self.stargiftsCount as Any), ("starrefProgram", self.starrefProgram as Any), ("botVerification", self.botVerification as Any), ("sendPaidMessagesStars", self.sendPaidMessagesStars as Any), ("disallowedGifts", self.disallowedGifts as Any), ("starsRating", self.starsRating as Any), ("starsMyPendingRating", self.starsMyPendingRating as Any), ("starsMyPendingRatingDate", self.starsMyPendingRatingDate as Any), ("mainTab", self.mainTab as Any), ("savedMusic", self.savedMusic as Any), ("note", self.note as Any)]) + return ("userFull", [("flags", self.flags as Any), ("flags2", self.flags2 as Any), ("id", self.id as Any), ("about", self.about as Any), ("settings", self.settings as Any), ("personalPhoto", self.personalPhoto as Any), ("profilePhoto", self.profilePhoto as Any), ("fallbackPhoto", self.fallbackPhoto as Any), ("notifySettings", self.notifySettings as Any), ("botInfo", self.botInfo as Any), ("pinnedMsgId", self.pinnedMsgId as Any), ("commonChatsCount", self.commonChatsCount as Any), ("folderId", self.folderId as Any), ("ttlPeriod", self.ttlPeriod as Any), ("theme", self.theme as Any), ("privateForwardName", self.privateForwardName as Any), ("botGroupAdminRights", self.botGroupAdminRights as Any), ("botBroadcastAdminRights", self.botBroadcastAdminRights as Any), ("wallpaper", self.wallpaper as Any), ("stories", self.stories as Any), ("businessWorkHours", self.businessWorkHours as Any), ("businessLocation", self.businessLocation as Any), ("businessGreetingMessage", self.businessGreetingMessage as Any), ("businessAwayMessage", self.businessAwayMessage as Any), ("businessIntro", self.businessIntro as Any), ("birthday", self.birthday as Any), ("personalChannelId", self.personalChannelId as Any), ("personalChannelMessage", self.personalChannelMessage as Any), ("stargiftsCount", self.stargiftsCount as Any), ("starrefProgram", self.starrefProgram as Any), ("botVerification", self.botVerification as Any), ("sendPaidMessagesStars", self.sendPaidMessagesStars as Any), ("disallowedGifts", self.disallowedGifts as Any), ("starsRating", self.starsRating as Any), ("starsMyPendingRating", self.starsMyPendingRating as Any), ("starsMyPendingRatingDate", self.starsMyPendingRatingDate as Any), ("mainTab", self.mainTab as Any), ("savedMusic", self.savedMusic as Any), ("note", self.note as Any), ("botManagerId", self.botManagerId as Any)]) } } case userFull(Cons_userFull) @@ -1122,7 +1124,7 @@ public extension Api { switch self { case .userFull(let _data): if boxed { - buffer.appendInt32(-1607745218) + buffer.appendInt32(-1577103027) } serializeInt32(_data.flags, buffer: buffer, boxed: false) serializeInt32(_data.flags2, buffer: buffer, boxed: false) @@ -1229,6 +1231,9 @@ public extension Api { if Int(_data.flags2) & Int(1 << 22) != 0 { _data.note!.serialize(buffer, true) } + if Int(_data.flags2) & Int(1 << 23) != 0 { + serializeInt64(_data.botManagerId!, buffer: buffer, boxed: false) + } break } } @@ -1236,7 +1241,7 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { case .userFull(let _data): - return ("userFull", [("flags", _data.flags as Any), ("flags2", _data.flags2 as Any), ("id", _data.id as Any), ("about", _data.about as Any), ("settings", _data.settings as Any), ("personalPhoto", _data.personalPhoto as Any), ("profilePhoto", _data.profilePhoto as Any), ("fallbackPhoto", _data.fallbackPhoto as Any), ("notifySettings", _data.notifySettings as Any), ("botInfo", _data.botInfo as Any), ("pinnedMsgId", _data.pinnedMsgId as Any), ("commonChatsCount", _data.commonChatsCount as Any), ("folderId", _data.folderId as Any), ("ttlPeriod", _data.ttlPeriod as Any), ("theme", _data.theme as Any), ("privateForwardName", _data.privateForwardName as Any), ("botGroupAdminRights", _data.botGroupAdminRights as Any), ("botBroadcastAdminRights", _data.botBroadcastAdminRights as Any), ("wallpaper", _data.wallpaper as Any), ("stories", _data.stories as Any), ("businessWorkHours", _data.businessWorkHours as Any), ("businessLocation", _data.businessLocation as Any), ("businessGreetingMessage", _data.businessGreetingMessage as Any), ("businessAwayMessage", _data.businessAwayMessage as Any), ("businessIntro", _data.businessIntro as Any), ("birthday", _data.birthday as Any), ("personalChannelId", _data.personalChannelId as Any), ("personalChannelMessage", _data.personalChannelMessage as Any), ("stargiftsCount", _data.stargiftsCount as Any), ("starrefProgram", _data.starrefProgram as Any), ("botVerification", _data.botVerification as Any), ("sendPaidMessagesStars", _data.sendPaidMessagesStars as Any), ("disallowedGifts", _data.disallowedGifts as Any), ("starsRating", _data.starsRating as Any), ("starsMyPendingRating", _data.starsMyPendingRating as Any), ("starsMyPendingRatingDate", _data.starsMyPendingRatingDate as Any), ("mainTab", _data.mainTab as Any), ("savedMusic", _data.savedMusic as Any), ("note", _data.note as Any)]) + return ("userFull", [("flags", _data.flags as Any), ("flags2", _data.flags2 as Any), ("id", _data.id as Any), ("about", _data.about as Any), ("settings", _data.settings as Any), ("personalPhoto", _data.personalPhoto as Any), ("profilePhoto", _data.profilePhoto as Any), ("fallbackPhoto", _data.fallbackPhoto as Any), ("notifySettings", _data.notifySettings as Any), ("botInfo", _data.botInfo as Any), ("pinnedMsgId", _data.pinnedMsgId as Any), ("commonChatsCount", _data.commonChatsCount as Any), ("folderId", _data.folderId as Any), ("ttlPeriod", _data.ttlPeriod as Any), ("theme", _data.theme as Any), ("privateForwardName", _data.privateForwardName as Any), ("botGroupAdminRights", _data.botGroupAdminRights as Any), ("botBroadcastAdminRights", _data.botBroadcastAdminRights as Any), ("wallpaper", _data.wallpaper as Any), ("stories", _data.stories as Any), ("businessWorkHours", _data.businessWorkHours as Any), ("businessLocation", _data.businessLocation as Any), ("businessGreetingMessage", _data.businessGreetingMessage as Any), ("businessAwayMessage", _data.businessAwayMessage as Any), ("businessIntro", _data.businessIntro as Any), ("birthday", _data.birthday as Any), ("personalChannelId", _data.personalChannelId as Any), ("personalChannelMessage", _data.personalChannelMessage as Any), ("stargiftsCount", _data.stargiftsCount as Any), ("starrefProgram", _data.starrefProgram as Any), ("botVerification", _data.botVerification as Any), ("sendPaidMessagesStars", _data.sendPaidMessagesStars as Any), ("disallowedGifts", _data.disallowedGifts as Any), ("starsRating", _data.starsRating as Any), ("starsMyPendingRating", _data.starsMyPendingRating as Any), ("starsMyPendingRatingDate", _data.starsMyPendingRatingDate as Any), ("mainTab", _data.mainTab as Any), ("savedMusic", _data.savedMusic as Any), ("note", _data.note as Any), ("botManagerId", _data.botManagerId as Any)]) } } @@ -1435,6 +1440,10 @@ public extension Api { _39 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } } + var _40: Int64? + if Int(_2!) & Int(1 << 23) != 0 { + _40 = reader.readInt64() + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -1474,8 +1483,9 @@ public extension Api { let _c37 = (Int(_2!) & Int(1 << 20) == 0) || _37 != nil let _c38 = (Int(_2!) & Int(1 << 21) == 0) || _38 != nil let _c39 = (Int(_2!) & Int(1 << 22) == 0) || _39 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 { - return Api.UserFull.userFull(Cons_userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38, note: _39)) + let _c40 = (Int(_2!) & Int(1 << 23) == 0) || _40 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 && _c40 { + return Api.UserFull.userFull(Cons_userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38, note: _39, botManagerId: _40)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index 8afb9aa5d5..caa44c463d 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -54,6 +54,50 @@ public extension Api.bots { } } } +public extension Api.bots { + enum ExportedBotToken: TypeConstructorDescription { + public class Cons_exportedBotToken: TypeConstructorDescription { + public var token: String + public init(token: String) { + self.token = token + } + public func descriptionFields() -> (String, [(String, Any)]) { + return ("exportedBotToken", [("token", self.token as Any)]) + } + } + case exportedBotToken(Cons_exportedBotToken) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedBotToken(let _data): + if boxed { + buffer.appendInt32(1012971041) + } + serializeString(_data.token, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .exportedBotToken(let _data): + return ("exportedBotToken", [("token", _data.token as Any)]) + } + } + + public static func parse_exportedBotToken(_ reader: BufferReader) -> ExportedBotToken? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.bots.ExportedBotToken.exportedBotToken(Cons_exportedBotToken(token: _1!)) + } + else { + return nil + } + } + } +} public extension Api.bots { enum PopularAppBots: TypeConstructorDescription { public class Cons_popularAppBots: TypeConstructorDescription { diff --git a/submodules/TelegramApi/Sources/Api40.swift b/submodules/TelegramApi/Sources/Api40.swift index 6c1eb0298f..2592800c60 100644 --- a/submodules/TelegramApi/Sources/Api40.swift +++ b/submodules/TelegramApi/Sources/Api40.swift @@ -2557,6 +2557,38 @@ public extension Api.functions.bots { }) } } +public extension Api.functions.bots { + static func checkUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2014174821) + serializeString(username, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.checkUsername", parameters: [("username", String(describing: username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func createBot(name: String, username: String, managerId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1656313365) + serializeString(name, buffer: buffer, boxed: false) + serializeString(username, buffer: buffer, boxed: false) + managerId.serialize(buffer, true) + return (FunctionDescription(name: "bots.createBot", parameters: [("name", String(describing: name)), ("username", String(describing: username)), ("managerId", String(describing: managerId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} public extension Api.functions.bots { static func deletePreviewMedia(bot: Api.InputUser, langCode: String, media: [Api.InputMedia]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -2596,6 +2628,22 @@ public extension Api.functions.bots { }) } } +public extension Api.functions.bots { + static func exportBotToken(botId: Int64, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(6533257) + serializeInt64(botId, buffer: buffer, boxed: false) + revoke.serialize(buffer, true) + return (FunctionDescription(name: "bots.exportBotToken", parameters: [("botId", String(describing: botId)), ("revoke", String(describing: revoke))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.ExportedBotToken? in + let reader = BufferReader(buffer) + var result: Api.bots.ExportedBotToken? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.bots.ExportedBotToken + } + return result + }) + } +} public extension Api.functions.bots { static func getAdminedBots() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.User]>) { let buffer = Buffer() @@ -5241,6 +5289,23 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func addPollAnswer(peer: Api.InputPeer, msgId: Int32, answer: Api.PollAnswer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(431770477) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + answer.serialize(buffer, true) + return (FunctionDescription(name: "messages.addPollAnswer", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("answer", String(describing: answer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.messages { static func appendTodoList(peer: Api.InputPeer, msgId: Int32, list: [Api.TodoItem]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -9500,16 +9565,19 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func summarizeText(flags: Int32, peer: Api.InputPeer, id: Int32, toLang: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func summarizeText(flags: Int32, peer: Api.InputPeer, id: Int32, toLang: String?, tone: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1656683294) + buffer.appendInt32(-1413754042) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) serializeInt32(id, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 { serializeString(toLang!, buffer: buffer, boxed: false) } - return (FunctionDescription(name: "messages.summarizeText", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("toLang", String(describing: toLang))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.TextWithEntities? in + if Int(flags) & Int(1 << 2) != 0 { + serializeString(tone!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.summarizeText", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("toLang", String(describing: toLang)), ("tone", String(describing: tone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.TextWithEntities? in let reader = BufferReader(buffer) var result: Api.TextWithEntities? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaImage.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaImage.swift index 2713101ccb..1c287a59d7 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaImage.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaImage.swift @@ -44,6 +44,9 @@ func telegramMediaImageFromApiPhoto(_ photo: Api.Photo, video: Api.Document? = n if hasStickers { imageFlags.insert(.hasStickers) } + if video != nil { + imageFlags.insert(.isLivePhoto) + } var videoRepresentations: [TelegramMediaImage.VideoRepresentation] = [] var emojiMarkup: TelegramMediaImage.EmojiMarkup? diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift index b9e3fb7767..e18430feab 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift @@ -18,11 +18,22 @@ extension TelegramMediaPollOption { } self.init(text: answerText, entities: answerEntities, opaqueIdentifier: option.makeData()) + case let .inputPollAnswer(inputPollAnswerData): + let text = inputPollAnswerData.text + let answerText: String + let answerEntities: [MessageTextEntity] + switch text { + case let .textWithEntities(textWithEntitiesData): + let (text, entities) = (textWithEntitiesData.text, textWithEntitiesData.entities) + answerText = text + answerEntities = messageTextEntitiesFromApiEntities(entities) + } + self.init(text: answerText, entities: answerEntities, opaqueIdentifier: Data()) } } var apiOption: Api.PollAnswer { - return .pollAnswer(.init(text: .textWithEntities(.init(text: self.text, entities: apiEntitiesFromMessageTextEntities(self.entities, associatedPeers: SimpleDictionary()))), option: Buffer(data: self.opaqueIdentifier))) + return .pollAnswer(.init(flags: 0, text: .textWithEntities(.init(text: self.text, entities: apiEntitiesFromMessageTextEntities(self.entities, associatedPeers: SimpleDictionary()))), option: Buffer(data: self.opaqueIdentifier), media: nil)) } } diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 2a23e68f34..84e4094eb9 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -147,7 +147,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post } mediaIds.append(id) if let image = media as? TelegramMediaImage { - signals.append(uploadedMediaImageContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, forceReupload: forceReupload, isGrouped: isGrouped, peerId: peerId, image: image, messageId: messageId, text: "", attributes: [], autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, auxiliaryMethods: auxiliaryMethods)) + signals.append(uploadedMediaImageContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, peerId: peerId, image: image, messageId: messageId, text: "", attributes: [], autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, auxiliaryMethods: auxiliaryMethods)) } else if let file = media as? TelegramMediaFile { signals.append(uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, isPaid: true, passFetchProgress: false, forceNoBigParts: false, peerId: peerId, messageId: messageId, text: "", attributes: [], autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, file: file)) } @@ -191,9 +191,9 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(.inputEncryptedFile(.init(id: resource.fileId, accessHash: resource.accessHash)), resource.decryptedSize, resource.key), reuploadInfo: nil, cacheReferenceKey: nil))) } if peerId.namespace != Namespaces.Peer.SecretChat, let reference = image.reference, case let .cloud(id, accessHash, maybeFileReference) = reference, let fileReference = maybeFileReference { - return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaPhoto(.init(flags: 0, id: Api.InputPhoto.inputPhoto(.init(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference))), ttlSeconds: nil)), text), reuploadInfo: nil, cacheReferenceKey: nil))) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaPhoto(.init(flags: 0, id: Api.InputPhoto.inputPhoto(.init(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference))), ttlSeconds: nil, video: nil)), text), reuploadInfo: nil, cacheReferenceKey: nil))) } else { - return uploadedMediaImageContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, forceReupload: forceReupload, isGrouped: isGrouped, peerId: peerId, image: image, messageId: messageId, text: text, attributes: attributes, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, auxiliaryMethods: auxiliaryMethods) + return uploadedMediaImageContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, peerId: peerId, image: image, messageId: messageId, text: text, attributes: attributes, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, auxiliaryMethods: auxiliaryMethods) } } else if let file = media as? TelegramMediaFile { if let resource = file.resource as? CloudDocumentMediaResource { @@ -337,7 +337,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post mappedSolutionEntities = apiTextAttributeEntities(TextEntitiesMessageAttribute(entities: solution.entities), associatedPeers: SimpleDictionary()) pollMediaFlags |= 1 << 1 } - let inputPoll = Api.InputMedia.inputMediaPoll(.init(flags: pollMediaFlags, poll: Api.Poll.poll(.init(id: 0, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil)), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities)) + let inputPoll = Api.InputMedia.inputMediaPoll(.init(flags: pollMediaFlags, poll: Api.Poll.poll(.init(id: 0, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil)), correctAnswers: correctAnswers, attachedMedia: nil, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: nil)) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil, cacheReferenceKey: nil))) } else if let todo = media as? TelegramMediaTodo { var flags: Int32 = 0 @@ -498,30 +498,17 @@ private func maybeCacheUploadedResource(postbox: Postbox, key: CachedSentMediaRe } } -private func uploadedMediaImageContent(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, forceReupload: Bool, isGrouped: Bool, peerId: PeerId, image: TelegramMediaImage, messageId: MessageId?, text: String, attributes: [MessageAttribute], autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?, auxiliaryMethods: AccountAuxiliaryMethods) -> Signal { +private func uploadedMediaImageContent(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, forceReupload: Bool, isGrouped: Bool, peerId: PeerId, image: TelegramMediaImage, messageId: MessageId?, text: String, attributes: [MessageAttribute], autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?, auxiliaryMethods: AccountAuxiliaryMethods) -> Signal { guard let largestRepresentation = largestImageRepresentation(image.representations) else { return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil, cacheReferenceKey: nil))) } -/*#if DEBUG -if "".isEmpty { - return auxiliaryMethods.backgroundUpload(postbox, network, largestRepresentation.resource) - |> castError(PendingMessageUploadError.self) - |> mapToSignal { result -> Signal in - if let result = result { - return .single(.content(PendingMessageUploadedContentAndReuploadInfo( - content: .text(result), - reuploadInfo: nil, - cacheReferenceKey: nil - ))) - } else { - return .fail(.generic) - } + let predownloadedResource: Signal + if image.video != nil { + predownloadedResource = .single(.none) + } else { + predownloadedResource = maybePredownloadedImageResource(postbox: postbox, peerId: peerId, resource: largestRepresentation.resource, forceRefresh: forceReupload) } -} -#endif*/ - - let predownloadedResource: Signal = maybePredownloadedImageResource(postbox: postbox, peerId: peerId, resource: largestRepresentation.resource, forceRefresh: forceReupload) return predownloadedResource |> mapToSignal { result -> Signal in var referenceKey: CachedSentMediaReferenceKey? @@ -542,7 +529,7 @@ if "".isEmpty { } return .single(.progress(PendingMessageUploadedContentProgress(progress: 1.0))) |> then( - .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(.init(flags: flags, id: .inputPhoto(.init(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference))), ttlSeconds: ttlSeconds)), text), reuploadInfo: nil, cacheReferenceKey: nil))) + .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(.init(flags: flags, id: .inputPhoto(.init(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference))), ttlSeconds: ttlSeconds, video: nil)), text), reuploadInfo: nil, cacheReferenceKey: nil))) ) } referenceKey = key @@ -644,13 +631,39 @@ if "".isEmpty { } else { imageReference = .standalone(media: transformedImage) } - return multipartUpload(network: network, postbox: postbox, source: .resource(imageReference.resourceReference(largestRepresentation.resource)), encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false) + let photoUpload = multipartUpload(network: network, postbox: postbox, source: .resource(imageReference.resourceReference(largestRepresentation.resource)), encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false) |> mapError { _ -> PendingMessageUploadError in return .generic } - |> mapToSignal { next -> Signal in + let photoVideoUpload: Signal + if peerId.namespace != Namespaces.Peer.SecretChat, let video = transformedImage.video ?? image.video { + photoVideoUpload = uploadedMediaPhotoVideoContent(network: network, postbox: postbox, messageMediaPreuploadManager: messageMediaPreuploadManager, peerId: peerId, file: video) + |> map(Optional.init) + } else { + photoVideoUpload = .single(nil) + } + + return combineLatest(photoUpload, photoVideoUpload) + |> mapToSignal { next, photoVideo -> Signal in switch next { case let .progress(progress): - return .single(.progress(PendingMessageUploadedContentProgress(progress: progress))) + var combinedProgress = progress + if let photoVideo { + if case let .progress(photoVideoProgress) = photoVideo { + combinedProgress = (progress + photoVideoProgress) * 0.5 + } else { + combinedProgress = (progress + 1.0) * 0.5 + } + } + return .single(.progress(PendingMessageUploadedContentProgress(progress: combinedProgress))) case let .inputFile(file): + var video: Api.InputDocument? + if let photoVideo { + switch photoVideo { + case let .progress(progress): + return .single(.progress(PendingMessageUploadedContentProgress(progress: (1.0 + progress) * 0.5))) + case let .inputDocument(inputDocument): + video = inputDocument + } + } var flags: Int32 = 0 var ttlSeconds: Int32? if let autoclearMessageAttribute = autoclearMessageAttribute { @@ -676,42 +689,46 @@ if "".isEmpty { hasSpoiler = true } } + + if let _ = photoVideo { + flags |= 1 << 3 + } + return postbox.transaction { transaction -> Api.InputPeer? in return transaction.getPeer(peerId).flatMap(apiInputPeer) } |> mapError { _ -> PendingMessageUploadError in } |> mapToSignal { inputPeer -> Signal in if let inputPeer = inputPeer { - if autoclearMessageAttribute != nil { - return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedPhoto(.init(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds, video: nil)), text), reuploadInfo: nil, cacheReferenceKey: nil))) + if autoclearMessageAttribute != nil || photoVideo != nil { + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedPhoto(.init(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds, video: video)), text), reuploadInfo: nil, cacheReferenceKey: nil))) } - - return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedPhoto(.init(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds, video: nil)))) + return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedPhoto(.init(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds, video: video)))) |> mapError { _ -> PendingMessageUploadError in return .generic } |> mapToSignal { result -> Signal in switch result { - case let .messageMediaPhoto(messageMediaPhotoData): - let photo = messageMediaPhotoData.photo - if let photo = photo, let mediaImage = telegramMediaImageFromApiPhoto(photo), let reference = mediaImage.reference, case let .cloud(id, accessHash, maybeFileReference) = reference, let fileReference = maybeFileReference { - var flags: Int32 = 0 - var ttlSeconds: Int32? - if let autoclearMessageAttribute = autoclearMessageAttribute { - flags |= 1 << 0 - ttlSeconds = autoclearMessageAttribute.timeout - } - if hasSpoiler { - flags |= 1 << 1 - } - - let result: PendingMessageUploadedContentResult = .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(.init(flags: flags, id: .inputPhoto(.init(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference))), ttlSeconds: ttlSeconds)), text), reuploadInfo: nil, cacheReferenceKey: nil)) - if let _ = ttlSeconds { - return .single(result) - } else { - return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: result, media: mediaImage) - } + case let .messageMediaPhoto(messageMediaPhotoData): + let photo = messageMediaPhotoData.photo + if let photo = photo, let mediaImage = telegramMediaImageFromApiPhoto(photo, video: messageMediaPhotoData.video), let reference = mediaImage.reference, case let .cloud(id, accessHash, maybeFileReference) = reference, let fileReference = maybeFileReference { + var flags: Int32 = 0 + var ttlSeconds: Int32? + if let autoclearMessageAttribute = autoclearMessageAttribute { + flags |= 1 << 0 + ttlSeconds = autoclearMessageAttribute.timeout } - default: - break + if hasSpoiler { + flags |= 1 << 1 + } + + let result: PendingMessageUploadedContentResult = .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(.init(flags: flags, id: .inputPhoto(.init(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference))), ttlSeconds: ttlSeconds, video: nil)), text), reuploadInfo: nil, cacheReferenceKey: nil)) + if let _ = ttlSeconds { + return .single(result) + } else { + return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: result, media: mediaImage) + } + } + default: + break } return .fail(.generic) } @@ -821,11 +838,116 @@ private enum UploadedMediaThumbnailResult { case none } +private enum UploadedMediaPhotoVideoResult { + case progress(Float) + case inputDocument(Api.InputDocument) +} + private enum UploadedMediaFileAndThumbnail { case pending case done(TelegramMediaFile, UploadedMediaThumbnailResult, UploadedMediaThumbnailResult) } +private func uploadedMediaPhotoVideoContent(network: Network, postbox: Postbox, messageMediaPreuploadManager: MessageMediaPreuploadManager, peerId: PeerId, file: TelegramMediaFile) -> Signal { + var hintFileIsLarge = false + var hintSize: Int64? + if let size = file.size { + hintSize = size + } else if let resource = file.resource as? LocalFileReferenceMediaResource, let size = resource.size { + hintSize = size + } + + loop: for attribute in file.attributes { + switch attribute { + case .hintFileIsLarge: + hintFileIsLarge = true + break loop + default: + break + } + } + + let fileReference: AnyMediaReference + if let partialReference = file.partialReference { + fileReference = partialReference.mediaReference(file) + } else { + fileReference = .standalone(media: file) + } + + return messageMediaPreuploadManager.upload( + network: network, + postbox: postbox, + source: .resource(fileReference.resourceReference(file.resource)), + encrypt: false, + tag: TelegramMediaResourceFetchTag(statsCategory: .video, userContentType: .video), + hintFileSize: hintSize, + hintFileIsLarge: hintFileIsLarge, + forceNoBigParts: false + ) + |> mapError { _ -> PendingMessageUploadError in + return .generic + } + |> mapToSignal { result -> Signal in + switch result { + case let .progress(progress): + return .single(.progress(progress)) + case let .inputFile(inputFile): + return postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(peerId).flatMap(apiInputPeer) + } + |> mapError { _ -> PendingMessageUploadError in + } + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer else { + return .fail(.generic) + } + + return network.request(Api.functions.messages.uploadMedia( + flags: 0, + businessConnectionId: nil, + peer: inputPeer, + media: .inputMediaUploadedDocument(.init( + flags: 0, + file: inputFile, + thumb: nil, + mimeType: file.mimeType, + attributes: inputDocumentAttributesFromFileAttributes(file.attributes), + stickers: nil, + videoCover: nil, + videoTimestamp: nil, + ttlSeconds: nil + )) + )) + |> mapError { _ -> PendingMessageUploadError in + return .generic + } + |> mapToSignal { result -> Signal in + switch result { + case let .messageMediaDocument(messageMediaDocumentData): + let (document, altDocuments) = (messageMediaDocumentData.document, messageMediaDocumentData.altDocuments) + if let document = document, + let mediaFile = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments), + let resource = mediaFile.resource as? CloudDocumentMediaResource, + let fileReference = resource.fileReference { + return .single(.inputDocument(.inputDocument(.init( + id: resource.fileId, + accessHash: resource.accessHash, + fileReference: Buffer(data: fileReference) + )))) + } else { + return .fail(.generic) + } + default: + return .fail(.generic) + } + } + } + case .inputSecretFile: + return .fail(.generic) + } + } +} + 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 } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift index 0f63eda72f..05fd50a8a7 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift @@ -1059,11 +1059,7 @@ public final class TelegramMediaFile: Media, Equatable, Codable { } return false } - - public var isLivePhoto: Bool { - return false - } - + public var preloadSize: Int32? { for attribute in self.attributes { if case .Video(_, _, _, let preloadSize, _, _) = attribute { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift index 4f8cf01d5e..0ae834a4d8 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift @@ -28,10 +28,10 @@ public struct TelegramMediaPollOption: Equatable, PostboxCoding { public struct TelegramMediaPollOptionVoters: Equatable, PostboxCoding { public let selected: Bool public let opaqueIdentifier: Data - public let count: Int32 + public let count: Int32? public let isCorrect: Bool - public init(selected: Bool, opaqueIdentifier: Data, count: Int32, isCorrect: Bool) { + public init(selected: Bool, opaqueIdentifier: Data, count: Int32?, isCorrect: Bool) { self.selected = selected self.opaqueIdentifier = opaqueIdentifier self.count = count @@ -41,14 +41,18 @@ public struct TelegramMediaPollOptionVoters: Equatable, PostboxCoding { public init(decoder: PostboxDecoder) { self.selected = decoder.decodeInt32ForKey("s", orElse: 0) != 0 self.opaqueIdentifier = decoder.decodeDataForKey("i") ?? Data() - self.count = decoder.decodeInt32ForKey("c", orElse: 0) + self.count = decoder.decodeOptionalInt32ForKey("c") self.isCorrect = decoder.decodeInt32ForKey("cr", orElse: 0) != 0 } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.selected ? 1 : 0, forKey: "s") encoder.encodeData(self.opaqueIdentifier, forKey: "i") - encoder.encodeInt32(self.count, forKey: "c") + if let count = self.count { + encoder.encodeInt32(count, forKey: "c") + } else { + encoder.encodeNil(forKey: "c") + } encoder.encodeInt32(self.isCorrect ? 1 : 0, forKey: "cr") } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift index a2783c66b3..d13a6fd4b2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift @@ -147,7 +147,7 @@ func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager pollMediaFlags |= 1 << 1 } - return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(.init(flags: pollMediaFlags, poll: .poll(.init(id: poll.pollId.id, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil)), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities)), replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil)) + return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(.init(flags: pollMediaFlags, poll: .poll(.init(id: poll.pollId.id, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil)), correctAnswers: correctAnswers, attachedMedia: nil, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: nil)), replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -407,7 +407,7 @@ private final class PollResultsContextImpl { if let voters = poll.results.voters { for voter in voters { if voter.opaqueIdentifier == option.opaqueIdentifier { - count = Int(voter.count) + count = Int(voter.count ?? 0) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 17e820b528..ed1f838395 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1619,8 +1619,8 @@ func _internal_deleteBotPreviews(account: Account, peerId: PeerId, language: Str var inputMedia: [Api.InputMedia] = [] for item in media { if let image = item as? TelegramMediaImage, let resource = image.representations.last?.resource as? CloudPhotoSizeMediaResource { - inputMedia.append(.inputMediaPhoto(.init(flags: 0, id: .inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil))) - inputMedia.append(Api.InputMedia.inputMediaPhoto(.init(flags: 0, id: Api.InputPhoto.inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil))) + inputMedia.append(.inputMediaPhoto(.init(flags: 0, id: .inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil, video: nil))) + inputMedia.append(Api.InputMedia.inputMediaPhoto(.init(flags: 0, id: Api.InputPhoto.inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil, video: nil))) } else if let file = item as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource { inputMedia.append(.inputMediaDocument(.init(flags: 0, id: .inputDocument(.init(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data()))), videoCover: nil, videoTimestamp: nil, ttlSeconds: nil, query: nil))) } @@ -1677,8 +1677,8 @@ func _internal_deleteBotPreviewsLanguage(account: Account, peerId: PeerId, langu var inputMedia: [Api.InputMedia] = [] for item in media { if let image = item as? TelegramMediaImage, let resource = image.representations.last?.resource as? CloudPhotoSizeMediaResource { - inputMedia.append(.inputMediaPhoto(.init(flags: 0, id: .inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil))) - inputMedia.append(Api.InputMedia.inputMediaPhoto(.init(flags: 0, id: Api.InputPhoto.inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil))) + inputMedia.append(.inputMediaPhoto(.init(flags: 0, id: .inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil, video: nil))) + inputMedia.append(Api.InputMedia.inputMediaPhoto(.init(flags: 0, id: Api.InputPhoto.inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil, video: nil))) } else if let file = item as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource { inputMedia.append(.inputMediaDocument(.init(flags: 0, id: .inputDocument(.init(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data()))), videoCover: nil, videoTimestamp: nil, ttlSeconds: nil, query: nil))) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index b8ad57ef3a..af801abd0c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -3387,8 +3387,8 @@ public final class BotPreviewStoryListContext: StoryListContext { var inputMedia: [Api.InputMedia] = [] for item in media { if let image = item as? TelegramMediaImage, let resource = image.representations.last?.resource as? CloudPhotoSizeMediaResource { - inputMedia.append(.inputMediaPhoto(.init(flags: 0, id: .inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil))) - inputMedia.append(Api.InputMedia.inputMediaPhoto(.init(flags: 0, id: Api.InputPhoto.inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil))) + inputMedia.append(.inputMediaPhoto(.init(flags: 0, id: .inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil, video: nil))) + inputMedia.append(Api.InputMedia.inputMediaPhoto(.init(flags: 0, id: Api.InputPhoto.inputPhoto(.init(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), ttlSeconds: nil, video: nil))) } else if let file = item as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource { inputMedia.append(.inputMediaDocument(.init(flags: 0, id: .inputDocument(.init(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data()))), videoCover: nil, videoTimestamp: nil, ttlSeconds: nil, query: nil))) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Summarize.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Summarize.swift index e4d3641c62..4f7b63cec0 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Summarize.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Summarize.swift @@ -27,7 +27,7 @@ func _internal_summarizeMessage(account: Account, messageId: EngineMessage.Id, t flags |= (1 << 0) } - return account.network.request(Api.functions.messages.summarizeText(flags: flags, peer: inputPeer, id: messageId.id, toLang: translateToLang)) + return account.network.request(Api.functions.messages.summarizeText(flags: flags, peer: inputPeer, id: messageId.id, toLang: translateToLang, tone: nil)) |> map(Optional.init) |> mapError { error -> SummarizeError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 508d038a42..cfc6c4a937 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -3918,6 +3918,7 @@ private final class ResaleGiftsContextImpl { private var sorting: ResaleGiftsContext.Sorting = .value private var filterAttributes: [ResaleGiftsContext.Attribute] = [] + private var starsOnly = false private var gifts: [StarGift] = [] private var attributes: [StarGift.UniqueGift.Attribute] = [] @@ -3963,6 +3964,7 @@ private final class ResaleGiftsContextImpl { let network = self.account.network let postbox = self.account.postbox let sorting = self.sorting + let starsOnly = self.starsOnly let filterAttributes = self.filterAttributes let currentAttributesHash = self.attributesHash @@ -3987,6 +3989,10 @@ private final class ResaleGiftsContextImpl { case .number: flags |= (1 << 2) } + + if starsOnly { + flags |= (1 << 5) + } var apiAttributes: [Api.StarGiftAttributeId]? if !filterAttributes.isEmpty { @@ -4118,6 +4124,17 @@ private final class ResaleGiftsContextImpl { self.loadMore() } + func updateStarsOnly(_ starsOnly: Bool) { + guard self.starsOnly != starsOnly else { + return + } + self.starsOnly = starsOnly + self.dataState = .ready(canLoadMore: true, nextOffset: nil) + self.pushState() + + self.loadMore() + } + func buyStarGift(slug: String, peerId: EnginePeer.Id, price: CurrencyAmount?) -> Signal { var listingPrice: CurrencyAmount? if let gift = self.gifts.first(where: { gift in @@ -4192,6 +4209,7 @@ private final class ResaleGiftsContextImpl { let state = ResaleGiftsContext.State( sorting: self.sorting, filterAttributes: self.filterAttributes, + starsOnly: self.starsOnly, gifts: self.gifts, attributes: self.attributes, attributeCount: self.attributeCount, @@ -4224,6 +4242,7 @@ public final class ResaleGiftsContext { public var sorting: Sorting public var filterAttributes: [Attribute] + public var starsOnly: Bool public var gifts: [StarGift] public var attributes: [StarGift.UniqueGift.Attribute] public var attributeCount: [Attribute: Int32] @@ -4281,6 +4300,12 @@ public final class ResaleGiftsContext { } } + public func updateStarsOnly(_ starsOnly: Bool) { + self.impl.with { impl in + impl.updateStarsOnly(starsOnly) + } + } + public func buyStarGift(slug: String, peerId: EnginePeer.Id, price: CurrencyAmount? = nil) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 10f3653aa4..4084fa59f1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -449,6 +449,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr public let dateAndStatusNode: ChatMessageDateAndStatusNode private var badgeNode: ChatMessageInteractiveMediaBadge? + private var livePhotoIconNode: ASImageNode? + private var timestampContainerView: UIView? private var timestampMaskView: UIImageView? private var videoTimestampBackgroundLayer: SimpleLayer? @@ -1330,6 +1332,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } } + var effectiveFile: TelegramMediaFile? = media as? TelegramMediaFile if let story = media as? TelegramMediaStory { isStory = true @@ -1507,6 +1510,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr messageMediaImageCancelInteractiveFetch(context: context, messageId: message.id, image: image, resource: resource) } }) + + effectiveFile = image.video } else if let image = media as? TelegramMediaWebFile { if hasCurrentVideoNode { replaceVideoNode = true @@ -1525,7 +1530,57 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr }, cancel: { chatMessageWebFileCancelInteractiveFetch(account: context.account, image: image) }) - } else if var file = media as? TelegramMediaFile { + } else if let wallpaper = media as? WallpaperPreviewMedia { + updateImageSignal = { synchronousLoad, _ in + switch wallpaper.content { + case let .file(file, _, _, _, isTheme, _): + if isTheme { + return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, source: .file(FileMediaReference.message(message: MessageReference(message), media: file)), synchronousLoad: synchronousLoad) + } else { + var representations: [ImageRepresentationWithReference] = file.previewRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference($0.resource)) }) + if file.mimeType == "image/svg+xml" || file.mimeType == "application/x-tgwallpattern" { + representations.append(ImageRepresentationWithReference(representation: .init(dimensions: PixelDimensions(width: 1440, height: 2960), resource: file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource))) + } + if ["image/png", "image/svg+xml", "application/x-tgwallpattern"].contains(file.mimeType) { + return patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: representations, mode: .screen) + |> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in + if let value = value { + return .single(value.generator) + } else { + return .complete() + } + } + } else { + return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(message), media: file), representations: representations, alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true, synchronousLoad: synchronousLoad) + } + } + case let .image(representations): + return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: nil, representations: representations.map({ ImageRepresentationWithReference(representation: $0, reference: .standalone(resource: $0.resource)) }), alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true) + case let .themeSettings(settings): + return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, source: .settings(settings)) + case let .color(color): + return solidColorImage(color) + case let .gradient(colors, rotation): + return gradientImage(colors.map(UIColor.init(rgb:)), rotation: rotation ?? 0) + case .emoticon: + return solidColorImage(.black) + } + } + + if case let .file(file, _, _, _, _, _) = wallpaper.content { + updatedFetchControls = FetchControls(fetch: { manual in + if let strongSelf = self { + strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: manual).startStrict()) + } + }, cancel: { + messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: file) + }) + } else if case .themeSettings = wallpaper.content { + } else { + boundingSize = CGSize(width: boundingSize.width, height: boundingSize.width) + } + } + if var file = effectiveFile { var uploading = false if file.resource is VideoLibraryMediaResource { uploading = true @@ -1590,7 +1645,9 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr return chatSecretMessageVideo(account: context.account, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file)) } } else { - if let file = media as? TelegramMediaFile, let image = file.videoCover { + if let _ = media as? TelegramMediaImage { + + } else if let image = file.videoCover { updateImageSignal = { synchronousLoad, highQuality in return chatMessagePhoto(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), photoReference: .message(message: MessageReference(message), media: image), synchronousLoad: synchronousLoad, highQuality: highQuality) } @@ -1644,55 +1701,6 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: file) } }) - } else if let wallpaper = media as? WallpaperPreviewMedia { - updateImageSignal = { synchronousLoad, _ in - switch wallpaper.content { - case let .file(file, _, _, _, isTheme, _): - if isTheme { - return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, source: .file(FileMediaReference.message(message: MessageReference(message), media: file)), synchronousLoad: synchronousLoad) - } else { - var representations: [ImageRepresentationWithReference] = file.previewRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference($0.resource)) }) - if file.mimeType == "image/svg+xml" || file.mimeType == "application/x-tgwallpattern" { - representations.append(ImageRepresentationWithReference(representation: .init(dimensions: PixelDimensions(width: 1440, height: 2960), resource: file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource))) - } - if ["image/png", "image/svg+xml", "application/x-tgwallpattern"].contains(file.mimeType) { - return patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: representations, mode: .screen) - |> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in - if let value = value { - return .single(value.generator) - } else { - return .complete() - } - } - } else { - return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(message), media: file), representations: representations, alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true, synchronousLoad: synchronousLoad) - } - } - case let .image(representations): - return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: nil, representations: representations.map({ ImageRepresentationWithReference(representation: $0, reference: .standalone(resource: $0.resource)) }), alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true) - case let .themeSettings(settings): - return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, source: .settings(settings)) - case let .color(color): - return solidColorImage(color) - case let .gradient(colors, rotation): - return gradientImage(colors.map(UIColor.init(rgb:)), rotation: rotation ?? 0) - case .emoticon: - return solidColorImage(.black) - } - } - - if case let .file(file, _, _, _, _, _) = wallpaper.content { - updatedFetchControls = FetchControls(fetch: { manual in - if let strongSelf = self { - strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: manual).startStrict()) - } - }, cancel: { - messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: file) - }) - } else if case .themeSettings = wallpaper.content { - } else { - boundingSize = CGSize(width: boundingSize.width, height: boundingSize.width) - } } } if !reloadMedia { @@ -2024,11 +2032,29 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } videoNode.isHidden = !owns if owns { + videoNode.alpha = 1.0 videoNode.setBaseRate(1.0) - videoNode.continuePlayingWithoutSound() + if let image = strongSelf.media as? TelegramMediaImage, let _ = image.video { + videoNode.continuePlayingWithoutSound(actionAtEnd: .stop) + } else { + videoNode.continuePlayingWithoutSound() + } } } } + videoNode.playbackCompleted = { [weak self] in + guard let self else { + return + } + + if let image = self.media as? TelegramMediaImage, let _ = image.video { + self.videoNode?.alpha = 0.0 + self.videoNode?.layer.allowsGroupOpacity = true + self.videoNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { _ in + self.videoNode?.allowsGroupOpacity = true + }) + } + } strongSelf.videoContent = videoContent strongSelf.videoNode = videoNode @@ -2320,6 +2346,25 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr })) } + if let image = strongSelf.media as? TelegramMediaImage, let _ = image.video { + let livePhotoIconNode: ASImageNode + if let current = strongSelf.livePhotoIconNode { + livePhotoIconNode = current + } else { + livePhotoIconNode = ASImageNode() + livePhotoIconNode.image = UIImage(bundleImageName: "Chat/Message/LivePhoto") + strongSelf.pinchContainerNode.contentNode.addSubnode(livePhotoIconNode) + strongSelf.livePhotoIconNode = livePhotoIconNode + } + + if let icon = livePhotoIconNode.image { + livePhotoIconNode.frame = CGRect(origin: CGPoint(x: 8.0, y: 8.0), size: icon.size) + } + } else if let livePhotoIconNode = strongSelf.livePhotoIconNode { + strongSelf.livePhotoIconNode = nil + livePhotoIconNode.removeFromSupernode() + } + if let updatedFetchControls = updatedFetchControls { let _ = strongSelf.fetchControls.swap(updatedFetchControls) @@ -2890,7 +2935,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } } - if let file = media as? TelegramMediaFile, file.isLivePhoto { + if let image = media as? TelegramMediaImage, let _ = image.video { badgeContent = nil if case .progress = state { } else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index 7d0d161a05..6fdb5b8347 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -388,7 +388,8 @@ public final class ChatMessageAccessibilityData { inner: for optionVoters in voters { if optionVoters.opaqueIdentifier == poll.options[i].opaqueIdentifier { optionVoterCount[i] = optionVoters.count - maxOptionVoterCount = max(maxOptionVoterCount, optionVoters.count) + //TODO:correct + maxOptionVoterCount = max(maxOptionVoterCount, optionVoters.count ?? 0) break inner } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift index 8f8397d26e..7e2591dbd9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift @@ -125,6 +125,10 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramImage) { automaticDownload = .full } + + if let _ = telegramImage.video { + automaticPlayback = true + } } else if let telegramStory = media as? TelegramMediaStory { selectedMedia = telegramStory if let storyMedia = item.message.associatedStories[telegramStory.storyId], case let .item(storyItem) = storyMedia.get(Stories.StoredItem.self), let media = storyItem.media { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift index 11ea5ed6c0..ecbd07dc79 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift @@ -21,6 +21,7 @@ import ShimmeringLinkNode private final class ChatMessagePollOptionRadioNodeParameters: NSObject { let timestamp: Double + let isRectangle: Bool let staticColor: UIColor let animatedColor: UIColor let fillColor: UIColor @@ -29,8 +30,9 @@ private final class ChatMessagePollOptionRadioNodeParameters: NSObject { let isChecked: Bool? let checkTransition: ChatMessagePollOptionRadioNodeCheckTransition? - init(timestamp: Double, staticColor: UIColor, animatedColor: UIColor, fillColor: UIColor, foregroundColor: UIColor, offset: Double?, isChecked: Bool?, checkTransition: ChatMessagePollOptionRadioNodeCheckTransition?) { + init(timestamp: Double, isRectangle: Bool, staticColor: UIColor, animatedColor: UIColor, fillColor: UIColor, foregroundColor: UIColor, offset: Double?, isChecked: Bool?, checkTransition: ChatMessagePollOptionRadioNodeCheckTransition?) { self.timestamp = timestamp + self.isRectangle = isRectangle self.staticColor = staticColor self.animatedColor = animatedColor self.fillColor = fillColor @@ -58,6 +60,7 @@ private final class ChatMessagePollOptionRadioNodeCheckTransition { } private final class ChatMessagePollOptionRadioNode: ASDisplayNode { + private(set) var isRectangle = false private(set) var staticColor: UIColor? private(set) var animatedColor: UIColor? private(set) var fillColor: UIColor? @@ -116,9 +119,13 @@ private final class ChatMessagePollOptionRadioNode: ASDisplayNode { } } - func update(staticColor: UIColor, animatedColor: UIColor, fillColor: UIColor, foregroundColor: UIColor, isSelectable: Bool, isAnimating: Bool) { + func update(isRectangle: Bool, staticColor: UIColor, animatedColor: UIColor, fillColor: UIColor, foregroundColor: UIColor, isSelectable: Bool, isAnimating: Bool) { var updated = false let shouldHaveBeenAnimating = self.shouldBeAnimating + if self.isRectangle != isRectangle { + self.isRectangle = isRectangle + updated = true + } if !staticColor.isEqual(self.staticColor) { self.staticColor = staticColor updated = true @@ -191,7 +198,7 @@ private final class ChatMessagePollOptionRadioNode: ASDisplayNode { if let startTime = self.startTime { offset = CACurrentMediaTime() - startTime } - return ChatMessagePollOptionRadioNodeParameters(timestamp: timestamp, staticColor: staticColor, animatedColor: animatedColor, fillColor: fillColor, foregroundColor: foregroundColor, offset: offset, isChecked: self.isChecked, checkTransition: self.checkTransition) + return ChatMessagePollOptionRadioNodeParameters(timestamp: timestamp, isRectangle: self.isRectangle, staticColor: staticColor, animatedColor: animatedColor, fillColor: fillColor, foregroundColor: foregroundColor, offset: offset, isChecked: self.isChecked, checkTransition: self.checkTransition) } else { return nil } @@ -295,12 +302,23 @@ private final class ChatMessagePollOptionRadioNode: ASDisplayNode { if abs(diameter - 1.0) > CGFloat.ulpOfOne { context.setStrokeColor(parameters.staticColor.cgColor) - context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.5, y: 0.5), size: CGSize(width: bounds.width - 1.0, height: bounds.height - 1.0))) + + if parameters.isRectangle{ + context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: bounds.size).insetBy(dx: 0.5, dy: 0.5), cornerRadius: 6.0).cgPath) + context.strokePath() + } else { + context.strokeEllipse(in: CGRect(origin: .zero, size: bounds.size).insetBy(dx: 0.5, dy: 0.5)) + } } if !diameter.isZero { context.setFillColor(parameters.fillColor.withAlphaComponent(alpha).cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: (bounds.width - diameter) / 2.0, y: (bounds.width - diameter) / 2.0), size: CGSize(width: diameter, height: diameter))) + if parameters.isRectangle{ + context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: (bounds.width - diameter) / 2.0, y: (bounds.width - diameter) / 2.0), size: CGSize(width: diameter, height: diameter)), cornerRadius: 6.0).cgPath) + context.fillPath() + } else { + context.fillEllipse(in: CGRect(origin: CGPoint(x: (bounds.width - diameter) / 2.0, y: (bounds.width - diameter) / 2.0), size: CGSize(width: diameter, height: diameter))) + } context.setLineWidth(1.5) context.setLineJoin(.round) @@ -336,7 +354,13 @@ private final class ChatMessagePollOptionRadioNode: ASDisplayNode { } } else { context.setStrokeColor(parameters.staticColor.cgColor) - context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.5, y: 0.5), size: CGSize(width: bounds.width - 1.0, height: bounds.height - 1.0))) + + if parameters.isRectangle { + context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: bounds.size).insetBy(dx: 0.5, dy: 0.5), cornerRadius: 6.0).cgPath) + context.strokePath() + } else { + context.strokeEllipse(in: CGRect(origin: .zero, size: bounds.size).insetBy(dx: 0.5, dy: 0.5)) + } } } } @@ -694,7 +718,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { } let radioSize: CGFloat = 22.0 radioNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: CGSize(width: radioSize, height: radioSize)) - radioNode.update(staticColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioButton : presentationData.theme.theme.chat.message.outgoing.polls.radioButton, animatedColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioProgress : presentationData.theme.theme.chat.message.outgoing.polls.radioProgress, fillColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.bar : presentationData.theme.theme.chat.message.outgoing.polls.bar, foregroundColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.barIconForeground : presentationData.theme.theme.chat.message.outgoing.polls.barIconForeground, isSelectable: isSelectable, isAnimating: inProgress) + radioNode.update(isRectangle: poll.kind == .poll(multipleAnswers: true), staticColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioButton : presentationData.theme.theme.chat.message.outgoing.polls.radioButton, animatedColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioProgress : presentationData.theme.theme.chat.message.outgoing.polls.radioProgress, fillColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.bar : presentationData.theme.theme.chat.message.outgoing.polls.bar, foregroundColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.barIconForeground : presentationData.theme.theme.chat.message.outgoing.polls.barIconForeground, isSelectable: isSelectable, isAnimating: inProgress) } else if let radioNode = node.radioNode { node.radioNode = nil if animated { @@ -1301,7 +1325,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { inner: for optionVoters in voters { if optionVoters.opaqueIdentifier == poll.options[i].opaqueIdentifier { optionVoterCount[i] = optionVoters.count - maxOptionVoterCount = max(maxOptionVoterCount, optionVoters.count) + //TODO:correct + maxOptionVoterCount = max(maxOptionVoterCount, optionVoters.count ?? 0) break inner } } diff --git a/submodules/TelegramUI/Components/CheckComponent/Sources/CheckComponent.swift b/submodules/TelegramUI/Components/CheckComponent/Sources/CheckComponent.swift index 33db3b4933..4df8d67d72 100644 --- a/submodules/TelegramUI/Components/CheckComponent/Sources/CheckComponent.swift +++ b/submodules/TelegramUI/Components/CheckComponent/Sources/CheckComponent.swift @@ -5,6 +5,11 @@ import ComponentFlow import CheckNode public final class CheckComponent: Component { + public enum Mode { + case option + case checkbox + } + public struct Theme: Equatable { public let backgroundColor: UIColor public let strokeColor: UIColor @@ -40,21 +45,27 @@ public final class CheckComponent: Component { } } + let mode: Mode let theme: Theme let size: CGSize let selected: Bool public init( + mode: Mode = .option, theme: Theme, size: CGSize = CGSize(width: 22.0, height: 22.0), selected: Bool ) { + self.mode = mode self.theme = theme self.size = size self.selected = selected } public static func ==(lhs: CheckComponent, rhs: CheckComponent) -> Bool { + if lhs.mode != rhs.mode { + return false + } if lhs.theme != rhs.theme { return false } diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD index 700ed3aaa2..8c45e836c9 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD @@ -50,6 +50,7 @@ swift_library( "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/GlassBackgroundComponent", "//submodules/TelegramUI/Components/ContextControllerImpl", + "//submodules/TelegramUI/Components/CheckComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift index a9676d8f57..a04263b2c8 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift @@ -498,6 +498,15 @@ public final class GiftStoreContentComponent: Component { } } + func updateStarsOnly(_ starsOnly: Bool) { + guard let component = self.component else { + return + } + self.showLoading = true + component.resaleGiftsContext.updateStarsOnly(starsOnly) + component.scrollToTop() + } + func openSortContextMenu(sourceView: UIView) { guard let component = self.component, let controller = component.controller(), !self.effectiveIsLoading else { return @@ -540,6 +549,20 @@ public final class GiftStoreContentComponent: Component { component.scrollToTop() }))) + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: "All Listings", icon: { theme in + return component.resaleGiftsContext.currentState?.starsOnly == false ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage() + }, action: { [weak self] _, f in + f(.default) + self?.updateStarsOnly(false) + }))) + items.append(.action(ContextMenuActionItem(text: "For Stars Only", icon: { theme in + return component.resaleGiftsContext.currentState?.starsOnly == true ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage() + }, action: { [weak self] _, f in + f(.default) + self?.updateStarsOnly(true) + }))) + let contextController = makeContextController(presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil) contextController.dismissed = { [weak self] in guard let self else { @@ -1174,6 +1197,7 @@ final class GiftStoreScreenComponent: Component { private let title = ComponentView() private let subtitle = ComponentView() private let content = ComponentView() + private let starsFilter = ComponentView() private var starsStateDisposable: Disposable? private var starsState: StarsContext.State? @@ -1222,6 +1246,8 @@ final class GiftStoreScreenComponent: Component { self.scrollView.setContentOffset(CGPoint(), animated: true) } + var starsFilterIsHidden = false + var nextScrollTransition: ComponentTransition? func scrollViewDidScroll(_ scrollView: UIScrollView) { self.updateScrolling(bounds: scrollView.bounds, interactive: true, transition: self.nextScrollTransition ?? .immediate) @@ -1231,6 +1257,12 @@ final class GiftStoreScreenComponent: Component { if let contentView = self.content.view as? GiftStoreContentComponent.View { contentView.updateScrolling(bounds: bounds, interactive: interactive, transition: transition) } + + let starsFilterIsHidden = bounds.origin.y > 100.0 + if starsFilterIsHidden != self.starsFilterIsHidden { + self.starsFilterIsHidden = starsFilterIsHidden + self.state?.updated(transition: .spring(duration: 0.4)) + } } func presentBalanceMenu() { @@ -1471,6 +1503,30 @@ final class GiftStoreScreenComponent: Component { transition.setFrame(view: subtitleView, frame: subtitleFrame) } + //TODO:localize + let starsFilterSize = self.starsFilter.update( + transition: transition, + component: AnyComponent( + StarsFilterComponent(theme: theme, text: "Show listings for stars only", isSelected: component.resaleGiftsContext.currentState?.starsOnly ?? false, selectionUpdated: { [weak self] starsOnly in + guard let self else { + return + } + if let contentView = self.content.view as? GiftStoreContentComponent.View { + contentView.updateStarsOnly(starsOnly) + } + }) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - headerSideInset * 2.0, height: 100.0) + ) + let starsFilterFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - starsFilterSize.width) / 2.0), y: self.starsFilterIsHidden ? availableSize.height + 64.0 : availableSize.height - starsFilterSize.height - environment.safeInsets.bottom - 16.0), size: starsFilterSize) + if let starsFilterView = self.starsFilter.view { + if starsFilterView.superview == nil { + self.addSubview(starsFilterView) + } + transition.setFrame(view: starsFilterView, frame: starsFilterFrame) + } + let previousBounds = self.scrollView.bounds diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/StarsFilterComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/StarsFilterComponent.swift new file mode 100644 index 0000000000..bdfd7749f6 --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/StarsFilterComponent.swift @@ -0,0 +1,143 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit +import PlainButtonComponent +import MultilineTextComponent +import BundleIconComponent +import TextFormat +import AccountContext +import TelegramPresentationData +import GlassBackgroundComponent +import CheckComponent + +final class StarsFilterComponent: Component { + let theme: PresentationTheme + let text: String + let isSelected: Bool + let selectionUpdated: (Bool) -> Void + + init( + theme: PresentationTheme, + text: String, + isSelected: Bool, + selectionUpdated: @escaping (Bool) -> Void + ) { + self.theme = theme + self.text = text + self.isSelected = isSelected + self.selectionUpdated = selectionUpdated + } + + static func ==(lhs: StarsFilterComponent, rhs: StarsFilterComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.isSelected != rhs.isSelected { + return false + } + return true + } + + final class View: UIView { + private let backgroundView = GlassBackgroundView() + private let button = HighlightTrackingButton() + + private let check = ComponentView() + private let text = ComponentView() + + private var component: StarsFilterComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.addSubview(self.backgroundView) + self.backgroundView.contentView.addSubview(self.button) + + self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func buttonPressed() { + guard let component = self.component else { + return + } + component.selectionUpdated(!component.isSelected) + } + + func update(component: StarsFilterComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let inset: CGFloat = 16.0 + + let textSize = self.text.update( + transition: .immediate, + component: AnyComponent( + Text(text: component.text, font: Font.regular(17.0), color: component.theme.list.itemPrimaryTextColor) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - 44.0, height: 52.0) + ) + + let checkTheme = CheckComponent.Theme( + backgroundColor: component.theme.list.itemCheckColors.fillColor, + strokeColor: component.theme.list.itemCheckColors.foregroundColor, + borderColor: component.theme.list.itemCheckColors.strokeColor, + overlayBorder: false, + hasInset: false, + hasShadow: false + ) + let checkSize = self.check.update( + transition: .immediate, + component: AnyComponent( + CheckComponent(theme: checkTheme, selected: component.isSelected) + ), + environment: {}, + containerSize: CGSize(width: 22.0, height: 22.0) + ) + + let size = CGSize(width: inset + checkSize.width + inset + textSize.width + inset + 6.0, height: 52.0) + + let textFrame = CGRect(origin: CGPoint(x: inset + checkSize.width + inset, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) + if let textView = self.text.view { + if textView.superview == nil { + self.backgroundView.contentView.addSubview(textView) + textView.isUserInteractionEnabled = false + } + transition.setFrame(view: textView, frame: textFrame) + } + + let checkFrame = CGRect(origin: CGPoint(x: inset, y: floorToScreenPixels((size.height - checkSize.height) / 2.0)), size: checkSize) + if let checkView = self.check.view { + if checkView.superview == nil { + self.backgroundView.contentView.addSubview(checkView) + checkView.isUserInteractionEnabled = false + } + transition.setFrame(view: checkView, frame: checkFrame) + } + + self.button.frame = CGRect(origin: .zero, size: size) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size)) + self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +}