Update API

This commit is contained in:
Ilya Laktyushin 2025-01-06 16:00:43 +04:00
parent 8cfec897ee
commit cdc82e4235
54 changed files with 1529 additions and 617 deletions

View File

@ -13641,3 +13641,12 @@ Sorry for the inconvenience.";
"Gift.View.Outgoing.NameHidden" = "Only %@ can see your name."; "Gift.View.Outgoing.NameHidden" = "Only %@ can see your name.";
"Gift.View.Outgoing.NameAndMessageHidden" = "Only %@ can see your name and message."; "Gift.View.Outgoing.NameAndMessageHidden" = "Only %@ can see your name and message.";
"Conversation.ViewStarGift" = "VIEW COLLECTIBLE";
"ChatList.AddPhoto.Title" = "Add your photo! 📸";
"ChatList.AddPhoto.Text" = "Help your friends spot you easily.";
"Story.ViewGift" = "View Gift";
"Camera.OpenChat" = "Open Chat";

View File

@ -318,6 +318,7 @@ public enum ResolvedUrl {
case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?) case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?)
case premiumGiftCode(slug: String) case premiumGiftCode(slug: String)
case premiumMultiGift(reference: String?) case premiumMultiGift(reference: String?)
case collectible(gift: StarGift.UniqueGift?)
case messageLink(link: TelegramResolvedMessageLink?) case messageLink(link: TelegramResolvedMessageLink?)
} }
@ -1099,10 +1100,13 @@ public protocol SharedAccountContext: AnyObject {
func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController
func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController
func makeStarsIntroScreen(context: AccountContext) -> ViewController func makeStarsIntroScreen(context: AccountContext) -> ViewController
func makeGiftViewScreen(context: AccountContext, message: EngineMessage) -> ViewController func makeGiftViewScreen(context: AccountContext, message: EngineMessage, shareStory: (() -> Void)?) -> ViewController
func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: (() -> Void)?) -> ViewController
func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?) func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?)
func makeShareController(context: AccountContext, subject: ShareControllerSubject, forceExternal: Bool, shareStory: (() -> Void)?, actionCompleted: (() -> Void)?) -> ViewController
func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal<MiniAppListScreenInitialData, NoError> func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal<MiniAppListScreenInitialData, NoError>
func makeMiniAppListScreen(context: AccountContext, initialData: MiniAppListScreenInitialData) -> ViewController func makeMiniAppListScreen(context: AccountContext, initialData: MiniAppListScreenInitialData) -> ViewController
@ -1338,28 +1342,3 @@ public struct StickersSearchConfiguration {
} }
} }
} }
public protocol ShareControllerAccountContext: AnyObject {
var accountId: AccountRecordId { get }
var accountPeerId: EnginePeer.Id { get }
var stateManager: AccountStateManager { get }
var engineData: TelegramEngine.EngineData { get }
var animationCache: AnimationCache { get }
var animationRenderer: MultiAnimationRenderer { get }
var contentSettings: ContentSettings { get }
var appConfiguration: AppConfiguration { get }
func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError>
}
public protocol ShareControllerEnvironment: AnyObject {
var presentationData: PresentationData { get }
var updatedPresentationData: Signal<PresentationData, NoError> { get }
var isMainApp: Bool { get }
var energyUsageSettings: EnergyUsageSettings { get }
var mediaManager: MediaManager? { get }
func setAccountUserInterfaceInUse(id: AccountRecordId) -> Disposable
func donateSendMessageIntent(account: ShareControllerAccountContext, peerIds: [EnginePeer.Id])
}

View File

@ -0,0 +1,55 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import AnimationCache
import MultiAnimationRenderer
public protocol ShareControllerAccountContext: AnyObject {
var accountId: AccountRecordId { get }
var accountPeerId: EnginePeer.Id { get }
var stateManager: AccountStateManager { get }
var engineData: TelegramEngine.EngineData { get }
var animationCache: AnimationCache { get }
var animationRenderer: MultiAnimationRenderer { get }
var contentSettings: ContentSettings { get }
var appConfiguration: AppConfiguration { get }
func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError>
}
public protocol ShareControllerEnvironment: AnyObject {
var presentationData: PresentationData { get }
var updatedPresentationData: Signal<PresentationData, NoError> { get }
var isMainApp: Bool { get }
var energyUsageSettings: EnergyUsageSettings { get }
var mediaManager: MediaManager? { get }
func setAccountUserInterfaceInUse(id: AccountRecordId) -> Disposable
func donateSendMessageIntent(account: ShareControllerAccountContext, peerIds: [EnginePeer.Id])
}
public enum ShareControllerExternalStatus {
case preparing(Bool)
case progress(Float)
case done
}
public enum ShareControllerError {
case generic
case fileTooBig(Int64)
}
public enum ShareControllerSubject {
case url(String)
case text(String)
case quote(text: String, url: String)
case messages([Message])
case image([ImageRepresentationWithReference])
case media(AnyMediaReference)
case mapMedia(TelegramMediaMap)
case fromExternal(([PeerId], [PeerId: Int64], String, ShareControllerAccountContext, Bool) -> Signal<ShareControllerExternalStatus, ShareControllerError>)
}

View File

@ -6538,7 +6538,13 @@ private final class ChatListLocationContext {
if channel.flags.contains(.requestToJoin) { if channel.flags.contains(.requestToJoin) {
actionTitle = presentationData.strings.Group_ApplyToJoin actionTitle = presentationData.strings.Group_ApplyToJoin
} else { } else {
actionTitle = presentationData.strings.Channel_JoinChannel switch channel.info {
case .broadcast:
actionTitle = presentationData.strings.Channel_JoinChannel
case .group:
actionTitle = presentationData.strings.Group_JoinGroup
}
} }
toolbar = Toolbar(leftAction: nil, rightAction: nil, middleAction: ToolbarAction(title: actionTitle, isEnabled: true)) toolbar = Toolbar(leftAction: nil, rightAction: nil, middleAction: ToolbarAction(title: actionTitle, isEnabled: true))
} }

View File

@ -289,9 +289,8 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
titleString = attributedTitle titleString = attributedTitle
textString = NSAttributedString(string: text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) textString = NSAttributedString(string: text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
case let .setupPhoto(accountPeer): case let .setupPhoto(accountPeer):
//TODO:localize titleString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Title, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)
titleString = NSAttributedString(string: "Add your photo! 📸", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor) textString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
textString = NSAttributedString(string: "Help your friends spot you easily.", font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
avatarPeer = accountPeer avatarPeer = accountPeer
} }

View File

@ -98,7 +98,7 @@ swift_library(
"//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/LottieComponentResourceContent", "//submodules/TelegramUI/Components/LottieComponentResourceContent",
"//submodules/ImageTransparency", "//submodules/ImageTransparency",
"//submodules/GalleryUI", #"//submodules/GalleryUI",
"//submodules/MediaPlayer:UniversalMediaPlayer", "//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/TelegramUniversalVideoContent", "//submodules/TelegramUniversalVideoContent",
"//submodules/TelegramUI/Components/CameraButtonComponent", "//submodules/TelegramUI/Components/CameraButtonComponent",

View File

@ -142,6 +142,8 @@ public class DrawingStickerEntityView: DrawingEntityView {
return image return image
} else if case .message = self.stickerEntity.content { } else if case .message = self.stickerEntity.content {
return self.animatedImageView?.image return self.animatedImageView?.image
} else if case .gift = self.stickerEntity.content {
return self.animatedImageView?.image
} else { } else {
return nil return nil
} }
@ -167,7 +169,7 @@ public class DrawingStickerEntityView: DrawingEntityView {
return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
case .dualVideoReference: case .dualVideoReference:
return CGSize(width: 512.0, height: 512.0) return CGSize(width: 512.0, height: 512.0)
case let .message(_, size, _, _, _): case let .message(_, size, _, _, _), let .gift(_, size):
return size return size
} }
} }
@ -296,6 +298,43 @@ public class DrawingStickerEntityView: DrawingEntityView {
if let file, let _ = mediaRect { if let file, let _ = mediaRect {
self.setupWithVideo(file) self.setupWithVideo(file)
} }
} else if case let .gift(gift, _) = self.stickerEntity.content {
if let image = self.stickerEntity.renderImage {
self.setupWithImage(image, overlayImage: self.stickerEntity.overlayRenderImage)
}
var file: TelegramMediaFile?
for attribute in gift.attributes {
if case let .model(_, fileValue, _) = attribute {
file = fileValue
break
}
}
guard let file, let dimensions = file.dimensions else {
return
}
if self.animationNode == nil {
let animationNode = DefaultAnimatedStickerNodeImpl()
animationNode.clipsToBounds = true
animationNode.autoplay = false
self.animationNode = animationNode
animationNode.started = { [weak self, weak animationNode] in
self?.imageNode.isHidden = true
if let animationNode = animationNode {
let _ = (animationNode.status
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] status in
self?.started?(status.duration)
})
}
}
self.addSubnode(self.imageNode)
self.addSubnode(animationNode)
}
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, userLocation: .other, file: file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0))))
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: file.resource).start())
self.setNeedsLayout()
} }
} }
@ -418,7 +457,18 @@ public class DrawingStickerEntityView: DrawingEntityView {
if self.isPlaying != isPlaying { if self.isPlaying != isPlaying {
self.isPlaying = isPlaying self.isPlaying = isPlaying
if let file = self.file { var file: TelegramMediaFile?
if let fileValue = self.file {
file = fileValue
} else if case let .gift(gift, _) = self.stickerEntity.content {
for attribute in gift.attributes {
if case let .model(_, fileValue, _) = attribute {
file = fileValue
break
}
}
}
if let file {
if isPlaying && !self.didSetUpAnimationNode { if isPlaying && !self.didSetUpAnimationNode {
self.didSetUpAnimationNode = true self.didSetUpAnimationNode = true
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
@ -596,15 +646,22 @@ public class DrawingStickerEntityView: DrawingEntityView {
let imageSize = self.dimensions.aspectFitted(boundingSize) let imageSize = self.dimensions.aspectFitted(boundingSize)
let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() var animationSize = CGSize(width: imageSize.width, height: imageSize.width)
self.imageNode.frame = imageFrame var animationFrame = imageFrame
if case .gift = self.stickerEntity.content {
animationSize = CGSize(width: animationSize.width * 0.48, height: animationSize.height * 0.48)
animationFrame = CGRect(origin: CGPoint(x: floor((size.width - animationSize.width) / 2.0), y: 22.0), size: animationSize)
}
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: animationSize, boundingSize: animationSize, intrinsicInsets: UIEdgeInsets()))()
self.imageNode.frame = animationFrame
if let animationNode = self.animationNode { if let animationNode = self.animationNode {
if self.isReaction { if self.isReaction {
animationNode.cornerRadius = floor(imageSize.width * 0.1) animationNode.cornerRadius = floor(animationSize.width * 0.1)
} }
animationNode.frame = imageFrame animationNode.frame = animationFrame
animationNode.updateLayout(size: imageSize) animationNode.updateLayout(size: animationSize)
if !self.didApplyVisibility { if !self.didApplyVisibility {
self.didApplyVisibility = true self.didApplyVisibility = true
@ -818,6 +875,35 @@ public class DrawingStickerEntityView: DrawingEntityView {
return entities return entities
} }
} else if case let .gift(gift, _) = self.stickerEntity.content {
var file: TelegramMediaFile?
for attribute in gift.attributes {
if case let .model(_, fileValue, _) = attribute {
file = fileValue
break
}
}
if let file, let animationNode = self.animationNode {
let stickerSize = self.bounds.size
let stickerPosition = self.stickerEntity.position
let videoSize = animationNode.frame.size
let scale = self.stickerEntity.scale
let rotation = self.stickerEntity.rotation
let videoPosition = animationNode.position.offsetBy(dx: -stickerSize.width / 2.0, dy: -stickerSize.height / 2.0)
let videoScale = videoSize.width / stickerSize.width
let videoEntity = DrawingStickerEntity(content: .file(.standalone(media: file), .sticker))
videoEntity.referenceDrawingSize = self.stickerEntity.referenceDrawingSize
videoEntity.position = stickerPosition.offsetBy(
dx: (videoPosition.x * cos(rotation) - videoPosition.y * sin(rotation)) * scale,
dy: (videoPosition.y * cos(rotation) + videoPosition.x * sin(rotation)) * scale
)
videoEntity.scale = scale * videoScale
videoEntity.rotation = rotation
return [videoEntity]
}
} }
return [] return []
} }
@ -1103,6 +1189,9 @@ final class DrawingStickerEntitySelectionView: DrawingEntitySelectionView {
if case .message = entity.content { if case .message = entity.content {
cornerRadius *= 2.1 cornerRadius *= 2.1
count = 24 count = 24
} else if case .gift = entity.content {
cornerRadius *= 2.1
count = 24
} else if case .image = entity.content { } else if case .image = entity.content {
count = 24 count = 24
} }

View File

@ -147,7 +147,7 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity {
case let .image(image, _): case let .image(image, _):
self.file = nil self.file = nil
self.imagePromise.set(.single(image)) self.imagePromise.set(.single(image))
case .animatedImage, .video, .dualVideoReference, .message: case .animatedImage, .video, .dualVideoReference, .message, .gift:
self.file = nil self.file = nil
} }
} }

View File

@ -43,7 +43,9 @@ swift_library(
"//submodules/Components/MultilineTextComponent", "//submodules/Components/MultilineTextComponent",
"//submodules/Components/BundleIconComponent", "//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/LottieComponent",
#"//submodules/TelegramUI/Components/MessageInputPanelComponent", "//submodules/TelegramUI/Components/MessageInputPanelComponent",
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
"//submodules/ChatPresentationInterfaceState",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -40,17 +40,6 @@ public enum ShareControllerPreferredAction {
case custom(action: ShareControllerAction) case custom(action: ShareControllerAction)
} }
public enum ShareControllerExternalStatus {
case preparing(Bool)
case progress(Float)
case done
}
public enum ShareControllerError {
case generic
case fileTooBig(Int64)
}
public struct ShareControllerSegmentedValue { public struct ShareControllerSegmentedValue {
let title: String let title: String
let subject: ShareControllerSubject let subject: ShareControllerSubject
@ -65,17 +54,6 @@ public struct ShareControllerSegmentedValue {
} }
} }
public enum ShareControllerSubject {
case url(String)
case text(String)
case quote(text: String, url: String)
case messages([Message])
case image([ImageRepresentationWithReference])
case media(AnyMediaReference)
case mapMedia(TelegramMediaMap)
case fromExternal(([PeerId], [PeerId: Int64], String, ShareControllerAccountContext, Bool) -> Signal<ShareControllerExternalStatus, ShareControllerError>)
}
private enum ExternalShareItem { private enum ExternalShareItem {
case text(String) case text(String)
case url(URL) case url(URL)

View File

@ -18,7 +18,6 @@ swift_library(
"//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AccountContext:AccountContext", "//submodules/AccountContext:AccountContext",
"//submodules/TelegramUIPreferences:TelegramUIPreferences", "//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/ShareController:ShareController",
"//submodules/StickerResources:StickerResources", "//submodules/StickerResources:StickerResources",
"//submodules/AlertUI:AlertUI", "//submodules/AlertUI:AlertUI",
"//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/PresentationDataUtils:PresentationDataUtils",

View File

@ -7,7 +7,6 @@ import TelegramCore
import SwiftSignalKit import SwiftSignalKit
import TelegramUIPreferences import TelegramUIPreferences
import AccountContext import AccountContext
import ShareController
import StickerResources import StickerResources
import AlertUI import AlertUI
import PresentationDataUtils import PresentationDataUtils
@ -116,15 +115,19 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese
} }
if let stickerPackContentsValue = strongSelf.stickerPackContentsValue, case let .result(info, _, _) = stickerPackContentsValue, !info.shortName.isEmpty { if let stickerPackContentsValue = strongSelf.stickerPackContentsValue, case let .result(info, _, _) = stickerPackContentsValue, !info.shortName.isEmpty {
let shareController = ShareController(context: strongSelf.context, subject: .url("https://t.me/addstickers/\(info.shortName)"), externalShare: true)
let parentNavigationController = strongSelf.parentNavigationController let parentNavigationController = strongSelf.parentNavigationController
shareController.actionCompleted = { [weak parentNavigationController] in let shareController = strongSelf.context.sharedContext.makeShareController(
if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController { context: strongSelf.context,
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } subject: .url("https://t.me/addstickers/\(info.shortName)"),
controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) forceExternal: true,
shareStory: nil,
actionCompleted: { [weak parentNavigationController] in
if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
}
} }
} )
strongSelf.present(shareController, in: .window(.root)) strongSelf.present(shareController, in: .window(.root))
strongSelf.dismiss() strongSelf.dismiss()
} }

View File

@ -13,7 +13,6 @@ import ShimmerEffect
import ContextUI import ContextUI
import MoreButtonNode import MoreButtonNode
import UndoUI import UndoUI
import ShareController
import TextFormat import TextFormat
import PremiumUI import PremiumUI
import OverlayStatusController import OverlayStatusController
@ -1133,13 +1132,18 @@ private final class StickerPackContainer: ASDisplayNode {
if let strongSelf = self { if let strongSelf = self {
let parentNavigationController = strongSelf.controller?.parentNavigationController let parentNavigationController = strongSelf.controller?.parentNavigationController
let shareController = ShareController(context: strongSelf.context, subject: shareSubject) let shareController = strongSelf.context.sharedContext.makeShareController(
shareController.actionCompleted = { [weak parentNavigationController] in context: strongSelf.context,
if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController { subject: shareSubject,
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } forceExternal: false,
controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) shareStory: nil,
actionCompleted: { [weak parentNavigationController] in
if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
}
} }
} )
strongSelf.controller?.present(shareController, in: .window(.root)) strongSelf.controller?.present(shareController, in: .window(.root))
} }
}))) })))

View File

@ -534,6 +534,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1300094593] = { return Api.MediaArea.parse_inputMediaAreaVenue($0) } dict[-1300094593] = { return Api.MediaArea.parse_inputMediaAreaVenue($0) }
dict[1996756655] = { return Api.MediaArea.parse_mediaAreaChannelPost($0) } dict[1996756655] = { return Api.MediaArea.parse_mediaAreaChannelPost($0) }
dict[-891992787] = { return Api.MediaArea.parse_mediaAreaGeoPoint($0) } dict[-891992787] = { return Api.MediaArea.parse_mediaAreaGeoPoint($0) }
dict[1468491885] = { return Api.MediaArea.parse_mediaAreaStarGift($0) }
dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) } dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) }
dict[926421125] = { return Api.MediaArea.parse_mediaAreaUrl($0) } dict[926421125] = { return Api.MediaArea.parse_mediaAreaUrl($0) }
dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) }
@ -909,7 +910,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1301522832] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } dict[1301522832] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
dict[46953416] = { return Api.StarGift.parse_starGift($0) } dict[46953416] = { return Api.StarGift.parse_starGift($0) }
dict[1779697613] = { return Api.StarGift.parse_starGiftUnique($0) } dict[880997154] = { return Api.StarGift.parse_starGiftUnique($0) }
dict[-1809377438] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) } dict[-1809377438] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) }
dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) } dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) }
dict[-1070837941] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) } dict[-1070837941] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) }
@ -1156,6 +1157,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1355547603] = { return Api.WebPageAttribute.parse_webPageAttributeStickerSet($0) } dict[1355547603] = { return Api.WebPageAttribute.parse_webPageAttributeStickerSet($0) }
dict[781501415] = { return Api.WebPageAttribute.parse_webPageAttributeStory($0) } dict[781501415] = { return Api.WebPageAttribute.parse_webPageAttributeStory($0) }
dict[1421174295] = { return Api.WebPageAttribute.parse_webPageAttributeTheme($0) } dict[1421174295] = { return Api.WebPageAttribute.parse_webPageAttributeTheme($0) }
dict[-814781000] = { return Api.WebPageAttribute.parse_webPageAttributeUniqueStarGift($0) }
dict[211046684] = { return Api.WebViewMessageSent.parse_webViewMessageSent($0) } dict[211046684] = { return Api.WebViewMessageSent.parse_webViewMessageSent($0) }
dict[1294139288] = { return Api.WebViewResult.parse_webViewResultUrl($0) } dict[1294139288] = { return Api.WebViewResult.parse_webViewResultUrl($0) }
dict[-1389486888] = { return Api.account.AuthorizationForm.parse_authorizationForm($0) } dict[-1389486888] = { return Api.account.AuthorizationForm.parse_authorizationForm($0) }
@ -1361,6 +1363,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[870003448] = { return Api.messages.TranslatedText.parse_translateResult($0) } dict[870003448] = { return Api.messages.TranslatedText.parse_translateResult($0) }
dict[1218005070] = { return Api.messages.VotesList.parse_votesList($0) } dict[1218005070] = { return Api.messages.VotesList.parse_votesList($0) }
dict[-44166467] = { return Api.messages.WebPage.parse_webPage($0) } dict[-44166467] = { return Api.messages.WebPage.parse_webPage($0) }
dict[-1254192351] = { return Api.messages.WebPagePreview.parse_webPagePreview($0) }
dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) } dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) }
dict[675942550] = { return Api.payments.CheckedGiftCode.parse_checkedGiftCode($0) } dict[675942550] = { return Api.payments.CheckedGiftCode.parse_checkedGiftCode($0) }
dict[-1730811363] = { return Api.payments.ConnectedStarRefBots.parse_connectedStarRefBots($0) } dict[-1730811363] = { return Api.payments.ConnectedStarRefBots.parse_connectedStarRefBots($0) }
@ -1383,6 +1386,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[497778871] = { return Api.payments.StarsRevenueWithdrawalUrl.parse_starsRevenueWithdrawalUrl($0) } dict[497778871] = { return Api.payments.StarsRevenueWithdrawalUrl.parse_starsRevenueWithdrawalUrl($0) }
dict[1822222573] = { return Api.payments.StarsStatus.parse_starsStatus($0) } dict[1822222573] = { return Api.payments.StarsStatus.parse_starsStatus($0) }
dict[-1261053863] = { return Api.payments.SuggestedStarRefBots.parse_suggestedStarRefBots($0) } dict[-1261053863] = { return Api.payments.SuggestedStarRefBots.parse_suggestedStarRefBots($0) }
dict[-895289845] = { return Api.payments.UniqueStarGift.parse_uniqueStarGift($0) }
dict[1801827607] = { return Api.payments.UserStarGifts.parse_userStarGifts($0) } dict[1801827607] = { return Api.payments.UserStarGifts.parse_userStarGifts($0) }
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) } dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
dict[541839704] = { return Api.phone.ExportedGroupCallInvite.parse_exportedGroupCallInvite($0) } dict[541839704] = { return Api.phone.ExportedGroupCallInvite.parse_exportedGroupCallInvite($0) }
@ -2440,6 +2444,8 @@ public extension Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.messages.WebPage: case let _1 as Api.messages.WebPage:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.messages.WebPagePreview:
_1.serialize(buffer, boxed)
case let _1 as Api.payments.BankCardData: case let _1 as Api.payments.BankCardData:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.payments.CheckedGiftCode: case let _1 as Api.payments.CheckedGiftCode:
@ -2472,6 +2478,8 @@ public extension Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.payments.SuggestedStarRefBots: case let _1 as Api.payments.SuggestedStarRefBots:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.payments.UniqueStarGift:
_1.serialize(buffer, boxed)
case let _1 as Api.payments.UserStarGifts: case let _1 as Api.payments.UserStarGifts:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.payments.ValidatedRequestedInfo: case let _1 as Api.payments.ValidatedRequestedInfo:

View File

@ -868,6 +868,7 @@ public extension Api {
case inputMediaAreaVenue(coordinates: Api.MediaAreaCoordinates, queryId: Int64, resultId: String) case inputMediaAreaVenue(coordinates: Api.MediaAreaCoordinates, queryId: Int64, resultId: String)
case mediaAreaChannelPost(coordinates: Api.MediaAreaCoordinates, channelId: Int64, msgId: Int32) case mediaAreaChannelPost(coordinates: Api.MediaAreaCoordinates, channelId: Int64, msgId: Int32)
case mediaAreaGeoPoint(flags: Int32, coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint, address: Api.GeoPointAddress?) case mediaAreaGeoPoint(flags: Int32, coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint, address: Api.GeoPointAddress?)
case mediaAreaStarGift(coordinates: Api.MediaAreaCoordinates, slug: String)
case mediaAreaSuggestedReaction(flags: Int32, coordinates: Api.MediaAreaCoordinates, reaction: Api.Reaction) case mediaAreaSuggestedReaction(flags: Int32, coordinates: Api.MediaAreaCoordinates, reaction: Api.Reaction)
case mediaAreaUrl(coordinates: Api.MediaAreaCoordinates, url: String) case mediaAreaUrl(coordinates: Api.MediaAreaCoordinates, url: String)
case mediaAreaVenue(coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) case mediaAreaVenue(coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String)
@ -908,6 +909,13 @@ public extension Api {
geo.serialize(buffer, true) geo.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {address!.serialize(buffer, true)} if Int(flags) & Int(1 << 0) != 0 {address!.serialize(buffer, true)}
break break
case .mediaAreaStarGift(let coordinates, let slug):
if boxed {
buffer.appendInt32(1468491885)
}
coordinates.serialize(buffer, true)
serializeString(slug, buffer: buffer, boxed: false)
break
case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction): case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction):
if boxed { if boxed {
buffer.appendInt32(340088945) buffer.appendInt32(340088945)
@ -957,6 +965,8 @@ public extension Api {
return ("mediaAreaChannelPost", [("coordinates", coordinates as Any), ("channelId", channelId as Any), ("msgId", msgId as Any)]) return ("mediaAreaChannelPost", [("coordinates", coordinates as Any), ("channelId", channelId as Any), ("msgId", msgId as Any)])
case .mediaAreaGeoPoint(let flags, let coordinates, let geo, let address): case .mediaAreaGeoPoint(let flags, let coordinates, let geo, let address):
return ("mediaAreaGeoPoint", [("flags", flags as Any), ("coordinates", coordinates as Any), ("geo", geo as Any), ("address", address as Any)]) return ("mediaAreaGeoPoint", [("flags", flags as Any), ("coordinates", coordinates as Any), ("geo", geo as Any), ("address", address as Any)])
case .mediaAreaStarGift(let coordinates, let slug):
return ("mediaAreaStarGift", [("coordinates", coordinates as Any), ("slug", slug as Any)])
case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction): case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction):
return ("mediaAreaSuggestedReaction", [("flags", flags as Any), ("coordinates", coordinates as Any), ("reaction", reaction as Any)]) return ("mediaAreaSuggestedReaction", [("flags", flags as Any), ("coordinates", coordinates as Any), ("reaction", reaction as Any)])
case .mediaAreaUrl(let coordinates, let url): case .mediaAreaUrl(let coordinates, let url):
@ -1053,6 +1063,22 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_mediaAreaStarGift(_ reader: BufferReader) -> MediaArea? {
var _1: Api.MediaAreaCoordinates?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates
}
var _2: String?
_2 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MediaArea.mediaAreaStarGift(coordinates: _1!, slug: _2!)
}
else {
return nil
}
}
public static func parse_mediaAreaSuggestedReaction(_ reader: BufferReader) -> MediaArea? { public static func parse_mediaAreaSuggestedReaction(_ reader: BufferReader) -> MediaArea? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _1 = reader.readInt32()

View File

@ -575,7 +575,7 @@ public extension Api {
public extension Api { public extension Api {
enum StarGift: TypeConstructorDescription { enum StarGift: TypeConstructorDescription {
case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?) case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?)
case starGiftUnique(id: Int64, title: String, num: Int32, ownerId: Int64, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32) case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Int64?, ownerName: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -594,14 +594,17 @@ public extension Api {
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(lastSaleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(lastSaleDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 3) != 0 {serializeInt64(upgradeStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeInt64(upgradeStars!, buffer: buffer, boxed: false)}
break break
case .starGiftUnique(let id, let title, let num, let ownerId, let attributes, let availabilityIssued, let availabilityTotal): case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let attributes, let availabilityIssued, let availabilityTotal):
if boxed { if boxed {
buffer.appendInt32(1779697613) buffer.appendInt32(880997154)
} }
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false)
serializeString(slug, buffer: buffer, boxed: false)
serializeInt32(num, buffer: buffer, boxed: false) serializeInt32(num, buffer: buffer, boxed: false)
serializeInt64(ownerId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt64(ownerId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(ownerName!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
buffer.appendInt32(Int32(attributes.count)) buffer.appendInt32(Int32(attributes.count))
for item in attributes { for item in attributes {
@ -617,8 +620,8 @@ public extension Api {
switch self { switch self {
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars): case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars):
return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any)]) return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any)])
case .starGiftUnique(let id, let title, let num, let ownerId, let attributes, let availabilityIssued, let availabilityTotal): case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let attributes, let availabilityIssued, let availabilityTotal):
return ("starGiftUnique", [("id", id as Any), ("title", title as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any)]) return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any)])
} }
} }
@ -663,31 +666,40 @@ public extension Api {
} }
} }
public static func parse_starGiftUnique(_ reader: BufferReader) -> StarGift? { public static func parse_starGiftUnique(_ reader: BufferReader) -> StarGift? {
var _1: Int64? var _1: Int32?
_1 = reader.readInt64() _1 = reader.readInt32()
var _2: String? var _2: Int64?
_2 = parseString(reader) _2 = reader.readInt64()
var _3: Int32? var _3: String?
_3 = reader.readInt32() _3 = parseString(reader)
var _4: Int64? var _4: String?
_4 = reader.readInt64() _4 = parseString(reader)
var _5: [Api.StarGiftAttribute]? var _5: Int32?
_5 = reader.readInt32()
var _6: Int64?
if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt64() }
var _7: String?
if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) }
var _8: [Api.StarGiftAttribute]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self)
} }
var _6: Int32? var _9: Int32?
_6 = reader.readInt32() _9 = reader.readInt32()
var _7: Int32? var _10: Int32?
_7 = reader.readInt32() _10 = reader.readInt32()
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
let _c4 = _4 != nil let _c4 = _4 != nil
let _c5 = _5 != nil let _c5 = _5 != nil
let _c6 = _6 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
let _c7 = _7 != nil let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { let _c8 = _8 != nil
return Api.StarGift.starGiftUnique(id: _1!, title: _2!, num: _3!, ownerId: _4!, attributes: _5!, availabilityIssued: _6!, availabilityTotal: _7!) let _c9 = _9 != nil
let _c10 = _10 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, title: _3!, slug: _4!, num: _5!, ownerId: _6, ownerName: _7, attributes: _8!, availabilityIssued: _9!, availabilityTotal: _10!)
} }
else { else {
return nil return nil

View File

@ -207,6 +207,7 @@ public extension Api {
case webPageAttributeStickerSet(flags: Int32, stickers: [Api.Document]) case webPageAttributeStickerSet(flags: Int32, stickers: [Api.Document])
case webPageAttributeStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?) case webPageAttributeStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?)
case webPageAttributeTheme(flags: Int32, documents: [Api.Document]?, settings: Api.ThemeSettings?) case webPageAttributeTheme(flags: Int32, documents: [Api.Document]?, settings: Api.ThemeSettings?)
case webPageAttributeUniqueStarGift(gift: Api.StarGift)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -242,6 +243,12 @@ public extension Api {
}} }}
if Int(flags) & Int(1 << 1) != 0 {settings!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {settings!.serialize(buffer, true)}
break break
case .webPageAttributeUniqueStarGift(let gift):
if boxed {
buffer.appendInt32(-814781000)
}
gift.serialize(buffer, true)
break
} }
} }
@ -253,6 +260,8 @@ public extension Api {
return ("webPageAttributeStory", [("flags", flags as Any), ("peer", peer as Any), ("id", id as Any), ("story", story as Any)]) return ("webPageAttributeStory", [("flags", flags as Any), ("peer", peer as Any), ("id", id as Any), ("story", story as Any)])
case .webPageAttributeTheme(let flags, let documents, let settings): case .webPageAttributeTheme(let flags, let documents, let settings):
return ("webPageAttributeTheme", [("flags", flags as Any), ("documents", documents as Any), ("settings", settings as Any)]) return ("webPageAttributeTheme", [("flags", flags as Any), ("documents", documents as Any), ("settings", settings as Any)])
case .webPageAttributeUniqueStarGift(let gift):
return ("webPageAttributeUniqueStarGift", [("gift", gift as Any)])
} }
} }
@ -317,6 +326,19 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_webPageAttributeUniqueStarGift(_ reader: BufferReader) -> WebPageAttribute? {
var _1: Api.StarGift?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StarGift
}
let _c1 = _1 != nil
if _c1 {
return Api.WebPageAttribute.webPageAttributeUniqueStarGift(gift: _1!)
}
else {
return nil
}
}
} }
} }

View File

@ -744,6 +744,54 @@ public extension Api.messages {
} }
} }
public extension Api.messages {
indirect enum WebPagePreview: TypeConstructorDescription {
case webPagePreview(media: Api.MessageMedia, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .webPagePreview(let media, let users):
if boxed {
buffer.appendInt32(-1254192351)
}
media.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .webPagePreview(let media, let users):
return ("webPagePreview", [("media", media as Any), ("users", users as Any)])
}
}
public static func parse_webPagePreview(_ reader: BufferReader) -> WebPagePreview? {
var _1: Api.MessageMedia?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.MessageMedia
}
var _2: [Api.User]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.messages.WebPagePreview.webPagePreview(media: _1!, users: _2!)
}
else {
return nil
}
}
}
}
public extension Api.payments { public extension Api.payments {
enum BankCardData: TypeConstructorDescription { enum BankCardData: TypeConstructorDescription {
case bankCardData(title: String, openUrls: [Api.BankCardOpenUrl]) case bankCardData(title: String, openUrls: [Api.BankCardOpenUrl])
@ -1538,45 +1586,3 @@ public extension Api.payments {
} }
} }
public extension Api.payments {
enum StarGiftUpgradePreview: TypeConstructorDescription {
case starGiftUpgradePreview(sampleAttributes: [Api.StarGiftAttribute])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starGiftUpgradePreview(let sampleAttributes):
if boxed {
buffer.appendInt32(377215243)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(sampleAttributes.count))
for item in sampleAttributes {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starGiftUpgradePreview(let sampleAttributes):
return ("starGiftUpgradePreview", [("sampleAttributes", sampleAttributes as Any)])
}
}
public static func parse_starGiftUpgradePreview(_ reader: BufferReader) -> StarGiftUpgradePreview? {
var _1: [Api.StarGiftAttribute]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self)
}
let _c1 = _1 != nil
if _c1 {
return Api.payments.StarGiftUpgradePreview.starGiftUpgradePreview(sampleAttributes: _1!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,45 @@
public extension Api.payments {
enum StarGiftUpgradePreview: TypeConstructorDescription {
case starGiftUpgradePreview(sampleAttributes: [Api.StarGiftAttribute])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starGiftUpgradePreview(let sampleAttributes):
if boxed {
buffer.appendInt32(377215243)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(sampleAttributes.count))
for item in sampleAttributes {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starGiftUpgradePreview(let sampleAttributes):
return ("starGiftUpgradePreview", [("sampleAttributes", sampleAttributes as Any)])
}
}
public static func parse_starGiftUpgradePreview(_ reader: BufferReader) -> StarGiftUpgradePreview? {
var _1: [Api.StarGiftAttribute]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self)
}
let _c1 = _1 != nil
if _c1 {
return Api.payments.StarGiftUpgradePreview.starGiftUpgradePreview(sampleAttributes: _1!)
}
else {
return nil
}
}
}
}
public extension Api.payments { public extension Api.payments {
enum StarGifts: TypeConstructorDescription { enum StarGifts: TypeConstructorDescription {
case starGifts(hash: Int32, gifts: [Api.StarGift]) case starGifts(hash: Int32, gifts: [Api.StarGift])
@ -334,6 +376,54 @@ public extension Api.payments {
} }
} }
public extension Api.payments {
enum UniqueStarGift: TypeConstructorDescription {
case uniqueStarGift(gift: Api.StarGift, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .uniqueStarGift(let gift, let users):
if boxed {
buffer.appendInt32(-895289845)
}
gift.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .uniqueStarGift(let gift, let users):
return ("uniqueStarGift", [("gift", gift as Any), ("users", users as Any)])
}
}
public static func parse_uniqueStarGift(_ reader: BufferReader) -> UniqueStarGift? {
var _1: Api.StarGift?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StarGift
}
var _2: [Api.User]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.payments.UniqueStarGift.uniqueStarGift(gift: _1!, users: _2!)
}
else {
return nil
}
}
}
}
public extension Api.payments { public extension Api.payments {
enum UserStarGifts: TypeConstructorDescription { enum UserStarGifts: TypeConstructorDescription {
case userStarGifts(flags: Int32, count: Int32, gifts: [Api.UserStarGift], nextOffset: String?, users: [Api.User]) case userStarGifts(flags: Int32, count: Int32, gifts: [Api.UserStarGift], nextOffset: String?, users: [Api.User])
@ -1776,121 +1866,3 @@ public extension Api.stats {
} }
} }
public extension Api.stats {
enum PublicForwards: TypeConstructorDescription {
case publicForwards(flags: Int32, count: Int32, forwards: [Api.PublicForward], nextOffset: String?, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users):
if boxed {
buffer.appendInt32(-1828487648)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(forwards.count))
for item in forwards {
item.serialize(buffer, true)
}
if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users):
return ("publicForwards", [("flags", flags as Any), ("count", count as Any), ("forwards", forwards as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_publicForwards(_ reader: BufferReader) -> PublicForwards? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: [Api.PublicForward]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PublicForward.self)
}
var _4: String?
if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) }
var _5: [Api.Chat]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _6: [Api.User]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.stats.PublicForwards.publicForwards(flags: _1!, count: _2!, forwards: _3!, nextOffset: _4, chats: _5!, users: _6!)
}
else {
return nil
}
}
}
}
public extension Api.stats {
enum StoryStats: TypeConstructorDescription {
case storyStats(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .storyStats(let viewsGraph, let reactionsByEmotionGraph):
if boxed {
buffer.appendInt32(1355613820)
}
viewsGraph.serialize(buffer, true)
reactionsByEmotionGraph.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .storyStats(let viewsGraph, let reactionsByEmotionGraph):
return ("storyStats", [("viewsGraph", viewsGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any)])
}
}
public static func parse_storyStats(_ reader: BufferReader) -> StoryStats? {
var _1: Api.StatsGraph?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
var _2: Api.StatsGraph?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.stats.StoryStats.storyStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,121 @@
public extension Api.stats {
enum PublicForwards: TypeConstructorDescription {
case publicForwards(flags: Int32, count: Int32, forwards: [Api.PublicForward], nextOffset: String?, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users):
if boxed {
buffer.appendInt32(-1828487648)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(forwards.count))
for item in forwards {
item.serialize(buffer, true)
}
if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users):
return ("publicForwards", [("flags", flags as Any), ("count", count as Any), ("forwards", forwards as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_publicForwards(_ reader: BufferReader) -> PublicForwards? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: [Api.PublicForward]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PublicForward.self)
}
var _4: String?
if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) }
var _5: [Api.Chat]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _6: [Api.User]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.stats.PublicForwards.publicForwards(flags: _1!, count: _2!, forwards: _3!, nextOffset: _4, chats: _5!, users: _6!)
}
else {
return nil
}
}
}
}
public extension Api.stats {
enum StoryStats: TypeConstructorDescription {
case storyStats(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .storyStats(let viewsGraph, let reactionsByEmotionGraph):
if boxed {
buffer.appendInt32(1355613820)
}
viewsGraph.serialize(buffer, true)
reactionsByEmotionGraph.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .storyStats(let viewsGraph, let reactionsByEmotionGraph):
return ("storyStats", [("viewsGraph", viewsGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any)])
}
}
public static func parse_storyStats(_ reader: BufferReader) -> StoryStats? {
var _1: Api.StatsGraph?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
var _2: Api.StatsGraph?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.stats.StoryStats.storyStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!)
}
else {
return nil
}
}
}
}
public extension Api.stickers { public extension Api.stickers {
enum SuggestedShortName: TypeConstructorDescription { enum SuggestedShortName: TypeConstructorDescription {
case suggestedShortName(shortName: String) case suggestedShortName(shortName: String)
@ -1276,57 +1394,3 @@ public extension Api.upload {
} }
} }
public extension Api.upload {
enum WebFile: TypeConstructorDescription {
case webFile(size: Int32, mimeType: String, fileType: Api.storage.FileType, mtime: Int32, bytes: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .webFile(let size, let mimeType, let fileType, let mtime, let bytes):
if boxed {
buffer.appendInt32(568808380)
}
serializeInt32(size, buffer: buffer, boxed: false)
serializeString(mimeType, buffer: buffer, boxed: false)
fileType.serialize(buffer, true)
serializeInt32(mtime, buffer: buffer, boxed: false)
serializeBytes(bytes, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .webFile(let size, let mimeType, let fileType, let mtime, let bytes):
return ("webFile", [("size", size as Any), ("mimeType", mimeType as Any), ("fileType", fileType as Any), ("mtime", mtime as Any), ("bytes", bytes as Any)])
}
}
public static func parse_webFile(_ reader: BufferReader) -> WebFile? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: Api.storage.FileType?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.storage.FileType
}
var _4: Int32?
_4 = reader.readInt32()
var _5: Buffer?
_5 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.upload.WebFile.webFile(size: _1!, mimeType: _2!, fileType: _3!, mtime: _4!, bytes: _5!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,57 @@
public extension Api.upload {
enum WebFile: TypeConstructorDescription {
case webFile(size: Int32, mimeType: String, fileType: Api.storage.FileType, mtime: Int32, bytes: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .webFile(let size, let mimeType, let fileType, let mtime, let bytes):
if boxed {
buffer.appendInt32(568808380)
}
serializeInt32(size, buffer: buffer, boxed: false)
serializeString(mimeType, buffer: buffer, boxed: false)
fileType.serialize(buffer, true)
serializeInt32(mtime, buffer: buffer, boxed: false)
serializeBytes(bytes, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .webFile(let size, let mimeType, let fileType, let mtime, let bytes):
return ("webFile", [("size", size as Any), ("mimeType", mimeType as Any), ("fileType", fileType as Any), ("mtime", mtime as Any), ("bytes", bytes as Any)])
}
}
public static func parse_webFile(_ reader: BufferReader) -> WebFile? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: Api.storage.FileType?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.storage.FileType
}
var _4: Int32?
_4 = reader.readInt32()
var _5: Buffer?
_5 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.upload.WebFile.webFile(size: _1!, mimeType: _2!, fileType: _3!, mtime: _4!, bytes: _5!)
}
else {
return nil
}
}
}
}
public extension Api.users { public extension Api.users {
enum UserFull: TypeConstructorDescription { enum UserFull: TypeConstructorDescription {
case userFull(fullUser: Api.UserFull, chats: [Api.Chat], users: [Api.User]) case userFull(fullUser: Api.UserFull, chats: [Api.Chat], users: [Api.User])

View File

@ -2383,12 +2383,11 @@ public extension Api.functions.bots {
} }
} }
public extension Api.functions.bots { public extension Api.functions.bots {
static func getBotRecommendations(flags: Int32, bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.users.Users>) { static func getBotRecommendations(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.users.Users>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(676707937) buffer.appendInt32(-1581840363)
serializeInt32(flags, buffer: buffer, boxed: false)
bot.serialize(buffer, true) bot.serialize(buffer, true)
return (FunctionDescription(name: "bots.getBotRecommendations", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.Users? in return (FunctionDescription(name: "bots.getBotRecommendations", parameters: [("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.Users? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.users.Users? var result: Api.users.Users?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -7063,9 +7062,9 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func getWebPagePreview(flags: Int32, message: String, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.MessageMedia>) { static func getWebPagePreview(flags: Int32, message: String, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.WebPagePreview>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1956073268) buffer.appendInt32(1460498287)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(message, buffer: buffer, boxed: false) serializeString(message, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
@ -7073,11 +7072,11 @@ public extension Api.functions.messages {
for item in entities! { for item in entities! {
item.serialize(buffer, true) item.serialize(buffer, true)
}} }}
return (FunctionDescription(name: "messages.getWebPagePreview", parameters: [("flags", String(describing: flags)), ("message", String(describing: message)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in return (FunctionDescription(name: "messages.getWebPagePreview", parameters: [("flags", String(describing: flags)), ("message", String(describing: message)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.WebPagePreview? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.MessageMedia? var result: Api.messages.WebPagePreview?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.MessageMedia result = Api.parse(reader, signature: signature) as? Api.messages.WebPagePreview
} }
return result return result
}) })
@ -9479,6 +9478,21 @@ public extension Api.functions.payments {
}) })
} }
} }
public extension Api.functions.payments {
static func getUniqueStarGift(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.UniqueStarGift>) {
let buffer = Buffer()
buffer.appendInt32(-1583919758)
serializeString(slug, buffer: buffer, boxed: false)
return (FunctionDescription(name: "payments.getUniqueStarGift", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.UniqueStarGift? in
let reader = BufferReader(buffer)
var result: Api.payments.UniqueStarGift?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.payments.UniqueStarGift
}
return result
})
}
}
public extension Api.functions.payments { public extension Api.functions.payments {
static func getUserStarGift(msgId: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.UserStarGifts>) { static func getUserStarGift(msgId: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.UserStarGifts>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -542,6 +542,8 @@ func mediaAreaFromApiMediaArea(_ mediaArea: Api.MediaArea) -> MediaArea? {
return .channelMessage(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), messageId: EngineMessage.Id(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId)) return .channelMessage(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), messageId: EngineMessage.Id(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId))
case let .mediaAreaWeather(coordinates, emoji, temperatureC, color): case let .mediaAreaWeather(coordinates, emoji, temperatureC, color):
return .weather(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), emoji: emoji, temperature: temperatureC, color: color) return .weather(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), emoji: emoji, temperature: temperatureC, color: color)
case let .mediaAreaStarGift(coordinates, slug):
return .starGift(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), slug: slug)
} }
} }
@ -596,6 +598,8 @@ func apiMediaAreasFromMediaAreas(_ mediaAreas: [MediaArea], transaction: Transac
apiMediaAreas.append(.mediaAreaUrl(coordinates: inputCoordinates, url: url)) apiMediaAreas.append(.mediaAreaUrl(coordinates: inputCoordinates, url: url))
case let .weather(_, emoji, temperature, color): case let .weather(_, emoji, temperature, color):
apiMediaAreas.append(.mediaAreaWeather(coordinates: inputCoordinates, emoji: emoji, temperatureC: temperature, color: color)) apiMediaAreas.append(.mediaAreaWeather(coordinates: inputCoordinates, emoji: emoji, temperatureC: temperature, color: color))
case let .starGift(_, slug):
apiMediaAreas.append(.mediaAreaStarGift(coordinates: inputCoordinates, slug: slug))
} }
} }
return apiMediaAreas return apiMediaAreas

View File

@ -23,6 +23,11 @@ func telegramMediaWebpageAttributeFromApiWebpageAttribute(_ attribute: Api.WebPa
var files: [TelegramMediaFile] = [] var files: [TelegramMediaFile] = []
files = stickers.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: []) } files = stickers.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: []) }
return .stickerPack(TelegramMediaWebpageStickerPackAttribute(flags: flags, files: files)) return .stickerPack(TelegramMediaWebpageStickerPackAttribute(flags: flags, files: files))
case let .webPageAttributeUniqueStarGift(gift):
if let starGift = StarGift(apiStarGift: gift) {
return .starGift(TelegramMediaWebpageStarGiftAttribute(gift: starGift))
}
return nil
case .webPageAttributeStory: case .webPageAttributeStory:
return nil return nil
} }

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization { public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt { public func currentLayer() -> UInt {
return 196 return 197
} }
public func parseMessage(_ data: Data!) -> Any! { public func parseMessage(_ data: Data!) -> Any! {

View File

@ -4,12 +4,14 @@ private enum TelegramMediaWebpageAttributeTypes: Int32 {
case unsupported case unsupported
case theme case theme
case stickerPack case stickerPack
case starGift
} }
public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable { public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable {
case unsupported case unsupported
case theme(TelegraMediaWebpageThemeAttribute) case theme(TelegraMediaWebpageThemeAttribute)
case stickerPack(TelegramMediaWebpageStickerPackAttribute) case stickerPack(TelegramMediaWebpageStickerPackAttribute)
case starGift(TelegramMediaWebpageStarGiftAttribute)
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("r", orElse: 0) { switch decoder.decodeInt32ForKey("r", orElse: 0) {
@ -17,6 +19,8 @@ public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable {
self = .theme(decoder.decodeObjectForKey("a", decoder: { TelegraMediaWebpageThemeAttribute(decoder: $0) }) as! TelegraMediaWebpageThemeAttribute) self = .theme(decoder.decodeObjectForKey("a", decoder: { TelegraMediaWebpageThemeAttribute(decoder: $0) }) as! TelegraMediaWebpageThemeAttribute)
case TelegramMediaWebpageAttributeTypes.stickerPack.rawValue: case TelegramMediaWebpageAttributeTypes.stickerPack.rawValue:
self = .stickerPack(decoder.decodeObjectForKey("a", decoder: { TelegramMediaWebpageStickerPackAttribute(decoder: $0) }) as! TelegramMediaWebpageStickerPackAttribute) self = .stickerPack(decoder.decodeObjectForKey("a", decoder: { TelegramMediaWebpageStickerPackAttribute(decoder: $0) }) as! TelegramMediaWebpageStickerPackAttribute)
case TelegramMediaWebpageAttributeTypes.starGift.rawValue:
self = .starGift(decoder.decodeObjectForKey("a", decoder: { TelegramMediaWebpageStarGiftAttribute(decoder: $0) }) as! TelegramMediaWebpageStarGiftAttribute)
default: default:
self = .unsupported self = .unsupported
} }
@ -32,6 +36,9 @@ public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable {
case let .stickerPack(attribute): case let .stickerPack(attribute):
encoder.encodeInt32(TelegramMediaWebpageAttributeTypes.stickerPack.rawValue, forKey: "r") encoder.encodeInt32(TelegramMediaWebpageAttributeTypes.stickerPack.rawValue, forKey: "r")
encoder.encodeObject(attribute, forKey: "a") encoder.encodeObject(attribute, forKey: "a")
case let .starGift(attribute):
encoder.encodeInt32(TelegramMediaWebpageAttributeTypes.starGift.rawValue, forKey: "r")
encoder.encodeObject(attribute, forKey: "a")
} }
} }
} }
@ -127,6 +134,29 @@ public final class TelegramMediaWebpageStickerPackAttribute: PostboxCoding, Equa
} }
} }
public final class TelegramMediaWebpageStarGiftAttribute: PostboxCoding, Equatable {
public static func == (lhs: TelegramMediaWebpageStarGiftAttribute, rhs: TelegramMediaWebpageStarGiftAttribute) -> Bool {
if lhs.gift != rhs.gift {
return false
}
return true
}
public let gift: StarGift
public init(gift: StarGift) {
self.gift = gift
}
public init(decoder: PostboxDecoder) {
self.gift = decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObject(self.gift, forKey: "gift")
}
}
public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
public let url: String public let url: String
public let displayUrl: String public let displayUrl: String

View File

@ -152,6 +152,7 @@ public enum MediaArea: Codable, Equatable {
case channelMessage(coordinates: Coordinates, messageId: EngineMessage.Id) case channelMessage(coordinates: Coordinates, messageId: EngineMessage.Id)
case link(coordinates: Coordinates, url: String) case link(coordinates: Coordinates, url: String)
case weather(coordinates: Coordinates, emoji: String, temperature: Double, color: Int32) case weather(coordinates: Coordinates, emoji: String, temperature: Double, color: Int32)
case starGift(coordinates: Coordinates, slug: String)
public struct ReactionFlags: OptionSet { public struct ReactionFlags: OptionSet {
public var rawValue: Int32 public var rawValue: Int32
@ -174,6 +175,7 @@ public enum MediaArea: Codable, Equatable {
case channelMessage case channelMessage
case link case link
case weather case weather
case starGift
} }
public enum DecodingError: Error { public enum DecodingError: Error {
@ -210,6 +212,10 @@ public enum MediaArea: Codable, Equatable {
let temperature = try container.decode(Double.self, forKey: .temperature) let temperature = try container.decode(Double.self, forKey: .temperature)
let color = try container.decodeIfPresent(Int32.self, forKey: .color) ?? 0 let color = try container.decodeIfPresent(Int32.self, forKey: .color) ?? 0
self = .weather(coordinates: coordinates, emoji: emoji, temperature: temperature, color: color) self = .weather(coordinates: coordinates, emoji: emoji, temperature: temperature, color: color)
case .starGift:
let coordinates = try container.decode(MediaArea.Coordinates.self, forKey: .coordinates)
let slug = try container.decode(String.self, forKey: .value)
self = .starGift(coordinates: coordinates, slug: slug)
} }
} }
@ -240,6 +246,10 @@ public enum MediaArea: Codable, Equatable {
try container.encode(emoji, forKey: .value) try container.encode(emoji, forKey: .value)
try container.encode(temperature, forKey: .temperature) try container.encode(temperature, forKey: .temperature)
try container.encode(color, forKey: .color) try container.encode(color, forKey: .color)
case let .starGift(coordinates, slug):
try container.encode(MediaAreaType.starGift.rawValue, forKey: .type)
try container.encode(coordinates, forKey: .coordinates)
try container.encode(slug, forKey: .value)
} }
} }
} }
@ -257,6 +267,8 @@ public extension MediaArea {
return coordinates return coordinates
case let .weather(coordinates, _, _, _): case let .weather(coordinates, _, _, _):
return coordinates return coordinates
case let .starGift(coordinates, _):
return coordinates
} }
} }
} }

View File

@ -204,7 +204,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
case id case id
case title case title
case number case number
case slug
case ownerPeerId case ownerPeerId
case ownerName
case attributes case attributes
case availability case availability
} }
@ -428,6 +430,11 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
encoder.encodeInt32(self.total, forKey: CodingKeys.total.rawValue) encoder.encodeInt32(self.total, forKey: CodingKeys.total.rawValue)
} }
} }
public enum Owner: Equatable {
case peerId(EnginePeer.Id)
case name(String)
}
public enum DecodingError: Error { public enum DecodingError: Error {
case generic case generic
@ -436,15 +443,17 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
public let id: Int64 public let id: Int64
public let title: String public let title: String
public let number: Int32 public let number: Int32
public let ownerPeerId: EnginePeer.Id public let slug: String
public let owner: Owner
public let attributes: [Attribute] public let attributes: [Attribute]
public let availability: Availability public let availability: Availability
public init(id: Int64, title: String, number: Int32, ownerPeerId: EnginePeer.Id, attributes: [Attribute], availability: Availability) { public init(id: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability) {
self.id = id self.id = id
self.title = title self.title = title
self.number = number self.number = number
self.ownerPeerId = ownerPeerId self.slug = slug
self.owner = owner
self.attributes = attributes self.attributes = attributes
self.availability = availability self.availability = availability
} }
@ -454,7 +463,14 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
self.id = try container.decode(Int64.self, forKey: .id) self.id = try container.decode(Int64.self, forKey: .id)
self.title = try container.decode(String.self, forKey: .title) self.title = try container.decode(String.self, forKey: .title)
self.number = try container.decode(Int32.self, forKey: .number) self.number = try container.decode(Int32.self, forKey: .number)
self.ownerPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(try container.decode(Int64.self, forKey: .ownerPeerId))) self.slug = try container.decodeIfPresent(String.self, forKey: .slug) ?? ""
if let ownerId = try container.decodeIfPresent(Int64.self, forKey: .ownerPeerId) {
self.owner = .peerId(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(ownerId)))
} else if let ownerName = try container.decodeIfPresent(String.self, forKey: .ownerName) {
self.owner = .name(ownerName)
} else {
self.owner = .name("Unknown")
}
self.attributes = try container.decode([UniqueGift.Attribute].self, forKey: .attributes) self.attributes = try container.decode([UniqueGift.Attribute].self, forKey: .attributes)
self.availability = try container.decode(UniqueGift.Availability.self, forKey: .availability) self.availability = try container.decode(UniqueGift.Availability.self, forKey: .availability)
} }
@ -463,7 +479,14 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
self.id = decoder.decodeInt64ForKey(CodingKeys.id.rawValue, orElse: 0) self.id = decoder.decodeInt64ForKey(CodingKeys.id.rawValue, orElse: 0)
self.title = decoder.decodeStringForKey(CodingKeys.title.rawValue, orElse: "") self.title = decoder.decodeStringForKey(CodingKeys.title.rawValue, orElse: "")
self.number = decoder.decodeInt32ForKey(CodingKeys.number.rawValue, orElse: 0) self.number = decoder.decodeInt32ForKey(CodingKeys.number.rawValue, orElse: 0)
self.ownerPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(decoder.decodeInt64ForKey(CodingKeys.ownerPeerId.rawValue, orElse: 0))) self.slug = decoder.decodeStringForKey(CodingKeys.slug.rawValue, orElse: "")
if let ownerId = decoder.decodeOptionalInt64ForKey(CodingKeys.ownerPeerId.rawValue) {
self.owner = .peerId(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(ownerId)))
} else if let ownerName = decoder.decodeOptionalStringForKey(CodingKeys.ownerName.rawValue) {
self.owner = .name(ownerName)
} else {
self.owner = .name("Unknown")
}
self.attributes = (try? decoder.decodeObjectArrayWithCustomDecoderForKey(CodingKeys.attributes.rawValue, decoder: { UniqueGift.Attribute(decoder: $0) })) ?? [] self.attributes = (try? decoder.decodeObjectArrayWithCustomDecoderForKey(CodingKeys.attributes.rawValue, decoder: { UniqueGift.Attribute(decoder: $0) })) ?? []
self.availability = decoder.decodeObjectForKey(CodingKeys.availability.rawValue, decoder: { UniqueGift.Availability(decoder: $0) }) as! UniqueGift.Availability self.availability = decoder.decodeObjectForKey(CodingKeys.availability.rawValue, decoder: { UniqueGift.Availability(decoder: $0) }) as! UniqueGift.Availability
} }
@ -473,7 +496,13 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
try container.encode(self.id, forKey: .id) try container.encode(self.id, forKey: .id)
try container.encode(self.title, forKey: .title) try container.encode(self.title, forKey: .title)
try container.encode(self.number, forKey: .number) try container.encode(self.number, forKey: .number)
try container.encode(self.ownerPeerId.id._internalGetInt64Value(), forKey: .ownerPeerId) try container.encode(self.slug, forKey: .slug)
switch self.owner {
case let .peerId(peerId):
try container.encode(peerId.id._internalGetInt64Value(), forKey: .ownerPeerId)
case let .name(name):
try container.encode(name, forKey: .ownerName)
}
try container.encode(self.attributes, forKey: .attributes) try container.encode(self.attributes, forKey: .attributes)
try container.encode(self.availability, forKey: .availability) try container.encode(self.availability, forKey: .availability)
} }
@ -482,7 +511,13 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
encoder.encodeInt64(self.id, forKey: CodingKeys.id.rawValue) encoder.encodeInt64(self.id, forKey: CodingKeys.id.rawValue)
encoder.encodeString(self.title, forKey: CodingKeys.title.rawValue) encoder.encodeString(self.title, forKey: CodingKeys.title.rawValue)
encoder.encodeInt32(self.number, forKey: CodingKeys.number.rawValue) encoder.encodeInt32(self.number, forKey: CodingKeys.number.rawValue)
encoder.encodeInt64(self.ownerPeerId.id._internalGetInt64Value(), forKey: CodingKeys.ownerPeerId.rawValue) encoder.encodeString(self.slug, forKey: CodingKeys.slug.rawValue)
switch self.owner {
case let .peerId(peerId):
encoder.encodeInt64(peerId.id._internalGetInt64Value(), forKey: CodingKeys.ownerPeerId.rawValue)
case let .name(name):
encoder.encodeString(name, forKey: CodingKeys.ownerName.rawValue)
}
encoder.encodeObjectArray(self.attributes, forKey: CodingKeys.attributes.rawValue) encoder.encodeObjectArray(self.attributes, forKey: CodingKeys.attributes.rawValue)
encoder.encodeObject(self.availability, forKey: CodingKeys.availability.rawValue) encoder.encodeObject(self.availability, forKey: CodingKeys.availability.rawValue)
} }
@ -570,8 +605,16 @@ extension StarGift {
return nil return nil
} }
self = .generic(StarGift.Gift(id: id, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars)) self = .generic(StarGift.Gift(id: id, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars))
case let .starGiftUnique(id, title, num, ownerId, attributes, availabilityIssued, availabilityTotal): case let .starGiftUnique(_, id, title, slug, num, ownerId, ownerName, attributes, availabilityIssued, availabilityTotal):
self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, ownerPeerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(ownerId)), attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal))) let owner: StarGift.UniqueGift.Owner
if let ownerId {
owner = .peerId(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(ownerId)))
} else if let ownerName {
owner = .name(ownerName)
} else {
return nil
}
self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal)))
} }
} }
} }
@ -1311,3 +1354,29 @@ extension StarGift.UniqueGift.Attribute {
} }
} }
} }
func _internal_getUniqueStarGift(account: Account, slug: String) -> Signal<StarGift.UniqueGift?, NoError> {
return account.network.request(Api.functions.payments.getUniqueStarGift(slug: slug))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.payments.UniqueStarGift?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<StarGift.UniqueGift?, NoError> in
if let result = result {
switch result {
case let .uniqueStarGift(gift, users):
return account.postbox.transaction { transaction in
let parsedPeers = AccumulatedPeers(users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
guard case let .unique(uniqueGift) = StarGift(apiStarGift: gift) else {
return nil
}
return uniqueGift
}
}
} else {
return .single(nil)
}
}
}

View File

@ -132,5 +132,9 @@ public extension TelegramEngine {
public func starGiftUpgradePreview(giftId: Int64) -> Signal<[StarGift.UniqueGift.Attribute], NoError> { public func starGiftUpgradePreview(giftId: Int64) -> Signal<[StarGift.UniqueGift.Attribute], NoError> {
return _internal_starGiftUpgradePreview(account: self.account, giftId: giftId) return _internal_starGiftUpgradePreview(account: self.account, giftId: giftId)
} }
public func getUniqueStarGift(slug: String) -> Signal<StarGift.UniqueGift?, NoError> {
return _internal_getUniqueStarGift(account: self.account, slug: slug)
}
} }
} }

View File

@ -155,8 +155,8 @@ public func webpagePreviewWithProgress(account: Account, urls: [String], webpage
} }
return account.network.requestWithAdditionalInfo(Api.functions.messages.getWebPagePreview(flags: 0, message: urls.joined(separator: " "), entities: nil), info: .progress) return account.network.requestWithAdditionalInfo(Api.functions.messages.getWebPagePreview(flags: 0, message: urls.joined(separator: " "), entities: nil), info: .progress)
|> `catch` { _ -> Signal<NetworkRequestResult<Api.MessageMedia>, NoError> in |> `catch` { _ -> Signal<NetworkRequestResult<Api.messages.WebPagePreview>, NoError> in
return .single(.result(.messageMediaEmpty)) return .single(.result(.webPagePreview(media: .messageMediaEmpty, users: [])))
} }
|> mapToSignal { result -> Signal<WebpagePreviewWithProgressResult, NoError> in |> mapToSignal { result -> Signal<WebpagePreviewWithProgressResult, NoError> in
switch result { switch result {
@ -169,36 +169,44 @@ public func webpagePreviewWithProgress(account: Account, urls: [String], webpage
return .complete() return .complete()
} }
case let .result(result): case let .result(result):
if let preCachedResources = result.preCachedResources { if case let .webPagePreview(result, _) = result, let preCachedResources = result.preCachedResources {
for (resource, data) in preCachedResources { for (resource, data) in preCachedResources {
account.postbox.mediaBox.storeResourceData(resource.id, data: data) account.postbox.mediaBox.storeResourceData(resource.id, data: data)
} }
} }
switch result { switch result {
case let .messageMediaWebPage(flags, webpage): case let .webPagePreview(media, users):
let _ = flags switch media {
if let media = telegramMediaWebpageFromApiWebpage(webpage), let url = media.content.url { case let .messageMediaWebPage(_, webpage):
if case .Loaded = media.content { return account.postbox.transaction { transaction -> Signal<WebpagePreviewWithProgressResult, NoError> in
return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url))) let peers = AccumulatedPeers(users: users)
} else { updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: peers)
return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url)))
|> then( if let media = telegramMediaWebpageFromApiWebpage(webpage), let url = media.content.url {
account.stateManager.updatedWebpage(media.webpageId) if case .Loaded = media.content {
|> take(1) return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url)))
|> map { next -> WebpagePreviewWithProgressResult in } else {
if let url = next.content.url { return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url)))
return .result(WebpagePreviewResult.Result(webpage: next, sourceUrl: url)) |> then(
} else { account.stateManager.updatedWebpage(media.webpageId)
return .result(nil) |> take(1)
} |> map { next -> WebpagePreviewWithProgressResult in
if let url = next.content.url {
return .result(WebpagePreviewResult.Result(webpage: next, sourceUrl: url))
} else {
return .result(nil)
}
}
)
} }
) } else {
return .single(.result(nil))
}
} }
} else { |> switchToLatest
default:
return .single(.result(nil)) return .single(.result(nil))
} }
default:
return .single(.result(nil))
} }
} }
} }

View File

@ -189,8 +189,7 @@ final class CameraCodeResultComponent: Component {
view.frame = CGRect(origin: CGPoint(x: 54.0, y: 9.0), size: titleSize) view.frame = CGRect(origin: CGPoint(x: 54.0, y: 9.0), size: titleSize)
} }
//TODO:localize let subtitleString = NSMutableAttributedString(string: "\(presentationData.strings.Camera_OpenChat) >", font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7))
let subtitleString = NSMutableAttributedString(string: "Open Chat >", font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7))
if let range = subtitleString.string.range(of: ">"), let arrowImage = UIImage(bundleImageName: "Item List/InlineTextRightArrow") { if let range = subtitleString.string.range(of: ">"), let arrowImage = UIImage(bundleImageName: "Item List/InlineTextRightArrow") {
subtitleString.addAttribute(.attachment, value: arrowImage, range: NSRange(range, in: subtitleString.string)) subtitleString.addAttribute(.attachment, value: arrowImage, range: NSRange(range, in: subtitleString.string))
subtitleString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: subtitleString.string)) subtitleString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: subtitleString.string))

View File

@ -366,7 +366,6 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
var buttonTitle = item.presentationData.strings.Notification_PremiumGift_View var buttonTitle = item.presentationData.strings.Notification_PremiumGift_View
var buttonIcon: String? var buttonIcon: String?
var ribbonTitle = "" var ribbonTitle = ""
var hasServiceMessage = true
var textSpacing: CGFloat = 0.0 var textSpacing: CGFloat = 0.0
var isStarGift = false var isStarGift = false
@ -381,6 +380,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
var uniquePatternColor: UIColor? var uniquePatternColor: UIColor?
var uniquePatternFile: TelegramMediaFile? var uniquePatternFile: TelegramMediaFile?
let isStoryEntity = item.message.id.id == -1
var hasServiceMessage = !isStoryEntity
for media in item.message.media { for media in item.message.media {
if let action = media as? TelegramMediaAction { if let action = media as? TelegramMediaAction {
switch action.action { switch action.action {
@ -560,10 +562,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} else { } else {
authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
} }
title = item.presentationData.strings.Notification_StarGift_Title(authorName).string title = isStoryEntity ? uniqueGift.title : item.presentationData.strings.Notification_StarGift_Title(authorName).string
text = "**\(uniqueGift.title) #\(uniqueGift.number)**" text = isStoryEntity ? "**Collectible #\(uniqueGift.number)**" : "**\(uniqueGift.title) #\(uniqueGift.number)**"
ribbonTitle = item.presentationData.strings.Notification_StarGift_Gift ribbonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_Gift
buttonTitle = item.presentationData.strings.Notification_StarGift_View buttonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_View
modelTitle = item.presentationData.strings.Notification_StarGift_Model modelTitle = item.presentationData.strings.Notification_StarGift_Model
backdropTitle = item.presentationData.strings.Notification_StarGift_Backdrop backdropTitle = item.presentationData.strings.Notification_StarGift_Backdrop
symbolTitle = item.presentationData.strings.Notification_StarGift_Symbol symbolTitle = item.presentationData.strings.Notification_StarGift_Symbol
@ -697,6 +699,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
if !buttonTitle.isEmpty { if !buttonTitle.isEmpty {
giftSize.height += 48.0 giftSize.height += 48.0
} else if isStoryEntity {
giftSize.height += 12.0
} }
var labelRects = labelLayout.linesRects() var labelRects = labelLayout.linesRects()
@ -754,7 +758,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
} }
let overlayColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) let overlayColor = item.presentationData.theme.theme.overallDarkAppearance && uniquePatternFile == nil ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12)
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - giftSize.width) / 2.0), y: hasServiceMessage ? labelLayout.size.height + 12.0 : 0.0), size: giftSize) let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - giftSize.width) / 2.0), y: hasServiceMessage ? labelLayout.size.height + 12.0 : 0.0), size: giftSize)
let mediaBackgroundFrame = imageFrame.insetBy(dx: -2.0, dy: -2.0) let mediaBackgroundFrame = imageFrame.insetBy(dx: -2.0, dy: -2.0)
@ -767,11 +771,12 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
let animationFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0 + iconOffset), size: iconSize) let animationFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0 + iconOffset), size: iconSize)
strongSelf.animationNode.frame = animationFrame strongSelf.animationNode.frame = animationFrame
strongSelf.animationNode.isHidden = isStoryEntity
strongSelf.buttonNode.isHidden = buttonTitle.isEmpty strongSelf.buttonNode.isHidden = buttonTitle.isEmpty
strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty
if strongSelf.item == nil { if strongSelf.item == nil && !isStoryEntity {
strongSelf.animationNode.started = { [weak self] in strongSelf.animationNode.started = { [weak self] in
if let strongSelf = self { if let strongSelf = self {
let current = CACurrentMediaTime() let current = CACurrentMediaTime()
@ -883,63 +888,78 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.dustNode = nil strongSelf.dustNode = nil
} }
var middleX = mediaBackgroundFrame.width / 2.0 let attributeSpacing: CGFloat = 6.0
if let (modelValueLayout, _) = modelValueLayoutAndApply, let (backdropValueLayout, _) = backdropValueLayoutAndApply, let (symbolValueLayout, _) = symbolValueLayoutAndApply { let attributeVerticalSpacing: CGFloat = 22.0
let maxWidth = max(modelValueLayout.size.width, max(backdropValueLayout.size.width, symbolValueLayout.size.width)) var attributeMidpoints: [CGFloat] = []
middleX = min(mediaBackgroundFrame.width - maxWidth - 16.0, middleX)
func appendAttributeMidpoint(titleLayout: TextNodeLayout?, valueLayout: TextNodeLayout?) {
if let titleLayout, let valueLayout {
let totalWidth = titleLayout.size.width + attributeSpacing + valueLayout.size.width
let titleOffset = titleLayout.size.width + attributeSpacing / 2.0
let midpoint = (mediaBackgroundFrame.width - totalWidth) / 2.0 + titleOffset
attributeMidpoints.append(midpoint)
}
} }
appendAttributeMidpoint(titleLayout: modelTitleLayoutAndApply?.0, valueLayout: modelValueLayoutAndApply?.0)
let titleMaxX: CGFloat = mediaBackgroundFrame.minX + middleX - 2.0 appendAttributeMidpoint(titleLayout: backdropTitleLayoutAndApply?.0, valueLayout: backdropValueLayoutAndApply?.0)
let valueMinX: CGFloat = mediaBackgroundFrame.minX + middleX + 3.0 appendAttributeMidpoint(titleLayout: symbolTitleLayoutAndApply?.0, valueLayout: symbolValueLayoutAndApply?.0)
let middleX = attributeMidpoints.isEmpty ? mediaBackgroundFrame.width / 2.0 : attributeMidpoints.reduce(0, +) / CGFloat(attributeMidpoints.count)
let titleMaxX: CGFloat = mediaBackgroundFrame.minX + middleX - attributeSpacing / 2.0
let valueMinX: CGFloat = mediaBackgroundFrame.minX + middleX + attributeSpacing / 2.0
if let (modelTitleLayout, modelTitleApply) = modelTitleLayoutAndApply { func positionAttributeNodes(
if strongSelf.modelTitleTextNode.supernode == nil { titleTextNode: TextNode,
strongSelf.addSubnode(strongSelf.modelTitleTextNode) valueTextNode: TextNode,
titleLayoutAndApply: (TextNodeLayout, () -> TextNode)?,
valueLayoutAndApply: (TextNodeLayout, () -> TextNode)?,
yOffset: CGFloat
) {
if let (titleLayout, titleApply) = titleLayoutAndApply {
if titleTextNode.supernode == nil {
strongSelf.addSubnode(titleTextNode)
}
let _ = titleApply()
titleTextNode.frame = CGRect(
origin: CGPoint(x: titleMaxX - titleLayout.size.width, y: clippingTextFrame.maxY + yOffset),
size: titleLayout.size
)
}
if let (valueLayout, valueApply) = valueLayoutAndApply {
if valueTextNode.supernode == nil {
strongSelf.addSubnode(valueTextNode)
}
let _ = valueApply()
valueTextNode.frame = CGRect(
origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + yOffset),
size: valueLayout.size
)
} }
let _ = modelTitleApply()
strongSelf.modelTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - modelTitleLayout.size.width, y: clippingTextFrame.maxY + 10.0), size: modelTitleLayout.size)
}
if let (modelValueLayout, modelValueApply) = modelValueLayoutAndApply {
if strongSelf.modelValueTextNode.supernode == nil {
strongSelf.addSubnode(strongSelf.modelValueTextNode)
}
let _ = modelValueApply()
strongSelf.modelValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 10.0), size: modelValueLayout.size)
}
if let (backdropTitleLayout, backdropTitleApply) = backdropTitleLayoutAndApply {
if strongSelf.backdropTitleTextNode.supernode == nil {
strongSelf.addSubnode(strongSelf.backdropTitleTextNode)
}
let _ = backdropTitleApply()
strongSelf.backdropTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - backdropTitleLayout.size.width, y: clippingTextFrame.maxY + 32.0), size: backdropTitleLayout.size)
}
if let (backdropValueLayout, backdropValueApply) = backdropValueLayoutAndApply {
if strongSelf.backdropValueTextNode.supernode == nil {
strongSelf.addSubnode(strongSelf.backdropValueTextNode)
}
let _ = backdropValueApply()
strongSelf.backdropValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 32.0), size: backdropValueLayout.size)
}
if let (symbolTitleLayout, symbolTitleApply) = symbolTitleLayoutAndApply {
if strongSelf.symbolTitleTextNode.supernode == nil {
strongSelf.addSubnode(strongSelf.symbolTitleTextNode)
}
let _ = symbolTitleApply()
strongSelf.symbolTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - symbolTitleLayout.size.width, y: clippingTextFrame.maxY + 54.0), size: symbolTitleLayout.size)
}
if let (symbolValueLayout, symbolValueApply) = symbolValueLayoutAndApply {
if strongSelf.symbolValueTextNode.supernode == nil {
strongSelf.addSubnode(strongSelf.symbolValueTextNode)
}
let _ = symbolValueApply()
strongSelf.symbolValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 54.0), size: symbolValueLayout.size)
} }
positionAttributeNodes(
titleTextNode: strongSelf.modelTitleTextNode,
valueTextNode: strongSelf.modelValueTextNode,
titleLayoutAndApply: modelTitleLayoutAndApply,
valueLayoutAndApply: modelValueLayoutAndApply,
yOffset: 10.0
)
positionAttributeNodes(
titleTextNode: strongSelf.backdropTitleTextNode,
valueTextNode: strongSelf.backdropValueTextNode,
titleLayoutAndApply: backdropTitleLayoutAndApply,
valueLayoutAndApply: backdropValueLayoutAndApply,
yOffset: 10.0 + attributeVerticalSpacing
)
positionAttributeNodes(
titleTextNode: strongSelf.symbolTitleTextNode,
valueTextNode: strongSelf.symbolValueTextNode,
titleLayoutAndApply: symbolTitleLayoutAndApply,
valueLayoutAndApply: symbolValueLayoutAndApply,
yOffset: 10.0 + attributeVerticalSpacing * 2
)
var buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0) var buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0)
var buttonOriginY = clippingTextFrame.maxY + 10.0 var buttonOriginY = clippingTextFrame.maxY + 10.0
if modelTitleLayoutAndApply != nil { if modelTitleLayoutAndApply != nil {

View File

@ -457,6 +457,8 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
} }
} }
actionTitle = isEmoji ? item.presentationData.strings.Conversation_ViewEmojis : item.presentationData.strings.Conversation_ViewStickers actionTitle = isEmoji ? item.presentationData.strings.Conversation_ViewEmojis : item.presentationData.strings.Conversation_ViewStickers
case "telegram_nft":
actionTitle = item.presentationData.strings.Conversation_ViewStarGift
default: default:
break break
} }

View File

@ -1381,6 +1381,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break break
case .premiumMultiGift: case .premiumMultiGift:
break break
case .collectible:
break
case .messageLink: case .messageLink:
break break
} }

View File

@ -431,10 +431,9 @@ public final class GiftItemComponent: Component {
} }
price = priceValue price = priceValue
case .uniqueGift: case .uniqueGift:
//TODO:localize
buttonColor = UIColor.white buttonColor = UIColor.white
starsColor = UIColor.white starsColor = UIColor.white
price = "Unique" price = ""
} }
let buttonSize = self.button.update( let buttonSize = self.button.update(

View File

@ -42,6 +42,7 @@ swift_library(
"//submodules/ConfettiEffect", "//submodules/ConfettiEffect",
"//submodules/TooltipUI", "//submodules/TooltipUI",
"//submodules/TelegramUI/Components/Gifts/GiftItemComponent", "//submodules/TelegramUI/Components/Gifts/GiftItemComponent",
"//submodules/MoreButtonNode",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -0,0 +1,143 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import BundleIconComponent
import MultilineTextComponent
import MoreButtonNode
import AccountContext
import TelegramPresentationData
final class ButtonsComponent: Component {
let theme: PresentationTheme
let isOverlay: Bool
let showMoreButton: Bool
let closePressed: () -> Void
let morePressed: (ASDisplayNode, ContextGesture?) -> Void
init(
theme: PresentationTheme,
isOverlay: Bool,
showMoreButton: Bool,
closePressed: @escaping () -> Void,
morePressed: @escaping (ASDisplayNode, ContextGesture?) -> Void
) {
self.theme = theme
self.isOverlay = isOverlay
self.showMoreButton = showMoreButton
self.closePressed = closePressed
self.morePressed = morePressed
}
static func ==(lhs: ButtonsComponent, rhs: ButtonsComponent) -> Bool {
return lhs.theme === rhs.theme && lhs.isOverlay == rhs.isOverlay && lhs.showMoreButton == rhs.showMoreButton
}
final class View: UIView {
private let backgroundView = UIView()
private let closeButton = HighlightTrackingButton()
private let closeIcon = UIImageView()
private let moreNode = MoreButtonNode(theme: defaultPresentationTheme, size: CGSize(width: 36.0, height: 36.0), encircled: false)
private var component: ButtonsComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundView.clipsToBounds = true
self.addSubview(self.backgroundView)
self.closeIcon.image = generateCloseButtonImage()
self.moreNode.updateColor(.white, transition: .immediate)
self.backgroundView.addSubview(self.moreNode.view)
self.backgroundView.addSubview(self.closeButton)
self.backgroundView.addSubview(self.closeIcon)
self.closeButton.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.closeIcon.layer.removeAnimation(forKey: "opacity")
self.closeIcon.alpha = 0.6
} else {
self.closeIcon.alpha = 1.0
self.closeIcon.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
}
}
self.closeButton.addTarget(self, action: #selector(self.closePressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func closePressed() {
guard let component = self.component else {
return
}
component.closePressed()
}
func update(component: ButtonsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let backgroundSize = CGSize(width: component.showMoreButton ? 70.0 : 30.0, height: 30.0)
self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0
let backgroundColor: UIColor = component.isOverlay ? UIColor(rgb: 0xffffff, alpha: 0.1) : UIColor(rgb: 0x808084, alpha: 0.1)
let foregroundColor: UIColor = component.isOverlay ? .white : component.theme.actionSheet.inputClearButtonColor
transition.setBackgroundColor(view: self.backgroundView, color: backgroundColor)
transition.setTintColor(view: self.closeIcon, color: foregroundColor)
let backgroundFrame = CGRect(origin: .zero, size: backgroundSize)
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
transition.setFrame(view: self.moreNode.view, frame: CGRect(origin: CGPoint(x: -7.0, y: -4.0), size: CGSize(width: 36.0, height: 36.0)))
transition.setAlpha(view: self.moreNode.view, alpha: component.showMoreButton ? 1.0 : 0.0)
self.moreNode.action = { [weak self] node, gesture in
guard let self, let component = self.component else {
return
}
component.morePressed(node, gesture)
}
let closeFrame = CGRect(origin: CGPoint(x: backgroundSize.width - 30.0 - (component.showMoreButton ? 3.0 : 0.0), y: 0.0), size: CGSize(width: 30.0, height: 30.0))
transition.setFrame(view: self.closeIcon, frame: closeFrame)
transition.setFrame(view: self.closeButton, frame: closeFrame)
return backgroundSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private func generateCloseButtonImage() -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setStrokeColor(UIColor.white.cgColor)
context.move(to: CGPoint(x: 10.0, y: 10.0))
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
context.strokePath()
context.move(to: CGPoint(x: 20.0, y: 10.0))
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
context.strokePath()
})?.withRenderingMode(.alwaysTemplate)
}

View File

@ -29,6 +29,7 @@ import CheckComponent
import TooltipUI import TooltipUI
import GiftAnimationComponent import GiftAnimationComponent
import LottieComponent import LottieComponent
import ContextUI
private let modelButtonTag = GenericComponentViewTag() private let modelButtonTag = GenericComponentViewTag()
private let backdropButtonTag = GenericComponentViewTag() private let backdropButtonTag = GenericComponentViewTag()
@ -50,6 +51,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>) let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)
let showAttributeInfo: (Any, Float) -> Void let showAttributeInfo: (Any, Float) -> Void
let viewUpgraded: (EngineMessage.Id) -> Void let viewUpgraded: (EngineMessage.Id) -> Void
let openMore: (ASDisplayNode, ContextGesture?) -> Void
let getController: () -> ViewController? let getController: () -> ViewController?
init( init(
@ -66,6 +68,7 @@ private final class GiftViewSheetContent: CombinedComponent {
upgradeGift: @escaping ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>), upgradeGift: @escaping ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>),
showAttributeInfo: @escaping (Any, Float) -> Void, showAttributeInfo: @escaping (Any, Float) -> Void,
viewUpgraded: @escaping (EngineMessage.Id) -> Void, viewUpgraded: @escaping (EngineMessage.Id) -> Void,
openMore: @escaping (ASDisplayNode, ContextGesture?) -> Void,
getController: @escaping () -> ViewController? getController: @escaping () -> ViewController?
) { ) {
self.context = context self.context = context
@ -81,6 +84,7 @@ private final class GiftViewSheetContent: CombinedComponent {
self.upgradeGift = upgradeGift self.upgradeGift = upgradeGift
self.showAttributeInfo = showAttributeInfo self.showAttributeInfo = showAttributeInfo
self.viewUpgraded = viewUpgraded self.viewUpgraded = viewUpgraded
self.openMore = openMore
self.getController = getController self.getController = getController
} }
@ -108,8 +112,6 @@ private final class GiftViewSheetContent: CombinedComponent {
var cachedCircleImage: UIImage? var cachedCircleImage: UIImage?
var cachedStarImage: (UIImage, PresentationTheme)? var cachedStarImage: (UIImage, PresentationTheme)?
var cachedCloseImage: (UIImage, PresentationTheme)?
var cachedOverlayCloseImage: UIImage?
var cachedChevronImage: (UIImage, PresentationTheme)? var cachedChevronImage: (UIImage, PresentationTheme)?
var cachedSmallChevronImage: (UIImage, PresentationTheme)? var cachedSmallChevronImage: (UIImage, PresentationTheme)?
@ -158,12 +160,17 @@ private final class GiftViewSheetContent: CombinedComponent {
self.keepOriginalInfo = true self.keepOriginalInfo = true
} }
var peerIds: [EnginePeer.Id] = [arguments.peerId, context.account.peerId] var peerIds: [EnginePeer.Id] = [context.account.peerId]
if let peerId = arguments.peerId {
peerIds.append(peerId)
}
if let fromPeerId = arguments.fromPeerId, !peerIds.contains(fromPeerId) { if let fromPeerId = arguments.fromPeerId, !peerIds.contains(fromPeerId) {
peerIds.append(fromPeerId) peerIds.append(fromPeerId)
} }
if case let .unique(gift) = arguments.gift { if case let .unique(gift) = arguments.gift {
peerIds.append(gift.ownerPeerId) if case let .peerId(peerId) = gift.owner {
peerIds.append(peerId)
}
for attribute in gift.attributes { for attribute in gift.attributes {
if case let .originalInfo(senderPeerId, recipientPeerId, _, _, _) = attribute { if case let .originalInfo(senderPeerId, recipientPeerId, _, _, _) = attribute {
if let senderPeerId { if let senderPeerId {
@ -272,10 +279,10 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
func commitUpgrade() { func commitUpgrade() {
guard let arguments = self.subject.arguments, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { guard let arguments = self.subject.arguments, let peerId = arguments.peerId, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else {
return return
} }
let peerId = arguments.peerId
let proceed: (Int64?) -> Void = { formId in let proceed: (Int64?) -> Void = { formId in
self.inProgress = true self.inProgress = true
self.updated() self.updated()
@ -332,7 +339,7 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
static var body: Body { static var body: Body {
let closeButton = Child(Button.self) let buttons = Child(ButtonsComponent.self)
let animation = Child(GiftCompositionComponent.self) let animation = Child(GiftCompositionComponent.self)
let title = Child(MultilineTextComponent.self) let title = Child(MultilineTextComponent.self)
let description = Child(MultilineTextComponent.self) let description = Child(MultilineTextComponent.self)
@ -436,22 +443,7 @@ private final class GiftViewSheetContent: CombinedComponent {
convertStars = nil convertStars = nil
titleString = "" titleString = ""
} }
let closeImage: UIImage
let closeOverlayImage: UIImage
if let (image, theme) = state.cachedCloseImage, theme === environment.theme {
closeImage = image
} else {
closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)!
state.cachedCloseImage = (closeImage, theme)
}
if let image = state.cachedOverlayCloseImage {
closeOverlayImage = image
} else {
closeOverlayImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.1), foregroundColor: .white)!
state.cachedOverlayCloseImage = closeOverlayImage
}
var showUpgradePreview = false var showUpgradePreview = false
if state.inUpgradePreview, let _ = state.sampleGiftAttributes { if state.inUpgradePreview, let _ = state.sampleGiftAttributes {
showUpgradePreview = true showUpgradePreview = true
@ -460,10 +452,12 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
let cancel = component.cancel let cancel = component.cancel
let closeButton = closeButton.update( let buttons = buttons.update(
component: Button( component: ButtonsComponent(
content: AnyComponent(Image(image: showUpgradePreview || uniqueGift != nil ? closeOverlayImage : closeImage)), theme: theme,
action: { [weak state] in isOverlay: showUpgradePreview || uniqueGift != nil,
showMoreButton: uniqueGift != nil,
closePressed: { [weak state] in
guard let state else { guard let state else {
return return
} }
@ -473,10 +467,13 @@ private final class GiftViewSheetContent: CombinedComponent {
} else { } else {
cancel(true) cancel(true)
} }
},
morePressed: { node, gesture in
component.openMore(node, gesture)
} }
), ),
availableSize: CGSize(width: 30.0, height: 30.0), availableSize: CGSize(width: 30.0, height: 30.0),
transition: .immediate transition: context.transition
) )
var originY: CGFloat = 0.0 var originY: CGFloat = 0.0
@ -888,70 +885,81 @@ private final class GiftViewSheetContent: CombinedComponent {
if !soldOut { if !soldOut {
if let uniqueGift { if let uniqueGift {
if let peer = state.peerMap[uniqueGift.ownerPeerId] { switch uniqueGift.owner {
let ownerComponent: AnyComponent<Empty> case let .peerId(peerId):
if let _ = subject.arguments?.transferStars { if let peer = state.peerMap[peerId] {
ownerComponent = AnyComponent( let ownerComponent: AnyComponent<Empty>
HStack([ if let _ = subject.arguments?.transferStars {
AnyComponentWithIdentity( ownerComponent = AnyComponent(
id: AnyHashable(0), HStack([
component: AnyComponent(Button( AnyComponentWithIdentity(
content: AnyComponent( id: AnyHashable(0),
PeerCellComponent( component: AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
context: component.context,
theme: theme,
strings: strings,
peer: peer
)
),
action: {
component.openPeer(peer)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
}
))
),
AnyComponentWithIdentity(
id: AnyHashable(1),
component: AnyComponent(Button(
content: AnyComponent(ButtonContentComponent(
context: component.context, context: component.context,
theme: theme, text: strings.Gift_Unique_Transfer,
strings: strings, color: theme.list.itemAccentColor
peer: peer )),
) action: {
), component.transferGift()
action: { Queue.mainQueue().after(1.0, {
component.openPeer(peer) component.cancel(false)
Queue.mainQueue().after(1.0, { })
component.cancel(false) }
}) ))
} )
)) ], spacing: 4.0)
)
} else {
ownerComponent = AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
context: component.context,
theme: theme,
strings: strings,
peer: peer
)
), ),
AnyComponentWithIdentity( action: {
id: AnyHashable(1), component.openPeer(peer)
component: AnyComponent(Button( Queue.mainQueue().after(1.0, {
content: AnyComponent(ButtonContentComponent( component.cancel(false)
context: component.context, })
text: strings.Gift_Unique_Transfer, }
color: theme.list.itemAccentColor ))
)), }
action: { tableItems.append(.init(
component.transferGift() id: "owner",
Queue.mainQueue().after(1.0, { title: strings.Gift_Unique_Owner,
component.cancel(false) component: ownerComponent
})
}
))
)
], spacing: 4.0)
)
} else {
ownerComponent = AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
context: component.context,
theme: theme,
strings: strings,
peer: peer
)
),
action: {
component.openPeer(peer)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
}
)) ))
} }
case let .name(name):
tableItems.append(.init( tableItems.append(.init(
id: "owner", id: "anon_owner",
title: strings.Gift_Unique_Owner, title: strings.Gift_Unique_Owner,
component: ownerComponent component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)))
)
)) ))
} }
} else if let peerId = subject.arguments?.fromPeerId, let peer = state.peerMap[peerId] { } else if let peerId = subject.arguments?.fromPeerId, let peer = state.peerMap[peerId] {
@ -1593,8 +1601,8 @@ private final class GiftViewSheetContent: CombinedComponent {
originY += buttonChild.size.height originY += buttonChild.size.height
originY += 7.0 originY += 7.0
context.add(closeButton context.add(buttons
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - closeButton.size.width, y: 28.0)) .position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - 16.0 - buttons.size.width / 2.0, y: 28.0))
) )
let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom) let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom)
@ -1618,6 +1626,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
let transferGift: () -> Void let transferGift: () -> Void
let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>) let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)
let viewUpgraded: (EngineMessage.Id) -> Void let viewUpgraded: (EngineMessage.Id) -> Void
let openMore: (ASDisplayNode, ContextGesture?) -> Void
let showAttributeInfo: (Any, Float) -> Void let showAttributeInfo: (Any, Float) -> Void
init( init(
@ -1632,6 +1641,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
transferGift: @escaping () -> Void, transferGift: @escaping () -> Void,
upgradeGift: @escaping ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>), upgradeGift: @escaping ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>),
viewUpgraded: @escaping (EngineMessage.Id) -> Void, viewUpgraded: @escaping (EngineMessage.Id) -> Void,
openMore: @escaping (ASDisplayNode, ContextGesture?) -> Void,
showAttributeInfo: @escaping (Any, Float) -> Void showAttributeInfo: @escaping (Any, Float) -> Void
) { ) {
self.context = context self.context = context
@ -1645,6 +1655,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
self.transferGift = transferGift self.transferGift = transferGift
self.upgradeGift = upgradeGift self.upgradeGift = upgradeGift
self.viewUpgraded = viewUpgraded self.viewUpgraded = viewUpgraded
self.openMore = openMore
self.showAttributeInfo = showAttributeInfo self.showAttributeInfo = showAttributeInfo
} }
@ -1695,6 +1706,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
upgradeGift: context.component.upgradeGift, upgradeGift: context.component.upgradeGift,
showAttributeInfo: context.component.showAttributeInfo, showAttributeInfo: context.component.showAttributeInfo,
viewUpgraded: context.component.viewUpgraded, viewUpgraded: context.component.viewUpgraded,
openMore: context.component.openMore,
getController: controller getController: controller
)), )),
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
@ -1764,11 +1776,12 @@ private final class GiftViewSheetComponent: CombinedComponent {
public class GiftViewScreen: ViewControllerComponentContainer { public class GiftViewScreen: ViewControllerComponentContainer {
public enum Subject: Equatable { public enum Subject: Equatable {
case message(EngineMessage) case message(EngineMessage)
case uniqueGift(StarGift.UniqueGift)
case profileGift(EnginePeer.Id, ProfileGiftsContext.State.StarGift) case profileGift(EnginePeer.Id, ProfileGiftsContext.State.StarGift)
case soldOutGift(StarGift.Gift) case soldOutGift(StarGift.Gift)
case upgradePreview([StarGift.UniqueGift.Attribute], String) case upgradePreview([StarGift.UniqueGift.Attribute], String)
var arguments: (peerId: EnginePeer.Id, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, canExportDate: Int32?, upgradeMessageId: Int32?)? { var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, canExportDate: Int32?, upgradeMessageId: Int32?)? {
switch self { switch self {
case let .message(message): case let .message(message):
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction { if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction {
@ -1793,6 +1806,8 @@ public class GiftViewScreen: ViewControllerComponentContainer {
return nil return nil
} }
} }
case let .uniqueGift(gift):
return (nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, false, false, false, nil, nil, nil, nil)
case let .profileGift(peerId, gift): case let .profileGift(peerId, gift):
return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, gift.messageId, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, gift.canExportDate, nil) return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, gift.messageId, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, gift.canExportDate, nil)
case .soldOutGift: case .soldOutGift:
@ -1817,7 +1832,8 @@ public class GiftViewScreen: ViewControllerComponentContainer {
updateSavedToProfile: ((EngineMessage.Id, Bool) -> Void)? = nil, updateSavedToProfile: ((EngineMessage.Id, Bool) -> Void)? = nil,
convertToStars: (() -> Void)? = nil, convertToStars: (() -> Void)? = nil,
transferGift: ((Bool, EnginePeer.Id) -> Void)? = nil, transferGift: ((Bool, EnginePeer.Id) -> Void)? = nil,
upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)? = nil upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)? = nil,
shareStory: (() -> Void)? = nil
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
@ -1831,6 +1847,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
var transferGiftImpl: (() -> Void)? var transferGiftImpl: (() -> Void)?
var showAttributeInfoImpl: ((Any, Float) -> Void)? var showAttributeInfoImpl: ((Any, Float) -> Void)?
var upgradeGiftImpl: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)? var upgradeGiftImpl: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)?
var openMoreImpl: ((ASDisplayNode, ContextGesture?) -> Void)?
var viewUpgradedImpl: ((EngineMessage.Id) -> Void)? var viewUpgradedImpl: ((EngineMessage.Id) -> Void)?
super.init( super.init(
@ -1865,6 +1882,9 @@ public class GiftViewScreen: ViewControllerComponentContainer {
viewUpgraded: { messageId in viewUpgraded: { messageId in
viewUpgradedImpl?(messageId) viewUpgradedImpl?(messageId)
}, },
openMore: { node, gesture in
openMoreImpl?(node, gesture)
},
showAttributeInfo: { tag, rarity in showAttributeInfo: { tag, rarity in
showAttributeInfoImpl?(tag, rarity) showAttributeInfoImpl?(tag, rarity)
} }
@ -2151,6 +2171,66 @@ public class GiftViewScreen: ViewControllerComponentContainer {
}) })
self.present(controller, in: .current) self.present(controller, in: .current)
} }
openMoreImpl = { [weak self] node, gesture in
guard let self, let arguments = self.subject.arguments, case let .unique(gift) = arguments.gift else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
let link = "https://t.me/nft/\(gift.slug)"
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in
c?.dismiss(completion: nil)
guard let self else {
return
}
UIPasteboard.general.string = link
self.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
})))
items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in
c?.dismiss(completion: nil)
guard let self else {
return
}
let shareController = context.sharedContext.makeShareController(
context: context,
subject: .url(link),
forceExternal: false,
shareStory: shareStory,
actionCompleted: { [weak self] in
self?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}
)
self.present(shareController, in: .window(.root))
})))
if let _ = arguments.transferStars {
items.append(.action(ContextMenuActionItem(text: "Transfer", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor)
}, action: { c, _ in
c?.dismiss(completion: nil)
transferGiftImpl?()
})))
}
let contextController = ContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.presentInGlobalOverlay(contextController)
}
} }
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {
@ -2298,6 +2378,7 @@ private final class TableComponent: CombinedComponent {
let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor
let borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6) let borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6)
let secondaryBackgroundColor = context.component.theme.overallDarkAppearance ? context.component.theme.list.itemModalBlocksBackgroundColor : context.component.theme.list.itemInputField.backgroundColor
var leftColumnWidth: CGFloat = 0.0 var leftColumnWidth: CGFloat = 0.0
@ -2388,7 +2469,7 @@ private final class TableComponent: CombinedComponent {
if hasLastBackground { if hasLastBackground {
let lastRowHeight = rowHeights[i - 1] ?? 0 let lastRowHeight = rowHeights[i - 1] ?? 0
let lastBackground = lastBackground.update( let lastBackground = lastBackground.update(
component: Rectangle(color: context.component.theme.list.itemInputField.backgroundColor), component: Rectangle(color: secondaryBackgroundColor),
availableSize: CGSize(width: context.availableSize.width, height: lastRowHeight), availableSize: CGSize(width: context.availableSize.width, height: lastRowHeight),
transition: context.transition transition: context.transition
) )
@ -2399,7 +2480,7 @@ private final class TableComponent: CombinedComponent {
} }
let leftColumnBackground = leftColumnBackground.update( let leftColumnBackground = leftColumnBackground.update(
component: Rectangle(color: context.component.theme.list.itemInputField.backgroundColor), component: Rectangle(color: secondaryBackgroundColor),
availableSize: CGSize(width: leftColumnWidth, height: innerTotalHeight), availableSize: CGSize(width: leftColumnWidth, height: innerTotalHeight),
transition: context.transition transition: context.transition
) )
@ -2597,27 +2678,6 @@ private final class PeerCellComponent: Component {
} }
} }
private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(backgroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setStrokeColor(foregroundColor.cgColor)
context.move(to: CGPoint(x: 10.0, y: 10.0))
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
context.strokePath()
context.move(to: CGPoint(x: 20.0, y: 10.0))
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
context.strokePath()
})
}
private final class ButtonContentComponent: Component { private final class ButtonContentComponent: Component {
let context: AccountContext let context: AccountContext
let text: String let text: String
@ -2914,3 +2974,17 @@ private final class ParagraphComponent: CombinedComponent {
} }
} }
} }
private final class GiftViewContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController
private let sourceNode: ASDisplayNode
init(controller: ViewController, sourceNode: ASDisplayNode) {
self.controller = controller
self.sourceNode = sourceNode
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View File

@ -180,6 +180,11 @@ public enum CodableDrawingEntity: Equatable {
coordinates: coordinates, coordinates: coordinates,
messageId: messageId messageId: messageId
) )
} else if case let .gift(gift, _) = entity.content {
return .starGift(
coordinates: coordinates,
slug: gift.slug
)
} else { } else {
return nil return nil
} }

View File

@ -39,6 +39,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
case video(TelegramMediaFile) case video(TelegramMediaFile)
case dualVideoReference(Bool) case dualVideoReference(Bool)
case message([MessageId], CGSize, TelegramMediaFile?, CGRect?, CGFloat?) case message([MessageId], CGSize, TelegramMediaFile?, CGRect?, CGFloat?)
case gift(StarGift.UniqueGift, CGSize)
public static func == (lhs: Content, rhs: Content) -> Bool { public static func == (lhs: Content, rhs: Content) -> Bool {
switch lhs { switch lhs {
@ -78,6 +79,12 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
} else { } else {
return false return false
} }
case let .gift(lhsGift, lhsSize):
if case let .gift(rhsGift, rhsSize) = rhs {
return lhsGift == rhsGift && lhsSize == rhsSize
} else {
return false
}
} }
} }
} }
@ -98,6 +105,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
case messageSize case messageSize
case messageMediaRect case messageMediaRect
case messageMediaCornerRadius case messageMediaCornerRadius
case gift
case referenceDrawingSize case referenceDrawingSize
case position case position
case scale case scale
@ -120,6 +130,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
self.scale = max(0.59, min(1.77, self.scale)) self.scale = max(0.59, min(1.77, self.scale))
} else if case .message = self.content { } else if case .message = self.content {
self.scale = max(2.5, self.scale) self.scale = max(2.5, self.scale)
} else if case .gift = self.content {
self.scale = max(2.5, self.scale)
} }
} }
} }
@ -164,6 +176,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
dimensions = CGSize(width: 512.0, height: 512.0) dimensions = CGSize(width: 512.0, height: 512.0)
case let .message(_, size, _, _, _): case let .message(_, size, _, _, _):
dimensions = size dimensions = size
case let .gift(_, size):
dimensions = size
} }
let boundingSize = CGSize(width: size, height: size) let boundingSize = CGSize(width: size, height: size)
@ -191,7 +205,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
return true return true
case .dualVideoReference: case .dualVideoReference:
return true return true
case .message: case .message, .gift:
return !(self.renderSubEntities ?? []).isEmpty return !(self.renderSubEntities ?? []).isEmpty
} }
} }
@ -202,7 +216,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
return imageType == .rectangle return imageType == .rectangle
case .video: case .video:
return true return true
case .message: case .message, .gift:
return true return true
default: default:
return false return false
@ -232,7 +246,10 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid) self.uuid = try container.decode(UUID.self, forKey: .uuid)
if let messageIds = try container.decodeIfPresent([MessageId].self, forKey: .messageIds) { if let gift = try container.decodeIfPresent(StarGift.UniqueGift.self, forKey: .gift) {
let size = try container.decodeIfPresent(CGSize.self, forKey: .messageSize) ?? .zero
self.content = .gift(gift, size)
} else if let messageIds = try container.decodeIfPresent([MessageId].self, forKey: .messageIds) {
let size = try container.decodeIfPresent(CGSize.self, forKey: .messageSize) ?? .zero let size = try container.decodeIfPresent(CGSize.self, forKey: .messageSize) ?? .zero
let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .messageFile) let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .messageFile)
let mediaRect = try container.decodeIfPresent(CGRect.self, forKey: .messageMediaRect) let mediaRect = try container.decodeIfPresent(CGRect.self, forKey: .messageMediaRect)
@ -343,6 +360,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
try container.encodeIfPresent(file, forKey: .messageFile) try container.encodeIfPresent(file, forKey: .messageFile)
try container.encodeIfPresent(mediaRect, forKey: .messageMediaRect) try container.encodeIfPresent(mediaRect, forKey: .messageMediaRect)
try container.encodeIfPresent(mediaCornerRadius, forKey: .messageMediaCornerRadius) try container.encodeIfPresent(mediaCornerRadius, forKey: .messageMediaCornerRadius)
case let .gift(gift, size):
try container.encode(gift, forKey: .gift)
try container.encode(size, forKey: .messageSize)
} }
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize) try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position) try container.encode(self.position, forKey: .position)

View File

@ -87,17 +87,26 @@ public final class DrawingMessageRenderer {
private let isNight: Bool private let isNight: Bool
private let isOverlay: Bool private let isOverlay: Bool
private let isLink: Bool private let isLink: Bool
private let isGift: Bool
private let messagesContainerNode: ASDisplayNode private let messagesContainerNode: ASDisplayNode
private var avatarHeaderNode: ListViewItemHeaderNode? private var avatarHeaderNode: ListViewItemHeaderNode?
private var messageNodes: [ListViewItemNode]? private var messageNodes: [ListViewItemNode]?
init(context: AccountContext, messages: [Message], isNight: Bool = false, isOverlay: Bool = false, isLink: Bool = false) { init(
context: AccountContext,
messages: [Message],
isNight: Bool = false,
isOverlay: Bool = false,
isLink: Bool = false,
isGift: Bool = false
) {
self.context = context self.context = context
self.messages = messages self.messages = messages
self.isNight = isNight self.isNight = isNight
self.isOverlay = isOverlay self.isOverlay = isOverlay
self.isLink = isLink self.isLink = isLink
self.isGift = isGift
self.messagesContainerNode = ASDisplayNode() self.messagesContainerNode = ASDisplayNode()
self.messagesContainerNode.clipsToBounds = true self.messagesContainerNode.clipsToBounds = true
@ -198,7 +207,13 @@ public final class DrawingMessageRenderer {
let avatarHeaderItem: ListViewItemHeader? let avatarHeaderItem: ListViewItemHeader?
if let author = self.messages.first?.author { if let author = self.messages.first?.author {
avatarHeaderItem = self.context.sharedContext.makeChatMessageAvatarHeaderItem(context: self.context, timestamp: self.messages.first?.timestamp ?? 0, peer: self.messages.first!.peers[author.id]!, message: self.messages.first!, theme: theme, strings: presentationData.strings, wallpaper: presentationData.chatWallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder) let avatarPeer: Peer
if let peer = self.messages.first!.peers[author.id] {
avatarPeer = peer
} else {
avatarPeer = author
}
avatarHeaderItem = self.context.sharedContext.makeChatMessageAvatarHeaderItem(context: self.context, timestamp: self.messages.first?.timestamp ?? 0, peer: avatarPeer, message: self.messages.first!, theme: theme, strings: presentationData.strings, wallpaper: presentationData.chatWallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder)
} else { } else {
avatarHeaderItem = nil avatarHeaderItem = nil
} }
@ -208,6 +223,8 @@ public final class DrawingMessageRenderer {
var leftInset: CGFloat = 37.0 var leftInset: CGFloat = 37.0
if self.isLink { if self.isLink {
leftInset = -6.0 leftInset = -6.0
} else if self.isGift {
leftInset = -50.0
} }
let containerWidth = layout.size.width - inset * 2.0 let containerWidth = layout.size.width - inset * 2.0
let params = ListViewItemLayoutParams(width: containerWidth, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) let params = ListViewItemLayoutParams(width: containerWidth, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height)
@ -329,13 +346,19 @@ public final class DrawingMessageRenderer {
private let nightContainerNode: ContainerNode private let nightContainerNode: ContainerNode
private let overlayContainerNode: ContainerNode private let overlayContainerNode: ContainerNode
public init(context: AccountContext, messages: [Message], parentView: UIView, isLink: Bool = false) { public init(
context: AccountContext,
messages: [Message],
parentView: UIView,
isLink: Bool = false,
isGift: Bool = false
) {
self.context = context self.context = context
self.messages = messages self.messages = messages
self.dayContainerNode = ContainerNode(context: context, messages: messages, isLink: isLink) self.dayContainerNode = ContainerNode(context: context, messages: messages, isLink: isLink, isGift: isGift)
self.nightContainerNode = ContainerNode(context: context, messages: messages, isNight: true, isLink: isLink) self.nightContainerNode = ContainerNode(context: context, messages: messages, isNight: true, isLink: isLink, isGift: isGift)
self.overlayContainerNode = ContainerNode(context: context, messages: messages, isOverlay: true, isLink: isLink) self.overlayContainerNode = ContainerNode(context: context, messages: messages, isOverlay: true, isLink: isLink, isGift: isGift)
parentView.addSubview(self.dayContainerNode.view) parentView.addSubview(self.dayContainerNode.view)
parentView.addSubview(self.nightContainerNode.view) parentView.addSubview(self.nightContainerNode.view)

View File

@ -168,6 +168,7 @@ public final class MediaEditor {
case asset(PHAsset) case asset(PHAsset)
case draft(MediaEditorDraft) case draft(MediaEditorDraft)
case message(MessageId) case message(MessageId)
case gift(StarGift.UniqueGift)
case sticker(TelegramMediaFile) case sticker(TelegramMediaFile)
var dimensions: PixelDimensions { var dimensions: PixelDimensions {
@ -178,7 +179,7 @@ public final class MediaEditor {
return PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight)) return PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight))
case let .draft(draft): case let .draft(draft):
return draft.dimensions return draft.dimensions
case .message, .sticker, .videoCollage: case .message, .gift, .sticker, .videoCollage:
return PixelDimensions(width: 1080, height: 1920) return PixelDimensions(width: 1080, height: 1920)
} }
} }
@ -817,7 +818,7 @@ public final class MediaEditor {
player = self.makePlayer(asset: asset) player = self.makePlayer(asset: asset)
} }
} }
return getChatWallpaperImage(context: self.context, messageId: messageId) return getChatWallpaperImage(context: self.context, peerId: messageId.peerId)
|> map { _, image, nightImage in |> map { _, image, nightImage in
return TextureSourceResult( return TextureSourceResult(
image: image, image: image,
@ -828,6 +829,17 @@ public final class MediaEditor {
) )
} }
} }
case .gift:
textureSource = getChatWallpaperImage(context: self.context, peerId: self.context.account.peerId)
|> map { _, image, nightImage in
return TextureSourceResult(
image: image,
nightImage: nightImage,
player: nil,
playerIsReference: true,
gradientColors: GradientColors(top: .black, bottom: .black)
)
}
case let .sticker(file): case let .sticker(file):
let entity = MediaEditorComposerStickerEntity( let entity = MediaEditorComposerStickerEntity(
postbox: self.context.account.postbox, postbox: self.context.account.postbox,

View File

@ -94,7 +94,7 @@ func composerEntitiesForDrawingEntity(postbox: Postbox, textScale: CGFloat, enti
content = .video(file) content = .video(file)
case .dualVideoReference: case .dualVideoReference:
return [] return []
case .message: case .message, .gift:
if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) { if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) {
var entities: [MediaEditorComposerEntity] = [] var entities: [MediaEditorComposerEntity] = []
entities.append(MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: false)) entities.append(MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: false))

View File

@ -135,7 +135,7 @@ func getTextureImage(device: MTLDevice, texture: MTLTexture, mirror: Bool = fals
return UIImage(cgImage: cgImage) return UIImage(cgImage: cgImage)
} }
public func getChatWallpaperImage(context: AccountContext, messageId: EngineMessage.Id) -> Signal<(CGSize, UIImage?, UIImage?), NoError> { public func getChatWallpaperImage(context: AccountContext, peerId: EnginePeer.Id) -> Signal<(CGSize, UIImage?, UIImage?), NoError> {
let themeSettings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) let themeSettings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings])
|> map { sharedData -> PresentationThemeSettings in |> map { sharedData -> PresentationThemeSettings in
let themeSettings: PresentationThemeSettings let themeSettings: PresentationThemeSettings
@ -148,7 +148,7 @@ public func getChatWallpaperImage(context: AccountContext, messageId: EngineMess
} }
let peerWallpaper = context.account.postbox.transaction { transaction -> TelegramWallpaper? in let peerWallpaper = context.account.postbox.transaction { transaction -> TelegramWallpaper? in
return (transaction.getPeerCachedData(peerId: messageId.peerId) as? CachedChannelData)?.wallpaper return (transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData)?.wallpaper
} }
return combineLatest(themeSettings, peerWallpaper) return combineLatest(themeSettings, peerWallpaper)

View File

@ -203,7 +203,7 @@ extension MediaEditorScreenImpl {
} else if let image = UIImage(contentsOfFile: draft.fullPath(engine: context.engine)) { } else if let image = UIImage(contentsOfFile: draft.fullPath(engine: context.engine)) {
innerSaveDraft(media: .image(image: image, dimensions: draft.dimensions)) innerSaveDraft(media: .image(image: image, dimensions: draft.dimensions))
} }
case .message: case .message, .gift:
if let pixel = generateSingleColorImage(size: CGSize(width: 1, height: 1), color: .black) { if let pixel = generateSingleColorImage(size: CGSize(width: 1, height: 1), color: .black) {
innerSaveDraft(media: .image(image: pixel, dimensions: PixelDimensions(width: 1080, height: 1920))) innerSaveDraft(media: .image(image: pixel, dimensions: PixelDimensions(width: 1080, height: 1920)))
} }

View File

@ -433,7 +433,6 @@ final class MediaEditorScreenComponent: Component {
} }
}, },
dismissTextInput: { dismissTextInput: {
}, },
insertText: { [weak self] text in insertText: { [weak self] text in
if let self { if let self {
@ -1661,7 +1660,17 @@ final class MediaEditorScreenComponent: Component {
var topButtonOffsetX: CGFloat = 0.0 var topButtonOffsetX: CGFloat = 0.0
var topButtonOffsetY: CGFloat = 0.0 var topButtonOffsetY: CGFloat = 0.0
if let subject = controller.node.subject, case .message = subject { var hasDayNightSelection = false
if let subject = controller.node.subject {
switch subject {
case .message, .gift:
hasDayNightSelection = true
default:
break
}
}
if hasDayNightSelection {
let isNightTheme = mediaEditor?.values.nightTheme == true let isNightTheme = mediaEditor?.values.nightTheme == true
let dayNightContentComponent: AnyComponentWithIdentity<Empty> let dayNightContentComponent: AnyComponentWithIdentity<Empty>
@ -3145,6 +3154,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
isSavingAvailable = true isSavingAvailable = true
case .message: case .message:
isSavingAvailable = true isSavingAvailable = true
case .gift:
isSavingAvailable = true
default: default:
isSavingAvailable = false isSavingAvailable = false
} }
@ -3237,8 +3248,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
mediaEditor.seek(initialVideoPosition, andPlay: true) mediaEditor.seek(initialVideoPosition, andPlay: true)
} }
} }
if case .message = subject, self.context.sharedContext.currentPresentationData.with({$0}).autoNightModeTriggered { if self.context.sharedContext.currentPresentationData.with({$0}).autoNightModeTriggered {
mediaEditor.setNightTheme(true) switch subject {
case .message, .gift:
mediaEditor.setNightTheme(true)
default:
break
}
} }
mediaEditor.valuesUpdated = { [weak self] values in mediaEditor.valuesUpdated = { [weak self] values in
if let self, let controller = self.controller, values.gradientColors != nil, controller.previousSavedValues != values { if let self, let controller = self.controller, values.gradientColors != nil, controller.previousSavedValues != values {
@ -3284,28 +3300,33 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
self.stickerMaskDrawingView?.clearWithEmptyColor() self.stickerMaskDrawingView?.clearWithEmptyColor()
} }
if case .message = effectiveSubject { switch effectiveSubject {
} else { case .message, .gift:
break
default:
self.readyValue.set(.single(true)) self.readyValue.set(.single(true))
} }
if case let .image(_, _, additionalImage, position) = effectiveSubject, let additionalImage { switch effectiveSubject {
let image = generateImage(CGSize(width: additionalImage.size.width, height: additionalImage.size.width), contextGenerator: { size, context in case let .image(_, _, additionalImage, position):
let bounds = CGRect(origin: .zero, size: size) if let additionalImage {
context.clear(bounds) let image = generateImage(CGSize(width: additionalImage.size.width, height: additionalImage.size.width), contextGenerator: { size, context in
context.addEllipse(in: bounds) let bounds = CGRect(origin: .zero, size: size)
context.clip() context.clear(bounds)
context.addEllipse(in: bounds)
if let cgImage = additionalImage.cgImage { context.clip()
context.draw(cgImage, in: CGRect(origin: CGPoint(x: (size.width - additionalImage.size.width) / 2.0, y: (size.height - additionalImage.size.height) / 2.0), size: additionalImage.size))
} if let cgImage = additionalImage.cgImage {
}, scale: 1.0) context.draw(cgImage, in: CGRect(origin: CGPoint(x: (size.width - additionalImage.size.width) / 2.0, y: (size.height - additionalImage.size.height) / 2.0), size: additionalImage.size))
let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage, .dualPhoto)) }
imageEntity.referenceDrawingSize = storyDimensions }, scale: 1.0)
imageEntity.scale = 1.625 let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage, .dualPhoto))
imageEntity.position = position.getPosition(storyDimensions) imageEntity.referenceDrawingSize = storyDimensions
self.entitiesView.add(imageEntity, announce: false) imageEntity.scale = 1.625
} else if case let .video(_, _, mirror, additionalVideoPath, _, _, _, changes, position) = effectiveSubject { imageEntity.position = position.getPosition(storyDimensions)
self.entitiesView.add(imageEntity, announce: false)
}
case let .video(_, _, mirror, additionalVideoPath, _, _, _, changes, position):
mediaEditor.setVideoIsMirrored(mirror) mediaEditor.setVideoIsMirrored(mirror)
if let additionalVideoPath { if let additionalVideoPath {
let videoEntity = DrawingStickerEntity(content: .dualVideoReference(false)) let videoEntity = DrawingStickerEntity(content: .dualVideoReference(false))
@ -3324,24 +3345,41 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
} }
} }
} }
} else if case let .videoCollage(items) = effectiveSubject { case let .videoCollage(items):
mediaEditor.setupCollage(items.map { $0.editorItem }) mediaEditor.setupCollage(items.map { $0.editorItem })
} else if case let .message(messageIds) = effectiveSubject { case let .sticker(_, emoji):
controller.stickerSelectedEmoji = emoji
case .message, .gift:
var isGift = false
let messages: Signal<[Message], NoError>
if case let .message(messageIds) = effectiveSubject {
messages = self.context.engine.data.get(
EngineDataMap(messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:)))
)
|> map { result in
var messages: [Message] = []
for id in messageIds {
if let maybeMessage = result[id], let message = maybeMessage {
messages.append(message._asMessage())
}
}
return messages
}
} else if case let .gift(gift) = effectiveSubject {
isGift = true
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false))]
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: Namespaces.Message.Cloud, id: -1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: media, peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
messages = .single([message])
} else {
fatalError()
}
let isNightTheme = mediaEditor.values.nightTheme let isNightTheme = mediaEditor.values.nightTheme
let _ = ((self.context.engine.data.get( let _ = (messages
EngineDataMap(messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:))) |> deliverOnMainQueue).start(next: { [weak self] messages in
))
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else { guard let self else {
return return
} }
var messages: [Message] = []
for id in messageIds {
if let maybeMessage = result[id], let message = maybeMessage {
messages.append(message._asMessage())
}
}
var messageFile: TelegramMediaFile? var messageFile: TelegramMediaFile?
if let maybeFile = messages.first?.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, maybeFile.isVideo, let _ = self.context.account.postbox.mediaBox.completedResourcePath(maybeFile.resource, pathExtension: nil) { if let maybeFile = messages.first?.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, maybeFile.isVideo, let _ = self.context.account.postbox.mediaBox.completedResourcePath(maybeFile.resource, pathExtension: nil) {
messageFile = maybeFile messageFile = maybeFile
@ -3350,7 +3388,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
messageFile = nil messageFile = nil
} }
let renderer = DrawingMessageRenderer(context: self.context, messages: messages, parentView: self.view) let renderer = DrawingMessageRenderer(context: self.context, messages: messages, parentView: self.view, isGift: isGift)
renderer.render(completion: { result in renderer.render(completion: { result in
if case .draft = subject, let existingEntityView = self.entitiesView.getView(where: { entityView in if case .draft = subject, let existingEntityView = self.entitiesView.getView(where: { entityView in
if let stickerEntityView = entityView as? DrawingStickerEntityView, case .message = (stickerEntityView.entity as! DrawingStickerEntity).content { if let stickerEntityView = entityView as? DrawingStickerEntityView, case .message = (stickerEntityView.entity as! DrawingStickerEntity).content {
@ -3366,12 +3404,25 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
messageEntity.overlayRenderImage = result.overlayImage messageEntity.overlayRenderImage = result.overlayImage
existingEntityView.update(animated: false) existingEntityView.update(animated: false)
} else { } else {
let messageEntity = DrawingStickerEntity(content: .message(messageIds, result.size, messageFile, result.mediaFrame?.rect, result.mediaFrame?.cornerRadius)) var content: DrawingStickerEntity.Content
var position: CGPoint
switch effectiveSubject {
case let .message(messageIds):
content = .message(messageIds, result.size, messageFile, result.mediaFrame?.rect, result.mediaFrame?.cornerRadius)
position = CGPoint(x: storyDimensions.width / 2.0 - 54.0, y: storyDimensions.height / 2.0)
case let .gift(gift):
content = .gift(gift, result.size)
position = CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0)
default:
fatalError()
}
let messageEntity = DrawingStickerEntity(content: content)
messageEntity.renderImage = result.dayImage messageEntity.renderImage = result.dayImage
messageEntity.secondaryRenderImage = result.nightImage messageEntity.secondaryRenderImage = result.nightImage
messageEntity.overlayRenderImage = result.overlayImage messageEntity.overlayRenderImage = result.overlayImage
messageEntity.referenceDrawingSize = storyDimensions messageEntity.referenceDrawingSize = storyDimensions
messageEntity.position = CGPoint(x: storyDimensions.width / 2.0 - 54.0, y: storyDimensions.height / 2.0) messageEntity.position = position
let fraction = max(result.size.width, result.size.height) / 353.0 let fraction = max(result.size.width, result.size.height) / 353.0
messageEntity.scale = min(6.0, 3.3 * fraction) messageEntity.scale = min(6.0, 3.3 * fraction)
@ -3386,10 +3437,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
self.readyValue.set(.single(true)) self.readyValue.set(.single(true))
}) })
}) })
} else if case let .sticker(_, emoji) = effectiveSubject { default:
controller.stickerSelectedEmoji = emoji break
} }
self.gradientColorsDisposable = mediaEditor.gradientColors.start(next: { [weak self] colors in self.gradientColorsDisposable = mediaEditor.gradientColors.start(next: { [weak self] colors in
if let self, let colors { if let self, let colors {
let gradientImage = generateGradientImage(size: CGSize(width: 5.0, height: 640.0), colors: colors.array, locations: [0.0, 1.0]) let gradientImage = generateGradientImage(size: CGSize(width: 5.0, height: 640.0), colors: colors.array, locations: [0.0, 1.0])
@ -3749,10 +3800,12 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
} }
private var canEnhance: Bool { private var canEnhance: Bool {
if case .message = self.subject { switch self.subject {
case .message, .gift:
return false return false
default:
return true
} }
return true
} }
private var enhanceInitialTranslation: Float? private var enhanceInitialTranslation: Float?
@ -3856,8 +3909,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
guard !self.isCollageTimelineOpen else { guard !self.isCollageTimelineOpen else {
return return
} }
if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, case .message = subject, !self.entitiesView.hasSelection { if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, !self.entitiesView.hasSelection {
return switch subject {
case .message, .gift:
return
default:
break
}
} }
let currentTimestamp = CACurrentMediaTime() let currentTimestamp = CACurrentMediaTime()
if let previousPanTimestamp = self.previousPanTimestamp, currentTimestamp - previousPanTimestamp < 0.016, case .changed = gestureRecognizer.state { if let previousPanTimestamp = self.previousPanTimestamp, currentTimestamp - previousPanTimestamp < 0.016, case .changed = gestureRecognizer.state {
@ -3871,8 +3929,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
guard !self.isCollageTimelineOpen else { guard !self.isCollageTimelineOpen else {
return return
} }
if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, case .message = subject, !self.entitiesView.hasSelection { if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, !self.entitiesView.hasSelection {
return switch subject {
case .message, .gift:
return
default:
break
}
} }
let currentTimestamp = CACurrentMediaTime() let currentTimestamp = CACurrentMediaTime()
if let previousPinchTimestamp = self.previousPinchTimestamp, currentTimestamp - previousPinchTimestamp < 0.016, case .changed = gestureRecognizer.state { if let previousPinchTimestamp = self.previousPinchTimestamp, currentTimestamp - previousPinchTimestamp < 0.016, case .changed = gestureRecognizer.state {
@ -3886,8 +3949,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
guard !self.isCollageTimelineOpen else { guard !self.isCollageTimelineOpen else {
return return
} }
if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, case .message = subject, !self.entitiesView.hasSelection { if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, !self.entitiesView.hasSelection {
return switch subject {
case .message, .gift:
return
default:
break
}
} }
let currentTimestamp = CACurrentMediaTime() let currentTimestamp = CACurrentMediaTime()
if let previousRotateTimestamp = self.previousRotateTimestamp, currentTimestamp - previousRotateTimestamp < 0.016, case .changed = gestureRecognizer.state { if let previousRotateTimestamp = self.previousRotateTimestamp, currentTimestamp - previousRotateTimestamp < 0.016, case .changed = gestureRecognizer.state {
@ -4072,7 +4140,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
var animateIn = false var animateIn = false
if let subject { if let subject {
switch subject { switch subject {
case .empty, .message, .sticker, .image: case .empty, .message, .gift, .sticker, .image:
animateIn = true animateIn = true
default: default:
break break
@ -5924,6 +5992,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
case asset(PHAsset) case asset(PHAsset)
case draft(MediaEditorDraft, Int64?) case draft(MediaEditorDraft, Int64?)
case message([MessageId]) case message([MessageId])
case gift(StarGift.UniqueGift)
case sticker(TelegramMediaFile, [String]) case sticker(TelegramMediaFile, [String])
var dimensions: PixelDimensions { var dimensions: PixelDimensions {
@ -5936,7 +6005,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
return PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight)) return PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight))
case let .draft(draft, _): case let .draft(draft, _):
return draft.dimensions return draft.dimensions
case .message, .sticker, .videoCollage: case .message, .gift, .sticker, .videoCollage:
return PixelDimensions(width: 1080, height: 1920) return PixelDimensions(width: 1080, height: 1920)
} }
} }
@ -5960,6 +6029,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
return .draft(draft) return .draft(draft)
case let .message(messageIds): case let .message(messageIds):
return .message(messageIds.first!) return .message(messageIds.first!)
case let .gift(gift):
return .gift(gift)
case let .sticker(sticker, _): case let .sticker(sticker, _):
return .sticker(sticker) return .sticker(sticker)
} }
@ -5985,6 +6056,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
return draft.isVideo return draft.isVideo
case .message: case .message:
return false return false
case .gift:
return false
case .sticker: case .sticker:
return false return false
} }
@ -6189,9 +6262,12 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
if self.isEditingStory { if self.isEditingStory {
needsAudioSession = true needsAudioSession = true
} }
if case .message = subject { switch subject {
case .message, .gift:
needsAudioSession = true needsAudioSession = true
checkPostingAvailability = true checkPostingAvailability = true
default:
break
} }
if needsAudioSession { if needsAudioSession {
self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .record(speaker: false, video: true, withOthers: true), activate: { _ in self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .record(speaker: false, video: true, withOthers: true), activate: { _ in
@ -7195,9 +7271,16 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
firstFrame = .single((UIImage(), nil)) firstFrame = .single((UIImage(), nil))
} }
} }
case let .message(messages): case .message, .gift:
let peerId: EnginePeer.Id
if case let .message(messageIds) = subject {
peerId = messageIds.first!.peerId
} else {
peerId = self.context.account.peerId
}
let isNightTheme = mediaEditor.values.nightTheme let isNightTheme = mediaEditor.values.nightTheme
let wallpaper = getChatWallpaperImage(context: self.context, messageId: messages.first!) let wallpaper = getChatWallpaperImage(context: self.context, peerId: peerId)
|> map { _, image, nightImage -> UIImage? in |> map { _, image, nightImage -> UIImage? in
if isNightTheme { if isNightTheme {
return nightImage ?? image return nightImage ?? image
@ -8055,7 +8138,18 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
} }
case let .message(messages): case let .message(messages):
let isNightTheme = mediaEditor.values.nightTheme let isNightTheme = mediaEditor.values.nightTheme
exportSubject = getChatWallpaperImage(context: self.context, messageId: messages.first!) exportSubject = getChatWallpaperImage(context: self.context, peerId: messages.first!.peerId)
|> mapToSignal { _, image, nightImage -> Signal<MediaEditorVideoExport.Subject, NoError> in
if isNightTheme {
let effectiveImage = nightImage ?? image
return effectiveImage.flatMap({ .single(.image(image: $0)) }) ?? .complete()
} else {
return image.flatMap({ .single(.image(image: $0)) }) ?? .complete()
}
}
case .gift:
let isNightTheme = mediaEditor.values.nightTheme
exportSubject = getChatWallpaperImage(context: self.context, peerId: self.context.account.peerId)
|> mapToSignal { _, image, nightImage -> Signal<MediaEditorVideoExport.Subject, NoError> in |> mapToSignal { _, image, nightImage -> Signal<MediaEditorVideoExport.Subject, NoError> in
if isNightTheme { if isNightTheme {
let effectiveImage = nightImage ?? image let effectiveImage = nightImage ?? image

View File

@ -3342,14 +3342,14 @@ final class StoryItemSetContainerSendMessage {
useGesturePosition = true useGesturePosition = true
let action = { [weak self, weak view, weak controller] in let action = { [weak self, weak view, weak controller] in
let _ = ((context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: true)) let _ = ((context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: true))
|> mapToSignal { result -> Signal<Message?, GetMessagesError> in |> mapToSignal { result -> Signal<Message?, GetMessagesError> in
if case let .result(messages) = result { if case let .result(messages) = result {
return .single(messages.first) return .single(messages.first)
} else { } else {
return .complete() return .complete()
} }
}) })
|> deliverOnMainQueue).startStandalone(next: { [weak self, weak view, weak controller] message in |> deliverOnMainQueue).startStandalone(next: { [weak self, weak view, weak controller] message in
guard let self, let view else { guard let self, let view else {
return return
} }
@ -3365,7 +3365,7 @@ final class StoryItemSetContainerSendMessage {
switch error { switch error {
case .privateChannel: case .privateChannel:
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)) let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId))
|> deliverOnMainQueue).startStandalone(next: { [weak self, weak view] peer in |> deliverOnMainQueue).startStandalone(next: { [weak self, weak view] peer in
guard let self, let view else { guard let self, let view else {
return return
} }
@ -3416,6 +3416,31 @@ final class StoryItemSetContainerSendMessage {
})) }))
case .weather: case .weather:
return return
case let .starGift(_, slug):
useGesturePosition = true
let action = {
let _ = openUserGeneratedUrl(context: component.context, peerId: nil, url: "https://t.me/nft/\(slug)", concealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: true, present: { [weak controller] c in
controller?.present(c, in: .window(.root))
}, openResolved: { [weak self, weak view] resolved in
guard let self, let view else {
return
}
self.openResolved(view: view, result: resolved, forceExternal: false, concealed: false)
}, alertDisplayUpdated: { [weak self, weak view] alertController in
guard let self, let view else {
return
}
self.statusController = alertController
view.updateIsProgressPaused()
})
}
if immediate {
action()
return
}
actions.append(ContextMenuAction(content: .textWithIcon(title: updatedPresentationData.initial.strings.Story_ViewGift, icon: generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: .white)), action: {
action()
}))
} }
self.selectedMediaArea = mediaArea self.selectedMediaArea = mediaArea

View File

@ -27,10 +27,21 @@ import StoryContainerScreen
import SaveToCameraRoll import SaveToCameraRoll
import MediaEditorScreen import MediaEditorScreen
enum StorySharingSubject {
case messages([Message])
case gift(StarGift.UniqueGift)
}
extension ChatControllerImpl { extension ChatControllerImpl {
func openStorySharing(messages: [Message]) { func openStorySharing(subject: StorySharingSubject) {
let context = self.context let context = self.context
let subject: Signal<MediaEditorScreenImpl.Subject?, NoError> = .single(.message(messages.map { $0.id })) let editorSubject: Signal<MediaEditorScreenImpl.Subject?, NoError>
switch subject {
case let .messages(messages):
editorSubject = .single(.message(messages.map { $0.id }))
case let .gift(gift):
editorSubject = .single(.gift(gift))
}
let externalState = MediaEditorTransitionOutExternalState( let externalState = MediaEditorTransitionOutExternalState(
storyTarget: nil, storyTarget: nil,
@ -42,7 +53,7 @@ extension ChatControllerImpl {
let controller = MediaEditorScreenImpl( let controller = MediaEditorScreenImpl(
context: context, context: context,
mode: .storyEditor, mode: .storyEditor,
subject: subject, subject: editorSubject,
transitionIn: nil, transitionIn: nil,
transitionOut: { _, _ in transitionOut: { _, _ in
return nil return nil

View File

@ -1198,7 +1198,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.push(controller) strongSelf.push(controller)
return true return true
case .starGift, .starGiftUnique: case .starGift, .starGiftUnique:
let controller = strongSelf.context.sharedContext.makeGiftViewScreen(context: strongSelf.context, message: EngineMessage(message)) let controller = strongSelf.context.sharedContext.makeGiftViewScreen(context: strongSelf.context, message: EngineMessage(message), shareStory: { [weak self] in
if let self, case let .starGiftUnique(gift, _, _, _, _, _, _) = action.action, case let .unique(uniqueGift) = gift {
Queue.mainQueue().after(0.15) {
self.openStorySharing(subject: .gift(uniqueGift))
}
}
})
strongSelf.push(controller) strongSelf.push(controller)
return true return true
case .giftStars: case .giftStars:

View File

@ -134,7 +134,7 @@ extension ChatControllerImpl {
return return
} }
Queue.mainQueue().after(0.15) { Queue.mainQueue().after(0.15) {
self.openStorySharing(messages: messages) self.openStorySharing(subject: .messages(messages))
} }
} }
} }

View File

@ -1211,6 +1211,13 @@ func openResolvedUrlImpl(
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
} }
}) })
case let .collectible(gift):
if let gift {
let controller = context.sharedContext.makeGiftViewScreen(context: context, gift: gift, shareStory: nil)
navigationController?.pushViewController(controller)
} else {
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
case let .messageLink(link): case let .messageLink(link):
if let link { if let link {
if let navigationController = navigationController { if let navigationController = navigationController {

View File

@ -655,6 +655,22 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
convertedUrl = "https://t.me/addtheme/\(parameter)" convertedUrl = "https://t.me/addtheme/\(parameter)"
} }
} }
} else if parsedUrl.host == "nft" {
if let components = URLComponents(string: "/?" + query) {
var slug: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "slug" {
slug = value
}
}
}
}
if let slug {
convertedUrl = "https://t.me/nft/\(slug)"
}
}
} else if parsedUrl.host == "privatepost" { } else if parsedUrl.host == "privatepost" {
if let components = URLComponents(string: "/?" + query) { if let components = URLComponents(string: "/?" + query) {
var channelId: Int64? var channelId: Int64?

View File

@ -76,6 +76,7 @@ import StarsIntroScreen
import ContentReportScreen import ContentReportScreen
import AffiliateProgramSetupScreen import AffiliateProgramSetupScreen
import GalleryUI import GalleryUI
import ShareController
private final class AccountUserInterfaceInUseContext { private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>() let subscribers = Bag<(Bool) -> Void>()
@ -2922,8 +2923,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return StarsIntroScreen(context: context) return StarsIntroScreen(context: context)
} }
public func makeGiftViewScreen(context: AccountContext, message: EngineMessage) -> ViewController { public func makeGiftViewScreen(context: AccountContext, message: EngineMessage, shareStory: (() -> Void)?) -> ViewController {
return GiftViewScreen(context: context, subject: .message(message)) return GiftViewScreen(context: context, subject: .message(message), shareStory: shareStory)
}
public func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: (() -> Void)?) -> ViewController {
return GiftViewScreen(context: context, subject: .uniqueGift(gift), shareStory: shareStory)
} }
public func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?) { public func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?) {
@ -2935,6 +2940,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}) })
} }
public func makeShareController(context: AccountContext, subject: ShareControllerSubject, forceExternal: Bool, shareStory: (() -> Void)?, actionCompleted: (() -> Void)?) -> ViewController {
let controller = ShareController(context: context, subject: subject, externalShare: forceExternal)
controller.shareStory = shareStory
controller.actionCompleted = actionCompleted
return controller
}
public func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal<MiniAppListScreenInitialData, NoError> { public func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal<MiniAppListScreenInitialData, NoError> {
return MiniAppListScreen.initialData(context: context) return MiniAppListScreen.initialData(context: context)
} }

View File

@ -105,6 +105,7 @@ public enum ParsedInternalUrl {
case chatFolder(slug: String) case chatFolder(slug: String)
case premiumGiftCode(slug: String) case premiumGiftCode(slug: String)
case messageLink(slug: String) case messageLink(slug: String)
case collectible(slug: String)
case externalUrl(url: String) case externalUrl(url: String)
} }
@ -523,6 +524,8 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou
return .wallpaper(parameter) return .wallpaper(parameter)
} else if pathComponents[0] == "addtheme" { } else if pathComponents[0] == "addtheme" {
return .theme(pathComponents[1]) return .theme(pathComponents[1])
} else if pathComponents[0] == "nft" {
return .collectible(slug: pathComponents[1])
} else if pathComponents[0] == "addlist" || pathComponents[0] == "folder" || pathComponents[0] == "list" { } else if pathComponents[0] == "addlist" || pathComponents[0] == "folder" || pathComponents[0] == "list" {
return .chatFolder(slug: pathComponents[1]) return .chatFolder(slug: pathComponents[1])
} else if pathComponents[0] == "boost", pathComponents.count == 2 { } else if pathComponents[0] == "boost", pathComponents.count == 2 {
@ -1086,6 +1089,11 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
} }
case let .premiumGiftCode(slug): case let .premiumGiftCode(slug):
return .single(.result(.premiumGiftCode(slug: slug))) return .single(.result(.premiumGiftCode(slug: slug)))
case let .collectible(slug):
return .single(.progress) |> then(context.engine.payments.getUniqueStarGift(slug: slug)
|> map { gift -> ResolveInternalUrlResult in
return .result(.collectible(gift: gift))
})
case let .messageLink(slug): case let .messageLink(slug):
return .single(.progress) return .single(.progress)
|> then(context.engine.peers.resolveMessageLink(slug: slug) |> then(context.engine.peers.resolveMessageLink(slug: slug)