mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Merge commit '85b6a8ffe96bf8a5b1c7c5b5180024bc07770ba2'
This commit is contained in:
commit
98433a4a6f
@ -13218,3 +13218,6 @@ Sorry for the inconvenience.";
|
||||
"Gift.Convert.Period.Unavailable.Text" = "Sorry, you can't convert this gift.\n\nStars can only be claimed within %@ after receiving a gift.";
|
||||
"Gift.Convert.Period.Unavailable.Days_1" = "%@ day";
|
||||
"Gift.Convert.Period.Unavailable.Days_any" = "%@ days";
|
||||
|
||||
"Gift.Send.TitleTo" = "Gift to %@";
|
||||
"Gift.Send.SendShort" = "Send";
|
||||
|
@ -278,6 +278,12 @@ public enum StickerPackUrlType {
|
||||
case emoji
|
||||
}
|
||||
|
||||
public enum ResolvedStartAppMode {
|
||||
case generic
|
||||
case compact
|
||||
case fullscreen
|
||||
}
|
||||
|
||||
public enum ResolvedUrl {
|
||||
case externalUrl(String)
|
||||
case urlAuth(String)
|
||||
@ -1044,7 +1050,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal<MiniAppListScreenInitialData, NoError>
|
||||
func makeMiniAppListScreen(context: AccountContext, initialData: MiniAppListScreenInitialData) -> ViewController
|
||||
|
||||
func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peer: EnginePeer, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?)
|
||||
func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?)
|
||||
|
||||
func makeDebugSettingsController(context: AccountContext?) -> ViewController?
|
||||
|
||||
|
@ -301,13 +301,13 @@ public struct ChatControllerInitialBotAppStart {
|
||||
public let botApp: BotApp?
|
||||
public let payload: String?
|
||||
public let justInstalled: Bool
|
||||
public let compact: Bool
|
||||
public let mode: ResolvedStartAppMode
|
||||
|
||||
public init(botApp: BotApp?, payload: String?, justInstalled: Bool, compact: Bool) {
|
||||
public init(botApp: BotApp?, payload: String?, justInstalled: Bool, mode: ResolvedStartAppMode) {
|
||||
self.botApp = botApp
|
||||
self.payload = payload
|
||||
self.justInstalled = justInstalled
|
||||
self.compact = compact
|
||||
self.mode = mode
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -524,7 +524,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
var containerTopInset: CGFloat
|
||||
if isLandscape {
|
||||
if isLandscape || controllers.last?.isFullscreen == true {
|
||||
containerTopInset = 0.0
|
||||
containerLayout = layout
|
||||
|
||||
|
@ -124,7 +124,7 @@ public protocol AttachmentContainable: ViewController, MinimizableController {
|
||||
var isInnerPanGestureEnabled: (() -> Bool)? { get }
|
||||
var mediaPickerContext: AttachmentMediaPickerContext? { get }
|
||||
var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { get }
|
||||
|
||||
|
||||
func isContainerPanningUpdated(_ panning: Bool)
|
||||
|
||||
func resetForReuse()
|
||||
@ -165,6 +165,10 @@ public extension AttachmentContainable {
|
||||
return nil
|
||||
}
|
||||
|
||||
var isFullscreen: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var minimizedTopEdgeOffset: CGFloat? {
|
||||
return nil
|
||||
}
|
||||
@ -363,6 +367,10 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
public var minimizedIcon: UIImage? {
|
||||
return self.mainController.minimizedIcon
|
||||
}
|
||||
|
||||
public var isFullscreen: Bool {
|
||||
return self.mainController.isFullscreen
|
||||
}
|
||||
|
||||
private final class Node: ASDisplayNode {
|
||||
private weak var controller: AttachmentController?
|
||||
@ -1268,6 +1276,10 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
|
||||
public var ensureUnfocused = true
|
||||
|
||||
public func requestMinimize(topEdgeOffset: CGFloat?, initialVelocity: CGFloat?) {
|
||||
self.node.minimize()
|
||||
}
|
||||
|
||||
public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
if self.ensureUnfocused {
|
||||
self.view.endEditing(true)
|
||||
@ -1397,7 +1409,9 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
public func makeContentSnapshotView() -> UIView? {
|
||||
let snapshotView = self.view.snapshotView(afterScreenUpdates: false)
|
||||
if let contentSnapshotView = self.mainController.makeContentSnapshotView() {
|
||||
contentSnapshotView.frame = contentSnapshotView.frame.offsetBy(dx: 0.0, dy: 64.0 + 56.0)
|
||||
if !self.mainController.isFullscreen {
|
||||
contentSnapshotView.frame = contentSnapshotView.frame.offsetBy(dx: 0.0, dy: 64.0 + 56.0)
|
||||
}
|
||||
snapshotView?.addSubview(contentSnapshotView)
|
||||
}
|
||||
return snapshotView
|
||||
|
@ -638,8 +638,11 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
self.webView.reloadInputViews()
|
||||
}
|
||||
|
||||
self.webView.customBottomInset = safeInsets.bottom * (1.0 - insets.bottom / fullInsets.bottom)
|
||||
|
||||
if fullInsets.bottom.isZero {
|
||||
self.webView.customBottomInset = safeInsets.bottom
|
||||
} else {
|
||||
self.webView.customBottomInset = safeInsets.bottom * (1.0 - insets.bottom / fullInsets.bottom)
|
||||
}
|
||||
// self.webView.scrollView.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right)
|
||||
// self.webView.scrollView.horizontalScrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right)
|
||||
|
||||
@ -1457,8 +1460,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
}
|
||||
}
|
||||
|
||||
if result.isEmpty, let webViewUrl = self.webView.url {
|
||||
let schemeAndHostUrl = webViewUrl.deletingPathExtension()
|
||||
if result.isEmpty, let webViewUrl = self.webView.url, let schemeAndHostUrl = URL(string: "/", relativeTo: webViewUrl) {
|
||||
let url = schemeAndHostUrl.appendingPathComponent("favicon.ico")
|
||||
result.insert(Favicon(url: url.absoluteString, dimensions: nil))
|
||||
}
|
||||
|
@ -3816,7 +3816,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
context: self.context,
|
||||
parentController: parentController,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer,
|
||||
botPeer: peer,
|
||||
chatPeer: nil,
|
||||
threadId: nil,
|
||||
buttonText: "",
|
||||
url: "",
|
||||
@ -3840,7 +3841,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
context: self.context,
|
||||
parentController: parentController,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer,
|
||||
botPeer: peer,
|
||||
chatPeer: nil,
|
||||
threadId: nil,
|
||||
buttonText: "",
|
||||
url: "",
|
||||
|
@ -30,6 +30,7 @@ public protocol MinimizableController: ViewController {
|
||||
var isMinimizable: Bool { get }
|
||||
var minimizedIcon: UIImage? { get }
|
||||
var minimizedProgress: Float? { get }
|
||||
var isFullscreen: Bool { get }
|
||||
|
||||
func requestMinimize(topEdgeOffset: CGFloat?, initialVelocity: CGFloat?)
|
||||
func makeContentSnapshotView() -> UIView?
|
||||
@ -41,6 +42,10 @@ public protocol MinimizableController: ViewController {
|
||||
}
|
||||
|
||||
public extension MinimizableController {
|
||||
var isFullscreen: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var minimizedTopEdgeOffset: CGFloat? {
|
||||
return nil
|
||||
}
|
||||
|
@ -920,6 +920,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
|
||||
}
|
||||
|
||||
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:editableItem intent:intent adjustments:nil caption:nil screenImage:thumbnailImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent:!_disableStickers] selectedTab:TGPhotoEditorCropTab];
|
||||
controller.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
controller.editingContext = _editingContext;
|
||||
controller.stickersContext = _stickersContext;
|
||||
controller.dontHideStatusBar = true;
|
||||
|
@ -449,6 +449,7 @@
|
||||
}
|
||||
|
||||
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:_context item:editableItem intent:intent adjustments:nil caption:nil screenImage:thumbnailImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent:_intent != TGMediaAssetsControllerSetSignupProfilePhotoIntent] selectedTab:TGPhotoEditorCropTab];
|
||||
controller.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
controller.stickersContext = self.stickersContext;
|
||||
controller.editingContext = self.editingContext;
|
||||
controller.didFinishRenderingFullSizeImage = ^(UIImage *resultImage)
|
||||
|
@ -340,6 +340,7 @@
|
||||
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:_saveEditedPhotos saveCapturedMedia:_saveCapturedMedia camera:cameraView.previewView.camera previewView:cameraView.previewView intent:_signup ? TGCameraControllerSignupAvatarIntent : TGCameraControllerAvatarIntent];
|
||||
else
|
||||
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:_saveEditedPhotos saveCapturedMedia:_saveCapturedMedia intent:_signup ? TGCameraControllerSignupAvatarIntent : TGCameraControllerAvatarIntent];
|
||||
controller.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
controller.stickersContext = _stickersContext;
|
||||
controller.shouldStoreCapturedAssets = true;
|
||||
|
||||
|
@ -13,18 +13,27 @@ public final class MoreButtonNode: ASDisplayNode {
|
||||
case search
|
||||
}
|
||||
|
||||
private let encircled: Bool
|
||||
private let duration: Double = 0.21
|
||||
public var iconState: State = .search
|
||||
|
||||
init() {
|
||||
super.init(size: CGSize(width: 30.0, height: 30.0))
|
||||
init(size: CGSize = CGSize(width: 30.0, height: 30.0), encircled: Bool) {
|
||||
self.encircled = encircled
|
||||
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_moretosearch"), frames: .range(startFrame: 90, endFrame: 90), duration: 0.0))
|
||||
super.init(size: size)
|
||||
|
||||
if self.encircled {
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_moretosearch"), frames: .range(startFrame: 90, endFrame: 90), duration: 0.0))
|
||||
} else {
|
||||
self.iconState = .more
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_baremoredots"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.0))
|
||||
}
|
||||
}
|
||||
|
||||
func play() {
|
||||
if case .more = self.iconState {
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_moredots"), frames: .range(startFrame: 0, endFrame: 46), duration: 0.76))
|
||||
let animationName = self.encircled ? "anim_moredots" : "anim_baremoredots"
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local(animationName), frames: .range(startFrame: 0, endFrame: 46), duration: 0.76))
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,6 +90,7 @@ public final class MoreButtonNode: ASDisplayNode {
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
private let size: CGSize
|
||||
|
||||
public func updateColor(_ color: UIColor?, transition: ContainedViewLayoutTransition) {
|
||||
self.color = color
|
||||
@ -104,15 +114,16 @@ public final class MoreButtonNode: ASDisplayNode {
|
||||
self.iconNode.customColor = color
|
||||
}
|
||||
|
||||
public init(theme: PresentationTheme) {
|
||||
public init(theme: PresentationTheme, size: CGSize = CGSize(width: 30.0, height: 30.0), encircled: Bool = true) {
|
||||
self.theme = theme
|
||||
self.size = size
|
||||
|
||||
self.contextSourceNode = ContextReferenceContentNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.containerNode.animateScale = false
|
||||
|
||||
self.buttonNode = HighlightableButtonNode()
|
||||
self.iconNode = MoreIconNode()
|
||||
self.iconNode = MoreIconNode(size: size, encircled: encircled)
|
||||
self.iconNode.customColor = self.theme.rootController.navigationBar.buttonColor
|
||||
|
||||
super.init()
|
||||
@ -143,7 +154,7 @@ public final class MoreButtonNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
let animationSize = CGSize(width: 30.0, height: 30.0)
|
||||
let animationSize = self.size
|
||||
let inset: CGFloat = 0.0
|
||||
let iconFrame = CGRect(origin: CGPoint(x: inset + 6.0, y: floor((constrainedSize.height - animationSize.height) / 2.0) + 1.0), size: animationSize)
|
||||
|
||||
|
@ -83,6 +83,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1262359766] = { return Api.Boost.parse_boost($0) }
|
||||
dict[-1778593322] = { return Api.BotApp.parse_botApp($0) }
|
||||
dict[1571189943] = { return Api.BotApp.parse_botAppNotModified($0) }
|
||||
dict[-2103898979] = { return Api.BotAppSettings.parse_botAppSettings($0) }
|
||||
dict[-1989921868] = { return Api.BotBusinessConnection.parse_botBusinessConnection($0) }
|
||||
dict[-1032140601] = { return Api.BotCommand.parse_botCommand($0) }
|
||||
dict[-1180016534] = { return Api.BotCommandScope.parse_botCommandScopeChatAdmins($0) }
|
||||
@ -92,7 +93,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1071145937] = { return Api.BotCommandScope.parse_botCommandScopePeerAdmins($0) }
|
||||
dict[169026035] = { return Api.BotCommandScope.parse_botCommandScopePeerUser($0) }
|
||||
dict[1011811544] = { return Api.BotCommandScope.parse_botCommandScopeUsers($0) }
|
||||
dict[-2109505932] = { return Api.BotInfo.parse_botInfo($0) }
|
||||
dict[912290611] = { return Api.BotInfo.parse_botInfo($0) }
|
||||
dict[1984755728] = { return Api.BotInlineMessage.parse_botInlineMessageMediaAuto($0) }
|
||||
dict[416402882] = { return Api.BotInlineMessage.parse_botInlineMessageMediaContact($0) }
|
||||
dict[85477117] = { return Api.BotInlineMessage.parse_botInlineMessageMediaGeo($0) }
|
||||
@ -487,7 +488,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-193992412] = { return Api.InputWebFileLocation.parse_inputWebFileAudioAlbumThumbLocation($0) }
|
||||
dict[-1625153079] = { return Api.InputWebFileLocation.parse_inputWebFileGeoPointLocation($0) }
|
||||
dict[-1036396922] = { return Api.InputWebFileLocation.parse_inputWebFileLocation($0) }
|
||||
dict[1572428309] = { return Api.Invoice.parse_invoice($0) }
|
||||
dict[77522308] = { return Api.Invoice.parse_invoice($0) }
|
||||
dict[-1059185703] = { return Api.JSONObjectValue.parse_jsonObjectValue($0) }
|
||||
dict[-146520221] = { return Api.JSONValue.parse_jsonArray($0) }
|
||||
dict[-952869270] = { return Api.JSONValue.parse_jsonBool($0) }
|
||||
@ -899,7 +900,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1798404822] = { return Api.StarsGiveawayOption.parse_starsGiveawayOption($0) }
|
||||
dict[1411605001] = { return Api.StarsGiveawayWinnersOption.parse_starsGiveawayWinnersOption($0) }
|
||||
dict[2033461574] = { return Api.StarsRevenueStatus.parse_starsRevenueStatus($0) }
|
||||
dict[1401868056] = { return Api.StarsSubscription.parse_starsSubscription($0) }
|
||||
dict[779004698] = { return Api.StarsSubscription.parse_starsSubscription($0) }
|
||||
dict[88173912] = { return Api.StarsSubscriptionPricing.parse_starsSubscriptionPricing($0) }
|
||||
dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($0) }
|
||||
dict[903148150] = { return Api.StarsTransaction.parse_starsTransaction($0) }
|
||||
@ -974,6 +975,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[675009298] = { return Api.Update.parse_updateBotPurchasedPaidMedia($0) }
|
||||
dict[-1246823043] = { return Api.Update.parse_updateBotShippingQuery($0) }
|
||||
dict[-997782967] = { return Api.Update.parse_updateBotStopped($0) }
|
||||
dict[756270830] = { return Api.Update.parse_updateBotSubscriptionExpire($0) }
|
||||
dict[-2095595325] = { return Api.Update.parse_updateBotWebhookJSON($0) }
|
||||
dict[-1684914010] = { return Api.Update.parse_updateBotWebhookJSONQuery($0) }
|
||||
dict[-539401739] = { return Api.Update.parse_updateBroadcastRevenueTransactions($0) }
|
||||
@ -1265,6 +1267,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1626924713] = { return Api.messages.AvailableReactions.parse_availableReactionsNotModified($0) }
|
||||
dict[-347034123] = { return Api.messages.BotApp.parse_botApp($0) }
|
||||
dict[911761060] = { return Api.messages.BotCallbackAnswer.parse_botCallbackAnswer($0) }
|
||||
dict[-1899035375] = { return Api.messages.BotPreparedInlineMessage.parse_botPreparedInlineMessage($0) }
|
||||
dict[-534646026] = { return Api.messages.BotResults.parse_botResults($0) }
|
||||
dict[-1231326505] = { return Api.messages.ChatAdminsWithInvites.parse_chatAdminsWithInvites($0) }
|
||||
dict[-438840932] = { return Api.messages.ChatFull.parse_chatFull($0) }
|
||||
@ -1306,6 +1309,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-83926371] = { return Api.messages.MyStickers.parse_myStickers($0) }
|
||||
dict[863093588] = { return Api.messages.PeerDialogs.parse_peerDialogs($0) }
|
||||
dict[1753266509] = { return Api.messages.PeerSettings.parse_peerSettings($0) }
|
||||
dict[1636301421] = { return Api.messages.PreparedInlineMessage.parse_preparedInlineMessage($0) }
|
||||
dict[-963811691] = { return Api.messages.QuickReplies.parse_quickReplies($0) }
|
||||
dict[1603398491] = { return Api.messages.QuickReplies.parse_quickRepliesNotModified($0) }
|
||||
dict[-352454890] = { return Api.messages.Reactions.parse_reactions($0) }
|
||||
@ -1510,6 +1514,8 @@ public extension Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.BotApp:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.BotAppSettings:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.BotBusinessConnection:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.BotCommand:
|
||||
@ -2296,6 +2302,8 @@ public extension Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.BotCallbackAnswer:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.BotPreparedInlineMessage:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.BotResults:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.ChatAdminsWithInvites:
|
||||
@ -2354,6 +2362,8 @@ public extension Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.PeerSettings:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.PreparedInlineMessage:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.QuickReplies:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.Reactions:
|
||||
|
@ -1112,6 +1112,64 @@ public extension Api {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum BotAppSettings: TypeConstructorDescription {
|
||||
case botAppSettings(flags: Int32, placeholderDocument: Api.Document?, backgroundColor: Int32?, backgroundDarkColor: Int32?, headerColor: Int32?, headerDarkColor: Int32?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .botAppSettings(let flags, let placeholderDocument, let backgroundColor, let backgroundDarkColor, let headerColor, let headerDarkColor):
|
||||
if boxed {
|
||||
buffer.appendInt32(-2103898979)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {placeholderDocument!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(backgroundColor!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(backgroundDarkColor!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(headerColor!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt32(headerDarkColor!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .botAppSettings(let flags, let placeholderDocument, let backgroundColor, let backgroundDarkColor, let headerColor, let headerDarkColor):
|
||||
return ("botAppSettings", [("flags", flags as Any), ("placeholderDocument", placeholderDocument as Any), ("backgroundColor", backgroundColor as Any), ("backgroundDarkColor", backgroundDarkColor as Any), ("headerColor", headerColor as Any), ("headerDarkColor", headerDarkColor as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_botAppSettings(_ reader: BufferReader) -> BotAppSettings? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.Document?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.Document
|
||||
} }
|
||||
var _3: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
|
||||
var _4: Int32?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() }
|
||||
var _5: Int32?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {_5 = reader.readInt32() }
|
||||
var _6: Int32?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {_6 = reader.readInt32() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.BotAppSettings.botAppSettings(flags: _1!, placeholderDocument: _2, backgroundColor: _3, backgroundDarkColor: _4, headerColor: _5, headerDarkColor: _6)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum BotBusinessConnection: TypeConstructorDescription {
|
||||
case botBusinessConnection(flags: Int32, connectionId: String, userId: Int64, dcId: Int32, date: Int32)
|
||||
@ -1164,43 +1222,3 @@ public extension Api {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum BotCommand: TypeConstructorDescription {
|
||||
case botCommand(command: String, description: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .botCommand(let command, let description):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1032140601)
|
||||
}
|
||||
serializeString(command, buffer: buffer, boxed: false)
|
||||
serializeString(description, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .botCommand(let command, let description):
|
||||
return ("botCommand", [("command", command as Any), ("description", description as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_botCommand(_ reader: BufferReader) -> BotCommand? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.BotCommand.botCommand(command: _1!, description: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -416,13 +416,13 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
enum Invoice: TypeConstructorDescription {
|
||||
case invoice(flags: Int32, currency: String, prices: [Api.LabeledPrice], maxTipAmount: Int64?, suggestedTipAmounts: [Int64]?, termsUrl: String?)
|
||||
case invoice(flags: Int32, currency: String, prices: [Api.LabeledPrice], maxTipAmount: Int64?, suggestedTipAmounts: [Int64]?, termsUrl: String?, subscriptionPeriod: Int32?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts, let termsUrl):
|
||||
case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts, let termsUrl, let subscriptionPeriod):
|
||||
if boxed {
|
||||
buffer.appendInt32(1572428309)
|
||||
buffer.appendInt32(77522308)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(currency, buffer: buffer, boxed: false)
|
||||
@ -438,14 +438,15 @@ public extension Api {
|
||||
serializeInt64(item, buffer: buffer, boxed: false)
|
||||
}}
|
||||
if Int(flags) & Int(1 << 10) != 0 {serializeString(termsUrl!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 11) != 0 {serializeInt32(subscriptionPeriod!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts, let termsUrl):
|
||||
return ("invoice", [("flags", flags as Any), ("currency", currency as Any), ("prices", prices as Any), ("maxTipAmount", maxTipAmount as Any), ("suggestedTipAmounts", suggestedTipAmounts as Any), ("termsUrl", termsUrl as Any)])
|
||||
case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts, let termsUrl, let subscriptionPeriod):
|
||||
return ("invoice", [("flags", flags as Any), ("currency", currency as Any), ("prices", prices as Any), ("maxTipAmount", maxTipAmount as Any), ("suggestedTipAmounts", suggestedTipAmounts as Any), ("termsUrl", termsUrl as Any), ("subscriptionPeriod", subscriptionPeriod as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -466,14 +467,17 @@ public extension Api {
|
||||
} }
|
||||
var _6: String?
|
||||
if Int(_1!) & Int(1 << 10) != 0 {_6 = parseString(reader) }
|
||||
var _7: Int32?
|
||||
if Int(_1!) & Int(1 << 11) != 0 {_7 = reader.readInt32() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 10) == 0) || _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.Invoice.invoice(flags: _1!, currency: _2!, prices: _3!, maxTipAmount: _4, suggestedTipAmounts: _5, termsUrl: _6)
|
||||
let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.Invoice.invoice(flags: _1!, currency: _2!, prices: _3!, maxTipAmount: _4, suggestedTipAmounts: _5, termsUrl: _6, subscriptionPeriod: _7)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -1,3 +1,43 @@
|
||||
public extension Api {
|
||||
enum BotCommand: TypeConstructorDescription {
|
||||
case botCommand(command: String, description: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .botCommand(let command, let description):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1032140601)
|
||||
}
|
||||
serializeString(command, buffer: buffer, boxed: false)
|
||||
serializeString(description, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .botCommand(let command, let description):
|
||||
return ("botCommand", [("command", command as Any), ("description", description as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_botCommand(_ reader: BufferReader) -> BotCommand? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.BotCommand.botCommand(command: _1!, description: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
indirect enum BotCommandScope: TypeConstructorDescription {
|
||||
case botCommandScopeChatAdmins
|
||||
@ -136,13 +176,13 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
enum BotInfo: TypeConstructorDescription {
|
||||
case botInfo(flags: Int32, userId: Int64?, description: String?, descriptionPhoto: Api.Photo?, descriptionDocument: Api.Document?, commands: [Api.BotCommand]?, menuButton: Api.BotMenuButton?, privacyPolicyUrl: String?)
|
||||
case botInfo(flags: Int32, userId: Int64?, description: String?, descriptionPhoto: Api.Photo?, descriptionDocument: Api.Document?, commands: [Api.BotCommand]?, menuButton: Api.BotMenuButton?, privacyPolicyUrl: String?, appSettings: Api.BotAppSettings?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .botInfo(let flags, let userId, let description, let descriptionPhoto, let descriptionDocument, let commands, let menuButton, let privacyPolicyUrl):
|
||||
case .botInfo(let flags, let userId, let description, let descriptionPhoto, let descriptionDocument, let commands, let menuButton, let privacyPolicyUrl, let appSettings):
|
||||
if boxed {
|
||||
buffer.appendInt32(-2109505932)
|
||||
buffer.appendInt32(912290611)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(userId!, buffer: buffer, boxed: false)}
|
||||
@ -156,14 +196,15 @@ public extension Api {
|
||||
}}
|
||||
if Int(flags) & Int(1 << 3) != 0 {menuButton!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 7) != 0 {serializeString(privacyPolicyUrl!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 8) != 0 {appSettings!.serialize(buffer, true)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .botInfo(let flags, let userId, let description, let descriptionPhoto, let descriptionDocument, let commands, let menuButton, let privacyPolicyUrl):
|
||||
return ("botInfo", [("flags", flags as Any), ("userId", userId as Any), ("description", description as Any), ("descriptionPhoto", descriptionPhoto as Any), ("descriptionDocument", descriptionDocument as Any), ("commands", commands as Any), ("menuButton", menuButton as Any), ("privacyPolicyUrl", privacyPolicyUrl as Any)])
|
||||
case .botInfo(let flags, let userId, let description, let descriptionPhoto, let descriptionDocument, let commands, let menuButton, let privacyPolicyUrl, let appSettings):
|
||||
return ("botInfo", [("flags", flags as Any), ("userId", userId as Any), ("description", description as Any), ("descriptionPhoto", descriptionPhoto as Any), ("descriptionDocument", descriptionDocument as Any), ("commands", commands as Any), ("menuButton", menuButton as Any), ("privacyPolicyUrl", privacyPolicyUrl as Any), ("appSettings", appSettings as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,6 +233,10 @@ public extension Api {
|
||||
} }
|
||||
var _8: String?
|
||||
if Int(_1!) & Int(1 << 7) != 0 {_8 = parseString(reader) }
|
||||
var _9: Api.BotAppSettings?
|
||||
if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() {
|
||||
_9 = Api.parse(reader, signature: signature) as? Api.BotAppSettings
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||
@ -200,8 +245,9 @@ public extension Api {
|
||||
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil
|
||||
let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.BotInfo.botInfo(flags: _1!, userId: _2, description: _3, descriptionPhoto: _4, descriptionDocument: _5, commands: _6, menuButton: _7, privacyPolicyUrl: _8)
|
||||
let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.BotInfo.botInfo(flags: _1!, userId: _2, description: _3, descriptionPhoto: _4, descriptionDocument: _5, commands: _6, menuButton: _7, privacyPolicyUrl: _8, appSettings: _9)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -1164,49 +1210,3 @@ public extension Api {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum BusinessGreetingMessage: TypeConstructorDescription {
|
||||
case businessGreetingMessage(shortcutId: Int32, recipients: Api.BusinessRecipients, noActivityDays: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .businessGreetingMessage(let shortcutId, let recipients, let noActivityDays):
|
||||
if boxed {
|
||||
buffer.appendInt32(-451302485)
|
||||
}
|
||||
serializeInt32(shortcutId, buffer: buffer, boxed: false)
|
||||
recipients.serialize(buffer, true)
|
||||
serializeInt32(noActivityDays, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .businessGreetingMessage(let shortcutId, let recipients, let noActivityDays):
|
||||
return ("businessGreetingMessage", [("shortcutId", shortcutId as Any), ("recipients", recipients as Any), ("noActivityDays", noActivityDays as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_businessGreetingMessage(_ reader: BufferReader) -> BusinessGreetingMessage? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.BusinessRecipients?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.BusinessRecipients
|
||||
}
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.BusinessGreetingMessage.businessGreetingMessage(shortcutId: _1!, recipients: _2!, noActivityDays: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -858,13 +858,13 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
enum StarsSubscription: TypeConstructorDescription {
|
||||
case starsSubscription(flags: Int32, id: String, peer: Api.Peer, untilDate: Int32, pricing: Api.StarsSubscriptionPricing, chatInviteHash: String?)
|
||||
case starsSubscription(flags: Int32, id: String, peer: Api.Peer, untilDate: Int32, pricing: Api.StarsSubscriptionPricing, chatInviteHash: String?, title: String?, photo: Api.WebDocument?, invoiceSlug: String?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsSubscription(let flags, let id, let peer, let untilDate, let pricing, let chatInviteHash):
|
||||
case .starsSubscription(let flags, let id, let peer, let untilDate, let pricing, let chatInviteHash, let title, let photo, let invoiceSlug):
|
||||
if boxed {
|
||||
buffer.appendInt32(1401868056)
|
||||
buffer.appendInt32(779004698)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(id, buffer: buffer, boxed: false)
|
||||
@ -872,14 +872,17 @@ public extension Api {
|
||||
serializeInt32(untilDate, buffer: buffer, boxed: false)
|
||||
pricing.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 3) != 0 {serializeString(chatInviteHash!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 5) != 0 {photo!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 6) != 0 {serializeString(invoiceSlug!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starsSubscription(let flags, let id, let peer, let untilDate, let pricing, let chatInviteHash):
|
||||
return ("starsSubscription", [("flags", flags as Any), ("id", id as Any), ("peer", peer as Any), ("untilDate", untilDate as Any), ("pricing", pricing as Any), ("chatInviteHash", chatInviteHash as Any)])
|
||||
case .starsSubscription(let flags, let id, let peer, let untilDate, let pricing, let chatInviteHash, let title, let photo, let invoiceSlug):
|
||||
return ("starsSubscription", [("flags", flags as Any), ("id", id as Any), ("peer", peer as Any), ("untilDate", untilDate as Any), ("pricing", pricing as Any), ("chatInviteHash", chatInviteHash as Any), ("title", title as Any), ("photo", photo as Any), ("invoiceSlug", invoiceSlug as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -900,14 +903,25 @@ public extension Api {
|
||||
}
|
||||
var _6: String?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {_6 = parseString(reader) }
|
||||
var _7: String?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {_7 = parseString(reader) }
|
||||
var _8: Api.WebDocument?
|
||||
if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() {
|
||||
_8 = Api.parse(reader, signature: signature) as? Api.WebDocument
|
||||
} }
|
||||
var _9: String?
|
||||
if Int(_1!) & Int(1 << 6) != 0 {_9 = parseString(reader) }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.StarsSubscription.starsSubscription(flags: _1!, id: _2!, peer: _3!, untilDate: _4!, pricing: _5!, chatInviteHash: _6)
|
||||
let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil
|
||||
let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil
|
||||
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.StarsSubscription.starsSubscription(flags: _1!, id: _2!, peer: _3!, untilDate: _4!, pricing: _5!, chatInviteHash: _6, title: _7, photo: _8, invoiceSlug: _9)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -645,6 +645,7 @@ public extension Api {
|
||||
case updateBotPurchasedPaidMedia(userId: Int64, payload: String, qts: Int32)
|
||||
case updateBotShippingQuery(queryId: Int64, userId: Int64, payload: Buffer, shippingAddress: Api.PostAddress)
|
||||
case updateBotStopped(userId: Int64, date: Int32, stopped: Api.Bool, qts: Int32)
|
||||
case updateBotSubscriptionExpire(userId: Int64, payload: String, invoiceSlug: String, untilDate: Int32, qts: Int32)
|
||||
case updateBotWebhookJSON(data: Api.DataJSON)
|
||||
case updateBotWebhookJSONQuery(queryId: Int64, data: Api.DataJSON, timeout: Int32)
|
||||
case updateBroadcastRevenueTransactions(peer: Api.Peer, balances: Api.BroadcastRevenueBalances)
|
||||
@ -969,6 +970,16 @@ public extension Api {
|
||||
stopped.serialize(buffer, true)
|
||||
serializeInt32(qts, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .updateBotSubscriptionExpire(let userId, let payload, let invoiceSlug, let untilDate, let qts):
|
||||
if boxed {
|
||||
buffer.appendInt32(756270830)
|
||||
}
|
||||
serializeInt64(userId, buffer: buffer, boxed: false)
|
||||
serializeString(payload, buffer: buffer, boxed: false)
|
||||
serializeString(invoiceSlug, buffer: buffer, boxed: false)
|
||||
serializeInt32(untilDate, buffer: buffer, boxed: false)
|
||||
serializeInt32(qts, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .updateBotWebhookJSON(let data):
|
||||
if boxed {
|
||||
buffer.appendInt32(-2095595325)
|
||||
@ -2041,6 +2052,8 @@ public extension Api {
|
||||
return ("updateBotShippingQuery", [("queryId", queryId as Any), ("userId", userId as Any), ("payload", payload as Any), ("shippingAddress", shippingAddress as Any)])
|
||||
case .updateBotStopped(let userId, let date, let stopped, let qts):
|
||||
return ("updateBotStopped", [("userId", userId as Any), ("date", date as Any), ("stopped", stopped as Any), ("qts", qts as Any)])
|
||||
case .updateBotSubscriptionExpire(let userId, let payload, let invoiceSlug, let untilDate, let qts):
|
||||
return ("updateBotSubscriptionExpire", [("userId", userId as Any), ("payload", payload as Any), ("invoiceSlug", invoiceSlug as Any), ("untilDate", untilDate as Any), ("qts", qts as Any)])
|
||||
case .updateBotWebhookJSON(let data):
|
||||
return ("updateBotWebhookJSON", [("data", data as Any)])
|
||||
case .updateBotWebhookJSONQuery(let queryId, let data, let timeout):
|
||||
@ -2732,6 +2745,29 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updateBotSubscriptionExpire(_ reader: BufferReader) -> Update? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
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.Update.updateBotSubscriptionExpire(userId: _1!, payload: _2!, invoiceSlug: _3!, untilDate: _4!, qts: _5!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updateBotWebhookJSON(_ reader: BufferReader) -> Update? {
|
||||
var _1: Api.DataJSON?
|
||||
if let signature = reader.readInt32() {
|
||||
|
@ -1,3 +1,49 @@
|
||||
public extension Api {
|
||||
enum BusinessGreetingMessage: TypeConstructorDescription {
|
||||
case businessGreetingMessage(shortcutId: Int32, recipients: Api.BusinessRecipients, noActivityDays: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .businessGreetingMessage(let shortcutId, let recipients, let noActivityDays):
|
||||
if boxed {
|
||||
buffer.appendInt32(-451302485)
|
||||
}
|
||||
serializeInt32(shortcutId, buffer: buffer, boxed: false)
|
||||
recipients.serialize(buffer, true)
|
||||
serializeInt32(noActivityDays, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .businessGreetingMessage(let shortcutId, let recipients, let noActivityDays):
|
||||
return ("businessGreetingMessage", [("shortcutId", shortcutId as Any), ("recipients", recipients as Any), ("noActivityDays", noActivityDays as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_businessGreetingMessage(_ reader: BufferReader) -> BusinessGreetingMessage? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.BusinessRecipients?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.BusinessRecipients
|
||||
}
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.BusinessGreetingMessage.businessGreetingMessage(shortcutId: _1!, recipients: _2!, noActivityDays: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum BusinessIntro: TypeConstructorDescription {
|
||||
case businessIntro(flags: Int32, title: String, description: String, sticker: Api.Document?)
|
||||
|
@ -260,6 +260,46 @@ public extension Api.messages {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum BotPreparedInlineMessage: TypeConstructorDescription {
|
||||
case botPreparedInlineMessage(id: String, expireDate: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .botPreparedInlineMessage(let id, let expireDate):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1899035375)
|
||||
}
|
||||
serializeString(id, buffer: buffer, boxed: false)
|
||||
serializeInt32(expireDate, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .botPreparedInlineMessage(let id, let expireDate):
|
||||
return ("botPreparedInlineMessage", [("id", id as Any), ("expireDate", expireDate as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_botPreparedInlineMessage(_ reader: BufferReader) -> BotPreparedInlineMessage? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.messages.BotPreparedInlineMessage.botPreparedInlineMessage(id: _1!, expireDate: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum BotResults: TypeConstructorDescription {
|
||||
case botResults(flags: Int32, queryId: Int64, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, switchWebview: Api.InlineBotWebView?, results: [Api.BotInlineResult], cacheTime: Int32, users: [Api.User])
|
||||
@ -1398,61 +1438,3 @@ public extension Api.messages {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum FoundStickerSets: TypeConstructorDescription {
|
||||
case foundStickerSets(hash: Int64, sets: [Api.StickerSetCovered])
|
||||
case foundStickerSetsNotModified
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .foundStickerSets(let hash, let sets):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1963942446)
|
||||
}
|
||||
serializeInt64(hash, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(sets.count))
|
||||
for item in sets {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
case .foundStickerSetsNotModified:
|
||||
if boxed {
|
||||
buffer.appendInt32(223655517)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .foundStickerSets(let hash, let sets):
|
||||
return ("foundStickerSets", [("hash", hash as Any), ("sets", sets as Any)])
|
||||
case .foundStickerSetsNotModified:
|
||||
return ("foundStickerSetsNotModified", [])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_foundStickerSets(_ reader: BufferReader) -> FoundStickerSets? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: [Api.StickerSetCovered]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.messages.FoundStickerSets.foundStickerSets(hash: _1!, sets: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_foundStickerSetsNotModified(_ reader: BufferReader) -> FoundStickerSets? {
|
||||
return Api.messages.FoundStickerSets.foundStickerSetsNotModified
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,61 @@
|
||||
public extension Api.messages {
|
||||
enum FoundStickerSets: TypeConstructorDescription {
|
||||
case foundStickerSets(hash: Int64, sets: [Api.StickerSetCovered])
|
||||
case foundStickerSetsNotModified
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .foundStickerSets(let hash, let sets):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1963942446)
|
||||
}
|
||||
serializeInt64(hash, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(sets.count))
|
||||
for item in sets {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
case .foundStickerSetsNotModified:
|
||||
if boxed {
|
||||
buffer.appendInt32(223655517)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .foundStickerSets(let hash, let sets):
|
||||
return ("foundStickerSets", [("hash", hash as Any), ("sets", sets as Any)])
|
||||
case .foundStickerSetsNotModified:
|
||||
return ("foundStickerSetsNotModified", [])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_foundStickerSets(_ reader: BufferReader) -> FoundStickerSets? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: [Api.StickerSetCovered]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.messages.FoundStickerSets.foundStickerSets(hash: _1!, sets: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_foundStickerSetsNotModified(_ reader: BufferReader) -> FoundStickerSets? {
|
||||
return Api.messages.FoundStickerSets.foundStickerSetsNotModified
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum HighScores: TypeConstructorDescription {
|
||||
case highScores(scores: [Api.HighScore], users: [Api.User])
|
||||
@ -806,6 +864,68 @@ public extension Api.messages {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum PreparedInlineMessage: TypeConstructorDescription {
|
||||
case preparedInlineMessage(queryId: Int64, result: Api.BotInlineResult, peerTypes: [Api.InlineQueryPeerType], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .preparedInlineMessage(let queryId, let result, let peerTypes, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(1636301421)
|
||||
}
|
||||
serializeInt64(queryId, buffer: buffer, boxed: false)
|
||||
result.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(peerTypes.count))
|
||||
for item in peerTypes {
|
||||
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 .preparedInlineMessage(let queryId, let result, let peerTypes, let users):
|
||||
return ("preparedInlineMessage", [("queryId", queryId as Any), ("result", result as Any), ("peerTypes", peerTypes as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_preparedInlineMessage(_ reader: BufferReader) -> PreparedInlineMessage? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Api.BotInlineResult?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.BotInlineResult
|
||||
}
|
||||
var _3: [Api.InlineQueryPeerType]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InlineQueryPeerType.self)
|
||||
}
|
||||
var _4: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.messages.PreparedInlineMessage.preparedInlineMessage(queryId: _1!, result: _2!, peerTypes: _3!, users: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum QuickReplies: TypeConstructorDescription {
|
||||
case quickReplies(quickReplies: [Api.QuickReply], messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
|
||||
@ -1340,203 +1460,3 @@ public extension Api.messages {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum SearchResultsCalendar: TypeConstructorDescription {
|
||||
case searchResultsCalendar(flags: Int32, count: Int32, minDate: Int32, minMsgId: Int32, offsetIdOffset: Int32?, periods: [Api.SearchResultsCalendarPeriod], messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .searchResultsCalendar(let flags, let count, let minDate, let minMsgId, let offsetIdOffset, let periods, let messages, let chats, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(343859772)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
serializeInt32(minDate, buffer: buffer, boxed: false)
|
||||
serializeInt32(minMsgId, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(periods.count))
|
||||
for item in periods {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(messages.count))
|
||||
for item in messages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
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 .searchResultsCalendar(let flags, let count, let minDate, let minMsgId, let offsetIdOffset, let periods, let messages, let chats, let users):
|
||||
return ("searchResultsCalendar", [("flags", flags as Any), ("count", count as Any), ("minDate", minDate as Any), ("minMsgId", minMsgId as Any), ("offsetIdOffset", offsetIdOffset as Any), ("periods", periods as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_searchResultsCalendar(_ reader: BufferReader) -> SearchResultsCalendar? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() }
|
||||
var _6: [Api.SearchResultsCalendarPeriod]?
|
||||
if let _ = reader.readInt32() {
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsCalendarPeriod.self)
|
||||
}
|
||||
var _7: [Api.Message]?
|
||||
if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
|
||||
}
|
||||
var _8: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _9: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
let _c9 = _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.messages.SearchResultsCalendar.searchResultsCalendar(flags: _1!, count: _2!, minDate: _3!, minMsgId: _4!, offsetIdOffset: _5, periods: _6!, messages: _7!, chats: _8!, users: _9!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum SearchResultsPositions: TypeConstructorDescription {
|
||||
case searchResultsPositions(count: Int32, positions: [Api.SearchResultsPosition])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .searchResultsPositions(let count, let positions):
|
||||
if boxed {
|
||||
buffer.appendInt32(1404185519)
|
||||
}
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(positions.count))
|
||||
for item in positions {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .searchResultsPositions(let count, let positions):
|
||||
return ("searchResultsPositions", [("count", count as Any), ("positions", positions as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_searchResultsPositions(_ reader: BufferReader) -> SearchResultsPositions? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: [Api.SearchResultsPosition]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsPosition.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.messages.SearchResultsPositions.searchResultsPositions(count: _1!, positions: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum SentEncryptedMessage: TypeConstructorDescription {
|
||||
case sentEncryptedFile(date: Int32, file: Api.EncryptedFile)
|
||||
case sentEncryptedMessage(date: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .sentEncryptedFile(let date, let file):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1802240206)
|
||||
}
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
file.serialize(buffer, true)
|
||||
break
|
||||
case .sentEncryptedMessage(let date):
|
||||
if boxed {
|
||||
buffer.appendInt32(1443858741)
|
||||
}
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .sentEncryptedFile(let date, let file):
|
||||
return ("sentEncryptedFile", [("date", date as Any), ("file", file as Any)])
|
||||
case .sentEncryptedMessage(let date):
|
||||
return ("sentEncryptedMessage", [("date", date as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_sentEncryptedFile(_ reader: BufferReader) -> SentEncryptedMessage? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.EncryptedFile?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.EncryptedFile
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.messages.SentEncryptedMessage.sentEncryptedFile(date: _1!, file: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_sentEncryptedMessage(_ reader: BufferReader) -> SentEncryptedMessage? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.messages.SentEncryptedMessage.sentEncryptedMessage(date: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,203 @@
|
||||
public extension Api.messages {
|
||||
enum SearchResultsCalendar: TypeConstructorDescription {
|
||||
case searchResultsCalendar(flags: Int32, count: Int32, minDate: Int32, minMsgId: Int32, offsetIdOffset: Int32?, periods: [Api.SearchResultsCalendarPeriod], messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .searchResultsCalendar(let flags, let count, let minDate, let minMsgId, let offsetIdOffset, let periods, let messages, let chats, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(343859772)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
serializeInt32(minDate, buffer: buffer, boxed: false)
|
||||
serializeInt32(minMsgId, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(periods.count))
|
||||
for item in periods {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(messages.count))
|
||||
for item in messages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
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 .searchResultsCalendar(let flags, let count, let minDate, let minMsgId, let offsetIdOffset, let periods, let messages, let chats, let users):
|
||||
return ("searchResultsCalendar", [("flags", flags as Any), ("count", count as Any), ("minDate", minDate as Any), ("minMsgId", minMsgId as Any), ("offsetIdOffset", offsetIdOffset as Any), ("periods", periods as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_searchResultsCalendar(_ reader: BufferReader) -> SearchResultsCalendar? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() }
|
||||
var _6: [Api.SearchResultsCalendarPeriod]?
|
||||
if let _ = reader.readInt32() {
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsCalendarPeriod.self)
|
||||
}
|
||||
var _7: [Api.Message]?
|
||||
if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
|
||||
}
|
||||
var _8: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _9: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
let _c9 = _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.messages.SearchResultsCalendar.searchResultsCalendar(flags: _1!, count: _2!, minDate: _3!, minMsgId: _4!, offsetIdOffset: _5, periods: _6!, messages: _7!, chats: _8!, users: _9!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum SearchResultsPositions: TypeConstructorDescription {
|
||||
case searchResultsPositions(count: Int32, positions: [Api.SearchResultsPosition])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .searchResultsPositions(let count, let positions):
|
||||
if boxed {
|
||||
buffer.appendInt32(1404185519)
|
||||
}
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(positions.count))
|
||||
for item in positions {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .searchResultsPositions(let count, let positions):
|
||||
return ("searchResultsPositions", [("count", count as Any), ("positions", positions as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_searchResultsPositions(_ reader: BufferReader) -> SearchResultsPositions? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: [Api.SearchResultsPosition]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsPosition.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.messages.SearchResultsPositions.searchResultsPositions(count: _1!, positions: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum SentEncryptedMessage: TypeConstructorDescription {
|
||||
case sentEncryptedFile(date: Int32, file: Api.EncryptedFile)
|
||||
case sentEncryptedMessage(date: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .sentEncryptedFile(let date, let file):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1802240206)
|
||||
}
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
file.serialize(buffer, true)
|
||||
break
|
||||
case .sentEncryptedMessage(let date):
|
||||
if boxed {
|
||||
buffer.appendInt32(1443858741)
|
||||
}
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .sentEncryptedFile(let date, let file):
|
||||
return ("sentEncryptedFile", [("date", date as Any), ("file", file as Any)])
|
||||
case .sentEncryptedMessage(let date):
|
||||
return ("sentEncryptedMessage", [("date", date as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_sentEncryptedFile(_ reader: BufferReader) -> SentEncryptedMessage? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.EncryptedFile?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.EncryptedFile
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.messages.SentEncryptedMessage.sentEncryptedFile(date: _1!, file: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_sentEncryptedMessage(_ reader: BufferReader) -> SentEncryptedMessage? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.messages.SentEncryptedMessage.sentEncryptedMessage(date: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum SponsoredMessages: TypeConstructorDescription {
|
||||
case sponsoredMessages(flags: Int32, postsBetween: Int32?, messages: [Api.SponsoredMessage], chats: [Api.Chat], users: [Api.User])
|
||||
@ -1330,243 +1530,3 @@ public extension Api.payments {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum StarsRevenueStats: TypeConstructorDescription {
|
||||
case starsRevenueStats(revenueGraph: Api.StatsGraph, status: Api.StarsRevenueStatus, usdRate: Double)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsRevenueStats(let revenueGraph, let status, let usdRate):
|
||||
if boxed {
|
||||
buffer.appendInt32(-919881925)
|
||||
}
|
||||
revenueGraph.serialize(buffer, true)
|
||||
status.serialize(buffer, true)
|
||||
serializeDouble(usdRate, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starsRevenueStats(let revenueGraph, let status, let usdRate):
|
||||
return ("starsRevenueStats", [("revenueGraph", revenueGraph as Any), ("status", status as Any), ("usdRate", usdRate as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starsRevenueStats(_ reader: BufferReader) -> StarsRevenueStats? {
|
||||
var _1: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _2: Api.StarsRevenueStatus?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.StarsRevenueStatus
|
||||
}
|
||||
var _3: Double?
|
||||
_3 = reader.readDouble()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.payments.StarsRevenueStats.starsRevenueStats(revenueGraph: _1!, status: _2!, usdRate: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum StarsRevenueWithdrawalUrl: TypeConstructorDescription {
|
||||
case starsRevenueWithdrawalUrl(url: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsRevenueWithdrawalUrl(let url):
|
||||
if boxed {
|
||||
buffer.appendInt32(497778871)
|
||||
}
|
||||
serializeString(url, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starsRevenueWithdrawalUrl(let url):
|
||||
return ("starsRevenueWithdrawalUrl", [("url", url as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starsRevenueWithdrawalUrl(_ reader: BufferReader) -> StarsRevenueWithdrawalUrl? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.payments.StarsRevenueWithdrawalUrl.starsRevenueWithdrawalUrl(url: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum StarsStatus: TypeConstructorDescription {
|
||||
case starsStatus(flags: Int32, balance: Int64, subscriptions: [Api.StarsSubscription]?, subscriptionsNextOffset: String?, subscriptionsMissingBalance: Int64?, history: [Api.StarsTransaction]?, nextOffset: String?, chats: [Api.Chat], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsStatus(let flags, let balance, let subscriptions, let subscriptionsNextOffset, let subscriptionsMissingBalance, let history, let nextOffset, let chats, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1141231252)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(balance, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(subscriptions!.count))
|
||||
for item in subscriptions! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeString(subscriptionsNextOffset!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(subscriptionsMissingBalance!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(history!.count))
|
||||
for item in history! {
|
||||
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 .starsStatus(let flags, let balance, let subscriptions, let subscriptionsNextOffset, let subscriptionsMissingBalance, let history, let nextOffset, let chats, let users):
|
||||
return ("starsStatus", [("flags", flags as Any), ("balance", balance as Any), ("subscriptions", subscriptions as Any), ("subscriptionsNextOffset", subscriptionsNextOffset as Any), ("subscriptionsMissingBalance", subscriptionsMissingBalance as Any), ("history", history as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starsStatus(_ reader: BufferReader) -> StarsStatus? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: [Api.StarsSubscription]?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsSubscription.self)
|
||||
} }
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {_4 = parseString(reader) }
|
||||
var _5: Int64?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt64() }
|
||||
var _6: [Api.StarsTransaction]?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTransaction.self)
|
||||
} }
|
||||
var _7: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_7 = parseString(reader) }
|
||||
var _8: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _9: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
let _c9 = _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.payments.StarsStatus.starsStatus(flags: _1!, balance: _2!, subscriptions: _3, subscriptionsNextOffset: _4, subscriptionsMissingBalance: _5, history: _6, nextOffset: _7, chats: _8!, users: _9!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum UserStarGifts: TypeConstructorDescription {
|
||||
case userStarGifts(flags: Int32, count: Int32, gifts: [Api.UserStarGift], nextOffset: String?, users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .userStarGifts(let flags, let count, let gifts, let nextOffset, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(1801827607)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(gifts.count))
|
||||
for item in gifts {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)}
|
||||
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 .userStarGifts(let flags, let count, let gifts, let nextOffset, let users):
|
||||
return ("userStarGifts", [("flags", flags as Any), ("count", count as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_userStarGifts(_ reader: BufferReader) -> UserStarGifts? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: [Api.UserStarGift]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.UserStarGift.self)
|
||||
}
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) }
|
||||
var _5: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_5 = 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
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.payments.UserStarGifts.userStarGifts(flags: _1!, count: _2!, gifts: _3!, nextOffset: _4, users: _5!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,243 @@
|
||||
public extension Api.payments {
|
||||
enum StarsRevenueStats: TypeConstructorDescription {
|
||||
case starsRevenueStats(revenueGraph: Api.StatsGraph, status: Api.StarsRevenueStatus, usdRate: Double)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsRevenueStats(let revenueGraph, let status, let usdRate):
|
||||
if boxed {
|
||||
buffer.appendInt32(-919881925)
|
||||
}
|
||||
revenueGraph.serialize(buffer, true)
|
||||
status.serialize(buffer, true)
|
||||
serializeDouble(usdRate, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starsRevenueStats(let revenueGraph, let status, let usdRate):
|
||||
return ("starsRevenueStats", [("revenueGraph", revenueGraph as Any), ("status", status as Any), ("usdRate", usdRate as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starsRevenueStats(_ reader: BufferReader) -> StarsRevenueStats? {
|
||||
var _1: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _2: Api.StarsRevenueStatus?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.StarsRevenueStatus
|
||||
}
|
||||
var _3: Double?
|
||||
_3 = reader.readDouble()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.payments.StarsRevenueStats.starsRevenueStats(revenueGraph: _1!, status: _2!, usdRate: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum StarsRevenueWithdrawalUrl: TypeConstructorDescription {
|
||||
case starsRevenueWithdrawalUrl(url: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsRevenueWithdrawalUrl(let url):
|
||||
if boxed {
|
||||
buffer.appendInt32(497778871)
|
||||
}
|
||||
serializeString(url, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starsRevenueWithdrawalUrl(let url):
|
||||
return ("starsRevenueWithdrawalUrl", [("url", url as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starsRevenueWithdrawalUrl(_ reader: BufferReader) -> StarsRevenueWithdrawalUrl? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.payments.StarsRevenueWithdrawalUrl.starsRevenueWithdrawalUrl(url: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum StarsStatus: TypeConstructorDescription {
|
||||
case starsStatus(flags: Int32, balance: Int64, subscriptions: [Api.StarsSubscription]?, subscriptionsNextOffset: String?, subscriptionsMissingBalance: Int64?, history: [Api.StarsTransaction]?, nextOffset: String?, chats: [Api.Chat], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsStatus(let flags, let balance, let subscriptions, let subscriptionsNextOffset, let subscriptionsMissingBalance, let history, let nextOffset, let chats, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1141231252)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(balance, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(subscriptions!.count))
|
||||
for item in subscriptions! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeString(subscriptionsNextOffset!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(subscriptionsMissingBalance!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(history!.count))
|
||||
for item in history! {
|
||||
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 .starsStatus(let flags, let balance, let subscriptions, let subscriptionsNextOffset, let subscriptionsMissingBalance, let history, let nextOffset, let chats, let users):
|
||||
return ("starsStatus", [("flags", flags as Any), ("balance", balance as Any), ("subscriptions", subscriptions as Any), ("subscriptionsNextOffset", subscriptionsNextOffset as Any), ("subscriptionsMissingBalance", subscriptionsMissingBalance as Any), ("history", history as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starsStatus(_ reader: BufferReader) -> StarsStatus? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: [Api.StarsSubscription]?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsSubscription.self)
|
||||
} }
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {_4 = parseString(reader) }
|
||||
var _5: Int64?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt64() }
|
||||
var _6: [Api.StarsTransaction]?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTransaction.self)
|
||||
} }
|
||||
var _7: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_7 = parseString(reader) }
|
||||
var _8: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _9: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
let _c9 = _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.payments.StarsStatus.starsStatus(flags: _1!, balance: _2!, subscriptions: _3, subscriptionsNextOffset: _4, subscriptionsMissingBalance: _5, history: _6, nextOffset: _7, chats: _8!, users: _9!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum UserStarGifts: TypeConstructorDescription {
|
||||
case userStarGifts(flags: Int32, count: Int32, gifts: [Api.UserStarGift], nextOffset: String?, users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .userStarGifts(let flags, let count, let gifts, let nextOffset, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(1801827607)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(gifts.count))
|
||||
for item in gifts {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)}
|
||||
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 .userStarGifts(let flags, let count, let gifts, let nextOffset, let users):
|
||||
return ("userStarGifts", [("flags", flags as Any), ("count", count as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_userStarGifts(_ reader: BufferReader) -> UserStarGifts? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: [Api.UserStarGift]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.UserStarGift.self)
|
||||
}
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) }
|
||||
var _5: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_5 = 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
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.payments.UserStarGifts.userStarGifts(flags: _1!, count: _2!, gifts: _3!, nextOffset: _4, users: _5!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum ValidatedRequestedInfo: TypeConstructorDescription {
|
||||
case validatedRequestedInfo(flags: Int32, id: String?, shippingOptions: [Api.ShippingOption]?)
|
||||
|
@ -2574,6 +2574,22 @@ public extension Api.functions.bots {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.bots {
|
||||
static func toggleUserEmojiStatusPermission(bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(115237778)
|
||||
bot.serialize(buffer, true)
|
||||
enabled.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "bots.toggleUserEmojiStatusPermission", parameters: [("bot", String(describing: bot)), ("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.bots {
|
||||
static func toggleUsername(bot: Api.InputUser, username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
@ -2591,6 +2607,22 @@ public extension Api.functions.bots {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.bots {
|
||||
static func updateUserEmojiStatus(userId: Api.InputUser, emojiStatus: Api.EmojiStatus) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-308334395)
|
||||
userId.serialize(buffer, true)
|
||||
emojiStatus.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "bots.updateUserEmojiStatus", parameters: [("userId", String(describing: userId)), ("emojiStatus", String(describing: emojiStatus))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.channels {
|
||||
static func checkUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
@ -6493,6 +6525,22 @@ public extension Api.functions.messages {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.messages {
|
||||
static func getPreparedInlineMessage(bot: Api.InputUser, id: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.PreparedInlineMessage>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-2055291464)
|
||||
bot.serialize(buffer, true)
|
||||
serializeString(id, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "messages.getPreparedInlineMessage", parameters: [("bot", String(describing: bot)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PreparedInlineMessage? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.messages.PreparedInlineMessage?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.messages.PreparedInlineMessage
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.messages {
|
||||
static func getQuickReplies(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.QuickReplies>) {
|
||||
let buffer = Buffer()
|
||||
@ -7608,6 +7656,28 @@ public extension Api.functions.messages {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.messages {
|
||||
static func savePreparedInlineMessage(flags: Int32, result: Api.InputBotInlineResult, userId: Api.InputUser, peerTypes: [Api.InlineQueryPeerType]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.BotPreparedInlineMessage>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-232816849)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
result.serialize(buffer, true)
|
||||
userId.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(peerTypes!.count))
|
||||
for item in peerTypes! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
return (FunctionDescription(name: "messages.savePreparedInlineMessage", parameters: [("flags", String(describing: flags)), ("result", String(describing: result)), ("userId", String(describing: userId)), ("peerTypes", String(describing: peerTypes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotPreparedInlineMessage? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.messages.BotPreparedInlineMessage?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.messages.BotPreparedInlineMessage
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.messages {
|
||||
static func saveRecentSticker(flags: Int32, id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
@ -8775,6 +8845,24 @@ public extension Api.functions.payments {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func botCancelStarsSubscription(flags: Int32, userId: Api.InputUser, invoiceSlug: String?, chargeId: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1475996902)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
userId.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(invoiceSlug!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeString(chargeId!, buffer: buffer, boxed: false)}
|
||||
return (FunctionDescription(name: "payments.botCancelStarsSubscription", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("invoiceSlug", String(describing: invoiceSlug)), ("chargeId", String(describing: chargeId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func canPurchasePremium(purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
|
@ -13,10 +13,25 @@ extension BotMenuButton {
|
||||
}
|
||||
}
|
||||
|
||||
extension BotAppSettings {
|
||||
init(apiBotAppSettings: Api.BotAppSettings) {
|
||||
switch apiBotAppSettings {
|
||||
case let .botAppSettings(_, placeholderDocument, backgroundColor, backgroundDarkColor, headerColor, headerDarkColor):
|
||||
self.init(
|
||||
placeholder: placeholderDocument.flatMap { telegramMediaFileFromApiDocument($0, altDocuments: []) },
|
||||
backgroundColor: backgroundColor,
|
||||
backgroundDarkColor: backgroundDarkColor,
|
||||
headerColor: headerColor,
|
||||
headerDarkColor: headerDarkColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BotInfo {
|
||||
convenience init(apiBotInfo: Api.BotInfo) {
|
||||
switch apiBotInfo {
|
||||
case let .botInfo(_, _, description, descriptionPhoto, descriptionDocument, apiCommands, apiMenuButton, privacyPolicyUrl):
|
||||
case let .botInfo(_, _, description, descriptionPhoto, descriptionDocument, apiCommands, apiMenuButton, privacyPolicyUrl, appSettings):
|
||||
let photo: TelegramMediaImage? = descriptionPhoto.flatMap(telegramMediaImageFromApiPhoto)
|
||||
let video: TelegramMediaFile? = descriptionDocument.flatMap { telegramMediaFileFromApiDocument($0, altDocuments: []) }
|
||||
var commands: [BotCommand] = []
|
||||
@ -32,7 +47,7 @@ extension BotInfo {
|
||||
if let apiMenuButton = apiMenuButton {
|
||||
menuButton = BotMenuButton(apiBotMenuButton: apiMenuButton)
|
||||
}
|
||||
self.init(description: description ?? "", photo: photo, video: video, commands: commands, menuButton: menuButton, privacyPolicyUrl: privacyPolicyUrl)
|
||||
self.init(description: description ?? "", photo: photo, video: video, commands: commands, menuButton: menuButton, privacyPolicyUrl: privacyPolicyUrl, appSettings: appSettings.flatMap { BotAppSettings(apiBotAppSettings: $0) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -549,7 +549,7 @@ extension ChatContextResultMessage {
|
||||
if let replyMarkup = replyMarkup {
|
||||
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
|
||||
}
|
||||
self = .invoice(media: TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: currency, totalAmount: totalAmount, startParam: "", extendedMedia: nil, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion), replyMarkup: parsedReplyMarkup)
|
||||
self = .invoice(media: TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: currency, totalAmount: totalAmount, startParam: "", extendedMedia: nil, subscriptionPeriod: nil, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion), replyMarkup: parsedReplyMarkup)
|
||||
case let .botInlineMessageMediaWebPage(flags, message, entities, url, replyMarkup):
|
||||
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
|
||||
if let replyMarkup = replyMarkup {
|
||||
|
@ -2,6 +2,22 @@ import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
extension ReplyMarkupButtonAction.PeerTypes {
|
||||
init?(apiType: Api.InlineQueryPeerType) {
|
||||
switch apiType {
|
||||
case .inlineQueryPeerTypePM:
|
||||
self = .users
|
||||
case .inlineQueryPeerTypeBotPM:
|
||||
self = .bots
|
||||
case .inlineQueryPeerTypeBroadcast:
|
||||
self = .channels
|
||||
case .inlineQueryPeerTypeChat, .inlineQueryPeerTypeMegagroup:
|
||||
self = .groups
|
||||
case .inlineQueryPeerTypeSameBotPM:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ReplyMarkupButton {
|
||||
init(apiButton: Api.KeyboardButton) {
|
||||
|
@ -395,7 +395,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
parsedFlags.insert(.shippingAddressRequested)
|
||||
}
|
||||
return (TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, extendedMedia: apiExtendedMedia.flatMap({ TelegramExtendedMedia(apiExtendedMedia: $0, peerId: peerId) }), flags: parsedFlags, version: TelegramMediaInvoice.lastVersion), nil, nil, nil, nil)
|
||||
return (TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, extendedMedia: apiExtendedMedia.flatMap({ TelegramExtendedMedia(apiExtendedMedia: $0, peerId: peerId) }), subscriptionPeriod: nil, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion), nil, nil, nil, nil)
|
||||
case let .messageMediaPoll(poll, results):
|
||||
switch poll {
|
||||
case let .poll(id, flags, question, answers, closePeriod, _):
|
||||
|
@ -1706,14 +1706,14 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
||||
updatedState.updateCachedPeerData(peer.peerId, { current in
|
||||
if peer.peerId.namespace == Namespaces.Peer.CloudUser, let previous = current as? CachedUserData {
|
||||
if let botInfo = previous.botInfo {
|
||||
return previous.withUpdatedBotInfo(BotInfo(description: botInfo.description, photo: botInfo.photo, video: botInfo.video, commands: commands, menuButton: botInfo.menuButton, privacyPolicyUrl: botInfo.privacyPolicyUrl))
|
||||
return previous.withUpdatedBotInfo(BotInfo(description: botInfo.description, photo: botInfo.photo, video: botInfo.video, commands: commands, menuButton: botInfo.menuButton, privacyPolicyUrl: botInfo.privacyPolicyUrl, appSettings: botInfo.appSettings))
|
||||
}
|
||||
} else if peer.peerId.namespace == Namespaces.Peer.CloudGroup, let previous = current as? CachedGroupData {
|
||||
if let index = previous.botInfos.firstIndex(where: { $0.peerId == botPeerId }) {
|
||||
var updatedBotInfos = previous.botInfos
|
||||
let previousBotInfo = updatedBotInfos[index]
|
||||
updatedBotInfos.remove(at: index)
|
||||
updatedBotInfos.insert(CachedPeerBotInfo(peerId: botPeerId, botInfo: BotInfo(description: previousBotInfo.botInfo.description, photo: previousBotInfo.botInfo.photo, video: previousBotInfo.botInfo.video, commands: commands, menuButton: previousBotInfo.botInfo.menuButton, privacyPolicyUrl: previousBotInfo.botInfo.privacyPolicyUrl)), at: index)
|
||||
updatedBotInfos.insert(CachedPeerBotInfo(peerId: botPeerId, botInfo: BotInfo(description: previousBotInfo.botInfo.description, photo: previousBotInfo.botInfo.photo, video: previousBotInfo.botInfo.video, commands: commands, menuButton: previousBotInfo.botInfo.menuButton, privacyPolicyUrl: previousBotInfo.botInfo.privacyPolicyUrl, appSettings: previousBotInfo.botInfo.appSettings)), at: index)
|
||||
return previous.withUpdatedBotInfos(updatedBotInfos)
|
||||
}
|
||||
} else if peer.peerId.namespace == Namespaces.Peer.CloudChannel, let previous = current as? CachedChannelData {
|
||||
@ -1721,7 +1721,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
||||
var updatedBotInfos = previous.botInfos
|
||||
let previousBotInfo = updatedBotInfos[index]
|
||||
updatedBotInfos.remove(at: index)
|
||||
updatedBotInfos.insert(CachedPeerBotInfo(peerId: botPeerId, botInfo: BotInfo(description: previousBotInfo.botInfo.description, photo: previousBotInfo.botInfo.photo, video: previousBotInfo.botInfo.video, commands: commands, menuButton: previousBotInfo.botInfo.menuButton, privacyPolicyUrl: previousBotInfo.botInfo.privacyPolicyUrl)), at: index)
|
||||
updatedBotInfos.insert(CachedPeerBotInfo(peerId: botPeerId, botInfo: BotInfo(description: previousBotInfo.botInfo.description, photo: previousBotInfo.botInfo.photo, video: previousBotInfo.botInfo.video, commands: commands, menuButton: previousBotInfo.botInfo.menuButton, privacyPolicyUrl: previousBotInfo.botInfo.privacyPolicyUrl, appSettings: previousBotInfo.botInfo.appSettings)), at: index)
|
||||
return previous.withUpdatedBotInfos(updatedBotInfos)
|
||||
}
|
||||
}
|
||||
@ -1733,7 +1733,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
||||
updatedState.updateCachedPeerData(botPeerId, { current in
|
||||
if let previous = current as? CachedUserData {
|
||||
if let botInfo = previous.botInfo {
|
||||
return previous.withUpdatedBotInfo(BotInfo(description: botInfo.description, photo: botInfo.photo, video: botInfo.video, commands: botInfo.commands, menuButton: menuButton, privacyPolicyUrl: botInfo.privacyPolicyUrl))
|
||||
return previous.withUpdatedBotInfo(BotInfo(description: botInfo.description, photo: botInfo.photo, video: botInfo.video, commands: botInfo.commands, menuButton: menuButton, privacyPolicyUrl: botInfo.privacyPolicyUrl, appSettings: botInfo.appSettings))
|
||||
}
|
||||
}
|
||||
return current
|
||||
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
||||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 192
|
||||
return 194
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
@ -45,6 +45,62 @@ public enum BotMenuButton: PostboxCoding, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct BotAppSettings: PostboxCoding, Equatable {
|
||||
public let placeholder: TelegramMediaFile?
|
||||
public let backgroundColor: Int32?
|
||||
public let backgroundDarkColor: Int32?
|
||||
public let headerColor: Int32?
|
||||
public let headerDarkColor: Int32?
|
||||
|
||||
public init(placeholder: TelegramMediaFile?, backgroundColor: Int32?, backgroundDarkColor: Int32?, headerColor: Int32?, headerDarkColor: Int32?) {
|
||||
self.placeholder = placeholder
|
||||
self.backgroundColor = backgroundColor
|
||||
self.backgroundDarkColor = backgroundDarkColor
|
||||
self.headerColor = headerColor
|
||||
self.headerDarkColor = headerDarkColor
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
if let placeholder = decoder.decodeObjectForKey("p", decoder: { TelegramMediaFile(decoder: $0) }) as? TelegramMediaFile {
|
||||
self.placeholder = placeholder
|
||||
} else {
|
||||
self.placeholder = nil
|
||||
}
|
||||
self.backgroundColor = decoder.decodeOptionalInt32ForKey("b")
|
||||
self.backgroundDarkColor = decoder.decodeOptionalInt32ForKey("bd")
|
||||
self.headerColor = decoder.decodeOptionalInt32ForKey("h")
|
||||
self.headerDarkColor = decoder.decodeOptionalInt32ForKey("hd")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
if let placeholder = self.placeholder {
|
||||
encoder.encodeObject(placeholder, forKey: "p")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "p")
|
||||
}
|
||||
if let backgroundColor = self.backgroundColor {
|
||||
encoder.encodeInt32(backgroundColor, forKey: "b")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "b")
|
||||
}
|
||||
if let backgroundDarkColor = self.backgroundDarkColor {
|
||||
encoder.encodeInt32(backgroundDarkColor, forKey: "bd")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "bd")
|
||||
}
|
||||
if let headerColor = self.headerColor {
|
||||
encoder.encodeInt32(headerColor, forKey: "h")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "h")
|
||||
}
|
||||
if let headerDarkColor = self.headerDarkColor {
|
||||
encoder.encodeInt32(headerDarkColor, forKey: "hd")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "hd")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class BotInfo: PostboxCoding, Equatable {
|
||||
public let description: String
|
||||
public let photo: TelegramMediaImage?
|
||||
@ -52,14 +108,16 @@ public final class BotInfo: PostboxCoding, Equatable {
|
||||
public let commands: [BotCommand]
|
||||
public let menuButton: BotMenuButton
|
||||
public let privacyPolicyUrl: String?
|
||||
public let appSettings: BotAppSettings?
|
||||
|
||||
public init(description: String, photo: TelegramMediaImage?, video: TelegramMediaFile?, commands: [BotCommand], menuButton: BotMenuButton, privacyPolicyUrl: String?) {
|
||||
public init(description: String, photo: TelegramMediaImage?, video: TelegramMediaFile?, commands: [BotCommand], menuButton: BotMenuButton, privacyPolicyUrl: String?, appSettings: BotAppSettings?) {
|
||||
self.description = description
|
||||
self.photo = photo
|
||||
self.video = video
|
||||
self.commands = commands
|
||||
self.menuButton = menuButton
|
||||
self.privacyPolicyUrl = privacyPolicyUrl
|
||||
self.appSettings = appSettings
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
@ -77,6 +135,11 @@ public final class BotInfo: PostboxCoding, Equatable {
|
||||
self.commands = decoder.decodeObjectArrayWithDecoderForKey("c")
|
||||
self.menuButton = (decoder.decodeObjectForKey("b", decoder: { BotMenuButton(decoder: $0) }) as? BotMenuButton) ?? .commands
|
||||
self.privacyPolicyUrl = decoder.decodeOptionalStringForKey("pp")
|
||||
if let appSettings = decoder.decodeObjectForKey("as", decoder: { BotAppSettings(decoder: $0) }) as? BotAppSettings {
|
||||
self.appSettings = appSettings
|
||||
} else {
|
||||
self.appSettings = nil
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
@ -98,9 +161,14 @@ public final class BotInfo: PostboxCoding, Equatable {
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "pp")
|
||||
}
|
||||
if let appSettings = self.appSettings {
|
||||
encoder.encodeObject(appSettings, forKey: "as")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "as")
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: BotInfo, rhs: BotInfo) -> Bool {
|
||||
return lhs.description == rhs.description && lhs.commands == rhs.commands && lhs.menuButton == rhs.menuButton && lhs.photo == rhs.photo && lhs.privacyPolicyUrl == rhs.privacyPolicyUrl
|
||||
return lhs.description == rhs.description && lhs.commands == rhs.commands && lhs.menuButton == rhs.menuButton && lhs.photo == rhs.photo && lhs.privacyPolicyUrl == rhs.privacyPolicyUrl && lhs.appSettings == rhs.appSettings
|
||||
}
|
||||
}
|
||||
|
@ -299,6 +299,7 @@ public struct CachedUserFlags: OptionSet {
|
||||
public static let premiumRequired = CachedUserFlags(rawValue: 1 << 3)
|
||||
public static let adsEnabled = CachedUserFlags(rawValue: 1 << 4)
|
||||
public static let canViewRevenue = CachedUserFlags(rawValue: 1 << 5)
|
||||
public static let botCanManageEmojiStatus = CachedUserFlags(rawValue: 1 << 6)
|
||||
}
|
||||
|
||||
public final class EditableBotInfo: PostboxCoding, Equatable {
|
||||
|
@ -103,10 +103,11 @@ public final class TelegramMediaInvoice: Media, Equatable {
|
||||
public let photo: TelegramMediaWebFile?
|
||||
public let flags: TelegramMediaInvoiceFlags
|
||||
public let extendedMedia: TelegramExtendedMedia?
|
||||
public let subscriptionPeriod: Int32?
|
||||
|
||||
public let version: Int32
|
||||
|
||||
public init(title: String, description: String, photo: TelegramMediaWebFile?, receiptMessageId: MessageId?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: TelegramExtendedMedia?, flags: TelegramMediaInvoiceFlags, version: Int32) {
|
||||
public init(title: String, description: String, photo: TelegramMediaWebFile?, receiptMessageId: MessageId?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: TelegramExtendedMedia?, subscriptionPeriod: Int32?, flags: TelegramMediaInvoiceFlags, version: Int32) {
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.photo = photo
|
||||
@ -114,8 +115,9 @@ public final class TelegramMediaInvoice: Media, Equatable {
|
||||
self.currency = currency
|
||||
self.totalAmount = totalAmount
|
||||
self.startParam = startParam
|
||||
self.flags = flags
|
||||
self.extendedMedia = extendedMedia
|
||||
self.subscriptionPeriod = subscriptionPeriod
|
||||
self.flags = flags
|
||||
self.version = version
|
||||
}
|
||||
|
||||
@ -126,8 +128,9 @@ public final class TelegramMediaInvoice: Media, Equatable {
|
||||
self.totalAmount = decoder.decodeInt64ForKey("ta", orElse: 0)
|
||||
self.startParam = decoder.decodeStringForKey("sp", orElse: "")
|
||||
self.photo = decoder.decodeObjectForKey("p") as? TelegramMediaWebFile
|
||||
self.flags = TelegramMediaInvoiceFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0))
|
||||
self.extendedMedia = decoder.decodeObjectForKey("m", decoder: { TelegramExtendedMedia(decoder: $0) }) as? TelegramExtendedMedia
|
||||
self.subscriptionPeriod = decoder.decodeOptionalInt32ForKey("sp")
|
||||
self.flags = TelegramMediaInvoiceFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0))
|
||||
|
||||
if let receiptMessageIdPeerId = decoder.decodeOptionalInt64ForKey("r.p") as Int64?, let receiptMessageIdNamespace = decoder.decodeOptionalInt32ForKey("r.n") as Int32?, let receiptMessageIdId = decoder.decodeOptionalInt32ForKey("r.i") as Int32? {
|
||||
self.receiptMessageId = MessageId(peerId: PeerId(receiptMessageIdPeerId), namespace: receiptMessageIdNamespace, id: receiptMessageIdId)
|
||||
@ -158,6 +161,12 @@ public final class TelegramMediaInvoice: Media, Equatable {
|
||||
encoder.encodeNil(forKey: "m")
|
||||
}
|
||||
|
||||
if let subscriptionPeriod = self.subscriptionPeriod {
|
||||
encoder.encodeInt32(subscriptionPeriod, forKey: "sp")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "sp")
|
||||
}
|
||||
|
||||
if let receiptMessageId = self.receiptMessageId {
|
||||
encoder.encodeInt64(receiptMessageId.peerId.toInt64(), forKey: "r.p")
|
||||
encoder.encodeInt32(receiptMessageId.namespace, forKey: "r.n")
|
||||
@ -212,6 +221,10 @@ public final class TelegramMediaInvoice: Media, Equatable {
|
||||
return false
|
||||
}
|
||||
|
||||
if self.subscriptionPeriod != other.subscriptionPeriod {
|
||||
return false
|
||||
}
|
||||
|
||||
if self.version != other.version {
|
||||
return false
|
||||
}
|
||||
@ -233,6 +246,7 @@ public final class TelegramMediaInvoice: Media, Equatable {
|
||||
totalAmount: self.totalAmount,
|
||||
startParam: self.startParam,
|
||||
extendedMedia: extendedMedia,
|
||||
subscriptionPeriod: self.subscriptionPeriod,
|
||||
flags: self.flags,
|
||||
version: self.version
|
||||
)
|
||||
|
@ -1126,6 +1126,34 @@ public extension TelegramEngine.EngineData.Item {
|
||||
}
|
||||
}
|
||||
|
||||
public struct CanManageEmojiStatus: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = Bool
|
||||
|
||||
fileprivate var id: EnginePeer.Id
|
||||
public var mapKey: EnginePeer.Id {
|
||||
return self.id
|
||||
}
|
||||
|
||||
public init(id: EnginePeer.Id) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
var key: PostboxViewKey {
|
||||
return .cachedPeerData(peerId: self.id)
|
||||
}
|
||||
|
||||
func extract(view: PostboxView) -> Result {
|
||||
guard let view = view as? CachedPeerDataView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
if let cachedData = view.cachedPeerData as? CachedUserData {
|
||||
return cachedData.flags.contains(.botCanManageEmojiStatus)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CanViewStarsRevenue: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = Bool
|
||||
|
||||
|
@ -56,6 +56,9 @@ func _internal_requestSimpleWebView(postbox: Postbox, network: Network, botId: P
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
resultFlags.insert(.fullSize)
|
||||
}
|
||||
if (flags & (1 << 2)) != 0 {
|
||||
resultFlags.insert(.fullScreen)
|
||||
}
|
||||
return .single(RequestWebViewResult(flags: resultFlags, queryId: queryId, url: url, keepAliveSignal: nil))
|
||||
}
|
||||
}
|
||||
@ -64,7 +67,7 @@ func _internal_requestSimpleWebView(postbox: Postbox, network: Network, botId: P
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
func _internal_requestMainWebView(postbox: Postbox, network: Network, botId: PeerId, source: RequestSimpleWebViewSource, themeParams: [String: Any]?) -> Signal<RequestWebViewResult, RequestWebViewError> {
|
||||
func _internal_requestMainWebView(postbox: Postbox, network: Network, peerId: PeerId, botId: PeerId, source: RequestSimpleWebViewSource, themeParams: [String: Any]?) -> Signal<RequestWebViewResult, RequestWebViewError> {
|
||||
var serializedThemeParams: Api.DataJSON?
|
||||
if let themeParams = themeParams, let data = try? JSONSerialization.data(withJSONObject: themeParams, options: []), let dataString = String(data: data, encoding: .utf8) {
|
||||
serializedThemeParams = .dataJSON(data: dataString)
|
||||
@ -73,7 +76,7 @@ func _internal_requestMainWebView(postbox: Postbox, network: Network, botId: Pee
|
||||
guard let bot = transaction.getPeer(botId), let inputUser = apiInputUser(bot) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
guard let peer = transaction.getPeer(botId), let inputPeer = apiInputPeer(peer) else {
|
||||
guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
@ -103,6 +106,9 @@ func _internal_requestMainWebView(postbox: Postbox, network: Network, botId: Pee
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
resultFlags.insert(.fullSize)
|
||||
}
|
||||
if (flags & (1 << 2)) != 0 {
|
||||
resultFlags.insert(.fullScreen)
|
||||
}
|
||||
return .single(RequestWebViewResult(flags: resultFlags, queryId: queryId, url: url, keepAliveSignal: nil))
|
||||
}
|
||||
}
|
||||
@ -128,6 +134,7 @@ public struct RequestWebViewResult {
|
||||
}
|
||||
|
||||
public static let fullSize = Flags(rawValue: 1 << 0)
|
||||
public static let fullScreen = Flags(rawValue: 1 << 1)
|
||||
}
|
||||
|
||||
public let flags: Flags
|
||||
@ -237,6 +244,9 @@ func _internal_requestWebView(postbox: Postbox, network: Network, stateManager:
|
||||
if (webViewFlags & (1 << 1)) != 0 {
|
||||
resultFlags.insert(.fullSize)
|
||||
}
|
||||
if (flags & (1 << 2)) != 0 {
|
||||
resultFlags.insert(.fullScreen)
|
||||
}
|
||||
let keepAlive: Signal<Never, KeepWebViewError>?
|
||||
if let queryId {
|
||||
keepAlive = keepWebViewSignal(network: network, stateManager: stateManager, flags: flags, peer: inputPeer, bot: inputBot, queryId: queryId, replyToMessageId: replyToMessageId, threadId: threadId, sendAs: nil)
|
||||
@ -276,7 +286,7 @@ func _internal_sendWebViewData(postbox: Postbox, network: Network, stateManager:
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
func _internal_requestAppWebView(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, appReference: BotAppReference, payload: String?, themeParams: [String: Any]?, compact: Bool, allowWrite: Bool) -> Signal<RequestWebViewResult, RequestWebViewError> {
|
||||
func _internal_requestAppWebView(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, appReference: BotAppReference, payload: String?, themeParams: [String: Any]?, compact: Bool, fullscreen: Bool, allowWrite: Bool) -> Signal<RequestWebViewResult, RequestWebViewError> {
|
||||
var serializedThemeParams: Api.DataJSON?
|
||||
if let themeParams = themeParams, let data = try? JSONSerialization.data(withJSONObject: themeParams, options: []), let dataString = String(data: data, encoding: .utf8) {
|
||||
serializedThemeParams = .dataJSON(data: dataString)
|
||||
@ -311,6 +321,9 @@ func _internal_requestAppWebView(postbox: Postbox, network: Network, stateManage
|
||||
if compact {
|
||||
flags |= (1 << 7)
|
||||
}
|
||||
if fullscreen {
|
||||
flags |= (1 << 8)
|
||||
}
|
||||
|
||||
return network.request(Api.functions.messages.requestAppWebView(flags: flags, peer: inputPeer, app: app, startParam: payload, themeParams: serializedThemeParams, platform: botWebViewPlatform))
|
||||
|> mapError { _ -> RequestWebViewError in
|
||||
@ -323,6 +336,9 @@ func _internal_requestAppWebView(postbox: Postbox, network: Network, stateManage
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
resultFlags.insert(.fullSize)
|
||||
}
|
||||
if (flags & (1 << 2)) != 0 {
|
||||
resultFlags.insert(.fullScreen)
|
||||
}
|
||||
return .single(RequestWebViewResult(flags: resultFlags, queryId: queryId, url: url, keepAliveSignal: nil))
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
public struct PreparedInlineMessage: Equatable {
|
||||
let queryId: Int64
|
||||
let result: ChatContextResult
|
||||
let peerTypes: [ReplyMarkupButtonAction.PeerTypes]
|
||||
}
|
||||
|
||||
func _internal_getPreparedInlineMessage(account: Account, botId: EnginePeer.Id, id: String) -> Signal<PreparedInlineMessage?, NoError> {
|
||||
return account.postbox.transaction { transaction -> Api.InputUser? in
|
||||
return transaction.getPeer(botId).flatMap(apiInputUser)
|
||||
}
|
||||
|> mapToSignal { inputBot -> Signal<PreparedInlineMessage?, NoError> in
|
||||
guard let inputBot else {
|
||||
return .single(nil)
|
||||
}
|
||||
return account.network.request(Api.functions.messages.getPreparedInlineMessage(bot: inputBot, id: id))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.PreparedInlineMessage?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<PreparedInlineMessage?, NoError> in
|
||||
guard let result else {
|
||||
return .single(nil)
|
||||
}
|
||||
return account.postbox.transaction { transaction -> PreparedInlineMessage? in
|
||||
switch result {
|
||||
case let .preparedInlineMessage(queryId, result, peerTypes, users):
|
||||
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(users: users))
|
||||
|
||||
return PreparedInlineMessage(queryId: queryId, result: ChatContextResult(apiResult: result, queryId: queryId), peerTypes: peerTypes.compactMap { ReplyMarkupButtonAction.PeerTypes(apiType: $0) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -403,6 +403,10 @@ public extension TelegramEngine {
|
||||
public func messageReadStats(id: MessageId) -> Signal<MessageReadStats?, NoError> {
|
||||
return _internal_messageReadStats(account: self.account, id: id)
|
||||
}
|
||||
|
||||
public func getPreparedInlineMessage(botId: EnginePeer.Id, id: String) -> Signal<PreparedInlineMessage?, NoError> {
|
||||
return _internal_getPreparedInlineMessage(account: self.account, botId: botId, id: id)
|
||||
}
|
||||
|
||||
public func requestCancelLiveLocation(ids: [MessageId]) -> Signal<Never, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> Void in
|
||||
@ -590,12 +594,12 @@ public extension TelegramEngine {
|
||||
return _internal_requestSimpleWebView(postbox: self.account.postbox, network: self.account.network, botId: botId, url: url, source: source, themeParams: themeParams)
|
||||
}
|
||||
|
||||
public func requestMainWebView(botId: PeerId, source: RequestSimpleWebViewSource, themeParams: [String: Any]?) -> Signal<RequestWebViewResult, RequestWebViewError> {
|
||||
return _internal_requestMainWebView(postbox: self.account.postbox, network: self.account.network, botId: botId, source: source, themeParams: themeParams)
|
||||
public func requestMainWebView(peerId: PeerId, botId: PeerId, source: RequestSimpleWebViewSource, themeParams: [String: Any]?) -> Signal<RequestWebViewResult, RequestWebViewError> {
|
||||
return _internal_requestMainWebView(postbox: self.account.postbox, network: self.account.network, peerId: peerId, botId: botId, source: source, themeParams: themeParams)
|
||||
}
|
||||
|
||||
public func requestAppWebView(peerId: PeerId, appReference: BotAppReference, payload: String?, themeParams: [String: Any]?, compact: Bool, allowWrite: Bool) -> Signal<RequestWebViewResult, RequestWebViewError> {
|
||||
return _internal_requestAppWebView(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, appReference: appReference, payload: payload, themeParams: themeParams, compact: compact, allowWrite: allowWrite)
|
||||
public func requestAppWebView(peerId: PeerId, appReference: BotAppReference, payload: String?, themeParams: [String: Any]?, compact: Bool, fullscreen: Bool, allowWrite: Bool) -> Signal<RequestWebViewResult, RequestWebViewError> {
|
||||
return _internal_requestAppWebView(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, appReference: appReference, payload: payload, themeParams: themeParams, compact: compact, fullscreen: fullscreen, allowWrite: allowWrite)
|
||||
}
|
||||
|
||||
public func sendWebViewData(botId: PeerId, buttonText: String, data: String) -> Signal<Never, SendWebViewDataError> {
|
||||
|
@ -63,14 +63,16 @@ public struct BotPaymentInvoice : Equatable {
|
||||
public let prices: [BotPaymentPrice]
|
||||
public let tip: Tip?
|
||||
public let termsInfo: RecurrentInfo?
|
||||
public let subscriptionPeriod: Int32?
|
||||
|
||||
public init(isTest: Bool, requestedFields: BotPaymentInvoiceFields, currency: String, prices: [BotPaymentPrice], tip: Tip?, termsInfo: RecurrentInfo?) {
|
||||
public init(isTest: Bool, requestedFields: BotPaymentInvoiceFields, currency: String, prices: [BotPaymentPrice], tip: Tip?, termsInfo: RecurrentInfo?, subscriptionPeriod: Int32?) {
|
||||
self.isTest = isTest
|
||||
self.requestedFields = requestedFields
|
||||
self.currency = currency
|
||||
self.prices = prices
|
||||
self.tip = tip
|
||||
self.termsInfo = termsInfo
|
||||
self.subscriptionPeriod = subscriptionPeriod
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,7 +177,7 @@ public enum BotPaymentFormRequestError {
|
||||
extension BotPaymentInvoice {
|
||||
init(apiInvoice: Api.Invoice) {
|
||||
switch apiInvoice {
|
||||
case let .invoice(flags, currency, prices, maxTipAmount, suggestedTipAmounts, termsUrl):
|
||||
case let .invoice(flags, currency, prices, maxTipAmount, suggestedTipAmounts, termsUrl, subscriptionPeriod):
|
||||
var fields = BotPaymentInvoiceFields()
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
fields.insert(.name)
|
||||
@ -212,7 +214,7 @@ extension BotPaymentInvoice {
|
||||
case let .labeledPrice(label, amount):
|
||||
return BotPaymentPrice(label: label, amount: amount)
|
||||
}
|
||||
}, tip: parsedTip, termsInfo: termsInfo)
|
||||
}, tip: parsedTip, termsInfo: termsInfo, subscriptionPeriod: subscriptionPeriod)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -400,13 +402,13 @@ func _internal_fetchBotPaymentInvoice(postbox: Postbox, network: Network, source
|
||||
parsedFlags.insert(.shippingAddressRequested)
|
||||
}
|
||||
|
||||
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: 0, startParam: "", extendedMedia: nil, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion)
|
||||
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: 0, startParam: "", extendedMedia: nil, subscriptionPeriod: parsedInvoice.subscriptionPeriod, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion)
|
||||
case let .paymentFormStars(_, _, _, title, description, photo, invoice, _):
|
||||
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
|
||||
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: parsedInvoice.prices.reduce(0, { $0 + $1.amount }), startParam: "", extendedMedia: nil, flags: [], version: TelegramMediaInvoice.lastVersion)
|
||||
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: parsedInvoice.prices.reduce(0, { $0 + $1.amount }), startParam: "", extendedMedia: nil, subscriptionPeriod: parsedInvoice.subscriptionPeriod, flags: [], version: TelegramMediaInvoice.lastVersion)
|
||||
case let .paymentFormStarGift(_, invoice):
|
||||
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
|
||||
return TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: parsedInvoice.prices.reduce(0, { $0 + $1.amount }), startParam: "", extendedMedia: nil, flags: [], version: TelegramMediaInvoice.lastVersion)
|
||||
return TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: parsedInvoice.prices.reduce(0, { $0 + $1.amount }), startParam: "", extendedMedia: nil, subscriptionPeriod: parsedInvoice.subscriptionPeriod, flags: [], version: TelegramMediaInvoice.lastVersion)
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> BotPaymentFormRequestError in }
|
||||
@ -798,6 +800,7 @@ func _internal_requestBotPaymentReceipt(account: Account, messageId: MessageId)
|
||||
totalAmount: totalAmount,
|
||||
startParam: "",
|
||||
extendedMedia: nil,
|
||||
subscriptionPeriod: parsedInvoice.subscriptionPeriod,
|
||||
flags: [],
|
||||
version: TelegramMediaInvoice.lastVersion
|
||||
)
|
||||
@ -820,6 +823,7 @@ func _internal_requestBotPaymentReceipt(account: Account, messageId: MessageId)
|
||||
totalAmount: totalAmount,
|
||||
startParam: "",
|
||||
extendedMedia: nil,
|
||||
subscriptionPeriod: parsedInvoice.subscriptionPeriod,
|
||||
flags: [],
|
||||
version: TelegramMediaInvoice.lastVersion
|
||||
)
|
||||
|
@ -554,7 +554,7 @@ private extension StarsContext.State.Transaction {
|
||||
private extension StarsContext.State.Subscription {
|
||||
init?(apiSubscription: Api.StarsSubscription, transaction: Transaction) {
|
||||
switch apiSubscription {
|
||||
case let .starsSubscription(apiFlags, id, apiPeer, untilDate, pricing, inviteHash):
|
||||
case let .starsSubscription(apiFlags, id, apiPeer, untilDate, pricing, inviteHash, title, photo, invoiceSlug):
|
||||
guard let peer = transaction.getPeer(apiPeer.peerId) else {
|
||||
return nil
|
||||
}
|
||||
@ -568,7 +568,7 @@ private extension StarsContext.State.Subscription {
|
||||
if (apiFlags & (1 << 2)) != 0 {
|
||||
flags.insert(.missingBalance)
|
||||
}
|
||||
self.init(flags: flags, id: id, peer: EnginePeer(peer), untilDate: untilDate, pricing: StarsSubscriptionPricing(apiStarsSubscriptionPricing: pricing), inviteHash: inviteHash)
|
||||
self.init(flags: flags, id: id, peer: EnginePeer(peer), untilDate: untilDate, pricing: StarsSubscriptionPricing(apiStarsSubscriptionPricing: pricing), inviteHash: inviteHash, title: title, photo: photo.flatMap(TelegramMediaWebFile.init), invoiceSlug: invoiceSlug)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -727,6 +727,9 @@ public final class StarsContext {
|
||||
public let untilDate: Int32
|
||||
public let pricing: StarsSubscriptionPricing
|
||||
public let inviteHash: String?
|
||||
public let title: String?
|
||||
public let photo: TelegramMediaWebFile?
|
||||
public let invoiceSlug: String?
|
||||
|
||||
public init(
|
||||
flags: Flags,
|
||||
@ -734,7 +737,10 @@ public final class StarsContext {
|
||||
peer: EnginePeer,
|
||||
untilDate: Int32,
|
||||
pricing: StarsSubscriptionPricing,
|
||||
inviteHash: String?
|
||||
inviteHash: String?,
|
||||
title: String?,
|
||||
photo: TelegramMediaWebFile?,
|
||||
invoiceSlug: String?
|
||||
) {
|
||||
self.flags = flags
|
||||
self.id = id
|
||||
@ -742,6 +748,9 @@ public final class StarsContext {
|
||||
self.untilDate = untilDate
|
||||
self.pricing = pricing
|
||||
self.inviteHash = inviteHash
|
||||
self.title = title
|
||||
self.photo = photo
|
||||
self.invoiceSlug = invoiceSlug
|
||||
}
|
||||
|
||||
public static func == (lhs: Subscription, rhs: Subscription) -> Bool {
|
||||
@ -763,6 +772,15 @@ public final class StarsContext {
|
||||
if lhs.inviteHash != rhs.inviteHash {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.photo != rhs.photo {
|
||||
return false
|
||||
}
|
||||
if lhs.invoiceSlug != rhs.invoiceSlug {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1192,7 +1210,7 @@ private final class StarsSubscriptionsContextImpl {
|
||||
} else {
|
||||
updatedFlags.remove(.isCancelled)
|
||||
}
|
||||
let updatedSubscription = StarsContext.State.Subscription(flags: updatedFlags, id: subscription.id, peer: subscription.peer, untilDate: subscription.untilDate, pricing: subscription.pricing, inviteHash: subscription.inviteHash)
|
||||
let updatedSubscription = StarsContext.State.Subscription(flags: updatedFlags, id: subscription.id, peer: subscription.peer, untilDate: subscription.untilDate, pricing: subscription.pricing, inviteHash: subscription.inviteHash, title: subscription.title, photo: subscription.photo, invoiceSlug: subscription.invoiceSlug)
|
||||
updatedState.subscriptions[index] = updatedSubscription
|
||||
}
|
||||
self.updateState(updatedState)
|
||||
|
@ -811,6 +811,10 @@ public extension TelegramEngine {
|
||||
return _internal_updateBotAbout(account: self.account, peerId: peerId, about: about)
|
||||
}
|
||||
|
||||
public func toggleBotEmojiStatusAccess(peerId: PeerId, enabled: Bool) -> Signal<Never, ToggleBotEmojiStatusAccessError> {
|
||||
return _internal_toggleBotEmojiStatusAccess(account: self.account, peerId: peerId, enabled: enabled)
|
||||
}
|
||||
|
||||
public func updatePeerNameColorAndEmoji(peerId: EnginePeer.Id, nameColor: PeerNameColor, backgroundEmojiId: Int64?, profileColor: PeerNameColor?, profileBackgroundEmojiId: Int64?) -> Signal<Void, UpdatePeerNameColorAndEmojiError> {
|
||||
return _internal_updatePeerNameColorAndEmoji(account: self.account, peerId: peerId, nameColor: nameColor, backgroundEmojiId: backgroundEmojiId, profileColor: profileColor, profileBackgroundEmojiId: profileBackgroundEmojiId)
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ func _internal_updateBotDescription(account: Account, peerId: PeerId, descriptio
|
||||
if let botInfo = current.botInfo {
|
||||
var updatedBotInfo = botInfo
|
||||
if botInfo.description == editableBotInfo.description {
|
||||
updatedBotInfo = BotInfo(description: description, photo: botInfo.photo, video: botInfo.video, commands: botInfo.commands, menuButton: botInfo.menuButton, privacyPolicyUrl: botInfo.privacyPolicyUrl)
|
||||
updatedBotInfo = BotInfo(description: description, photo: botInfo.photo, video: botInfo.video, commands: botInfo.commands, menuButton: botInfo.menuButton, privacyPolicyUrl: botInfo.privacyPolicyUrl, appSettings: botInfo.appSettings)
|
||||
}
|
||||
return current.withUpdatedEditableBotInfo(editableBotInfo.withUpdatedDescription(description)).withUpdatedBotInfo(updatedBotInfo)
|
||||
} else {
|
||||
@ -121,3 +121,43 @@ func _internal_updateBotDescription(account: Account, peerId: PeerId, descriptio
|
||||
|> mapError { _ -> UpdateBotInfoError in }
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public enum ToggleBotEmojiStatusAccessError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_toggleBotEmojiStatusAccess(account: Account, peerId: PeerId, enabled: Bool) -> Signal<Never, ToggleBotEmojiStatusAccessError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, ToggleBotEmojiStatusAccessError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
|
||||
return account.network.request(Api.functions.bots.toggleUserEmojiStatusPermission(bot: inputUser, enabled: enabled ? .boolTrue : .boolFalse))
|
||||
|> mapError { _ -> ToggleBotEmojiStatusAccessError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, ToggleBotEmojiStatusAccessError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if case .boolTrue = result {
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedUserData {
|
||||
var updatedFlags: CachedUserFlags = current.flags
|
||||
if enabled {
|
||||
updatedFlags.insert(.botCanManageEmojiStatus)
|
||||
} else {
|
||||
updatedFlags.remove(.botCanManageEmojiStatus)
|
||||
}
|
||||
return current.withUpdatedFlags(updatedFlags)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> ToggleBotEmojiStatusAccessError in }
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> ToggleBotEmojiStatusAccessError in }
|
||||
|> switchToLatest
|
||||
|> ignoreValues
|
||||
}
|
||||
|
@ -282,6 +282,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
||||
let translationsDisabled = (userFullFlags & (1 << 23)) != 0
|
||||
let adsEnabled = (userFullFlags2 & (1 << 7)) != 0
|
||||
let canViewRevenue = (userFullFlags2 & (1 << 9)) != 0
|
||||
let botCanManageEmojiStatus = (userFullFlags2 & (1 << 10)) != 0
|
||||
|
||||
var flags: CachedUserFlags = previous.flags
|
||||
if premiumRequired {
|
||||
@ -309,6 +310,11 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
||||
} else {
|
||||
flags.remove(.canViewRevenue)
|
||||
}
|
||||
if botCanManageEmojiStatus {
|
||||
flags.insert(.botCanManageEmojiStatus)
|
||||
} else {
|
||||
flags.remove(.botCanManageEmojiStatus)
|
||||
}
|
||||
|
||||
let callsPrivate = (userFullFlags & (1 << 5)) != 0
|
||||
let canPinMessages = (userFullFlags & (1 << 7)) != 0
|
||||
@ -446,7 +452,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
||||
var botInfos: [CachedPeerBotInfo] = []
|
||||
for botInfo in chatFullBotInfo ?? [] {
|
||||
switch botInfo {
|
||||
case let .botInfo(_, userId, _, _, _, _, _, _):
|
||||
case let .botInfo(_, userId, _, _, _, _, _, _, _):
|
||||
if let userId = userId {
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
let parsedBotInfo = BotInfo(apiBotInfo: botInfo)
|
||||
@ -636,7 +642,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
||||
var botInfos: [CachedPeerBotInfo] = []
|
||||
for botInfo in apiBotInfos {
|
||||
switch botInfo {
|
||||
case let .botInfo(_, userId, _, _, _, _, _, _):
|
||||
case let .botInfo(_, userId, _, _, _, _, _, _, _):
|
||||
if let userId = userId {
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
let parsedBotInfo = BotInfo(apiBotInfo: botInfo)
|
||||
|
@ -50,6 +50,18 @@ extension JSON {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public init?(dictionary: [String: Any]) {
|
||||
var values: [String: JSON] = [:]
|
||||
for (key, value) in dictionary {
|
||||
if let v = JSON(value) {
|
||||
values[key] = v
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
self = .dictionary(values)
|
||||
}
|
||||
}
|
||||
|
||||
extension JSON: Collection {
|
||||
@ -125,7 +137,7 @@ extension JSON {
|
||||
get {
|
||||
switch self {
|
||||
case .null:
|
||||
return 0
|
||||
return NSNull()
|
||||
case let .number(value):
|
||||
return value
|
||||
case let .string(value):
|
||||
@ -172,6 +184,18 @@ extension JSON {
|
||||
}
|
||||
}
|
||||
|
||||
extension JSON {
|
||||
public var string: String? {
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: self.value) else {
|
||||
return nil
|
||||
}
|
||||
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
return jsonDataString
|
||||
}
|
||||
}
|
||||
|
||||
extension JSON: ExpressibleByDictionaryLiteral {
|
||||
public init(dictionaryLiteral elements: (String, Any)...) {
|
||||
self = .dictionary(elements.reduce([String: JSON]()) { (dictionary, element) in
|
||||
@ -195,6 +219,12 @@ private protocol JSONValue {
|
||||
var jsonValue: JSON { get }
|
||||
}
|
||||
|
||||
extension NSNull: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .null
|
||||
}
|
||||
}
|
||||
|
||||
extension Int: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(Double(self))
|
||||
|
@ -48,7 +48,11 @@ public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDate
|
||||
}
|
||||
|
||||
if let dotIndex = balanceText.range(of: dateTimeFormat.decimalSeparator) {
|
||||
balanceText = String(balanceText[balanceText.startIndex ..< min(balanceText.endIndex, balanceText.index(dotIndex.upperBound, offsetBy: 2))])
|
||||
if let endIndex = balanceText.index(dotIndex.upperBound, offsetBy: 2, limitedBy: balanceText.endIndex) {
|
||||
balanceText = String(balanceText[balanceText.startIndex..<endIndex])
|
||||
} else {
|
||||
balanceText = String(balanceText[balanceText.startIndex..<balanceText.endIndex])
|
||||
}
|
||||
|
||||
let integerPartString = balanceText[..<dotIndex.lowerBound]
|
||||
if let integerPart = Int32(integerPartString) {
|
||||
|
@ -1283,14 +1283,14 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
photo.append(photoRepresentation)
|
||||
}
|
||||
let channel = TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), accessHash: .genericPublic(0), title: invite.title, username: nil, photo: photo, creationDate: 0, version: 0, participationStatus: .left, info: .broadcast(TelegramChannelBroadcastInfo(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: invite.nameColor, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil)
|
||||
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount, startParam: "", extendedMedia: nil, flags: [], version: 0)
|
||||
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount, startParam: "", extendedMedia: nil, subscriptionPeriod: nil, flags: [], version: 0)
|
||||
|
||||
inputData.set(.single(BotCheckoutController.InputData(
|
||||
form: BotPaymentForm(
|
||||
id: subscriptionFormId,
|
||||
canSaveCredentials: false,
|
||||
passwordMissing: false,
|
||||
invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: subscriptionPricing.amount)], tip: nil, termsInfo: nil),
|
||||
invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: subscriptionPricing.amount)], tip: nil, termsInfo: nil, subscriptionPeriod: subscriptionPricing.period),
|
||||
paymentBotId: channel.id,
|
||||
providerId: nil,
|
||||
url: nil,
|
||||
|
@ -44,6 +44,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
|
||||
"//submodules/InAppPurchaseManager",
|
||||
"//submodules/Components/BlurredBackgroundComponent",
|
||||
"//submodules/ProgressNavigationButtonNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -30,6 +30,7 @@ import AudioToolbox
|
||||
import TextFormat
|
||||
import InAppPurchaseManager
|
||||
import BlurredBackgroundComponent
|
||||
import ProgressNavigationButtonNode
|
||||
|
||||
final class GiftSetupScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@ -203,7 +204,7 @@ final class GiftSetupScreenComponent: Component {
|
||||
self.buttonSeparator.opacity = Float(bottomPanelAlpha)
|
||||
}
|
||||
|
||||
func proceed() {
|
||||
@objc private func proceed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
@ -215,7 +216,7 @@ final class GiftSetupScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
func proceedWithPremiumGift() {
|
||||
private func proceedWithPremiumGift() {
|
||||
guard let component = self.component, case let .premium(product) = component.subject, let storeProduct = product.storeProduct, let inAppPurchaseManager = component.context.inAppPurchaseManager else {
|
||||
return
|
||||
}
|
||||
@ -304,7 +305,7 @@ final class GiftSetupScreenComponent: Component {
|
||||
})
|
||||
}
|
||||
|
||||
func proceedWithStarGift() {
|
||||
private func proceedWithStarGift() {
|
||||
guard let component = self.component, case let .starGift(starGift) = component.subject, let starsContext = component.context.starsContext, let starsState = starsContext.currentState else {
|
||||
return
|
||||
}
|
||||
@ -450,6 +451,8 @@ final class GiftSetupScreenComponent: Component {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
let peerName = self.peerMap[component.peerId]?.compactDisplayTitle ?? ""
|
||||
|
||||
if self.component == nil {
|
||||
let _ = (component.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId),
|
||||
@ -588,26 +591,16 @@ final class GiftSetupScreenComponent: Component {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let alphaTransition: ComponentTransition
|
||||
if !transition.animation.isImmediate {
|
||||
alphaTransition = .easeInOut(duration: 0.25)
|
||||
} else {
|
||||
alphaTransition = .immediate
|
||||
}
|
||||
|
||||
if themeUpdated {
|
||||
self.backgroundColor = environment.theme.list.blocksBackgroundColor
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let _ = alphaTransition
|
||||
let _ = presentationData
|
||||
|
||||
|
||||
let navigationTitleSize = self.navigationTitle.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: environment.strings.Gift_Send_Title, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
|
||||
text: .plain(NSAttributedString(string: environment.strings.Gift_Send_TitleTo(peerName).string, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
|
||||
horizontalAlignment: .center
|
||||
)),
|
||||
environment: {},
|
||||
@ -720,7 +713,6 @@ final class GiftSetupScreenComponent: Component {
|
||||
))))
|
||||
self.resetText = nil
|
||||
|
||||
let peerName = self.peerMap[component.peerId]?.compactDisplayTitle ?? ""
|
||||
let introFooter: AnyComponent<Empty>?
|
||||
switch component.subject {
|
||||
case .premium:
|
||||
@ -960,6 +952,20 @@ final class GiftSetupScreenComponent: Component {
|
||||
buttonView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) / 2.0), y: availableSize.height - bottomPanelHeight + bottomPanelPadding), size: buttonSize)
|
||||
}
|
||||
|
||||
let controller = environment.controller()
|
||||
if inputHeight > 10.0 {
|
||||
if self.inProgress {
|
||||
let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: environment.theme.rootController.navigationBar.accentTextColor))
|
||||
controller?.navigationItem.rightBarButtonItem = item
|
||||
} else {
|
||||
let rightBarButtonItem = UIBarButtonItem(title: environment.strings.Gift_Send_SendShort, style: .done, target: self, action: #selector(self.proceed))
|
||||
rightBarButtonItem.isEnabled = buttonIsEnabled
|
||||
controller?.navigationItem.setRightBarButton(rightBarButtonItem, animated: controller?.navigationItem.rightBarButtonItem == nil)
|
||||
}
|
||||
} else {
|
||||
controller?.navigationItem.setRightBarButton(nil, animated: true)
|
||||
}
|
||||
|
||||
if self.textInputState.isEditing, let emojiSuggestion = self.textInputState.currentEmojiSuggestion, emojiSuggestion.disposable == nil {
|
||||
emojiSuggestion.disposable = (EmojiSuggestionsComponent.suggestionData(context: component.context, isSavedMessages: false, query: emojiSuggestion.position.value)
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak emojiSuggestion] result in
|
||||
@ -1090,7 +1096,6 @@ final class GiftSetupScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let previousBounds = self.scrollView.bounds
|
||||
|
||||
self.recenterOnTag = nil
|
||||
|
@ -35,6 +35,7 @@ public func presentedLegacyCamera(context: AccountContext, peer: Peer?, chatLoca
|
||||
} else {
|
||||
controller = TGCameraController(context: legacyController.context, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, saveCapturedMedia: saveCapturedPhotos && !isSecretChat)
|
||||
}
|
||||
controller.modalPresentationStyle = .fullScreen
|
||||
controller.inhibitMultipleCapture = editingMedia
|
||||
|
||||
if !initialCaption.string.isEmpty {
|
||||
|
@ -1077,13 +1077,23 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
||||
transition.updateTransform(node: itemNode, transform: CATransform3DIdentity)
|
||||
|
||||
if let _ = itemNode.snapshotView {
|
||||
if itemNode.item.controller.minimizedTopEdgeOffset == nil, let snapshotView = itemNode.snapshotView, snapshotView.frame.origin.y == -12.0 {
|
||||
if itemNode.item.controller.isFullscreen {
|
||||
if layout.size.width < layout.size.height {
|
||||
let snapshotFrame = itemNode.snapshotContainerView.frame.offsetBy(dx: 0.0, dy: 64.0)
|
||||
transition.updateFrame(view: itemNode.snapshotContainerView, frame: snapshotFrame)
|
||||
}
|
||||
} else if itemNode.item.controller.minimizedTopEdgeOffset == nil, let snapshotView = itemNode.snapshotView, snapshotView.frame.origin.y == -12.0 {
|
||||
let snapshotFrame = snapshotView.frame.offsetBy(dx: 0.0, dy: 12.0)
|
||||
transition.updateFrame(view: snapshotView, frame: snapshotFrame)
|
||||
}
|
||||
}
|
||||
|
||||
transition.updatePosition(node: itemNode, position: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0 + topInset + self.scrollView.contentOffset.y), completion: { _ in
|
||||
var maximizeTopInset = 0.0
|
||||
if !itemNode.item.controller.isFullscreen {
|
||||
maximizeTopInset = topInset
|
||||
}
|
||||
|
||||
transition.updatePosition(node: itemNode, position: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0 + maximizeTopInset + self.scrollView.contentOffset.y), completion: { _ in
|
||||
self.isApplyingTransition = false
|
||||
if self.currentTransition == currentTransition {
|
||||
self.currentTransition = nil
|
||||
|
@ -15,6 +15,7 @@ import DeviceAccess
|
||||
import PeerInfoVisualMediaPaneNode
|
||||
import PhotoResources
|
||||
import PeerInfoPaneNode
|
||||
import WebUI
|
||||
|
||||
enum PeerInfoUpdatingAvatar {
|
||||
case none
|
||||
@ -386,6 +387,7 @@ final class PeerInfoScreenData {
|
||||
let revenueStatsContext: RevenueStatsContext?
|
||||
let profileGiftsContext: ProfileGiftsContext?
|
||||
let premiumGiftOptions: [PremiumGiftCodeOption]
|
||||
let webAppPermissions: WebAppPermissionsState?
|
||||
|
||||
let _isContact: Bool
|
||||
var forceIsContact: Bool = false
|
||||
@ -434,7 +436,8 @@ final class PeerInfoScreenData {
|
||||
revenueStatsState: RevenueStats?,
|
||||
revenueStatsContext: RevenueStatsContext?,
|
||||
profileGiftsContext: ProfileGiftsContext?,
|
||||
premiumGiftOptions: [PremiumGiftCodeOption]
|
||||
premiumGiftOptions: [PremiumGiftCodeOption],
|
||||
webAppPermissions: WebAppPermissionsState?
|
||||
) {
|
||||
self.peer = peer
|
||||
self.chatPeer = chatPeer
|
||||
@ -472,6 +475,7 @@ final class PeerInfoScreenData {
|
||||
self.revenueStatsContext = revenueStatsContext
|
||||
self.profileGiftsContext = profileGiftsContext
|
||||
self.premiumGiftOptions = premiumGiftOptions
|
||||
self.webAppPermissions = webAppPermissions
|
||||
}
|
||||
}
|
||||
|
||||
@ -967,7 +971,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
||||
revenueStatsState: nil,
|
||||
revenueStatsContext: nil,
|
||||
profileGiftsContext: nil,
|
||||
premiumGiftOptions: []
|
||||
premiumGiftOptions: [],
|
||||
webAppPermissions: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1015,7 +1020,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
revenueStatsState: nil,
|
||||
revenueStatsContext: nil,
|
||||
profileGiftsContext: nil,
|
||||
premiumGiftOptions: []
|
||||
premiumGiftOptions: [],
|
||||
webAppPermissions: nil
|
||||
))
|
||||
case let .user(userPeerId, secretChatId, kind):
|
||||
let groupsInCommon: GroupsInCommonContext?
|
||||
@ -1310,7 +1316,16 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
return (revenueStatsContext, state.stats)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let webAppPermissions: Signal<WebAppPermissionsState?, NoError> = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> mapToSignal { peer -> Signal<WebAppPermissionsState?, NoError> in
|
||||
if let peer, case let .user(user) = peer, let _ = user.botInfo {
|
||||
return webAppPermissionsState(context: context, peerId: peerId)
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|
||||
return combineLatest(
|
||||
context.account.viewTracker.peerView(peerId, updateData: true),
|
||||
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: isMyProfile, chatLocationContextHolder: chatLocationContextHolder),
|
||||
@ -1329,9 +1344,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
privacySettings,
|
||||
starsRevenueContextAndState,
|
||||
revenueContextAndState,
|
||||
premiumGiftOptions
|
||||
premiumGiftOptions,
|
||||
webAppPermissions
|
||||
)
|
||||
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, revenueContextAndState, premiumGiftOptions -> PeerInfoScreenData in
|
||||
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, revenueContextAndState, premiumGiftOptions, webAppPermissions -> PeerInfoScreenData in
|
||||
var availablePanes = availablePanes
|
||||
if isMyProfile {
|
||||
availablePanes?.insert(.stories, at: 0)
|
||||
@ -1450,7 +1466,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
revenueStatsState: revenueContextAndState.1,
|
||||
revenueStatsContext: revenueContextAndState.0,
|
||||
profileGiftsContext: profileGiftsContext,
|
||||
premiumGiftOptions: premiumGiftOptions
|
||||
premiumGiftOptions: premiumGiftOptions,
|
||||
webAppPermissions: webAppPermissions
|
||||
)
|
||||
}
|
||||
case .channel:
|
||||
@ -1662,7 +1679,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
revenueStatsState: revenueContextAndState.1,
|
||||
revenueStatsContext: revenueContextAndState.0,
|
||||
profileGiftsContext: nil,
|
||||
premiumGiftOptions: []
|
||||
premiumGiftOptions: [],
|
||||
webAppPermissions: nil
|
||||
)
|
||||
}
|
||||
case let .group(groupId):
|
||||
@ -1965,7 +1983,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
revenueStatsState: nil,
|
||||
revenueStatsContext: nil,
|
||||
profileGiftsContext: nil,
|
||||
premiumGiftOptions: []
|
||||
premiumGiftOptions: [],
|
||||
webAppPermissions: nil
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -1209,6 +1209,7 @@ private enum InfoSection: Int, CaseIterable {
|
||||
case personalChannel
|
||||
case peerInfo
|
||||
case balances
|
||||
case permissions
|
||||
case peerInfoTrailing
|
||||
case peerMembers
|
||||
}
|
||||
@ -1380,7 +1381,8 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
context: context,
|
||||
parentController: parentController,
|
||||
updatedPresentationData: nil,
|
||||
peer: .user(user),
|
||||
botPeer: .user(user),
|
||||
chatPeer: nil,
|
||||
threadId: nil,
|
||||
buttonText: "",
|
||||
url: "",
|
||||
@ -1540,7 +1542,28 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
} else {
|
||||
print()
|
||||
}
|
||||
|
||||
|
||||
if let _ = user.botInfo {
|
||||
//TODO:localize
|
||||
items[.permissions]!.append(PeerInfoScreenHeaderItem(id: 30, text: "ALLOW ACCESS TO"))
|
||||
var canManageEmojiStatus = false
|
||||
if let cachedData = data.cachedData as? CachedUserData, cachedData.flags.contains(.botCanManageEmojiStatus) {
|
||||
canManageEmojiStatus = true
|
||||
}
|
||||
items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 31, text: "Emoji Status", value: canManageEmojiStatus, icon: UIImage(bundleImageName: "Chat/Info/Status"), isLocked: false, toggled: { value in
|
||||
let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: user.id, enabled: value)
|
||||
|> deliverOnMainQueue).startStandalone()
|
||||
}))
|
||||
|
||||
if data.webAppPermissions?.location?.isRequested == true {
|
||||
items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 32, text: "Geolocation", value: data.webAppPermissions?.location?.isAllowed ?? false, icon: UIImage(bundleImageName: "Chat/Info/Location"), isLocked: false, toggled: { value in
|
||||
let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: user.id) { current in
|
||||
return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: value))
|
||||
}.startStandalone()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
||||
items[currentPeerInfoSection]!.append(PeerInfoScreenDisclosureItem(id: 10, label: .none, text: presentationData.strings.Bot_Settings, icon: UIImage(bundleImageName: "Chat/Info/SettingsIcon"), action: {
|
||||
interaction.openEditing()
|
||||
|
@ -317,14 +317,18 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
return nil
|
||||
})
|
||||
|
||||
let scrollOffset: CGFloat = max(0.0, size.height - params.visibleHeight)
|
||||
|
||||
var scrollOffset: CGFloat = max(0.0, size.height - params.visibleHeight)
|
||||
|
||||
let buttonSideInset = sideInset + 16.0
|
||||
let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0)
|
||||
let bottomPanelHeight = bottomInset + buttonSize.height + 8.0
|
||||
if params.visibleHeight < 110.0 {
|
||||
scrollOffset -= bottomPanelHeight
|
||||
}
|
||||
|
||||
transition.setFrame(view: unlockButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - bottomInset - buttonSize.height - scrollOffset), size: buttonSize))
|
||||
let _ = unlockButton.updateLayout(width: buttonSize.width, transition: .immediate)
|
||||
|
||||
let bottomPanelHeight = bottomInset + buttonSize.height + 8.0
|
||||
transition.setFrame(view: unlockBackground.view, frame: CGRect(x: 0.0, y: size.height - bottomInset - buttonSize.height - 8.0 - scrollOffset, width: size.width, height: bottomPanelHeight))
|
||||
unlockBackground.update(size: CGSize(width: size.width, height: bottomPanelHeight), transition: transition.containedViewLayoutTransition)
|
||||
transition.setFrame(view: unlockSeparator.view, frame: CGRect(x: 0.0, y: size.height - bottomInset - buttonSize.height - 8.0 - scrollOffset, width: size.width, height: UIScreenPixel))
|
||||
|
@ -12,11 +12,18 @@ public final class PremiumPeerShortcutComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let peer: EnginePeer
|
||||
let icon: TelegramMediaFile?
|
||||
|
||||
public init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer) {
|
||||
public init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
peer: EnginePeer,
|
||||
icon: TelegramMediaFile? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.peer = peer
|
||||
self.icon = icon
|
||||
}
|
||||
|
||||
public static func ==(lhs: PremiumPeerShortcutComponent, rhs: PremiumPeerShortcutComponent) -> Bool {
|
||||
|
@ -254,6 +254,7 @@ public final class StarsImageComponent: Component {
|
||||
case extendedMedia([TelegramExtendedMedia])
|
||||
case transactionPeer(StarsContext.State.Transaction.Peer)
|
||||
case gift(Int64)
|
||||
case color(UIColor)
|
||||
|
||||
public static func == (lhs: StarsImageComponent.Subject, rhs: StarsImageComponent.Subject) -> Bool {
|
||||
switch lhs {
|
||||
@ -293,6 +294,12 @@ public final class StarsImageComponent: Component {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .color(lhsColor):
|
||||
if case let .color(rhsColor) = rhs, lhsColor == rhsColor {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -307,6 +314,7 @@ public final class StarsImageComponent: Component {
|
||||
public let diameter: CGFloat
|
||||
public let backgroundColor: UIColor
|
||||
public let icon: Icon?
|
||||
public let value: Int64?
|
||||
public let action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)?
|
||||
|
||||
public init(
|
||||
@ -316,6 +324,7 @@ public final class StarsImageComponent: Component {
|
||||
diameter: CGFloat,
|
||||
backgroundColor: UIColor,
|
||||
icon: Icon? = nil,
|
||||
value: Int64? = nil,
|
||||
action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? = nil
|
||||
) {
|
||||
self.context = context
|
||||
@ -324,6 +333,7 @@ public final class StarsImageComponent: Component {
|
||||
self.diameter = diameter
|
||||
self.backgroundColor = backgroundColor
|
||||
self.icon = icon
|
||||
self.value = value
|
||||
self.action = action
|
||||
}
|
||||
|
||||
@ -346,6 +356,9 @@ public final class StarsImageComponent: Component {
|
||||
if lhs.icon != rhs.icon {
|
||||
return false
|
||||
}
|
||||
if lhs.value != rhs.value {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -368,10 +381,14 @@ public final class StarsImageComponent: Component {
|
||||
private var dustNode: MediaDustNode?
|
||||
private var button: UIControl?
|
||||
|
||||
private var amountIconView: UIImageView?
|
||||
private var amountBackgroundView = ComponentView<Empty>()
|
||||
private let amountView = ComponentView<Empty>()
|
||||
|
||||
private var animationNode: AnimatedStickerNode?
|
||||
|
||||
private var lockView: UIImageView?
|
||||
private var countView = ComponentView<Empty>()
|
||||
private let countView = ComponentView<Empty>()
|
||||
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
private var hiddenMediaDisposable: Disposable?
|
||||
@ -471,6 +488,21 @@ public final class StarsImageComponent: Component {
|
||||
switch component.subject {
|
||||
case .none:
|
||||
break
|
||||
case let .color(color):
|
||||
let imageNode: TransformImageNode
|
||||
if let current = self.imageNode {
|
||||
imageNode = current
|
||||
} else {
|
||||
imageNode = TransformImageNode()
|
||||
imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
|
||||
containerNode.view.addSubview(imageNode.view)
|
||||
self.imageNode = imageNode
|
||||
|
||||
imageNode.setSignal(solidColorImage(color))
|
||||
}
|
||||
|
||||
imageNode.frame = imageFrame
|
||||
imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))()
|
||||
case let .photo(photo):
|
||||
let imageNode: TransformImageNode
|
||||
if let current = self.imageNode {
|
||||
@ -873,6 +905,62 @@ public final class StarsImageComponent: Component {
|
||||
smallIconOutlineView.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let amount = component.value {
|
||||
let smallIconView: UIImageView
|
||||
if let current = self.amountIconView {
|
||||
smallIconView = current
|
||||
} else {
|
||||
smallIconView = UIImageView()
|
||||
self.amountIconView = smallIconView
|
||||
|
||||
smallIconView.image = UIImage(bundleImageName: "Premium/SendStarsPeerBadgeStarIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
smallIconView.tintColor = .white
|
||||
}
|
||||
|
||||
let countSize = self.amountView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
Text(text: "\(amount)", font: Font.with(size: 12.0, design: .round, weight: .bold), color: .white)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: imageFrame.size
|
||||
)
|
||||
|
||||
let iconSize = CGSize(width: 11.0, height: 11.0)
|
||||
let iconSpacing: CGFloat = 1.0
|
||||
|
||||
let totalLabelWidth = iconSize.width + iconSpacing + countSize.width
|
||||
let iconFrame = CGRect(origin: CGPoint(x: imageFrame.minX + floorToScreenPixels((imageFrame.width - totalLabelWidth) / 2.0), y: imageFrame.maxY - countSize.height + 4.0), size: iconSize)
|
||||
smallIconView.frame = iconFrame
|
||||
|
||||
let countFrame = CGRect(origin: CGPoint(x: imageFrame.minX + floorToScreenPixels((imageFrame.width - totalLabelWidth) / 2.0) + iconSize.width + iconSpacing, y: imageFrame.maxY - countSize.height + 2.0), size: countSize)
|
||||
|
||||
let amountBackgroundFrame = CGRect(origin: CGPoint(x: imageFrame.minX + floorToScreenPixels((imageFrame.width - totalLabelWidth) / 2.0) - 7.0, y: imageFrame.maxY - countSize.height - 3.0), size: CGSize(width: totalLabelWidth + 14.0, height: countFrame.height + 10.0))
|
||||
|
||||
let _ = self.amountBackgroundView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
RoundedRectangle(colors: [UIColor(rgb: 0xffaa01)], cornerRadius: amountBackgroundFrame.height / 2.0, gradientDirection: .horizontal, stroke: 2.0 - UIScreenPixel, strokeColor: component.backgroundColor, size: amountBackgroundFrame.size)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: amountBackgroundFrame.size
|
||||
)
|
||||
if let backgroundView = self.amountBackgroundView.view {
|
||||
if backgroundView.superview == nil {
|
||||
containerNode.view.addSubview(backgroundView)
|
||||
}
|
||||
backgroundView.frame = amountBackgroundFrame
|
||||
}
|
||||
|
||||
if let countView = self.amountView.view {
|
||||
if countView.superview == nil {
|
||||
containerNode.view.addSubview(countView)
|
||||
containerNode.view.addSubview(smallIconView)
|
||||
}
|
||||
countView.frame = countFrame
|
||||
}
|
||||
}
|
||||
|
||||
if let _ = component.action {
|
||||
if self.button == nil {
|
||||
let button = UIControl(frame: imageFrame)
|
||||
|
@ -154,6 +154,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
let table = Child(TableComponent.self)
|
||||
let additional = Child(BalancedTextComponent.self)
|
||||
let status = Child(BalancedTextComponent.self)
|
||||
let cancelButton = Child(SolidRoundedButtonComponent.self)
|
||||
let button = Child(SolidRoundedButtonComponent.self)
|
||||
|
||||
let transactionStatusBackgound = Child(RoundedRectangle.self)
|
||||
@ -201,7 +202,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
var boostsText: String?
|
||||
let additionalText = strings.Stars_Transaction_Terms
|
||||
var buttonText: String? = strings.Common_OK
|
||||
var buttonIsDestructive = false
|
||||
|
||||
var cancelButtonText: String?
|
||||
var statusText: String?
|
||||
var statusIsDestructive = false
|
||||
|
||||
@ -223,6 +225,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
var isSubscription = false
|
||||
var isSubscriber = false
|
||||
var isSubscriptionFee = false
|
||||
var isBotSubscription = false
|
||||
var isCancelled = false
|
||||
var isReaction = false
|
||||
var giveawayMessageId: MessageId?
|
||||
@ -256,7 +259,16 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
transactionPeer = .peer(peer)
|
||||
isSubscriber = true
|
||||
case let .subscription(subscription):
|
||||
titleText = strings.Stars_Transaction_Subscription_Title
|
||||
if case let .user(user) = subscription.peer, user.botInfo != nil {
|
||||
isBotSubscription = true
|
||||
}
|
||||
if let title = subscription.title {
|
||||
titleText = title
|
||||
} else {
|
||||
titleText = strings.Stars_Transaction_Subscription_Title
|
||||
}
|
||||
photo = subscription.photo
|
||||
|
||||
descriptionText = ""
|
||||
count = subscription.pricing.amount
|
||||
date = subscription.untilDate
|
||||
@ -320,8 +332,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
}
|
||||
} else {
|
||||
statusText = strings.Stars_Transaction_Subscription_Active(stringForMediumDate(timestamp: subscription.untilDate, strings: strings, dateTimeFormat: dateTimeFormat, withTime: false)).string
|
||||
buttonText = strings.Stars_Transaction_Subscription_Cancel
|
||||
buttonIsDestructive = true
|
||||
cancelButtonText = strings.Stars_Transaction_Subscription_Cancel
|
||||
buttonText = strings.Common_OK
|
||||
}
|
||||
}
|
||||
case let .transaction(transaction, parentPeer):
|
||||
@ -571,7 +583,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
)
|
||||
|
||||
let imageSubject: StarsImageComponent.Subject
|
||||
let imageIcon: StarsImageComponent.Icon?
|
||||
var imageIcon: StarsImageComponent.Icon?
|
||||
if isGift {
|
||||
imageSubject = .gift(count)
|
||||
} else if !media.isEmpty {
|
||||
@ -590,6 +602,11 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
} else {
|
||||
imageIcon = nil
|
||||
}
|
||||
|
||||
if isSubscription && "".isEmpty {
|
||||
imageIcon = nil
|
||||
}
|
||||
|
||||
var starChild: _UpdatedChildComponent
|
||||
if let giftAnimation {
|
||||
starChild = gift.update(
|
||||
@ -690,7 +707,12 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
} else if let toPeer {
|
||||
let title: String
|
||||
if isSubscription {
|
||||
title = strings.Stars_Transaction_Subscription_Subscription
|
||||
if isBotSubscription {
|
||||
//TODO:localize
|
||||
title = "Bot"
|
||||
} else {
|
||||
title = strings.Stars_Transaction_Subscription_Subscription
|
||||
}
|
||||
} else if isSubscriber {
|
||||
title = strings.Stars_Transaction_Subscription_Subscriber
|
||||
} else {
|
||||
@ -724,6 +746,16 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
)
|
||||
)
|
||||
))
|
||||
if case let .subscription(subscription) = component.subject, let title = subscription.title {
|
||||
//TODO:localize
|
||||
tableItems.append(.init(
|
||||
id: "subscription",
|
||||
title: "Subscription",
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: title, font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
}
|
||||
} else if let via {
|
||||
tableItems.append(.init(
|
||||
id: "via",
|
||||
@ -1069,12 +1101,12 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
originY += status.size.height + (statusIsDestructive ? 23.0 : 13.0)
|
||||
}
|
||||
|
||||
if let buttonText {
|
||||
let button = button.update(
|
||||
if let cancelButtonText {
|
||||
let cancelButton = cancelButton.update(
|
||||
component: SolidRoundedButtonComponent(
|
||||
title: buttonText,
|
||||
theme: buttonIsDestructive ? SolidRoundedButtonComponent.Theme(backgroundColor: .clear, foregroundColor: destructiveColor) : SolidRoundedButtonComponent.Theme(theme: theme),
|
||||
font: buttonIsDestructive ? .regular : .bold,
|
||||
title: cancelButtonText,
|
||||
theme: SolidRoundedButtonComponent.Theme(backgroundColor: .clear, foregroundColor: linkColor),
|
||||
font: .regular,
|
||||
fontSize: 17.0,
|
||||
height: 50.0,
|
||||
cornerRadius: 10.0,
|
||||
@ -1094,6 +1126,39 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let cancelButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: originY), size: cancelButton.size)
|
||||
context.add(cancelButton
|
||||
.position(CGPoint(x: cancelButtonFrame.midX, y: cancelButtonFrame.midY))
|
||||
)
|
||||
originY += cancelButton.size.height
|
||||
originY += 8.0
|
||||
}
|
||||
|
||||
if let buttonText {
|
||||
let button = button.update(
|
||||
component: SolidRoundedButtonComponent(
|
||||
title: buttonText,
|
||||
theme: SolidRoundedButtonComponent.Theme(theme: theme),
|
||||
font: .bold,
|
||||
fontSize: 17.0,
|
||||
height: 50.0,
|
||||
cornerRadius: 10.0,
|
||||
gloss: false,
|
||||
iconName: nil,
|
||||
animationName: nil,
|
||||
iconPosition: .left,
|
||||
isLoading: state.inProgress,
|
||||
action: {
|
||||
component.cancel(true)
|
||||
if isSubscription && cancelButtonText == nil {
|
||||
component.updateSubscription()
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: originY), size: button.size)
|
||||
context.add(button
|
||||
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
|
||||
|
@ -698,8 +698,33 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
isExpired = true
|
||||
}
|
||||
}
|
||||
|
||||
if let title = subscription.title {
|
||||
let nameComponent = AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: title,
|
||||
font: Font.regular(fontBaseDisplaySize * 16.0 / 17.0),
|
||||
textColor: environment.theme.list.itemPrimaryTextColor
|
||||
))
|
||||
))
|
||||
var nameGroupComponent: AnyComponent<Empty>
|
||||
if let _ = subscription.photo {
|
||||
nameGroupComponent = AnyComponent(
|
||||
HStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(RoundedRectangle(color: .lightGray, cornerRadius: 3.0, size: CGSize(width: 19.0, height: 19.0)))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: nameComponent)
|
||||
], spacing: 6.0)
|
||||
)
|
||||
} else {
|
||||
nameGroupComponent = nameComponent
|
||||
}
|
||||
titleComponents.append(
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: nameGroupComponent)
|
||||
)
|
||||
}
|
||||
|
||||
titleComponents.append(
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
|
||||
AnyComponentWithIdentity(id: AnyHashable(2), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: dateText,
|
||||
font: Font.regular(floor(fontBaseDisplaySize * 15.0 / 17.0)),
|
||||
|
@ -19,6 +19,7 @@ import AccountContext
|
||||
import PresentationDataUtils
|
||||
import StarsImageComponent
|
||||
import ConfettiEffect
|
||||
import PremiumPeerShortcutComponent
|
||||
|
||||
private final class SheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@ -263,6 +264,8 @@ private final class SheetContent: CombinedComponent {
|
||||
let star = Child(StarsImageComponent.self)
|
||||
let closeButton = Child(Button.self)
|
||||
let title = Child(Text.self)
|
||||
let peerShortcut = Child(PremiumPeerShortcutComponent.self)
|
||||
|
||||
let text = Child(BalancedTextComponent.self)
|
||||
let button = Child(ButtonComponent.self)
|
||||
let balanceTitle = Child(MultilineTextComponent.self)
|
||||
@ -303,8 +306,15 @@ private final class SheetContent: CombinedComponent {
|
||||
subject = .none
|
||||
}
|
||||
|
||||
var isBot = false
|
||||
if case let .user(user) = state.botPeer, user.botInfo != nil {
|
||||
isBot = true
|
||||
}
|
||||
|
||||
var isSubscription = false
|
||||
if case .starsChatSubscription = context.component.source {
|
||||
if case .starsChatSubscription = component.source {
|
||||
isSubscription = true
|
||||
} else if let _ = component.invoice.subscriptionPeriod {
|
||||
isSubscription = true
|
||||
}
|
||||
let star = star.update(
|
||||
@ -314,7 +324,8 @@ private final class SheetContent: CombinedComponent {
|
||||
theme: theme,
|
||||
diameter: 90.0,
|
||||
backgroundColor: theme.actionSheet.opaqueItemBackgroundColor,
|
||||
icon: isSubscription ? .star : nil
|
||||
icon: isSubscription && !isBot ? .star : nil,
|
||||
value: isBot ? component.invoice.totalAmount : nil
|
||||
),
|
||||
availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0),
|
||||
transition: context.transition
|
||||
@ -350,7 +361,11 @@ private final class SheetContent: CombinedComponent {
|
||||
|
||||
let titleString: String
|
||||
if isSubscription {
|
||||
titleString = strings.Stars_Transfer_Subscribe_Channel_Title
|
||||
if isBot {
|
||||
titleString = "Subscription Name"
|
||||
} else {
|
||||
titleString = strings.Stars_Transfer_Subscribe_Channel_Title
|
||||
}
|
||||
} else {
|
||||
titleString = strings.Stars_Transfer_Title
|
||||
}
|
||||
@ -365,6 +380,24 @@ private final class SheetContent: CombinedComponent {
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
contentSize.height += 13.0
|
||||
|
||||
if isBot, let peer = state.botPeer {
|
||||
contentSize.height -= 3.0
|
||||
let peerShortcut = peerShortcut.update(
|
||||
component: PremiumPeerShortcutComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
peer: peer
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - 32.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(peerShortcut
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + peerShortcut.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += peerShortcut.size.height
|
||||
contentSize.height += 13.0
|
||||
}
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
@ -378,6 +411,8 @@ private final class SheetContent: CombinedComponent {
|
||||
let infoText: String
|
||||
if case .starsChatSubscription = context.component.source {
|
||||
infoText = strings.Stars_Transfer_SubscribeInfo(state.botPeer?.compactDisplayTitle ?? "", strings.Stars_Transfer_Info_Stars(Int32(amount))).string
|
||||
} else if let _ = component.invoice.subscriptionPeriod {
|
||||
infoText = "Do you want to subscribe to **\(component.invoice.title)** in **\(state.botPeer?.compactDisplayTitle ?? "")** for **\(strings.Stars_Transfer_Info_Stars(Int32(amount)))** per month?"
|
||||
} else if !component.extendedMedia.isEmpty {
|
||||
var description: String = ""
|
||||
var photoCount: Int32 = 0
|
||||
@ -499,7 +534,11 @@ private final class SheetContent: CombinedComponent {
|
||||
let amountString = presentationStringsFormattedNumber(Int32(amount), presentationData.dateTimeFormat.groupingSeparator)
|
||||
let buttonAttributedString: NSMutableAttributedString
|
||||
if case .starsChatSubscription = component.source {
|
||||
buttonAttributedString = NSMutableAttributedString(string: strings.Stars_Transfer_Subscribe, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
//TODO:localize
|
||||
buttonAttributedString = NSMutableAttributedString(string: "Subscribe for # \(amountString) / month", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
//buttonAttributedString = NSMutableAttributedString(string: strings.Stars_Transfer_Subscribe, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
} else if let _ = component.invoice.subscriptionPeriod {
|
||||
buttonAttributedString = NSMutableAttributedString(string: "Subscribe for # \(amountString) / month", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
} else {
|
||||
buttonAttributedString = NSMutableAttributedString(string: "\(strings.Stars_Transfer_Pay) # \(amountString)", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ swift_library(
|
||||
"//submodules/MediaPickerUI",
|
||||
"//submodules/LegacyMediaPickerUI",
|
||||
"//submodules/LocationUI",
|
||||
"//submodules/WebUI",
|
||||
"//submodules/TelegramUI/Components/ChatScheduleTimeController",
|
||||
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
||||
"//submodules/TextFormat",
|
||||
|
@ -12,7 +12,6 @@ import MediaPickerUI
|
||||
import LegacyMediaPickerUI
|
||||
import LocationUI
|
||||
import ChatEntityKeyboardInputNode
|
||||
import WebUI
|
||||
import ChatScheduleTimeController
|
||||
import TextFormat
|
||||
import PhoneNumberFormat
|
||||
@ -1808,37 +1807,38 @@ final class StoryItemSetContainerSendMessage {
|
||||
//TODO:gift controller
|
||||
break
|
||||
case let .app(bot):
|
||||
let params = WebAppParameters(source: .attachMenu, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, botVerified: bot.peer.isVerified, url: nil, queryId: nil, payload: nil, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false, fullSize: true)
|
||||
let theme = component.theme
|
||||
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) })
|
||||
let controller = WebAppController(context: component.context, updatedPresentationData: updatedPresentationData, params: params, replyToMessageId: nil, threadId: nil)
|
||||
controller.openUrl = { [weak self] url, _, _, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
//self?.openUrl(url, concealed: true, forceExternal: true)
|
||||
}
|
||||
controller.getNavigationController = { [weak view] in
|
||||
guard let view, let controller = view.component?.controller() else {
|
||||
return nil
|
||||
}
|
||||
return controller.navigationController as? NavigationController
|
||||
}
|
||||
controller.completion = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
/*if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) }
|
||||
})
|
||||
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
}*/
|
||||
}
|
||||
completion(controller, controller.mediaPickerContext)
|
||||
self.controllerNavigationDisposable.set(nil)
|
||||
let _ = bot
|
||||
// let params = WebAppParameters(source: .attachMenu, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, botVerified: bot.peer.isVerified, url: nil, queryId: nil, payload: nil, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false, fullSize: true)
|
||||
// let theme = component.theme
|
||||
// let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) })
|
||||
// let controller = WebAppController(context: component.context, updatedPresentationData: updatedPresentationData, params: params, replyToMessageId: nil, threadId: nil)
|
||||
// controller.openUrl = { [weak self] url, _, _, _ in
|
||||
// guard let self else {
|
||||
// return
|
||||
// }
|
||||
// let _ = self
|
||||
// //self?.openUrl(url, concealed: true, forceExternal: true)
|
||||
// }
|
||||
// controller.getNavigationController = { [weak view] in
|
||||
// guard let view, let controller = view.component?.controller() else {
|
||||
// return nil
|
||||
// }
|
||||
// return controller.navigationController as? NavigationController
|
||||
// }
|
||||
// controller.completion = { [weak self] in
|
||||
// guard let self else {
|
||||
// return
|
||||
// }
|
||||
// let _ = self
|
||||
// /*if let strongSelf = self {
|
||||
// strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
// $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) }
|
||||
// })
|
||||
// strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
// }*/
|
||||
// }
|
||||
// completion(controller, controller.mediaPickerContext)
|
||||
// self.controllerNavigationDisposable.set(nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Info/Location.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Info/Location.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "location.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
137
submodules/TelegramUI/Images.xcassets/Chat/Info/Location.imageset/location.pdf
vendored
Normal file
137
submodules/TelegramUI/Images.xcassets/Chat/Info/Location.imageset/location.pdf
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< /Filter /FlateDecode
|
||||
/Type /XObject
|
||||
/Length 2 0 R
|
||||
/Group << /Type /Group
|
||||
/S /Transparency
|
||||
>>
|
||||
/Subtype /Form
|
||||
/Resources << >>
|
||||
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
>>
|
||||
stream
|
||||
xmTKŠc1Üû^Œ°å<C2B0>ìcÌ4³H74}è’ã'-’Jé©TÒ³ó2¥1Ý௡ל<C397>ë3$ʳ' »ŒZ2ÀLàrü¹¾<C2B9>Æsˆ˜ÉÔˆZ™I8•.(ÂoDäJ}¤Ö”tžFî”Ef)ñ‚ÑZjê#²P<C2B2>m!j©Î‘µÞ&,Ì©÷È“¸Hª
¥<>JžµŽX¬™Ëìv*gb|9‡¶æÞÙGpûκÿƒ³N?Çfü@žµ×¹Â¡þ®ãg>ÃY}Ï›1<E280BA>³ocës?{ÍÁø½›`¿´y°_îÚƒù·^nß(wo^Ë6ÉG0ïÆ™uc¶óÆ}ÜÆÍîùNíÇë- £›èk›W+·ã|úl<C3BA>ð?|„¯ð/|ã¨#‡ðâ7wRàLÍñ‰Ð;iÙW wÒj¶ãJN&¹èBÀ(—MQ(ã‚÷vPY7¢<37>1¶S¯¬*s#Þ‚*³KœÂA¤Ò <C392>Kn¤—sêèk¤ùk8˜¬b;ÅHíÇ<C3AD>¹°áÍ–8Ÿ äÖGçrz"¬"‘þ³UÌåÜáä`ëS¦AfÑ¥¤ú\ ”þ+-i6#ëà9Ssêhë¤ù35›Â𣸸³Ù/ɬ#=
|
||||
endstream
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
470
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<< /Type /XObject
|
||||
/Length 4 0 R
|
||||
/Group << /Type /Group
|
||||
/S /Transparency
|
||||
>>
|
||||
/Subtype /Form
|
||||
/Resources << >>
|
||||
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
>>
|
||||
stream
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 18.799999 m
|
||||
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
|
||||
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
|
||||
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
|
||||
18.799999 30.000000 l
|
||||
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
|
||||
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
|
||||
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
|
||||
30.000000 11.200001 l
|
||||
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
|
||||
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
|
||||
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
|
||||
11.200000 0.000000 l
|
||||
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
|
||||
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
|
||||
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
|
||||
0.000000 18.799999 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
916
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /XObject << /X1 1 0 R >>
|
||||
/ExtGState << /E1 << /SMask << /Type /Mask
|
||||
/G 3 0 R
|
||||
/S /Alpha
|
||||
>>
|
||||
/Type /ExtGState
|
||||
>> >>
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Length 7 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
/E1 gs
|
||||
/X1 Do
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
46
|
||||
endobj
|
||||
|
||||
8 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 5 0 R
|
||||
/Contents 6 0 R
|
||||
/Parent 9 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
9 0 obj
|
||||
<< /Kids [ 8 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
10 0 obj
|
||||
<< /Pages 9 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 11
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000754 00000 n
|
||||
0000000776 00000 n
|
||||
0000001940 00000 n
|
||||
0000001962 00000 n
|
||||
0000002260 00000 n
|
||||
0000002362 00000 n
|
||||
0000002383 00000 n
|
||||
0000002556 00000 n
|
||||
0000002630 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 10 0 R
|
||||
/Size 11
|
||||
>>
|
||||
startxref
|
||||
2690
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Chat/Info/Status.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Info/Status.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "status (3).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Info/Status.imageset/status (3).pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Info/Status.imageset/status (3).pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Instant View/MinimizeArrow.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/MinimizeArrow.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "miniappminimize_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
92
submodules/TelegramUI/Images.xcassets/Instant View/MinimizeArrow.imageset/miniappminimize_30.pdf
vendored
Normal file
92
submodules/TelegramUI/Images.xcassets/Instant View/MinimizeArrow.imageset/miniappminimize_30.pdf
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 8.500000 8.804749 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.707107 9.402358 m
|
||||
0.316583 9.792883 -0.316583 9.792883 -0.707107 9.402358 c
|
||||
-1.097631 9.011834 -1.097631 8.378669 -0.707107 7.988145 c
|
||||
0.707107 9.402358 l
|
||||
h
|
||||
6.500000 2.195251 m
|
||||
5.792893 1.488145 l
|
||||
6.183417 1.097620 6.816583 1.097620 7.207107 1.488145 c
|
||||
6.500000 2.195251 l
|
||||
h
|
||||
13.707107 7.988145 m
|
||||
14.097631 8.378669 14.097631 9.011834 13.707107 9.402358 c
|
||||
13.316583 9.792883 12.683417 9.792883 12.292893 9.402358 c
|
||||
13.707107 7.988145 l
|
||||
h
|
||||
-0.707107 7.988145 m
|
||||
5.792893 1.488145 l
|
||||
7.207107 2.902358 l
|
||||
0.707107 9.402358 l
|
||||
-0.707107 7.988145 l
|
||||
h
|
||||
7.207107 1.488145 m
|
||||
13.707107 7.988145 l
|
||||
12.292893 9.402358 l
|
||||
5.792893 2.902358 l
|
||||
7.207107 1.488145 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
772
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000000862 00000 n
|
||||
0000000884 00000 n
|
||||
0000001057 00000 n
|
||||
0000001131 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1190
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Instant View/Verified.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/Verified.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "miniappverify_14.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Verified.imageset/miniappverify_14.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Verified.imageset/miniappverify_14.pdf
vendored
Normal file
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"nm":"Main Scene","ddd":0,"h":300,"w":300,"meta":{"g":"@lottiefiles/creator 1.31.1"},"layers":[{"ty":4,"nm":"more","sr":1,"st":-38,"op":47,"ip":-35,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[150,150],"ix":1},"s":{"a":0,"k":[100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[150,150],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"3","ix":2,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-8.284,0],[0,8.284],[8.284,0],[0,-8.284]],"o":[[8.284,0],[0,-8.284],[-8.284,0],[0,8.284]],"v":[[50,15],[65,0],[50,-15],[35,0]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[50,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[100,100],"t":15},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[125,125],"t":25},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[95,95],"t":35},{"s":[100,100],"t":45}],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[200,150],"t":10},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[200,120],"t":20},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[200,162],"t":30},{"s":[200,150],"t":40}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"2","ix":3,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 2","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-8.284,0],[0,8.284],[8.284,0],[0,-8.284]],"o":[[8.284,0],[0,-8.284],[-8.284,0],[0,8.284]],"v":[[0.5,15],[15.5,0],[0.5,-15],[-14.5,0]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[100,100],"t":10},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[125,125],"t":20},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[95,95],"t":30},{"s":[100,100],"t":40}],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[150,150],"t":5},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[150,120],"t":15},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[150,162],"t":25},{"s":[150,150],"t":35}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"1","ix":4,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 3","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-8.284,0],[0,8.284],[8.284,0],[0,-8.284]],"o":[[8.284,0],[0,-8.284],[-8.284,0],[0,8.284]],"v":[[-49,15],[-34,0],[-49,-15],[-64,0]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[-50,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[100,100],"t":5},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[125,125],"t":15},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[95,95],"t":25},{"s":[100,100],"t":35}],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[100,150],"t":0},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[100,120],"t":10},{"o":{"x":0.5,"y":0},"i":{"x":0.5,"y":1},"s":[100,162],"t":20},{"s":[100,150],"t":30}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1}],"v":"5.7.0","fr":60,"op":60,"ip":0,"assets":[]}
|
@ -0,0 +1 @@
|
||||
{"nm":"Main Scene","ddd":0,"h":99,"w":99,"meta":{"g":"@lottiefiles/creator 1.31.1"},"layers":[{"ty":4,"nm":"Artboard Copy 3 Outlines","sr":1,"st":0,"op":60,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[49.5,49.5,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[49.5,49.5],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":1,"k":[{"o":{"x":0.2,"y":0},"i":{"x":0.2,"y":1},"s":[{"c":false,"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-18.672,-16.5],[-2.172,0],[-18.672,16.5]]}],"t":0},{"o":{"x":0.8,"y":0},"i":{"x":0.8,"y":1},"s":[{"c":false,"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-8.25,-16.5],[8.25,0],[-8.25,16.5]]}],"t":15},{"o":{"x":0.2,"y":0},"i":{"x":0.2,"y":1},"s":[{"c":false,"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-8.25,-16.5],[8.25,0],[-8.25,16.5]]}],"t":30},{"s":[{"c":false,"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-18.672,-16.5],[-2.172,0],[-18.672,16.5]]}],"t":45}],"ix":2}},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.2,"y":0},"i":{"x":0.2,"y":1},"s":[50],"t":0},{"o":{"x":0.8,"y":0},"i":{"x":0.8,"y":1},"s":[100],"t":15},{"o":{"x":0.2,"y":0},"i":{"x":0.2,"y":1},"s":[100],"t":30},{"s":[50],"t":45}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":1,"k":[{"o":{"x":0.2,"y":0},"i":{"x":0.2,"y":1},"s":[50],"t":0},{"o":{"x":0.8,"y":0},"i":{"x":0.8,"y":1},"s":[0],"t":15},{"o":{"x":0.2,"y":0},"i":{"x":0.2,"y":1},"s":[0],"t":30},{"s":[50],"t":45}],"ix":1},"m":1},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"c":{"a":0,"k":[1,1,1],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[41.25,49.5],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 2","ix":2,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":1,"k":[{"o":{"x":0.2,"y":0},"i":{"x":0.2,"y":1},"s":[{"c":false,"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.5,-17.25],[-18.75,0],[-1.5,17.25]]}],"t":0},{"o":{"x":0.8,"y":0},"i":{"x":0.8,"y":1},"s":[{"c":false,"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[8.25,-16.5],[-8.25,0],[8.25,16.5]]}],"t":15},{"o":{"x":0.2,"y":0},"i":{"x":0.2,"y":1},"s":[{"c":false,"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[8.25,-16.5],[-8.25,0],[8.25,16.5]]}],"t":30},{"s":[{"c":false,"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.5,-17.25],[-18.75,0],[-1.5,17.25]]}],"t":45}],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"c":{"a":0,"k":[1,1,1],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[57.75,49.5],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1}],"v":"5.7.0","fr":60,"op":60,"ip":0,"assets":[]}
|
@ -946,7 +946,7 @@ final class AuthorizedApplicationContext {
|
||||
}
|
||||
|
||||
if openAppIfAny, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.rootController.viewControllers.last as? ViewController {
|
||||
self.context.sharedContext.openWebApp(context: self.context, parentController: parentController, updatedPresentationData: nil, peer: peer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: true, payload: nil)
|
||||
self.context.sharedContext.openWebApp(context: self.context, parentController: parentController, updatedPresentationData: nil, botPeer: peer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: true, payload: nil)
|
||||
} else {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: chatLocation, subject: isOutgoingMessage ? messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false) } : nil, activateInput: activateInput ? .text : nil))
|
||||
}
|
||||
|
@ -14,7 +14,20 @@ import UndoUI
|
||||
import UrlHandling
|
||||
import TelegramPresentationData
|
||||
|
||||
func openWebAppImpl(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peer: EnginePeer, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?) {
|
||||
func openWebAppImpl(
|
||||
context: AccountContext,
|
||||
parentController: ViewController,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
||||
botPeer: EnginePeer,
|
||||
chatPeer: EnginePeer?,
|
||||
threadId: Int64?,
|
||||
buttonText: String,
|
||||
url: String,
|
||||
simple: Bool,
|
||||
source: ChatOpenWebViewSource,
|
||||
skipTermsOfService: Bool,
|
||||
payload: String?
|
||||
) {
|
||||
let presentationData: PresentationData
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
presentationData = parentController.presentationData
|
||||
@ -30,9 +43,9 @@ func openWebAppImpl(context: AccountContext, parentController: ViewController, u
|
||||
botAddress = bot.addressName ?? ""
|
||||
botVerified = bot.isVerified
|
||||
} else {
|
||||
botName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
botAddress = peer.addressName ?? ""
|
||||
botVerified = peer.isVerified
|
||||
botName = botPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
botAddress = botPeer.addressName ?? ""
|
||||
botVerified = botPeer.isVerified
|
||||
}
|
||||
|
||||
if source == .generic {
|
||||
@ -95,7 +108,7 @@ func openWebAppImpl(context: AccountContext, parentController: ViewController, u
|
||||
|
||||
if let navigationController = parentController.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer {
|
||||
for controller in minimizedContainer.controllers {
|
||||
if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == peer.id && mainController.source == .menu {
|
||||
if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == botPeer.id && mainController.source == .menu {
|
||||
navigationController.maximizeViewController(controller, animated: true)
|
||||
return
|
||||
}
|
||||
@ -103,18 +116,24 @@ func openWebAppImpl(context: AccountContext, parentController: ViewController, u
|
||||
}
|
||||
|
||||
var fullSize = false
|
||||
var isFullscreen = false
|
||||
if isTelegramMeLink(url), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, url: url), case .peer(_, .appStart) = internalUrl {
|
||||
fullSize = !url.contains("?mode=compact")
|
||||
if url.contains("mode=fullscreen") {
|
||||
isFullscreen = true
|
||||
fullSize = true
|
||||
} else {
|
||||
fullSize = !url.contains("mode=compact")
|
||||
}
|
||||
}
|
||||
|
||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
||||
let params = WebAppParameters(source: .menu, peerId: peer.id, botId: peer.id, botName: botName, botVerified: botVerified, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: fullSize)
|
||||
let params = WebAppParameters(source: .menu, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: fullSize, isFullscreen: isFullscreen)
|
||||
let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: peer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botPeer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
presentImpl?(c, a)
|
||||
}, commit: commit)
|
||||
}, requestSwitchInline: { [weak parentController] query, chatTypes, completion in
|
||||
ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: peer.id, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
|
||||
ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: chatPeer?.id ?? botPeer.id, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
|
||||
}, getInputContainerNode: { [weak parentController] in
|
||||
if let parentController = parentController as? ChatControllerImpl, let layout = parentController.validLayout, case .compact = layout.metrics.widthClass {
|
||||
return (parentController.chatDisplayNode.getWindowInputAccessoryHeight(), parentController.chatDisplayNode.inputPanelContainerNode, {
|
||||
@ -154,10 +173,10 @@ func openWebAppImpl(context: AccountContext, parentController: ViewController, u
|
||||
}
|
||||
} else if simple {
|
||||
var isInline = false
|
||||
var botId = peer.id
|
||||
var botId = botPeer.id
|
||||
var botName = botName
|
||||
var botAddress = ""
|
||||
var botVerified = peer.isVerified
|
||||
var botVerified = botPeer.isVerified
|
||||
if case let .inline(bot) = source {
|
||||
isInline = true
|
||||
botId = bot.id
|
||||
@ -174,9 +193,14 @@ func openWebAppImpl(context: AccountContext, parentController: ViewController, u
|
||||
}
|
||||
|
||||
let webViewSignal: Signal<RequestWebViewResult, RequestWebViewError>
|
||||
let webViewSource: RequestSimpleWebViewSource = isInline ? .inline(startParam: payload) : .generic
|
||||
let webViewSource: RequestSimpleWebViewSource
|
||||
if let payload {
|
||||
webViewSource = .inline(startParam: payload)
|
||||
} else {
|
||||
webViewSource = .generic
|
||||
}
|
||||
if url.isEmpty {
|
||||
webViewSignal = context.engine.messages.requestMainWebView(botId: botId, source: webViewSource, themeParams: generateWebAppThemeParams(presentationData.theme))
|
||||
webViewSignal = context.engine.messages.requestMainWebView(peerId: chatPeer?.id ?? botId, botId: botId, source: webViewSource, themeParams: generateWebAppThemeParams(presentationData.theme))
|
||||
} else {
|
||||
webViewSignal = context.engine.messages.requestSimpleWebView(botId: botId, url: url, source: webViewSource, themeParams: generateWebAppThemeParams(presentationData.theme))
|
||||
}
|
||||
@ -196,13 +220,13 @@ func openWebAppImpl(context: AccountContext, parentController: ViewController, u
|
||||
} else {
|
||||
source = url.isEmpty ? .generic : .simple
|
||||
}
|
||||
let params = WebAppParameters(source: source, peerId: peer.id, botId: botId, botName: botName, botVerified: botVerified, url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize))
|
||||
let params = WebAppParameters(source: source, peerId: chatPeer?.id ?? botId, botId: botId, botName: botName, botVerified: botVerified, url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen))
|
||||
let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: peer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botId, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
presentImpl?(c, a)
|
||||
}, commit: commit)
|
||||
}, requestSwitchInline: { [weak parentController] query, chatTypes, completion in
|
||||
ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: peer.id, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
|
||||
ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: chatPeer?.id ?? botId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
|
||||
}, getNavigationController: { [weak parentController] in
|
||||
var navigationController: NavigationController?
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
@ -233,7 +257,7 @@ func openWebAppImpl(context: AccountContext, parentController: ViewController, u
|
||||
messageActionCallbackDisposable = MetaDisposable()
|
||||
}
|
||||
|
||||
messageActionCallbackDisposable.set(((context.engine.messages.requestWebView(peerId: peer.id, botId: peer.id, url: !url.isEmpty ? url : nil, payload: nil, themeParams: generateWebAppThemeParams(presentationData.theme), fromMenu: false, replyToMessageId: nil, threadId: threadId)
|
||||
messageActionCallbackDisposable.set(((context.engine.messages.requestWebView(peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, url: !url.isEmpty ? url : nil, payload: nil, themeParams: generateWebAppThemeParams(presentationData.theme), fromMenu: false, replyToMessageId: nil, threadId: threadId)
|
||||
|> afterDisposed {
|
||||
updateProgress()
|
||||
})
|
||||
@ -242,9 +266,9 @@ func openWebAppImpl(context: AccountContext, parentController: ViewController, u
|
||||
return
|
||||
}
|
||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
||||
let params = WebAppParameters(source: .button, peerId: peer.id, botId: peer.id, botName: botName, botVerified: botVerified, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize))
|
||||
let params = WebAppParameters(source: .button, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen))
|
||||
let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: peer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botPeer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
presentImpl?(c, a)
|
||||
}, commit: commit)
|
||||
}, completion: { [weak parentController] in
|
||||
@ -279,7 +303,7 @@ func openWebAppImpl(context: AccountContext, parentController: ViewController, u
|
||||
if skipTermsOfService {
|
||||
openWebView()
|
||||
} else {
|
||||
var botPeer = peer
|
||||
var botPeer = botPeer
|
||||
if case let .inline(bot) = source {
|
||||
botPeer = bot
|
||||
}
|
||||
@ -313,7 +337,7 @@ public extension ChatControllerImpl {
|
||||
}
|
||||
self.chatDisplayNode.dismissInput()
|
||||
|
||||
self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), threadId: self.chatLocation.threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: false, payload: nil)
|
||||
self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, botPeer: EnginePeer(peer), chatPeer: EnginePeer(peer), threadId: self.chatLocation.threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: false, payload: nil)
|
||||
}
|
||||
|
||||
static func botRequestSwitchInline(context: AccountContext, controller: ChatControllerImpl?, peerId: EnginePeer.Id, botAddress: String, query: String, chatTypes: [ReplyMarkupButtonRequestPeerType]?, completion: @escaping () -> Void) -> Void {
|
||||
@ -407,7 +431,7 @@ public extension ChatControllerImpl {
|
||||
}
|
||||
}
|
||||
|
||||
func presentBotApp(botApp: BotApp?, botPeer: EnginePeer, payload: String?, compact: Bool, concealed: Bool = false, commit: @escaping () -> Void = {}) {
|
||||
func presentBotApp(botApp: BotApp?, botPeer: EnginePeer, payload: String?, mode: ResolvedStartAppMode, concealed: Bool = false, commit: @escaping () -> Void = {}) {
|
||||
guard let peerId = self.chatLocation.peerId else {
|
||||
return
|
||||
}
|
||||
@ -463,7 +487,7 @@ public extension ChatControllerImpl {
|
||||
}
|
||||
|
||||
let botAddress = botPeer.addressName ?? ""
|
||||
strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), compact: compact, allowWrite: allowWrite)
|
||||
strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), compact: mode == .compact, fullscreen: mode == .fullscreen, allowWrite: allowWrite)
|
||||
|> afterDisposed {
|
||||
updateProgress()
|
||||
})
|
||||
@ -472,7 +496,7 @@ public extension ChatControllerImpl {
|
||||
return
|
||||
}
|
||||
let context = strongSelf.context
|
||||
let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize))
|
||||
let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen))
|
||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
||||
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, forceUpdate, commit in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
@ -564,7 +588,7 @@ public extension ChatControllerImpl {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, peer: botPeer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false, payload: payload)
|
||||
self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, botPeer: botPeer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false, payload: payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2880,7 +2880,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let sourceMessageId = message.forwardInfo?.sourceMessageId {
|
||||
messageId = sourceMessageId
|
||||
}
|
||||
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: paidContent.amount, startParam: "", extendedMedia: .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: nil), flags: [], version: 0)
|
||||
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: paidContent.amount, startParam: "", extendedMedia: .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: nil), subscriptionPeriod: nil, flags: [], version: 0)
|
||||
let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, starsContext: starsContext, invoice: invoice, source: .message(messageId), extendedMedia: paidContent.extendedMedia, inputData: starsInputData, completion: { _ in })
|
||||
strongSelf.push(controller)
|
||||
|
||||
@ -9856,16 +9856,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case let .withBotApp(botAppStart):
|
||||
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id))
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
|
||||
if let strongSelf = self, let peer {
|
||||
if let botApp = botAppStart.botApp {
|
||||
strongSelf.presentBotApp(botApp: botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact, concealed: concealed, commit: {
|
||||
dismissWebAppControllers()
|
||||
commit()
|
||||
})
|
||||
} else {
|
||||
strongSelf.context.sharedContext.openWebApp(context: strongSelf.context, parentController: strongSelf, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false, payload: botAppStart.payload)
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
if let botApp = botAppStart.botApp {
|
||||
self.presentBotApp(botApp: botApp, botPeer: peer, payload: botAppStart.payload, mode: botAppStart.mode, concealed: concealed, commit: {
|
||||
dismissWebAppControllers()
|
||||
commit()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, botPeer: peer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false, payload: botAppStart.payload)
|
||||
commit()
|
||||
}
|
||||
})
|
||||
default:
|
||||
|
@ -613,7 +613,7 @@ extension ChatControllerImpl {
|
||||
payload = botPayload
|
||||
fromAttachMenu = false
|
||||
}
|
||||
let params = WebAppParameters(source: fromAttachMenu ? .attachMenu : .generic, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, botVerified: bot.peer.isVerified, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false, fullSize: false)
|
||||
let params = WebAppParameters(source: fromAttachMenu ? .attachMenu : .generic, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, botVerified: bot.peer.isVerified, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false, fullSize: false, isFullscreen: false)
|
||||
let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject
|
||||
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, replyToMessageId: replyMessageSubject?.messageId, threadId: strongSelf.chatLocation.threadId)
|
||||
controller.openUrl = { [weak self] url, concealed, forceUpdate, commit in
|
||||
|
@ -668,9 +668,9 @@ func chatHistoryEntriesForView(
|
||||
if reverse {
|
||||
return (entries.reversed(), currentState)
|
||||
} else {
|
||||
#if DEBUG
|
||||
assert(entries.map(\.stableId) == entries.sorted().map(\.stableId))
|
||||
#endif
|
||||
// #if DEBUG
|
||||
// assert(entries.map(\.stableId) == entries.sorted().map(\.stableId))
|
||||
// #endif
|
||||
return (entries, currentState)
|
||||
}
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
||||
controller.presentAttachmentBot(botId: attachBotStart.botId, payload: attachBotStart.payload, justInstalled: attachBotStart.justInstalled)
|
||||
}
|
||||
if let botAppStart = params.botAppStart, case let .peer(peer) = params.chatLocation {
|
||||
controller.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact)
|
||||
controller.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, mode: botAppStart.mode)
|
||||
}
|
||||
params.setupController(controller)
|
||||
found = true
|
||||
@ -188,7 +188,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
||||
}
|
||||
if let botAppStart = params.botAppStart, case let .peer(peer) = params.chatLocation {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
controller.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact)
|
||||
controller.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, mode: botAppStart.mode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -196,7 +196,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
||||
|
||||
if let botAppStart = params.botAppStart, case let .peer(peer) = params.chatLocation {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
controller.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact)
|
||||
controller.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, mode: botAppStart.mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -301,14 +301,14 @@ func openResolvedUrlImpl(
|
||||
photo.append(photoRepresentation)
|
||||
}
|
||||
let channel = TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), accessHash: .genericPublic(0), title: invite.title, username: nil, photo: photo, creationDate: 0, version: 0, participationStatus: .left, info: .broadcast(TelegramChannelBroadcastInfo(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: invite.nameColor, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil)
|
||||
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount, startParam: "", extendedMedia: nil, flags: [], version: 0)
|
||||
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount, startParam: "", extendedMedia: nil, subscriptionPeriod: nil, flags: [], version: 0)
|
||||
|
||||
inputData.set(.single(BotCheckoutController.InputData(
|
||||
form: BotPaymentForm(
|
||||
id: subscriptionFormId,
|
||||
canSaveCredentials: false,
|
||||
passwordMissing: false,
|
||||
invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: subscriptionPricing.amount)], tip: nil, termsInfo: nil),
|
||||
invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: subscriptionPricing.amount)], tip: nil, termsInfo: nil, subscriptionPeriod: subscriptionPricing.period),
|
||||
paymentBotId: channel.id,
|
||||
providerId: nil,
|
||||
url: nil,
|
||||
|
@ -2825,8 +2825,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return MiniAppListScreen(context: context, initialData: initialData as! MiniAppListScreen.InitialData)
|
||||
}
|
||||
|
||||
public func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peer: EnginePeer, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?) {
|
||||
openWebAppImpl(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, peer: peer, threadId: threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: skipTermsOfService, payload: payload)
|
||||
public func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?) {
|
||||
openWebAppImpl(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: chatPeer, threadId: threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: skipTermsOfService, payload: payload)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,7 @@ private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
|
||||
case storySource = 11
|
||||
case mediaEditorState = 12
|
||||
case shareWithPeersState = 13
|
||||
case webAppPermissionsState = 14
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificItemCacheCollectionId {
|
||||
@ -94,6 +95,7 @@ public struct ApplicationSpecificItemCacheCollectionId {
|
||||
public static let storySource = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.storySource.rawValue)
|
||||
public static let mediaEditorState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaEditorState.rawValue)
|
||||
public static let shareWithPeersState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.shareWithPeersState.rawValue)
|
||||
public static let webAppPermissionsState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.webAppPermissionsState.rawValue)
|
||||
}
|
||||
|
||||
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {
|
||||
|
@ -50,6 +50,7 @@ public enum UndoOverlayContent {
|
||||
case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?)
|
||||
case messageTagged(context: AccountContext, isSingleMessage: Bool, customEmoji: TelegramMediaFile, isBuiltinReaction: Bool, customUndoText: String?)
|
||||
case media(context: AccountContext, file: FileMediaReference, title: String?, text: String, undoText: String?, customAction: (() -> Void)?)
|
||||
case progress(progress: CGFloat, title: String, text: String, undoText: String?)
|
||||
}
|
||||
|
||||
public enum UndoOverlayAction {
|
||||
|
@ -1351,6 +1351,45 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
if let updatedImageSignal = updatedImageSignal {
|
||||
stillStickerNode.setSignal(updatedImageSignal)
|
||||
}
|
||||
case let .progress(progress, title, text, customUndoText):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode?.displayWithoutProcessing = true
|
||||
self.iconNode?.displaysAsynchronously = false
|
||||
self.iconNode?.image = generateTintedImage(image: UIImage(bundleImageName: "Premium/File"), color: .white)
|
||||
self.iconImageSize = CGSize(width: 14.0, height: 14.0)
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = nil
|
||||
self.animatedStickerNode = nil
|
||||
|
||||
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
|
||||
return ("URL", contents)
|
||||
}), textAlignment: .natural)
|
||||
self.textNode.attributedText = attributedText
|
||||
|
||||
|
||||
if text.contains("](") {
|
||||
isUserInteractionEnabled = true
|
||||
}
|
||||
self.originalRemainingSeconds = 5
|
||||
|
||||
self.textNode.maximumNumberOfLines = 5
|
||||
|
||||
if let customUndoText = customUndoText {
|
||||
undoText = customUndoText
|
||||
displayUndo = true
|
||||
} else {
|
||||
displayUndo = false
|
||||
}
|
||||
|
||||
self.statusNode?.transitionToState(.progress(color: .white, lineWidth: nil, value: max(progress, 0.027), cancelEnabled: false, animateRotation: true), completion: {})
|
||||
}
|
||||
|
||||
self.remainingSeconds = self.originalRemainingSeconds
|
||||
@ -1394,7 +1433,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
} else {
|
||||
self.isUserInteractionEnabled = false
|
||||
}
|
||||
case .sticker, .customEmoji, .media:
|
||||
case .sticker, .customEmoji, .media, .progress:
|
||||
self.isUserInteractionEnabled = displayUndo
|
||||
case .dice:
|
||||
self.panelWrapperNode.clipsToBounds = true
|
||||
@ -1431,6 +1470,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.slotMachineNode.flatMap(self.panelWrapperNode.addSubnode)
|
||||
self.avatarNode.flatMap(self.panelWrapperNode.addSubnode)
|
||||
self.multiAvatarsNode.flatMap(self.panelWrapperNode.addSubnode)
|
||||
|
||||
self.panelWrapperNode.addSubnode(self.buttonNode)
|
||||
self.panelWrapperNode.addSubnode(self.titleNode)
|
||||
self.panelWrapperNode.addSubnode(self.textNode)
|
||||
@ -1629,6 +1669,15 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
|
||||
self.renewWithCurrentContent()
|
||||
transition = .animated(duration: 0.1, curve: .easeInOut)
|
||||
case let .progress(progress, title, text, _):
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||
self.textNode.attributedText = attributedText
|
||||
|
||||
self.statusNode?.transitionToState(.progress(color: .white, lineWidth: nil, value: max(progress, 0.027), cancelEnabled: false, animateRotation: true), completion: {})
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -1667,7 +1716,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
|
||||
var leftInset: CGFloat = 50.0
|
||||
if let iconImageSize = self.iconImageSize {
|
||||
leftInset = 9.0 + iconImageSize.width + 9.0
|
||||
if case .progress = self.content {
|
||||
} else {
|
||||
leftInset = 9.0 + iconImageSize.width + 9.0
|
||||
}
|
||||
} else if let iconSize = preferredSize {
|
||||
if iconSize.width > leftInset {
|
||||
leftInset = iconSize.width - 8.0
|
||||
@ -1802,8 +1854,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
iconSize = CGSize()
|
||||
}
|
||||
|
||||
var isProgress = false
|
||||
if case .progress = self.content {
|
||||
isProgress = true
|
||||
}
|
||||
|
||||
let iconFrame: CGRect
|
||||
if self.iconImageSize != nil {
|
||||
if self.iconImageSize != nil && !isProgress {
|
||||
iconFrame = CGRect(origin: CGPoint(x: 9.0, y: floor((contentHeight - iconSize.height) / 2.0) + verticalOffset), size: iconSize)
|
||||
} else {
|
||||
iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0) + verticalOffset), size: iconSize)
|
||||
@ -1885,13 +1942,17 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
transition.updateFrame(node: statusNode, frame: statusFrame)
|
||||
if !self.didStartStatusNode {
|
||||
let statusColor: UIColor
|
||||
if case .starsSent = self.content {
|
||||
statusColor = self.undoTextColor
|
||||
if case .progress = self.content {
|
||||
|
||||
} else {
|
||||
statusColor = .white
|
||||
let statusColor: UIColor
|
||||
if case .starsSent = self.content {
|
||||
statusColor = self.undoTextColor
|
||||
} else {
|
||||
statusColor = .white
|
||||
}
|
||||
statusNode.transitionToState(.secretTimeout(color: statusColor, icon: .none, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {})
|
||||
}
|
||||
statusNode.transitionToState(.secretTimeout(color: statusColor, icon: .none, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {})
|
||||
self.didStartStatusNode = true
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ public enum ParsedInternalPeerUrlParameter {
|
||||
case channelMessage(Int32, Double?)
|
||||
case replyThread(Int32, Int32)
|
||||
case voiceChat(String?)
|
||||
case appStart(String, String?, Bool)
|
||||
case appStart(String, String?, ResolvedStartAppMode)
|
||||
case story(Int32)
|
||||
case boost
|
||||
case text(String)
|
||||
@ -293,18 +293,25 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String)
|
||||
}
|
||||
return .startAttach(peerName, value, choose)
|
||||
} else if queryItem.name == "startapp" {
|
||||
var compact = false
|
||||
var mode: ResolvedStartAppMode = .generic
|
||||
if let queryItems = components.queryItems {
|
||||
for queryItem in queryItems {
|
||||
if let value = queryItem.value {
|
||||
if queryItem.name == "mode", value == "compact" {
|
||||
compact = true
|
||||
if queryItem.name == "mode" {
|
||||
switch value {
|
||||
case "compact":
|
||||
mode = .compact
|
||||
case "fullscreen":
|
||||
mode = .fullscreen
|
||||
default:
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return .peer(.name(peerName), .appStart("", queryItem.value, compact))
|
||||
return .peer(.name(peerName), .appStart("", queryItem.value, mode))
|
||||
} else if queryItem.name == "story" {
|
||||
if let id = Int32(value) {
|
||||
return .peer(.name(peerName), .story(id))
|
||||
@ -335,18 +342,25 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String)
|
||||
} else if queryItem.name == "profile" {
|
||||
return .peer(.name(peerName), .profile)
|
||||
} else if queryItem.name == "startapp" {
|
||||
var compact = false
|
||||
var mode: ResolvedStartAppMode = .generic
|
||||
if let queryItems = components.queryItems {
|
||||
for queryItem in queryItems {
|
||||
if let value = queryItem.value {
|
||||
if queryItem.name == "mode", value == "compact" {
|
||||
compact = true
|
||||
if queryItem.name == "mode" {
|
||||
switch value {
|
||||
case "compact":
|
||||
mode = .compact
|
||||
case "fullscreen":
|
||||
mode = .fullscreen
|
||||
default:
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return .peer(.name(peerName), .appStart("", nil, compact))
|
||||
return .peer(.name(peerName), .appStart("", queryItem.value, mode))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -615,19 +629,28 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String)
|
||||
} else if pathComponents.count == 2 {
|
||||
let appName = pathComponents[1]
|
||||
var startApp: String?
|
||||
var compact = false
|
||||
var mode: ResolvedStartAppMode = .generic
|
||||
if let queryItems = components.queryItems {
|
||||
for queryItem in queryItems {
|
||||
if let value = queryItem.value {
|
||||
if queryItem.name == "startapp" {
|
||||
startApp = value
|
||||
} else if queryItem.name == "mode", value == "compact" {
|
||||
compact = true
|
||||
}
|
||||
if queryItem.name == "mode" {
|
||||
switch value {
|
||||
case "compact":
|
||||
mode = .compact
|
||||
case "fullscreen":
|
||||
mode = .fullscreen
|
||||
default:
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return .peer(.name(peerName), .appStart(appName, startApp, compact))
|
||||
return .peer(.name(peerName), .appStart(appName, startApp, mode))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -745,10 +768,10 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .appStart(name, payload, compact):
|
||||
case let .appStart(name, payload, mode):
|
||||
if name.isEmpty {
|
||||
if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) {
|
||||
return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: nil, payload: payload, justInstalled: false, compact: compact)))))
|
||||
return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: nil, payload: payload, justInstalled: false, mode: mode)))))
|
||||
} else {
|
||||
return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))))
|
||||
}
|
||||
@ -760,7 +783,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
||||
}
|
||||
|> mapToSignal { botApp -> Signal<ResolveInternalUrlResult, NoError> in
|
||||
if let botApp {
|
||||
return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: botApp, payload: payload, justInstalled: false, compact: compact)))))
|
||||
return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: botApp, payload: payload, justInstalled: false, mode: mode)))))
|
||||
} else {
|
||||
return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))))
|
||||
}
|
||||
|
@ -41,6 +41,13 @@ swift_library(
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/OverlayStatusController",
|
||||
"//submodules/TelegramUIPreferences",
|
||||
"//submodules/Components/LottieAnimationComponent",
|
||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftAnimationComponent",
|
||||
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemImpl",
|
||||
"//submodules/DeviceLocationManager",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
75
submodules/WebUI/Sources/FileDownload.swift
Normal file
75
submodules/WebUI/Sources/FileDownload.swift
Normal file
@ -0,0 +1,75 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
|
||||
final class FileDownload: NSObject, URLSessionDownloadDelegate {
|
||||
private let fileSize: Int64?
|
||||
private var urlSession: URLSession!
|
||||
private var completion: ((URL?, Error?) -> Void)?
|
||||
private var progressHandler: ((Double) -> Void)?
|
||||
|
||||
init(from url: URL, fileSize: Int64?, progressHandler: @escaping (Double) -> Void, completion: @escaping (URL?, Error?) -> Void) {
|
||||
self.progressHandler = progressHandler
|
||||
self.fileSize = fileSize
|
||||
self.completion = completion
|
||||
|
||||
|
||||
super.init()
|
||||
|
||||
let configuration = URLSessionConfiguration.default
|
||||
urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
|
||||
|
||||
let downloadTask = self.urlSession.downloadTask(with: url)
|
||||
downloadTask.resume()
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
||||
var totalBytesExpectedToWrite = totalBytesExpectedToWrite
|
||||
if totalBytesExpectedToWrite == -1, let fileSize = self.fileSize {
|
||||
totalBytesExpectedToWrite = fileSize
|
||||
}
|
||||
let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
|
||||
progressHandler?(progress)
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
||||
completion?(location, nil)
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
if let error = error {
|
||||
completion?(nil, error)
|
||||
}
|
||||
}
|
||||
|
||||
static func getFileSize(url: String) -> Signal<Int64?, NoError> {
|
||||
if #available(iOS 13.0, *) {
|
||||
guard let url = URL(string: url) else {
|
||||
return .single(nil)
|
||||
}
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "HEAD"
|
||||
|
||||
return Signal { subscriber in
|
||||
let task = URLSession.shared.dataTask(with: request) { _, response, error in
|
||||
if let _ = error {
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
}
|
||||
var fileSize: Int64?
|
||||
if let httpResponse = response as? HTTPURLResponse, let contentLength = httpResponse.value(forHTTPHeaderField: "Content-Length"), let size = Int64(contentLength) {
|
||||
fileSize = size
|
||||
}
|
||||
subscriber.putNext(fileSize)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
task.resume()
|
||||
return ActionDisposable {
|
||||
task.cancel()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
}
|
352
submodules/WebUI/Sources/FullscreenControlsComponent.swift
Normal file
352
submodules/WebUI/Sources/FullscreenControlsComponent.swift
Normal file
@ -0,0 +1,352 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import BundleIconComponent
|
||||
import MultilineTextComponent
|
||||
import MoreButtonNode
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import LottieAnimationComponent
|
||||
|
||||
final class FullscreenControlsComponent: Component {
|
||||
let context: AccountContext
|
||||
let title: String
|
||||
let isVerified: Bool
|
||||
let insets: UIEdgeInsets
|
||||
var hasBack: Bool
|
||||
let backPressed: () -> Void
|
||||
let minimizePressed: () -> Void
|
||||
let morePressed: (ASDisplayNode, ContextGesture?) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
title: String,
|
||||
isVerified: Bool,
|
||||
insets: UIEdgeInsets,
|
||||
hasBack: Bool,
|
||||
backPressed: @escaping () -> Void,
|
||||
minimizePressed: @escaping () -> Void,
|
||||
morePressed: @escaping (ASDisplayNode, ContextGesture?) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.title = title
|
||||
self.isVerified = isVerified
|
||||
self.insets = insets
|
||||
self.hasBack = hasBack
|
||||
self.backPressed = backPressed
|
||||
self.minimizePressed = minimizePressed
|
||||
self.morePressed = morePressed
|
||||
}
|
||||
|
||||
static func ==(lhs: FullscreenControlsComponent, rhs: FullscreenControlsComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.isVerified != rhs.isVerified {
|
||||
return false
|
||||
}
|
||||
if lhs.insets != rhs.insets {
|
||||
return false
|
||||
}
|
||||
if lhs.hasBack != rhs.hasBack {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let leftBackgroundView: BlurredBackgroundView
|
||||
private let rightBackgroundView: BlurredBackgroundView
|
||||
|
||||
private let closeIcon = ComponentView<Empty>()
|
||||
private let leftButton = HighlightTrackingButton()
|
||||
|
||||
private let titleClippingView = UIView()
|
||||
private let title = ComponentView<Empty>()
|
||||
private let credibility = ComponentView<Empty>()
|
||||
private let buttonTitle = ComponentView<Empty>()
|
||||
private let minimizeButton = ComponentView<Empty>()
|
||||
private let moreNode = MoreButtonNode(theme: defaultPresentationTheme, size: CGSize(width: 36.0, height: 36.0), encircled: false)
|
||||
|
||||
private var displayTitle = true
|
||||
private var timer: Timer?
|
||||
|
||||
private var component: FullscreenControlsComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.leftBackgroundView = BlurredBackgroundView(color: nil)
|
||||
self.rightBackgroundView = BlurredBackgroundView(color: nil)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.titleClippingView.clipsToBounds = true
|
||||
self.titleClippingView.isUserInteractionEnabled = false
|
||||
|
||||
self.leftBackgroundView.clipsToBounds = true
|
||||
self.addSubview(self.leftBackgroundView)
|
||||
self.addSubview(self.leftButton)
|
||||
|
||||
self.addSubview(self.titleClippingView)
|
||||
|
||||
self.rightBackgroundView.clipsToBounds = true
|
||||
self.addSubview(self.rightBackgroundView)
|
||||
|
||||
self.addSubview(self.moreNode.view)
|
||||
|
||||
self.moreNode.updateColor(.white, transition: .immediate)
|
||||
|
||||
self.leftButton.highligthedChanged = { [weak self] highlighted in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if highlighted {
|
||||
if let view = self.closeIcon.view {
|
||||
view.layer.removeAnimation(forKey: "opacity")
|
||||
view.alpha = 0.6
|
||||
}
|
||||
if let view = self.buttonTitle.view {
|
||||
view.layer.removeAnimation(forKey: "opacity")
|
||||
view.alpha = 0.6
|
||||
}
|
||||
} else {
|
||||
if let view = self.closeIcon.view {
|
||||
view.alpha = 1.0
|
||||
view.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
|
||||
}
|
||||
if let view = self.buttonTitle.view {
|
||||
view.alpha = 1.0
|
||||
view.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.leftButton.addTarget(self, action: #selector(self.closePressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.timer?.invalidate()
|
||||
}
|
||||
|
||||
@objc private func closePressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.backPressed()
|
||||
}
|
||||
|
||||
@objc private func timerEvent() {
|
||||
self.timer?.invalidate()
|
||||
self.timer = nil
|
||||
|
||||
self.displayTitle = false
|
||||
self.state?.updated(transition: .spring(duration: 0.3))
|
||||
}
|
||||
|
||||
func update(component: FullscreenControlsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let isFirstTime = self.component == nil
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
let leftBackgroundSize = CGSize(width: 30.0, height: 30.0)
|
||||
let rightBackgroundSize = CGSize(width: 72.0, height: 30.0)
|
||||
|
||||
self.leftBackgroundView.updateColor(color: UIColor(white: 0.67, alpha: 0.35), transition: transition.containedViewLayoutTransition)
|
||||
self.rightBackgroundView.updateColor(color: UIColor(white: 0.67, alpha: 0.35), transition: transition.containedViewLayoutTransition)
|
||||
|
||||
let rightBackgroundFrame = CGRect(origin: CGPoint(x: availableSize.width - component.insets.right - sideInset - rightBackgroundSize.width, y: 0.0), size: rightBackgroundSize)
|
||||
self.rightBackgroundView.update(size: rightBackgroundSize, cornerRadius: rightBackgroundFrame.height / 2.0, transition: transition.containedViewLayoutTransition)
|
||||
transition.setFrame(view: self.rightBackgroundView, frame: rightBackgroundFrame)
|
||||
|
||||
var isAnimatingTextTransition = false
|
||||
|
||||
var additionalLeftWidth: CGFloat = 0.0
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: component.title, font: Font.with(size: 13.0, design: .round, weight: .semibold), textColor: .white)))),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: self.displayTitle ? 3.0 : -titleSize.width - 15.0, y: floorToScreenPixels((leftBackgroundSize.height - titleSize.height) / 2.0)), size: titleSize)
|
||||
if let view = self.title.view {
|
||||
if view.superview == nil {
|
||||
self.titleClippingView.addSubview(view)
|
||||
}
|
||||
|
||||
if !view.alpha.isZero && !self.displayTitle {
|
||||
isAnimatingTextTransition = true
|
||||
}
|
||||
|
||||
transition.setFrame(view: view, frame: titleFrame)
|
||||
transition.setAlpha(view: view, alpha: self.displayTitle ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
let buttonTitleUpdated = (previousComponent?.hasBack ?? false) != component.hasBack
|
||||
let animationMultiplier = !component.hasBack ? -1.0 : 1.0
|
||||
if buttonTitleUpdated {
|
||||
isAnimatingTextTransition = true
|
||||
|
||||
if let view = self.buttonTitle.view, let snapshotView = view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = view.frame
|
||||
self.titleClippingView.addSubview(snapshotView)
|
||||
snapshotView.layer.animatePosition(from: .zero, to: CGPoint(x: -(snapshotView.frame.width * 1.5) * animationMultiplier, y: 0.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
snapshotView.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let buttonTitleSize = self.buttonTitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: component.hasBack ? "Back" : "Close", font: Font.with(size: 13.0, design: .round, weight: .semibold), textColor: .white)))),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
if self.displayTitle {
|
||||
additionalLeftWidth += titleSize.width + 10.0
|
||||
} else {
|
||||
additionalLeftWidth += buttonTitleSize.width + 10.0
|
||||
}
|
||||
|
||||
let buttonTitleFrame = CGRect(origin: CGPoint(x: self.displayTitle ? leftBackgroundSize.width + additionalLeftWidth + 3.0 : 3.0, y: floorToScreenPixels((leftBackgroundSize.height - buttonTitleSize.height) / 2.0)), size: buttonTitleSize)
|
||||
if let view = self.buttonTitle.view {
|
||||
if view.superview == nil {
|
||||
self.titleClippingView.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: buttonTitleFrame)
|
||||
|
||||
if buttonTitleUpdated {
|
||||
view.layer.animatePosition(from: CGPoint(x: (view.frame.width * 1.5) * animationMultiplier, y: 0.0), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
if component.isVerified {
|
||||
let credibilitySize = self.credibility.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(name: "Instant View/Verified", tintColor: .white)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let view = self.credibility.view {
|
||||
if view.superview == nil {
|
||||
view.alpha = 0.6
|
||||
self.titleClippingView.addSubview(view)
|
||||
}
|
||||
let credibilityFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 2.0, y: floorToScreenPixels((leftBackgroundSize.height - credibilitySize.height) / 2.0)), size: credibilitySize)
|
||||
transition.setFrame(view: view, frame: credibilityFrame)
|
||||
}
|
||||
if self.displayTitle {
|
||||
additionalLeftWidth += credibilitySize.width + 2.0
|
||||
}
|
||||
}
|
||||
|
||||
var leftBackgroundTransition = transition
|
||||
if buttonTitleUpdated {
|
||||
leftBackgroundTransition = .spring(duration: 0.3)
|
||||
}
|
||||
|
||||
let leftBackgroundFrame = CGRect(origin: CGPoint(x: sideInset + component.insets.left, y: 0.0), size: CGSize(width: leftBackgroundSize.width + additionalLeftWidth, height: leftBackgroundSize.height))
|
||||
self.leftBackgroundView.update(size: leftBackgroundFrame.size, cornerRadius: leftBackgroundSize.height / 2.0, transition: leftBackgroundTransition.containedViewLayoutTransition)
|
||||
leftBackgroundTransition.setFrame(view: self.leftBackgroundView, frame: leftBackgroundFrame)
|
||||
self.leftButton.frame = leftBackgroundFrame
|
||||
|
||||
if isAnimatingTextTransition, self.titleClippingView.mask == nil {
|
||||
if let maskImage = generateGradientImage(size: CGSize(width: 42.0, height: 10.0), colors: [UIColor.clear, UIColor.black, UIColor.black, UIColor.clear], locations: [0.0, 0.1, 0.9, 1.0], direction: .horizontal) {
|
||||
let maskView = UIImageView(image: maskImage.stretchableImage(withLeftCapWidth: 4, topCapHeight: 0))
|
||||
self.titleClippingView.mask = maskView
|
||||
maskView.frame = CGRect(origin: .zero, size: CGSize(width: self.titleClippingView.bounds.width, height: self.titleClippingView.bounds.height))
|
||||
}
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.titleClippingView, frame: CGRect(origin: CGPoint(x: sideInset + component.insets.left + leftBackgroundSize.height - 3.0, y: 0.0), size: CGSize(width: leftBackgroundFrame.width - leftBackgroundSize.height, height: leftBackgroundSize.height)))
|
||||
if let maskView = self.titleClippingView.mask {
|
||||
leftBackgroundTransition.setFrame(view: maskView, frame: CGRect(origin: .zero, size: CGSize(width: self.titleClippingView.bounds.width, height: self.titleClippingView.bounds.height)), completion: { _ in
|
||||
self.titleClippingView.mask = nil
|
||||
})
|
||||
}
|
||||
|
||||
let backButtonSize = self.closeIcon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
name: "web_backToCancel",
|
||||
mode: .animating(loop: false),
|
||||
range: component.hasBack ? (0.5, 1.0) : (0.0, 0.5)
|
||||
),
|
||||
colors: ["__allcolors__": .white],
|
||||
size: CGSize(width: 30.0, height: 30.0)
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 30.0, height: 30.0)
|
||||
)
|
||||
if let view = self.closeIcon.view {
|
||||
if view.superview == nil {
|
||||
view.isUserInteractionEnabled = false
|
||||
self.addSubview(view)
|
||||
}
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: leftBackgroundFrame.minX, y: 0.0), size: backButtonSize)
|
||||
transition.setFrame(view: view, frame: buttonFrame)
|
||||
}
|
||||
|
||||
let minimizeButtonSize = self.minimizeButton.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(
|
||||
BundleIconComponent(name: "Instant View/MinimizeArrow", tintColor: .white)
|
||||
),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.minimizePressed()
|
||||
}
|
||||
).minSize(CGSize(width: 30.0, height: 30.0))),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 30.0, height: 30.0)
|
||||
)
|
||||
if let view = self.minimizeButton.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: rightBackgroundFrame.minX + 2.0, y: 0.0), size: minimizeButtonSize)
|
||||
transition.setFrame(view: view, frame: buttonFrame)
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.moreNode.view, frame: CGRect(origin: CGPoint(x: rightBackgroundFrame.maxX - 42.0, y: -4.0), size: CGSize(width: 36.0, height: 36.0)))
|
||||
self.moreNode.action = { [weak self] node, gesture in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.morePressed(node, gesture)
|
||||
}
|
||||
|
||||
if isFirstTime {
|
||||
let timer = Timer(timeInterval: 2.5, target: self, selector: #selector(self.timerEvent), userInfo: nil, repeats: false)
|
||||
self.timer = timer
|
||||
RunLoop.main.add(timer, forMode: .common)
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: leftBackgroundSize.height)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import ComponentFlow
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import AttachmentUI
|
||||
@ -30,6 +31,8 @@ import UndoUI
|
||||
import AvatarNode
|
||||
import OverlayStatusController
|
||||
import TelegramUIPreferences
|
||||
import CoreMotion
|
||||
import DeviceLocationManager
|
||||
|
||||
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
|
||||
|
||||
@ -64,6 +67,7 @@ public struct WebAppParameters {
|
||||
let keepAliveSignal: Signal<Never, KeepWebViewError>?
|
||||
let forceHasSettings: Bool
|
||||
let fullSize: Bool
|
||||
let isFullscreen: Bool
|
||||
|
||||
public init(
|
||||
source: Source,
|
||||
@ -77,7 +81,8 @@ public struct WebAppParameters {
|
||||
buttonText: String?,
|
||||
keepAliveSignal: Signal<Never, KeepWebViewError>?,
|
||||
forceHasSettings: Bool,
|
||||
fullSize: Bool
|
||||
fullSize: Bool,
|
||||
isFullscreen: Bool = false
|
||||
) {
|
||||
self.source = source
|
||||
self.peerId = peerId
|
||||
@ -91,6 +96,11 @@ public struct WebAppParameters {
|
||||
self.keepAliveSignal = keepAliveSignal
|
||||
self.forceHasSettings = forceHasSettings
|
||||
self.fullSize = fullSize
|
||||
// #if DEBUG
|
||||
// self.isFullscreen = true
|
||||
// #else
|
||||
self.isFullscreen = isFullscreen
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,6 +146,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
fileprivate var webView: WebAppWebView?
|
||||
private var placeholderIcon: (UIImage, Bool)?
|
||||
private var placeholderNode: ShimmerEffectNode?
|
||||
private var fullscreenControls: ComponentView<Empty>?
|
||||
|
||||
fileprivate let loadingProgressPromise = Promise<CGFloat?>(nil)
|
||||
|
||||
@ -158,6 +169,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
private var queryId: Int64?
|
||||
fileprivate let canMinimize = true
|
||||
|
||||
private var hasBackButton = false
|
||||
|
||||
private var placeholderDisposable = MetaDisposable()
|
||||
private var keepAliveDisposable: Disposable?
|
||||
private var paymentDisposable: Disposable?
|
||||
@ -180,7 +193,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.headerBackgroundNode = ASDisplayNode()
|
||||
self.topOverscrollNode = ASDisplayNode()
|
||||
|
||||
|
||||
super.init()
|
||||
|
||||
if self.presentationData.theme.list.plainBackgroundColor.rgb == 0x000000 {
|
||||
@ -321,6 +334,13 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.paymentDisposable?.dispose()
|
||||
|
||||
self.webView?.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress))
|
||||
|
||||
if self.motionManager.isAccelerometerActive {
|
||||
self.motionManager.stopAccelerometerUpdates()
|
||||
}
|
||||
if self.motionManager.isGyroActive {
|
||||
self.motionManager.stopGyroUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -380,7 +400,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
controller.dismiss()
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.messages.requestAppWebView(peerId: peer.id, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: appStart.payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), compact: appStart.compact, allowWrite: true)
|
||||
let _ = (self.context.engine.messages.requestAppWebView(peerId: peer.id, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: appStart.payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), compact: appStart.mode == .compact, fullscreen: appStart.mode == .fullscreen, allowWrite: true)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] result in
|
||||
guard let self, let parsedUrl = URL(string: result.url) else {
|
||||
return
|
||||
@ -593,11 +613,82 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let previousLayout = self.validLayout?.0
|
||||
self.validLayout = (layout, navigationBarHeight)
|
||||
|
||||
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
self.controller?.navigationBar?.alpha = controller.isFullscreen ? 0.0 : 1.0
|
||||
transition.updateAlpha(node: self.topOverscrollNode, alpha: controller.isFullscreen ? 0.0 : 1.0)
|
||||
transition.updateAlpha(node: self.headerBackgroundNode, alpha: controller.isFullscreen ? 0.0 : 1.0)
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: .zero, size: layout.size))
|
||||
transition.updateFrame(node: self.headerBackgroundNode, frame: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight)))
|
||||
transition.updateFrame(node: self.topOverscrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -1000.0), size: CGSize(width: layout.size.width, height: 1000.0)))
|
||||
|
||||
|
||||
var contentTopInset: CGFloat = 0.0
|
||||
if controller.isFullscreen {
|
||||
var added = false
|
||||
let fullscreenControls: ComponentView<Empty>
|
||||
if let current = self.fullscreenControls {
|
||||
fullscreenControls = current
|
||||
} else {
|
||||
fullscreenControls = ComponentView<Empty>()
|
||||
self.fullscreenControls = fullscreenControls
|
||||
added = true
|
||||
}
|
||||
let controlsMargin: CGFloat = 8.0
|
||||
let componentTransition: ComponentTransition = added ? .immediate : ComponentTransition(transition)
|
||||
let controlsSize = fullscreenControls.update(
|
||||
transition: componentTransition,
|
||||
component: AnyComponent(
|
||||
FullscreenControlsComponent(
|
||||
context: self.context,
|
||||
title: controller.botName,
|
||||
isVerified: controller.botVerified,
|
||||
insets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: 0.0, right: layout.safeInsets.right),
|
||||
hasBack: self.hasBackButton,
|
||||
backPressed: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controller?.cancelPressed()
|
||||
},
|
||||
minimizePressed: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controller?.requestMinimize(topEdgeOffset: nil, initialVelocity: nil)
|
||||
},
|
||||
morePressed: { [weak self] node, gesture in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controller?.morePressed(node: node, gesture: gesture)
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: layout.size
|
||||
)
|
||||
if let view = fullscreenControls.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
transition.updateFrame(view: view, frame: CGRect(origin: CGPoint(x: 0.0, y: (layout.statusBarHeight ?? 0.0) + controlsMargin), size: controlsSize))
|
||||
if added {
|
||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
contentTopInset = controlsSize.height + controlsMargin * 2.0
|
||||
} else if let fullscreenControls = self.fullscreenControls {
|
||||
self.fullscreenControls = nil
|
||||
fullscreenControls.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
fullscreenControls.view?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
if let webView = self.webView {
|
||||
var scrollInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0)
|
||||
var frameBottomInset: CGFloat = 0.0
|
||||
@ -606,8 +697,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
scrollInset.bottom = 0.0
|
||||
}
|
||||
|
||||
let frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: max(1.0, layout.size.height - navigationBarHeight - frameBottomInset)))
|
||||
if !webView.frame.width.isZero && webView.frame != frame {
|
||||
let topInset: CGFloat = controller.isFullscreen ? 0.0 : navigationBarHeight
|
||||
|
||||
let webViewFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: max(1.0, layout.size.height - topInset - frameBottomInset)))
|
||||
if !webView.frame.width.isZero && webView.frame != webViewFrame {
|
||||
self.updateWebViewWhenStable = true
|
||||
}
|
||||
|
||||
@ -615,7 +708,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
if let inputHeight = self.validLayout?.0.inputHeight, inputHeight > 44.0 {
|
||||
bottomInset = max(bottomInset, inputHeight)
|
||||
}
|
||||
let viewportFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: navigationBarHeight), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - navigationBarHeight - bottomInset)))
|
||||
let viewportFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: topInset), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - topInset - bottomInset)))
|
||||
|
||||
if webView.scrollView.contentInset != scrollInset {
|
||||
webView.scrollView.contentInset = scrollInset
|
||||
@ -628,30 +721,40 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
}, transition: transition)
|
||||
Queue.mainQueue().after(0.4, {
|
||||
if let inputHeight = self.validLayout?.0.inputHeight, inputHeight > 44.0 {
|
||||
transition.updateFrame(view: webView, frame: frame)
|
||||
transition.updateFrame(view: webView, frame: webViewFrame)
|
||||
Queue.mainQueue().after(0.1) {
|
||||
self.targetContentOffset = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
transition.updateFrame(view: webView, frame: frame)
|
||||
transition.updateFrame(view: webView, frame: webViewFrame)
|
||||
}
|
||||
|
||||
var customInsets: UIEdgeInsets = .zero
|
||||
if controller.isFullscreen {
|
||||
customInsets.top = layout.statusBarHeight ?? 0.0
|
||||
}
|
||||
if layout.intrinsicInsets.bottom > 44.0 {
|
||||
customInsets.bottom = 0.0
|
||||
} else {
|
||||
customInsets.bottom = layout.intrinsicInsets.bottom
|
||||
}
|
||||
customInsets.left = layout.safeInsets.left
|
||||
customInsets.right = layout.safeInsets.left
|
||||
webView.customInsets = customInsets
|
||||
|
||||
if let controller = self.controller {
|
||||
webView.updateMetrics(height: viewportFrame.height, isExpanded: controller.isContainerExpanded(), isStable: !controller.isContainerPanning(), transition: transition)
|
||||
|
||||
let contentInsetsData = "{top:\(contentTopInset), bottom:0.0, left:0.0, right:0.0}"
|
||||
webView.sendEvent(name: "content_safe_area_changed", data: contentInsetsData)
|
||||
|
||||
if self.updateWebViewWhenStable && !controller.isContainerPanning() {
|
||||
self.updateWebViewWhenStable = false
|
||||
webView.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
if layout.intrinsicInsets.bottom > 44.0 {
|
||||
webView.customBottomInset = 0.0
|
||||
} else {
|
||||
webView.customBottomInset = layout.intrinsicInsets.bottom
|
||||
}
|
||||
webView.customSideInset = layout.safeInsets.left
|
||||
}
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
@ -691,6 +794,12 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
|
||||
private weak var currentQrCodeScannerScreen: QrCodeScanScreen?
|
||||
|
||||
func requestLayout(transition: ContainedViewLayoutTransition) {
|
||||
if let (layout, navigationBarHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
private var delayedScriptMessages: [WKScriptMessage] = []
|
||||
private func handleScriptMessage(_ message: WKScriptMessage) {
|
||||
guard let controller = self.controller else {
|
||||
@ -786,9 +895,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
case "web_app_request_viewport":
|
||||
if let (layout, navigationBarHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
self.requestLayout(transition: .immediate)
|
||||
case "web_app_request_safe_area":
|
||||
self.requestLayout(transition: .immediate)
|
||||
case "web_app_request_content_safe_area":
|
||||
self.requestLayout(transition: .immediate)
|
||||
case "web_app_request_theme":
|
||||
self.sendThemeChangedEvent()
|
||||
case "web_app_expand":
|
||||
@ -939,7 +1050,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
}
|
||||
case "web_app_setup_back_button":
|
||||
if let json = json, let isVisible = json["is_visible"] as? Bool {
|
||||
self.hasBackButton = isVisible
|
||||
self.controller?.cancelButtonNode.setState(isVisible ? .back : .cancel, animated: true)
|
||||
if controller.isFullscreen {
|
||||
self.requestLayout(transition: .immediate)
|
||||
}
|
||||
}
|
||||
case "web_app_trigger_haptic_feedback":
|
||||
if let json = json, let type = json["type"] as? String {
|
||||
@ -1234,6 +1349,54 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
}
|
||||
})
|
||||
}
|
||||
case "web_app_request_fullscreen":
|
||||
self.setIsFullscreen(true)
|
||||
case "web_app_exit_fullscreen":
|
||||
self.setIsFullscreen(false)
|
||||
case "web_app_start_accelerometer":
|
||||
if let json = json, let refreshRate = json["refresh_rate"] as? Double {
|
||||
self.setIsAccelerometerActive(true, refreshRate: refreshRate)
|
||||
}
|
||||
case "web_app_stop_accelerometer":
|
||||
self.setIsAccelerometerActive(false)
|
||||
case "web_app_start_device_orientation":
|
||||
if let json = json, let refreshRate = json["refresh_rate"] as? Double {
|
||||
self.setIsDeviceOrientationActive(true, refreshRate: refreshRate)
|
||||
}
|
||||
case "web_app_stop_device_orientation":
|
||||
self.setIsDeviceOrientationActive(false)
|
||||
case "web_app_start_gyroscope":
|
||||
if let json = json, let refreshRate = json["refresh_rate"] as? Double {
|
||||
self.setIsGyroscopeActive(true, refreshRate: refreshRate)
|
||||
}
|
||||
case "web_app_stop_gyroscope":
|
||||
self.setIsGyroscopeActive(false)
|
||||
case "web_app_set_emoji_status":
|
||||
if let json = json, let emojiIdString = json["custom_emoji_id"] as? String, let emojiId = Int64(emojiIdString) {
|
||||
let expirationDate = json["expiration_date"] as? Double
|
||||
self.setEmojiStatus(emojiId, expirationDate: expirationDate.flatMap { Int32($0) })
|
||||
}
|
||||
case "web_app_add_to_home_screen":
|
||||
self.addToHomeScreen()
|
||||
case "web_app_check_home_screen":
|
||||
let data: JSON = ["status": "unknown"]
|
||||
self.webView?.sendEvent(name: "home_screen_checked", data: data.string)
|
||||
case "web_app_request_location":
|
||||
self.requestLocation()
|
||||
case "web_app_check_location":
|
||||
self.checkLocation()
|
||||
case "web_app_open_location_settings":
|
||||
break
|
||||
case "web_app_send_prepared_message":
|
||||
if let json = json, let id = json["id"] as? String {
|
||||
self.sendPreparedMessage(id: id)
|
||||
}
|
||||
case "web_app_request_emoji_status_access":
|
||||
self.requestEmojiStatusAccess()
|
||||
case "web_app_request_file_download":
|
||||
if let json = json, let url = json["url"] as? String, let fileName = json["file_name"] as? String {
|
||||
self.downloadFile(url: url, fileName: fileName)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -1469,11 +1632,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
fileprivate func shareAccountContact() {
|
||||
guard let controller = self.controller, let botId = self.controller?.botId, let botName = self.controller?.botName else {
|
||||
guard let context = self.controller?.context, let botId = self.controller?.botId, let botName = self.controller?.botName else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let sendEvent: (Bool) -> Void = { success in
|
||||
var paramsString: String
|
||||
if success {
|
||||
@ -1484,7 +1645,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.webView?.sendEvent(name: "phone_requested", data: paramsString)
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let _ = (self.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId),
|
||||
TelegramEngine.EngineData.Item.Peer.IsBlocked(id: botId)
|
||||
@ -1858,6 +2018,523 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
navigationController.pushViewController(settingsController)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func setIsFullscreen(_ isFullscreen: Bool) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
guard controller.isFullscreen != isFullscreen else {
|
||||
self.webView?.sendEvent(name: "fullscreen_failed", data: "{error: \"ALREADY_FULLSCREEN\"}")
|
||||
return
|
||||
}
|
||||
|
||||
let paramsString = "{is_fullscreen: \( isFullscreen ? "true" : "false" )}"
|
||||
self.webView?.sendEvent(name: "fullscreen_changed", data: paramsString)
|
||||
|
||||
controller.isFullscreen = isFullscreen
|
||||
(controller.parentController() as? AttachmentController)?.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
|
||||
private let motionManager = CMMotionManager()
|
||||
private var isAccelerometerActive = false
|
||||
fileprivate func setIsAccelerometerActive(_ isActive: Bool, refreshRate: Double? = nil) {
|
||||
guard self.motionManager.isAccelerometerAvailable else {
|
||||
self.webView?.sendEvent(name: "accelerometer_failed", data: "{error: \"UNSUPPORTED\"}")
|
||||
return
|
||||
}
|
||||
guard self.isAccelerometerActive != isActive else {
|
||||
return
|
||||
}
|
||||
self.isAccelerometerActive = isActive
|
||||
if isActive {
|
||||
self.webView?.sendEvent(name: "accelerometer_started", data: nil)
|
||||
|
||||
if let refreshRate {
|
||||
self.motionManager.accelerometerUpdateInterval = refreshRate * 0.001
|
||||
}
|
||||
self.motionManager.startAccelerometerUpdates(to: OperationQueue.main) { data, error in
|
||||
if let data = data {
|
||||
let gravityConstant = 9.81
|
||||
self.webView?.sendEvent(
|
||||
name: "accelerometer_changed",
|
||||
data: "{x: \(data.acceleration.x * gravityConstant), y: \(data.acceleration.y * gravityConstant), z: \(data.acceleration.z * gravityConstant)}"
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.motionManager.isAccelerometerActive {
|
||||
self.motionManager.stopAccelerometerUpdates()
|
||||
}
|
||||
self.webView?.sendEvent(name: "accelerometer_stopped", data: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private var isDeviceOrientationActive = false
|
||||
fileprivate func setIsDeviceOrientationActive(_ isActive: Bool, refreshRate: Double? = nil) {
|
||||
guard self.motionManager.isDeviceMotionAvailable else {
|
||||
self.webView?.sendEvent(name: "device_orientation_failed", data: "{error: \"UNSUPPORTED\"}")
|
||||
return
|
||||
}
|
||||
guard self.isDeviceOrientationActive != isActive else {
|
||||
return
|
||||
}
|
||||
self.isDeviceOrientationActive = isActive
|
||||
if isActive {
|
||||
self.webView?.sendEvent(name: "device_orientation_started", data: nil)
|
||||
|
||||
if let refreshRate {
|
||||
self.motionManager.deviceMotionUpdateInterval = refreshRate * 0.001
|
||||
}
|
||||
self.motionManager.startDeviceMotionUpdates(to: OperationQueue.main) { data, error in
|
||||
if let data {
|
||||
self.webView?.sendEvent(
|
||||
name: "device_orientation_changed",
|
||||
data: "{alpha: \(data.attitude.roll), beta: \(data.attitude.pitch), gamma: \(data.attitude.yaw)}"
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.motionManager.isDeviceMotionActive {
|
||||
self.motionManager.stopDeviceMotionUpdates()
|
||||
}
|
||||
self.webView?.sendEvent(name: "device_orientation_stopped", data: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private var isGyroscopeActive = false
|
||||
fileprivate func setIsGyroscopeActive(_ isActive: Bool, refreshRate: Double? = nil) {
|
||||
guard self.motionManager.isGyroAvailable else {
|
||||
self.webView?.sendEvent(name: "gyroscope_failed", data: "{error: \"UNSUPPORTED\"}")
|
||||
return
|
||||
}
|
||||
guard self.isGyroscopeActive != isActive else {
|
||||
return
|
||||
}
|
||||
self.isGyroscopeActive = isActive
|
||||
if isActive {
|
||||
self.webView?.sendEvent(name: "gyroscope_started", data: nil)
|
||||
|
||||
if let refreshRate {
|
||||
self.motionManager.gyroUpdateInterval = refreshRate * 0.001
|
||||
}
|
||||
self.motionManager.startGyroUpdates(to: OperationQueue.main) { data, error in
|
||||
if let data {
|
||||
self.webView?.sendEvent(
|
||||
name: "gyroscope_changed",
|
||||
data: "{x: \(data.rotationRate.x), y: \(data.rotationRate.y), z: \(data.rotationRate.z)}"
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.motionManager.isGyroActive {
|
||||
self.motionManager.stopGyroUpdates()
|
||||
}
|
||||
self.webView?.sendEvent(name: "gyroscope_stopped", data: nil)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func sendPreparedMessage(id: String) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.messages.getPreparedInlineMessage(botId: controller.botId, id: id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak controller] preparedMessage in
|
||||
guard let self, let controller, let preparedMessage else {
|
||||
self?.webView?.sendEvent(name: "prepared_message_failed", data: "{error: \"MESSAGE_EXPIRED\"}")
|
||||
return
|
||||
}
|
||||
let previewController = WebAppMessagePreviewScreen(context: controller.context, botName: controller.botName, preparedMessage: preparedMessage, completion: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if result {
|
||||
self.webView?.sendEvent(name: "prepared_message_sent", data: nil)
|
||||
} else {
|
||||
self.webView?.sendEvent(name: "prepared_message_failed", data: "{error: \"USER_DECLINED\"}")
|
||||
}
|
||||
})
|
||||
previewController.navigationPresentation = .flatModal
|
||||
controller.parentController()?.push(previewController)
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func downloadFile(url: String, fileName: String) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
var title: String?
|
||||
let photoExtensions = [".jpg", ".png", ".gif", ".tiff"]
|
||||
let videoExtensions = [".mp4", ".mov"]
|
||||
let lowercasedFilename = fileName.lowercased()
|
||||
for ext in photoExtensions {
|
||||
if lowercasedFilename.hasSuffix(ext) {
|
||||
title = "Download Photo"
|
||||
break
|
||||
}
|
||||
}
|
||||
if title == nil {
|
||||
for ext in videoExtensions {
|
||||
if lowercasedFilename.hasSuffix(ext) {
|
||||
title = "Download Video"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if title == nil {
|
||||
title = "Download Document"
|
||||
}
|
||||
|
||||
let _ = (FileDownload.getFileSize(url: url)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] fileSize in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var fileSizeString = ""
|
||||
if let fileSize {
|
||||
fileSizeString = " (\(dataSizeString(fileSize, formatting: DataSizeStringFormatting(presentationData: self.presentationData))))"
|
||||
}
|
||||
|
||||
let text: String = "**\(controller.botName)** suggests you to download **\(fileName)**\(fileSizeString)."
|
||||
let alertController = standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: title, text: text, actions: [
|
||||
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { [weak self] in
|
||||
self?.webView?.sendEvent(name: "file_download_requested", data: "{status: \"cancelled\"}")
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: "Download", action: { [weak self] in
|
||||
self?.startDownload(url: url, fileName: fileName, fileSize: fileSize)
|
||||
})
|
||||
], parseMarkdown: true)
|
||||
controller.present(alertController, in: .window(.root))
|
||||
})
|
||||
}
|
||||
|
||||
private var fileDownload: FileDownload?
|
||||
private weak var fileDownloadTooltip: UndoOverlayController?
|
||||
fileprivate func startDownload(url: String, fileName: String, fileSize: Int64?) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
self.webView?.sendEvent(name: "file_download_requested", data: "{status: \"downloading\"}")
|
||||
|
||||
self.fileDownload = FileDownload(
|
||||
from: URL(string: url)!,
|
||||
fileSize: fileSize,
|
||||
progressHandler: { [weak self] progress in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let text: String
|
||||
if let fileSize {
|
||||
let downloadedSize = Int64(Double(fileSize) * progress)
|
||||
text = "\(dataSizeString(downloadedSize, formatting: DataSizeStringFormatting(presentationData: self.presentationData))) / \(dataSizeString(fileSize, formatting: DataSizeStringFormatting(presentationData: self.presentationData)))"
|
||||
} else {
|
||||
text = "\(Int32(progress))%"
|
||||
}
|
||||
|
||||
self.fileDownloadTooltip?.content = .progress(
|
||||
progress: progress,
|
||||
title: fileName,
|
||||
text: text,
|
||||
undoText: "Cancel"
|
||||
)
|
||||
},
|
||||
completion: { resultUrl, _ in
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
let text: String
|
||||
if let fileSize {
|
||||
text = "0 KB / \(dataSizeString(fileSize, formatting: DataSizeStringFormatting(presentationData: self.presentationData)))"
|
||||
} else {
|
||||
text = "0%"
|
||||
}
|
||||
|
||||
let tooltipController = UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .progress(
|
||||
progress: 0.0,
|
||||
title: fileName,
|
||||
text: text,
|
||||
undoText: "Cancel"
|
||||
),
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
)
|
||||
controller.present(tooltipController, in: .window(.root))
|
||||
self.fileDownloadTooltip = tooltipController
|
||||
}
|
||||
|
||||
fileprivate func requestEmojiStatusAccess() {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let _ = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)),
|
||||
self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: controller.botId)),
|
||||
self.context.engine.stickers.loadedStickerPack(reference: .iconStatusEmoji, forceActualized: false)
|
||||
|> map { result -> [TelegramMediaFile] in
|
||||
switch result {
|
||||
case let .result(_, items, _):
|
||||
return items.map(\.file)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
).start(next: { [weak self] accountPeer, botPeer, iconStatusEmoji in
|
||||
guard let self, let accountPeer, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let alertController = webAppEmojiStatusAlertController(
|
||||
context: self.context,
|
||||
accountPeer: accountPeer,
|
||||
botName: controller.botName,
|
||||
icons: iconStatusEmoji,
|
||||
completion: { [weak self] result in
|
||||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
if result {
|
||||
let context = self.context
|
||||
let botId = controller.botId
|
||||
let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: botId, enabled: true)
|
||||
|> deliverOnMainQueue).startStandalone(completed: { [weak self] in
|
||||
self?.webView?.sendEvent(name: "emoji_status_access_requested", data: "{status: \"allowed\"}")
|
||||
})
|
||||
|
||||
//TODO:localize
|
||||
if let botPeer {
|
||||
let resultController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .invitedToVoiceChat(context: self.context, peer: botPeer, title: nil, text: "**\(controller.botName)** can now set your emoji status anytime.", action: "Undo", duration: 5.0),
|
||||
elevatedLayout: true,
|
||||
action: { action in
|
||||
if case .undo = action {
|
||||
let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: botId, enabled: false)
|
||||
|> deliverOnMainQueue).startStandalone()
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
controller.present(resultController, in: .window(.root))
|
||||
}
|
||||
} else {
|
||||
self.webView?.sendEvent(name: "emoji_status_access_requested", data: "{status: \"cancelled\"}")
|
||||
}
|
||||
}
|
||||
)
|
||||
controller.present(alertController, in: .window(.root))
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func setEmojiStatus(_ fileId: Int64, expirationDate: Int32? = nil) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let _ = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
self.context.engine.stickers.resolveInlineStickers(fileIds: [fileId]),
|
||||
self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)),
|
||||
self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: controller.botId))
|
||||
).start(next: { [weak self] files, accountPeer, botPeer in
|
||||
guard let self, let accountPeer, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
guard let file = files[fileId] else {
|
||||
self.webView?.sendEvent(name: "emoji_status_failed", data: "{error: \"SUGGESTED_EMOJI_INVALID\"}")
|
||||
return
|
||||
}
|
||||
let confirmController = WebAppSetEmojiStatusScreen(
|
||||
context: self.context,
|
||||
botName: controller.botName,
|
||||
accountPeer: accountPeer,
|
||||
file: file,
|
||||
completion: { [weak self, weak controller] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if result {
|
||||
let _ = (self.context.engine.accountData.setEmojiStatus(file: file, expirationDate: expirationDate)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
self?.webView?.sendEvent(name: "emoji_status_set", data: nil)
|
||||
})
|
||||
//TODO:localize
|
||||
let resultController = UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .sticker(context: context, file: file, loop: false, title: nil, text: "Your emoji status updated.", undoText: nil, customAction: nil),
|
||||
elevatedLayout: true,
|
||||
action: { action in
|
||||
if case .undo = action {
|
||||
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
controller?.present(resultController, in: .window(.root))
|
||||
} else {
|
||||
self.webView?.sendEvent(name: "emoji_status_failed", data: "{error: \"USER_DECLINED\"}")
|
||||
}
|
||||
}
|
||||
)
|
||||
controller.parentController()?.push(confirmController)
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func addToHomeScreen() {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: controller.botId))
|
||||
|> deliverOnMainQueue
|
||||
).start(next: { [weak controller] peer in
|
||||
guard let peer, let addressName = peer.addressName else {
|
||||
return
|
||||
}
|
||||
let encodedName = peer.compactDisplayTitle.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
|
||||
let encodedUsername = addressName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
|
||||
|
||||
let url = URL(string: "http://64.225.73.234/?name=\(encodedName)&username=\(encodedUsername)")!
|
||||
UIApplication.shared.open(url)
|
||||
|
||||
controller?.dismiss()
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func checkLocation() {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let _ = (webAppPermissionsState(context: self.context, peerId: controller.botId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
var data: [String: Any] = [:]
|
||||
data["available"] = true
|
||||
if let location = state?.location {
|
||||
data["access_requested"] = location.isRequested
|
||||
if location.isRequested {
|
||||
data["access_granted"] = location.isAllowed
|
||||
}
|
||||
} else {
|
||||
data["access_requested"] = false
|
||||
}
|
||||
if let serializedData = JSON(dictionary: data)?.string {
|
||||
self.webView?.sendEvent(name: "location_checked", data: serializedData)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func requestLocation() {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let context = controller.context
|
||||
let botId = controller.botId
|
||||
let _ = (webAppPermissionsState(context: self.context, peerId: botId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak controller] state in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var shouldRequest = false
|
||||
if let location = state?.location {
|
||||
if location.isRequested {
|
||||
if location.isAllowed {
|
||||
let locationCoordinates = Signal<CLLocation, NoError> { subscriber in
|
||||
return context.sharedContext.locationManager!.push(mode: DeviceLocationMode.preciseForeground, updated: { location, _ in
|
||||
subscriber.putNext(location)
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
} |> deliverOnMainQueue
|
||||
let _ = locationCoordinates.startStandalone(next: { location in
|
||||
var data: [String: Any] = [:]
|
||||
data["available"] = true
|
||||
data["latitude"] = location.coordinate.latitude
|
||||
data["longitude"] = location.coordinate.longitude
|
||||
data["altitude"] = location.altitude
|
||||
data["course"] = location.course
|
||||
data["speed"] = location.speed
|
||||
data["horizontal_accuracy"] = location.horizontalAccuracy
|
||||
data["vertical_accuracy"] = location.verticalAccuracy
|
||||
if #available(iOS 13.4, *) {
|
||||
data["course_accuracy"] = location.courseAccuracy
|
||||
} else {
|
||||
data["course_accuracy"] = NSNull()
|
||||
}
|
||||
data["speed_accuracy"] = location.speedAccuracy
|
||||
if let serializedData = JSON(dictionary: data)?.string {
|
||||
self.webView?.sendEvent(name: "location_requested", data: serializedData)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
var data: [String: Any] = [:]
|
||||
data["available"] = false
|
||||
self.webView?.sendEvent(name: "location_requested", data: JSON(dictionary: data)?.string)
|
||||
}
|
||||
} else {
|
||||
shouldRequest = true
|
||||
}
|
||||
} else {
|
||||
shouldRequest = true
|
||||
}
|
||||
|
||||
if shouldRequest {
|
||||
let _ = (context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: botId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak controller] accountPeer, botPeer in
|
||||
guard let accountPeer, let botPeer, let controller else {
|
||||
return
|
||||
}
|
||||
let alertController = webAppLocationAlertController(
|
||||
context: controller.context,
|
||||
accountPeer: accountPeer,
|
||||
botPeer: botPeer,
|
||||
completion: { [weak self, weak controller] result in
|
||||
guard let self, let controller else {
|
||||
return
|
||||
}
|
||||
if result {
|
||||
let resultController = UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .invitedToVoiceChat(context: context, peer: botPeer, title: nil, text: "**\(botPeer.compactDisplayTitle)** can now have access to your location.", action: "Undo", duration: 5.0),
|
||||
elevatedLayout: true,
|
||||
action: { action in
|
||||
if case .undo = action {
|
||||
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
controller.present(resultController, in: .window(.root))
|
||||
|
||||
Queue.mainQueue().after(0.1, {
|
||||
self.requestLocation()
|
||||
})
|
||||
} else {
|
||||
var data: [String: Any] = [:]
|
||||
data["available"] = false
|
||||
self.webView?.sendEvent(name: "location_requested", data: JSON(dictionary: data)?.string)
|
||||
}
|
||||
let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: botId) { current in
|
||||
return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: result))
|
||||
}.start()
|
||||
}
|
||||
)
|
||||
controller.present(alertController, in: .window(.root))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var controllerNode: Node {
|
||||
@ -1872,8 +2549,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
public let source: WebAppParameters.Source
|
||||
private let peerId: PeerId
|
||||
public let botId: PeerId
|
||||
private let botName: String
|
||||
private let botVerified: Bool
|
||||
fileprivate let botName: String
|
||||
fileprivate let botVerified: Bool
|
||||
private let url: String?
|
||||
private let queryId: Int64?
|
||||
private let payload: String?
|
||||
@ -1882,6 +2559,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
private let keepAliveSignal: Signal<Never, KeepWebViewError>?
|
||||
private let replyToMessageId: MessageId?
|
||||
private let threadId: Int64?
|
||||
public var isFullscreen: Bool
|
||||
|
||||
private var presentationData: PresentationData
|
||||
fileprivate let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
@ -1909,6 +2587,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.keepAliveSignal = params.keepAliveSignal
|
||||
self.replyToMessageId = replyToMessageId
|
||||
self.threadId = threadId
|
||||
self.isFullscreen = params.isFullscreen
|
||||
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
|
||||
@ -1927,22 +2606,22 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
|
||||
// self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode)
|
||||
self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed)
|
||||
self.navigationItem.leftBarButtonItem?.target = self
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode)
|
||||
self.navigationItem.rightBarButtonItem?.action = #selector(self.moreButtonPressed)
|
||||
self.navigationItem.rightBarButtonItem?.target = self
|
||||
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
|
||||
let titleView = WebAppTitleView(context: self.context, theme: self.presentationData.theme)
|
||||
titleView.title = WebAppTitle(title: params.botName, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: params.botVerified)
|
||||
self.navigationItem.titleView = titleView
|
||||
self.titleView = titleView
|
||||
if !self.isFullscreen {
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode)
|
||||
self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed)
|
||||
self.navigationItem.leftBarButtonItem?.target = self
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode)
|
||||
self.navigationItem.rightBarButtonItem?.action = #selector(self.moreButtonPressed)
|
||||
self.navigationItem.rightBarButtonItem?.target = self
|
||||
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
|
||||
let titleView = WebAppTitleView(context: self.context, theme: self.presentationData.theme)
|
||||
titleView.title = WebAppTitle(title: params.botName, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: params.botVerified)
|
||||
self.navigationItem.titleView = titleView
|
||||
self.titleView = titleView
|
||||
}
|
||||
|
||||
self.moreButtonNode.action = { [weak self] _, gesture in
|
||||
if let strongSelf = self {
|
||||
@ -2024,7 +2703,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.navigationBar?.updatePresentationData(navigationBarPresentationData)
|
||||
}
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
@objc fileprivate func cancelPressed() {
|
||||
if case .back = self.cancelButtonNode.state {
|
||||
self.controllerNode.sendBackButtonEvent()
|
||||
} else {
|
||||
@ -2034,11 +2713,14 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func moreButtonPressed() {
|
||||
@objc fileprivate func moreButtonPressed() {
|
||||
self.moreButtonNode.buttonPressed()
|
||||
}
|
||||
|
||||
@objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {
|
||||
@objc fileprivate func morePressed(node: ASDisplayNode, gesture: ContextGesture?) {
|
||||
guard let node = node as? ContextReferenceContentNode else {
|
||||
return
|
||||
}
|
||||
let context = self.context
|
||||
var presentationData = self.presentationData
|
||||
if !presentationData.theme.overallDarkAppearance, let headerColor = self.controllerNode.headerColor {
|
||||
@ -2170,7 +2852,18 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: self.presentationData.strings.WebApp_PrivacyPolicy_URL, forceExternal: false, presentationData: self.presentationData, navigationController: self.getNavigationController(), dismissInput: {})
|
||||
}
|
||||
})))
|
||||
|
||||
|
||||
#if DEBUG
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Add to Home Screen", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] c, _ in
|
||||
c?.dismiss(completion: nil)
|
||||
|
||||
self?.controllerNode.addToHomeScreen()
|
||||
})))
|
||||
#endif
|
||||
|
||||
if let _ = attachMenuBot, [.attachMenu, .settings, .generic].contains(source) {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_RemoveBot, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
@ -2267,6 +2960,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.requestLayout(transition: .immediate)
|
||||
self.controllerNode.webView?.setNeedsLayout()
|
||||
}
|
||||
|
||||
self.controllerNode.webView?.sendEvent(name: "visibility_changed", data: "{is_visible: \"\(self.isMinimized ? "false" : "true")\"}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2275,6 +2970,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
return true
|
||||
}
|
||||
|
||||
public func requestMinimize(topEdgeOffset: CGFloat?, initialVelocity: CGFloat?) {
|
||||
(self.parentController() as? AttachmentController)?.requestMinimize(topEdgeOffset: topEdgeOffset, initialVelocity: initialVelocity)
|
||||
}
|
||||
|
||||
public func shouldDismissImmediately() -> Bool {
|
||||
if self.controllerNode.needDismissConfirmation {
|
||||
return false
|
||||
|
364
submodules/WebUI/Sources/WebAppEmojiStatusAlertController.swift
Normal file
364
submodules/WebUI/Sources/WebAppEmojiStatusAlertController.swift
Normal file
@ -0,0 +1,364 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import AvatarNode
|
||||
import EmojiTextAttachmentView
|
||||
import TextFormat
|
||||
import Markdown
|
||||
|
||||
private final class IconsNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private var animationLayer: InlineStickerItemLayer?
|
||||
|
||||
private var files: [TelegramMediaFile]
|
||||
private var currentIndex = 0
|
||||
private var switchingToNext = false
|
||||
|
||||
private var timer: SwiftSignalKit.Timer?
|
||||
|
||||
private var currentParams: (size: CGSize, theme: PresentationTheme)?
|
||||
|
||||
init(context: AccountContext, files: [TelegramMediaFile]) {
|
||||
self.context = context
|
||||
self.files = files
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.timer?.invalidate()
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, theme: PresentationTheme) {
|
||||
self.currentParams = (size, theme)
|
||||
|
||||
if self.timer == nil {
|
||||
self.timer = SwiftSignalKit.Timer(timeout: 2.5, repeat: true, completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.switchingToNext = true
|
||||
if let (size, theme) = self.currentParams {
|
||||
self.updateLayout(size: size, theme: theme)
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
self.timer?.start()
|
||||
}
|
||||
|
||||
let animationLayer: InlineStickerItemLayer
|
||||
var disappearingAnimationLayer: InlineStickerItemLayer?
|
||||
if let current = self.animationLayer, !self.switchingToNext {
|
||||
animationLayer = current
|
||||
} else {
|
||||
if self.switchingToNext {
|
||||
self.currentIndex = (self.currentIndex + 1) % self.files.count
|
||||
disappearingAnimationLayer = self.animationLayer
|
||||
}
|
||||
let file = self.files[self.currentIndex]
|
||||
let emoji = ChatTextInputTextCustomEmojiAttribute(
|
||||
interactivelySelectedFromPackId: nil,
|
||||
fileId: file.fileId.id,
|
||||
file: file
|
||||
)
|
||||
animationLayer = InlineStickerItemLayer(
|
||||
context: .account(self.context),
|
||||
userLocation: .other,
|
||||
attemptSynchronousLoad: false,
|
||||
emoji: emoji,
|
||||
file: file,
|
||||
cache: self.context.animationCache,
|
||||
renderer: self.context.animationRenderer,
|
||||
unique: true,
|
||||
placeholderColor: theme.list.mediaPlaceholderColor,
|
||||
pointSize: CGSize(width: 20.0, height: 20.0),
|
||||
loopCount: 1
|
||||
)
|
||||
animationLayer.isVisibleForAnimations = true
|
||||
animationLayer.dynamicColor = theme.actionSheet.controlAccentColor
|
||||
self.view.layer.addSublayer(animationLayer)
|
||||
self.animationLayer = animationLayer
|
||||
|
||||
animationLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
animationLayer.animatePosition(from: CGPoint(x: 0.0, y: 10.0), to: .zero, duration: 0.2, additive: true)
|
||||
animationLayer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
animationLayer.frame = CGRect(origin: .zero, size: CGSize(width: 20.0, height: 20.0))
|
||||
|
||||
if let disappearingAnimationLayer {
|
||||
disappearingAnimationLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
disappearingAnimationLayer.removeFromSuperlayer()
|
||||
})
|
||||
disappearingAnimationLayer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -10.0), duration: 0.2, removeOnCompletion: false, additive: true)
|
||||
disappearingAnimationLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class WebAppEmojiStatusAlertContentNode: AlertContentNode {
|
||||
private let strings: PresentationStrings
|
||||
private let presentationTheme: PresentationTheme
|
||||
private let botName: String
|
||||
|
||||
private let textNode: ASTextNode
|
||||
private let iconBackgroundNode: ASImageNode
|
||||
private let iconAvatarNode: AvatarNode
|
||||
private let iconNameNode: ASTextNode
|
||||
private let iconAnimationNode: IconsNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: AlertControllerTheme,
|
||||
ptheme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
accountPeer: EnginePeer,
|
||||
botName: String,
|
||||
icons: [TelegramMediaFile],
|
||||
actions: [TextAlertAction]
|
||||
) {
|
||||
self.strings = strings
|
||||
self.presentationTheme = ptheme
|
||||
self.botName = botName
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
self.iconBackgroundNode = ASImageNode()
|
||||
self.iconBackgroundNode.displaysAsynchronously = false
|
||||
self.iconBackgroundNode.image = generateStretchableFilledCircleImage(radius: 16.0, color: theme.separatorColor)
|
||||
|
||||
self.iconAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0))
|
||||
self.iconAvatarNode.setPeer(context: context, theme: ptheme, peer: accountPeer)
|
||||
|
||||
self.iconNameNode = ASTextNode()
|
||||
self.iconNameNode.attributedText = NSAttributedString(string: accountPeer.compactDisplayTitle, font: Font.medium(15.0), textColor: theme.primaryColor)
|
||||
|
||||
self.iconAnimationNode = IconsNode(context: context, files: icons)
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.iconBackgroundNode)
|
||||
self.addSubnode(self.iconAvatarNode)
|
||||
self.addSubnode(self.iconNameNode)
|
||||
self.addSubnode(self.iconAnimationNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
//TODO:localize
|
||||
let string = "**\(self.botName)** requests access to set your **emoji status**. You will be able to revoke this access in the profile page of **\(self.botName)**."
|
||||
let attributedText = parseMarkdownIntoAttributedString(string, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
), textAlignment: .center)
|
||||
self.textNode.attributedText = attributedText
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width , 270.0)
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
|
||||
let iconSpacing: CGFloat = 6.0
|
||||
let iconSize = CGSize(width: 32.0, height: 32.0)
|
||||
let nameSize = self.iconNameNode.measure(size)
|
||||
let totalIconWidth = iconSize.width + iconSpacing + nameSize.width + 4.0 + iconSize.width
|
||||
|
||||
let iconBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalIconWidth) / 2.0), y: origin.y), size: CGSize(width: totalIconWidth, height: iconSize.height))
|
||||
transition.updateFrame(node: self.iconBackgroundNode, frame: iconBackgroundFrame)
|
||||
transition.updateFrame(node: self.iconAvatarNode, frame: CGRect(origin: iconBackgroundFrame.origin, size: iconSize).insetBy(dx: 1.0, dy: 1.0))
|
||||
transition.updateFrame(node: self.iconNameNode, frame: CGRect(origin: CGPoint(x: iconBackgroundFrame.minX + iconSize.width + iconSpacing, y: iconBackgroundFrame.minY + floorToScreenPixels((iconBackgroundFrame.height - nameSize.height) / 2.0)), size: nameSize))
|
||||
|
||||
self.iconAnimationNode.updateLayout(size: CGSize(width: 20.0, height: 20.0), theme: self.presentationTheme)
|
||||
self.iconAnimationNode.frame = CGRect(origin: CGPoint(x: iconBackgroundFrame.maxX - iconSize.width - 3.0, y: iconBackgroundFrame.minY), size: iconSize).insetBy(dx: 6.0, dy: 6.0)
|
||||
|
||||
origin.y += iconSize.height + 16.0
|
||||
|
||||
let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
|
||||
effectiveActionLayout = .vertical
|
||||
}
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||
case .vertical:
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
var contentWidth = minActionsWidth
|
||||
contentWidth = max(contentWidth, 234.0)
|
||||
|
||||
var actionsHeight: CGFloat = 0.0
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionsHeight = actionButtonHeight
|
||||
case .vertical:
|
||||
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
}
|
||||
|
||||
let resultWidth = contentWidth + insets.left + insets.right
|
||||
let resultSize = CGSize(width: resultWidth, height: iconSize.height + textSize.height + actionsHeight + 16.0 + insets.top + insets.bottom)
|
||||
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
case .vertical:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
case .vertical:
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += currentActionWidth
|
||||
case .vertical:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
}
|
||||
|
||||
func webAppEmojiStatusAlertController(
|
||||
context: AccountContext,
|
||||
accountPeer: EnginePeer,
|
||||
botName: String,
|
||||
icons: [TelegramMediaFile],
|
||||
completion: @escaping (Bool) -> Void
|
||||
) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let theme = presentationData.theme
|
||||
let strings = presentationData.strings
|
||||
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
var contentNode: WebAppEmojiStatusAlertContentNode?
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: "Decline", action: {
|
||||
dismissImpl?(true)
|
||||
|
||||
completion(false)
|
||||
}), TextAlertAction(type: .defaultAction, title: "Allow", action: {
|
||||
dismissImpl?(true)
|
||||
|
||||
completion(true)
|
||||
})]
|
||||
|
||||
contentNode = WebAppEmojiStatusAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, accountPeer: accountPeer, botName: botName, icons: icons, actions: actions)
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
|
||||
dismissImpl = { [weak controller] animated in
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
return controller
|
||||
}
|
293
submodules/WebUI/Sources/WebAppLocationAlertController.swift
Normal file
293
submodules/WebUI/Sources/WebAppLocationAlertController.swift
Normal file
@ -0,0 +1,293 @@
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import AvatarNode
|
||||
import Markdown
|
||||
import CheckNode
|
||||
|
||||
private func generateBoostIcon(theme: PresentationTheme) -> UIImage? {
|
||||
let size = CGSize(width: 28.0, height: 28.0)
|
||||
return generateImage(size, contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.addEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0))
|
||||
context.clip()
|
||||
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let colors: [CGColor] = [UIColor(rgb: 0x36c089).cgColor, UIColor(rgb: 0x3ca5eb).cgColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Location"), color: .white), let cgImage = image.cgImage {
|
||||
context.draw(cgImage, in: bounds.insetBy(dx: 6.0, dy: 6.0))
|
||||
}
|
||||
|
||||
context.resetClip()
|
||||
|
||||
let lineWidth = 2.0 - UIScreenPixel
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setStrokeColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0 + UIScreenPixel, dy: lineWidth / 2.0 + UIScreenPixel))
|
||||
}, opaque: false)
|
||||
}
|
||||
|
||||
private final class WebAppLocationAlertContentNode: AlertContentNode {
|
||||
private let strings: PresentationStrings
|
||||
private let text: String
|
||||
|
||||
private let textNode: ASTextNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let arrowNode: ASImageNode
|
||||
private let secondAvatarNode: AvatarNode
|
||||
private let iconNode: ASImageNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, accountPeer: EnginePeer, botPeer: EnginePeer, text: String, actions: [TextAlertAction]) {
|
||||
self.strings = strings
|
||||
self.text = text
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
|
||||
|
||||
self.arrowNode = ASImageNode()
|
||||
self.arrowNode.displaysAsynchronously = false
|
||||
self.arrowNode.displayWithoutProcessing = true
|
||||
|
||||
self.secondAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.image = generateBoostIcon(theme: ptheme)
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.avatarNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
self.addSubnode(self.secondAvatarNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
|
||||
self.avatarNode.setPeer(context: context, theme: ptheme, peer: accountPeer)
|
||||
self.secondAvatarNode.setPeer(context: context, theme: ptheme, peer: botPeer)
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
), textAlignment: .center)
|
||||
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor)
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width, 270.0)
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
|
||||
let avatarSize = CGSize(width: 60.0, height: 60.0)
|
||||
self.avatarNode.updateSize(size: avatarSize)
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize)
|
||||
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||
|
||||
if let arrowImage = self.arrowNode.image {
|
||||
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
|
||||
}
|
||||
|
||||
let secondAvatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize)
|
||||
transition.updateFrame(node: self.secondAvatarNode, frame: secondAvatarFrame)
|
||||
|
||||
if let icon = self.iconNode.image {
|
||||
let iconFrame = CGRect(origin: CGPoint(x: avatarFrame.maxX + 4.0 - icon.size.width, y: avatarFrame.maxY + 4.0 - icon.size.height), size: icon.size)
|
||||
transition.updateFrame(node: self.iconNode, frame: iconFrame)
|
||||
}
|
||||
|
||||
origin.y += avatarSize.height + 10.0
|
||||
|
||||
let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
origin.y += textSize.height + 10.0
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
|
||||
effectiveActionLayout = .vertical
|
||||
}
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||
case .vertical:
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
let contentWidth = max(size.width, minActionsWidth)
|
||||
|
||||
var actionsHeight: CGFloat = 0.0
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionsHeight = actionButtonHeight
|
||||
case .vertical:
|
||||
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
}
|
||||
|
||||
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + textSize.height + actionsHeight + 16.0 + insets.top + insets.bottom)
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
case .vertical:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
case .vertical:
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += currentActionWidth
|
||||
case .vertical:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
}
|
||||
|
||||
func webAppLocationAlertController(context: AccountContext, accountPeer: EnginePeer, botPeer: EnginePeer, completion: @escaping (Bool) -> Void) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
//TODO:localize
|
||||
let text = "**\(botPeer.compactDisplayTitle)** requests access to your **location**. You will be able to revoke this access in the profile page of **\(botPeer.compactDisplayTitle)**."
|
||||
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
var contentNode: WebAppLocationAlertContentNode?
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: "Decline", action: {
|
||||
dismissImpl?(true)
|
||||
completion(false)
|
||||
}), TextAlertAction(type: .defaultAction, title: "Allow", action: {
|
||||
dismissImpl?(true)
|
||||
completion(true)
|
||||
})]
|
||||
|
||||
contentNode = WebAppLocationAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, accountPeer: accountPeer, botPeer: botPeer, text: text, actions: actions)
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
|
||||
dismissImpl = { [weak controller] animated in
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
return controller
|
||||
}
|
454
submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift
Normal file
454
submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift
Normal file
@ -0,0 +1,454 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import WallpaperBackgroundNode
|
||||
import ListItemComponentAdaptor
|
||||
import ChatMessageItemImpl
|
||||
|
||||
final class PeerNameColorChatPreviewItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
|
||||
struct MessageItem: Equatable {
|
||||
static func ==(lhs: MessageItem, rhs: MessageItem) -> Bool {
|
||||
if lhs.outgoing != rhs.outgoing {
|
||||
return false
|
||||
}
|
||||
if lhs.peerId != rhs.peerId {
|
||||
return false
|
||||
}
|
||||
if lhs.author != rhs.author {
|
||||
return false
|
||||
}
|
||||
if lhs.photo != rhs.photo {
|
||||
return false
|
||||
}
|
||||
if lhs.nameColor != rhs.nameColor {
|
||||
return false
|
||||
}
|
||||
if lhs.backgroundEmojiId != rhs.backgroundEmojiId {
|
||||
return false
|
||||
}
|
||||
if let lhsReply = lhs.reply, let rhsReply = rhs.reply, lhsReply.0 != rhsReply.0 || lhsReply.1 != rhsReply.1 || lhsReply.2 != rhsReply.2 {
|
||||
return false
|
||||
} else if (lhs.reply == nil) != (rhs.reply == nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsLinkPreview = lhs.linkPreview, let rhsLinkPreview = rhs.linkPreview, lhsLinkPreview.0 != rhsLinkPreview.0 || lhsLinkPreview.1 != rhsLinkPreview.1 || lhsLinkPreview.2 != rhsLinkPreview.2 {
|
||||
return false
|
||||
} else if (lhs.linkPreview == nil) != (rhs.linkPreview == nil) {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
let outgoing: Bool
|
||||
let peerId: EnginePeer.Id
|
||||
let author: String
|
||||
let photo: [TelegramMediaImageRepresentation]
|
||||
let nameColor: PeerNameColor
|
||||
let backgroundEmojiId: Int64?
|
||||
let reply: (String, String, PeerNameColor)?
|
||||
let linkPreview: (String, String, String)?
|
||||
let text: String
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let componentTheme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let sectionId: ItemListSectionId
|
||||
let fontSize: PresentationFontSize
|
||||
let chatBubbleCorners: PresentationChatBubbleCorners
|
||||
let wallpaper: TelegramWallpaper
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
let nameDisplayOrder: PresentationPersonNameOrder
|
||||
let messageItems: [MessageItem]
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, messageItems: [MessageItem]) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.componentTheme = componentTheme
|
||||
self.strings = strings
|
||||
self.sectionId = sectionId
|
||||
self.fontSize = fontSize
|
||||
self.chatBubbleCorners = chatBubbleCorners
|
||||
self.wallpaper = wallpaper
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
self.messageItems = messageItems
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = PeerNameColorChatPreviewItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? PeerNameColorChatPreviewItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func item() -> ListViewItem {
|
||||
return self
|
||||
}
|
||||
|
||||
public static func ==(lhs: PeerNameColorChatPreviewItem, rhs: PeerNameColorChatPreviewItem) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.componentTheme !== rhs.componentTheme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.fontSize != rhs.fontSize {
|
||||
return false
|
||||
}
|
||||
if lhs.chatBubbleCorners != rhs.chatBubbleCorners {
|
||||
return false
|
||||
}
|
||||
if lhs.wallpaper != rhs.wallpaper {
|
||||
return false
|
||||
}
|
||||
if lhs.dateTimeFormat != rhs.dateTimeFormat {
|
||||
return false
|
||||
}
|
||||
if lhs.nameDisplayOrder != rhs.nameDisplayOrder {
|
||||
return false
|
||||
}
|
||||
if lhs.messageItems != rhs.messageItems {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
||||
private var backgroundNode: WallpaperBackgroundNode?
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let containerNode: ASDisplayNode
|
||||
private var messageNodes: [ListViewItemNode]?
|
||||
private var itemHeaderNodes: [ListViewItemNode.HeaderId: ListViewItemHeaderNode] = [:]
|
||||
|
||||
private var item: PeerNameColorChatPreviewItem?
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
init() {
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: PeerNameColorChatPreviewItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentNodes = self.messageNodes
|
||||
|
||||
var currentBackgroundNode = self.backgroundNode
|
||||
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, params, neighbors in
|
||||
if currentBackgroundNode == nil {
|
||||
currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false)
|
||||
currentBackgroundNode?.update(wallpaper: item.wallpaper, animated: false)
|
||||
currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.componentTheme, bubbleCorners: item.chatBubbleCorners)
|
||||
}
|
||||
|
||||
var insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(1))
|
||||
|
||||
var items: [ListViewItem] = []
|
||||
for messageItem in item.messageItems.reversed() {
|
||||
let authorPeerId = messageItem.peerId
|
||||
let replyAuthorPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(10))
|
||||
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
var messages = SimpleDictionary<MessageId, Message>()
|
||||
|
||||
peers[authorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: messageItem.author, lastName: "", username: nil, phone: nil, photo: messageItem.photo, botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: messageItem.nameColor, backgroundEmojiId: messageItem.backgroundEmojiId, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil)
|
||||
|
||||
|
||||
let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3)
|
||||
if let (replyAuthor, text, replyColor) = messageItem.reply {
|
||||
peers[replyAuthorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: replyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: replyColor, backgroundEmojiId: messageItem.backgroundEmojiId, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil)
|
||||
|
||||
messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[replyAuthorPeerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
}
|
||||
|
||||
var media: [Media] = []
|
||||
if let (site, title, text) = messageItem.linkPreview, params.width > 320.0 {
|
||||
media.append(TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "", displayUrl: "", hash: 0, type: nil, websiteName: site, title: title, text: text, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil))))
|
||||
}
|
||||
|
||||
var attributes: [MessageAttribute] = []
|
||||
if messageItem.reply != nil {
|
||||
attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false))
|
||||
}
|
||||
|
||||
attributes.append(InlineBotMessageAttribute(peerId: nil, title: "Test Attach"))
|
||||
|
||||
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: attributes, media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false))
|
||||
}
|
||||
|
||||
var nodes: [ListViewItemNode] = []
|
||||
if let messageNodes = currentNodes {
|
||||
nodes = messageNodes
|
||||
for i in 0 ..< items.count {
|
||||
let itemNode = messageNodes[i]
|
||||
items[i].updateNode(async: { $0() }, node: {
|
||||
return itemNode
|
||||
}, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in
|
||||
let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height))
|
||||
|
||||
itemNode.contentSize = layout.contentSize
|
||||
itemNode.insets = layout.insets
|
||||
itemNode.frame = nodeFrame
|
||||
itemNode.isUserInteractionEnabled = false
|
||||
|
||||
Queue.mainQueue().after(0.01) {
|
||||
apply(ListViewItemApply(isOnScreen: true))
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
var messageNodes: [ListViewItemNode] = []
|
||||
for i in 0 ..< items.count {
|
||||
var itemNode: ListViewItemNode?
|
||||
items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in
|
||||
itemNode = node
|
||||
apply().1(ListViewItemApply(isOnScreen: true))
|
||||
})
|
||||
itemNode!.isUserInteractionEnabled = false
|
||||
messageNodes.append(itemNode!)
|
||||
}
|
||||
nodes = messageNodes
|
||||
}
|
||||
|
||||
var contentSize = CGSize(width: params.width, height: 4.0 + 4.0)
|
||||
for node in nodes {
|
||||
contentSize.height += node.frame.size.height
|
||||
}
|
||||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
if params.width <= 320.0 {
|
||||
insets.top = 0.0
|
||||
}
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
|
||||
let leftInset = params.leftInset
|
||||
let rightInset = params.leftInset
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
if let currentBackgroundNode {
|
||||
currentBackgroundNode.update(wallpaper: item.wallpaper, animated: false)
|
||||
currentBackgroundNode.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners)
|
||||
}
|
||||
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
|
||||
|
||||
if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor || currentItem.messageItems.first?.backgroundEmojiId != item.messageItems.first?.backgroundEmojiId || currentItem.theme !== item.theme || currentItem.wallpaper != item.wallpaper {
|
||||
if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshot.frame = CGRect(origin: CGPoint(x: 0.0, y: -insets.top), size: snapshot.frame.size)
|
||||
strongSelf.view.addSubview(snapshot)
|
||||
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
snapshot.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.messageNodes = nodes
|
||||
var topOffset: CGFloat = 4.0
|
||||
for node in nodes {
|
||||
if node.supernode == nil {
|
||||
strongSelf.containerNode.addSubnode(node)
|
||||
}
|
||||
node.updateFrame(CGRect(origin: CGPoint(x: 0.0, y: topOffset), size: node.frame.size), within: layoutSize)
|
||||
topOffset += node.frame.size.height
|
||||
|
||||
if let header = node.headers()?.first(where: { $0 is ChatMessageAvatarHeader }) {
|
||||
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: 3.0 + node.frame.minY), size: CGSize(width: layoutSize.width, height: header.height))
|
||||
let stickLocationDistanceFactor: CGFloat = 0.0
|
||||
|
||||
let id = header.id
|
||||
let headerNode: ListViewItemHeaderNode
|
||||
if let current = strongSelf.itemHeaderNodes[id] {
|
||||
headerNode = current
|
||||
headerNode.updateFrame(headerFrame, within: layoutSize)
|
||||
|
||||
if headerNode.item !== header {
|
||||
header.updateNode(headerNode, previous: nil, next: nil)
|
||||
headerNode.item = header
|
||||
}
|
||||
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)
|
||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate)
|
||||
} else {
|
||||
headerNode = header.node(synchronousLoad: true)
|
||||
if headerNode.item !== header {
|
||||
header.updateNode(headerNode, previous: nil, next: nil)
|
||||
headerNode.item = header
|
||||
}
|
||||
headerNode.frame = headerFrame
|
||||
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)
|
||||
strongSelf.itemHeaderNodes[id] = headerNode
|
||||
|
||||
strongSelf.containerNode.addSubnode(headerNode)
|
||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let currentBackgroundNode = currentBackgroundNode, strongSelf.backgroundNode !== currentBackgroundNode {
|
||||
strongSelf.backgroundNode = currentBackgroundNode
|
||||
strongSelf.insertSubnode(currentBackgroundNode, at: 0)
|
||||
}
|
||||
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
}
|
||||
|
||||
if params.isStandalone {
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
strongSelf.bottomStripeNode.isHidden = true
|
||||
strongSelf.maskNode.isHidden = true
|
||||
} else {
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
let bottomStripeOffset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
}
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
|
||||
let displayMode: WallpaperDisplayMode
|
||||
if abs(params.availableHeight - params.width) < 100.0, params.availableHeight > 700.0 {
|
||||
displayMode = .halfAspectFill
|
||||
} else {
|
||||
if backgroundFrame.width > backgroundFrame.height * 4.0 {
|
||||
if params.availableHeight < 700.0 {
|
||||
displayMode = .halfAspectFill
|
||||
} else {
|
||||
displayMode = .aspectFill
|
||||
}
|
||||
} else {
|
||||
displayMode = .aspectFill
|
||||
}
|
||||
}
|
||||
|
||||
if let backgroundNode = strongSelf.backgroundNode {
|
||||
backgroundNode.frame = backgroundFrame
|
||||
backgroundNode.updateLayout(size: backgroundNode.bounds.size, displayMode: displayMode, transition: .immediate)
|
||||
}
|
||||
strongSelf.maskNode.frame = backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
355
submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift
Normal file
355
submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift
Normal file
@ -0,0 +1,355 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import Markdown
|
||||
import TextFormat
|
||||
import TelegramPresentationData
|
||||
import ViewControllerComponent
|
||||
import SheetComponent
|
||||
import BalancedTextComponent
|
||||
import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
import ButtonComponent
|
||||
import ItemListUI
|
||||
import AccountContext
|
||||
import PresentationDataUtils
|
||||
import ListSectionComponent
|
||||
import ListItemComponentAdaptor
|
||||
import TelegramStringFormatting
|
||||
|
||||
private final class SheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let botName: String
|
||||
let preparedMessage: PreparedInlineMessage
|
||||
let dismiss: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
botName: String,
|
||||
preparedMessage: PreparedInlineMessage,
|
||||
dismiss: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.botName = botName
|
||||
self.preparedMessage = preparedMessage
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
|
||||
static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let closeButton = Child(Button.self)
|
||||
let title = Child(Text.self)
|
||||
let amountSection = Child(ListSectionComponent.self)
|
||||
let button = Child(ButtonComponent.self)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
let component = context.component
|
||||
|
||||
let controller = environment.controller
|
||||
|
||||
let theme = environment.theme.withModalBlocksBackground()
|
||||
let strings = environment.strings
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 18.0)
|
||||
|
||||
let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0
|
||||
|
||||
let closeButton = closeButton.update(
|
||||
component: Button(
|
||||
content: AnyComponent(Text(text: "Cancel", font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)),
|
||||
action: {
|
||||
component.dismiss()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 120.0, height: 30.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(closeButton
|
||||
.position(CGPoint(x: closeButton.size.width / 2.0 + sideInset, y: 28.0))
|
||||
)
|
||||
|
||||
let title = title.update(
|
||||
component: Text(text: "Share Message", font: Font.bold(17.0), color: theme.list.itemPrimaryTextColor),
|
||||
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
contentSize.height += 40.0
|
||||
|
||||
let amountFont = Font.regular(13.0)
|
||||
let amountTextColor = theme.list.freeTextColor
|
||||
let amountMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: amountFont, textColor: amountTextColor), bold: MarkdownAttributeSet(font: amountFont, textColor: amountTextColor), link: MarkdownAttributeSet(font: amountFont, textColor: theme.list.itemAccentColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("Test Attach mini app suggests you to send this message to a chat you select.", attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||
let amountFooter = AnyComponent(MultilineTextComponent(
|
||||
text: .plain(amountInfoString),
|
||||
maximumNumberOfLines: 0,
|
||||
highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.1),
|
||||
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { attributes, _ in
|
||||
if let controller = controller() as? WebAppMessagePreviewScreen, let navigationController = controller.navigationController as? NavigationController {
|
||||
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_PaidContent_AmountInfo_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
||||
}
|
||||
}
|
||||
))
|
||||
|
||||
let messageItem = PeerNameColorChatPreviewItem.MessageItem(
|
||||
outgoing: true,
|
||||
peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)),
|
||||
author: "",
|
||||
photo: [],
|
||||
nameColor: .blue,
|
||||
backgroundEmojiId: nil,
|
||||
reply: nil,
|
||||
linkPreview: nil,
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||
)
|
||||
|
||||
let listItemParams = ListViewItemLayoutParams(width: context.availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true)
|
||||
|
||||
let amountSection = amountSection.update(
|
||||
component: ListSectionComponent(
|
||||
theme: theme,
|
||||
header: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Message Preview".uppercased(),
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||
textColor: theme.list.freeTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
footer: amountFooter,
|
||||
items: [
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor(
|
||||
itemGenerator: PeerNameColorChatPreviewItem(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
componentTheme: environment.theme,
|
||||
strings: environment.strings,
|
||||
sectionId: 0,
|
||||
fontSize: presentationData.chatFontSize,
|
||||
chatBubbleCorners: presentationData.chatBubbleCorners,
|
||||
wallpaper: presentationData.chatWallpaper,
|
||||
dateTimeFormat: environment.dateTimeFormat,
|
||||
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||
messageItems: [messageItem]
|
||||
),
|
||||
params: listItemParams
|
||||
)))
|
||||
]
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(amountSection
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + amountSection.size.height / 2.0))
|
||||
.clipsToBounds(true)
|
||||
.cornerRadius(10.0)
|
||||
)
|
||||
contentSize.height += amountSection.size.height
|
||||
contentSize.height += 32.0
|
||||
|
||||
let buttonString: String = "Share With..."
|
||||
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
|
||||
let button = button.update(
|
||||
component: ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: theme.list.itemCheckColors.fillColor,
|
||||
foreground: theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
|
||||
cornerRadius: 10.0
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))
|
||||
),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: {
|
||||
if let controller = controller() as? WebAppMessagePreviewScreen {
|
||||
let _ = controller
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(button
|
||||
.clipsToBounds(true)
|
||||
.cornerRadius(10.0)
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += button.size.height
|
||||
contentSize.height += 15.0
|
||||
|
||||
contentSize.height += max(environment.inputHeight, environment.safeInsets.bottom)
|
||||
|
||||
return contentSize
|
||||
}
|
||||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
var cachedCloseImage: (UIImage, PresentationTheme)?
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State()
|
||||
}
|
||||
}
|
||||
|
||||
private final class WebAppMessagePreviewSheetComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
private let context: AccountContext
|
||||
private let botName: String
|
||||
private let preparedMessage: PreparedInlineMessage
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
botName: String,
|
||||
preparedMessage: PreparedInlineMessage
|
||||
) {
|
||||
self.context = context
|
||||
self.botName = botName
|
||||
self.preparedMessage = preparedMessage
|
||||
}
|
||||
|
||||
static func ==(lhs: WebAppMessagePreviewSheetComponent, rhs: WebAppMessagePreviewSheetComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let sheet = Child(SheetComponent<(EnvironmentType)>.self)
|
||||
let animateOut = StoredActionSlot(Action<Void>.self)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
|
||||
let controller = environment.controller
|
||||
|
||||
let sheet = sheet.update(
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(SheetContent(
|
||||
context: context.component.context,
|
||||
botName: context.component.botName,
|
||||
preparedMessage: context.component.preparedMessage,
|
||||
dismiss: {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
)),
|
||||
backgroundColor: .color(environment.theme.list.blocksBackgroundColor),
|
||||
followContentSizeChanges: false,
|
||||
clipsContent: true,
|
||||
isScrollEnabled: false,
|
||||
animateOut: animateOut
|
||||
),
|
||||
environment: {
|
||||
environment
|
||||
SheetComponentEnvironment(
|
||||
isDisplaying: environment.value.isVisible,
|
||||
isCentered: environment.metrics.widthClass == .regular,
|
||||
hasInputHeight: !environment.inputHeight.isZero,
|
||||
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
|
||||
dismiss: { animated in
|
||||
if animated {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
context.add(sheet
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
|
||||
return context.availableSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
fileprivate let completion: (Bool) -> Void
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
botName: String,
|
||||
preparedMessage: PreparedInlineMessage,
|
||||
completion: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.completion = completion
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
component: WebAppMessagePreviewSheetComponent(
|
||||
context: context,
|
||||
botName: botName,
|
||||
preparedMessage: preparedMessage
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
theme: .default
|
||||
)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func dismissAnimated() {
|
||||
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
view.dismissAnimated()
|
||||
}
|
||||
}
|
||||
}
|
98
submodules/WebUI/Sources/WebAppPermissions.swift
Normal file
98
submodules/WebUI/Sources/WebAppPermissions.swift
Normal file
@ -0,0 +1,98 @@
|
||||
import Foundation
|
||||
import NaturalLanguage
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import TelegramUIPreferences
|
||||
|
||||
public struct WebAppPermissionsState: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case location
|
||||
}
|
||||
|
||||
public struct Location: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case isRequested
|
||||
case isAllowed
|
||||
}
|
||||
|
||||
public let isRequested: Bool
|
||||
public let isAllowed: Bool
|
||||
|
||||
public init(
|
||||
isRequested: Bool,
|
||||
isAllowed: Bool
|
||||
) {
|
||||
self.isRequested = isRequested
|
||||
self.isAllowed = isAllowed
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.isRequested = try container.decode(Bool.self, forKey: .isRequested)
|
||||
self.isAllowed = try container.decode(Bool.self, forKey: .isAllowed)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(self.isRequested, forKey: .isRequested)
|
||||
try container.encode(self.isAllowed, forKey: .isAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
public let location: Location?
|
||||
|
||||
public init(
|
||||
location: Location?
|
||||
) {
|
||||
self.location = location
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.location = try container.decode(WebAppPermissionsState.Location.self, forKey: .location)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encodeIfPresent(self.location, forKey: .location)
|
||||
}
|
||||
}
|
||||
|
||||
public func webAppPermissionsState(context: AccountContext, peerId: EnginePeer.Id) -> Signal<WebAppPermissionsState?, NoError> {
|
||||
let key = EngineDataBuffer(length: 8)
|
||||
key.setInt64(0, value: peerId.id._internalGetInt64Value())
|
||||
|
||||
return context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.webAppPermissionsState, id: key))
|
||||
|> map { entry -> WebAppPermissionsState? in
|
||||
return entry?.get(WebAppPermissionsState.self)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateWebAppPermissionsState(context: AccountContext, peerId: EnginePeer.Id, state: WebAppPermissionsState?) -> Signal<Never, NoError> {
|
||||
let key = EngineDataBuffer(length: 8)
|
||||
key.setInt64(0, value: peerId.id._internalGetInt64Value())
|
||||
|
||||
if let state {
|
||||
return context.engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.webAppPermissionsState, id: key, item: state)
|
||||
} else {
|
||||
return context.engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.webAppPermissionsState, id: key)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateWebAppPermissionsStateInteractively(context: AccountContext, peerId: EnginePeer.Id, _ f: @escaping (WebAppPermissionsState?) -> WebAppPermissionsState?) -> Signal<Never, NoError> {
|
||||
let key = EngineDataBuffer(length: 8)
|
||||
key.setInt64(0, value: peerId.id._internalGetInt64Value())
|
||||
|
||||
return context.engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.webAppPermissionsState, id: key))
|
||||
|> map { entry -> WebAppPermissionsState? in
|
||||
return entry?.get(WebAppPermissionsState.self)
|
||||
}
|
||||
|> mapToSignal { current -> Signal<Never, NoError> in
|
||||
return updateWebAppPermissionsState(context: context, peerId: peerId, state: f(current))
|
||||
}
|
||||
}
|
390
submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift
Normal file
390
submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift
Normal file
@ -0,0 +1,390 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import Markdown
|
||||
import TextFormat
|
||||
import TelegramPresentationData
|
||||
import ViewControllerComponent
|
||||
import SheetComponent
|
||||
import BalancedTextComponent
|
||||
import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
import ButtonComponent
|
||||
import AccountContext
|
||||
import PresentationDataUtils
|
||||
import PremiumPeerShortcutComponent
|
||||
import GiftAnimationComponent
|
||||
|
||||
private final class SheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let botName: String
|
||||
let accountPeer: EnginePeer
|
||||
let file: TelegramMediaFile
|
||||
let dismiss: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
botName: String,
|
||||
accountPeer: EnginePeer,
|
||||
file: TelegramMediaFile,
|
||||
dismiss: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.botName = botName
|
||||
self.accountPeer = accountPeer
|
||||
self.file = file
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
|
||||
static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.botName != rhs.botName {
|
||||
return false
|
||||
}
|
||||
if lhs.file != rhs.file {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
var cachedCloseImage: (UIImage, PresentationTheme)?
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State()
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let background = Child(RoundedRectangle.self)
|
||||
let animation = Child(GiftAnimationComponent.self)
|
||||
let closeButton = Child(Button.self)
|
||||
let title = Child(Text.self)
|
||||
let text = Child(BalancedTextComponent.self)
|
||||
|
||||
let peerShortcut = Child(PremiumPeerShortcutComponent.self)
|
||||
let button = Child(ButtonComponent.self)
|
||||
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
let component = context.component
|
||||
let state = context.state
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let theme = presentationData.theme
|
||||
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 18.0)
|
||||
|
||||
let background = background.update(
|
||||
component: RoundedRectangle(color: theme.actionSheet.opaqueItemBackgroundColor, cornerRadius: 8.0),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: 1000.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(background
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0))
|
||||
)
|
||||
|
||||
let animation = animation.update(
|
||||
component: GiftAnimationComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
file: component.file
|
||||
),
|
||||
availableSize: CGSize(width: 128.0, height: 128.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(animation
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: animation.size.height / 2.0 + 12.0))
|
||||
)
|
||||
|
||||
let closeImage: UIImage
|
||||
if let (image, cacheTheme) = state.cachedCloseImage, theme === cacheTheme {
|
||||
closeImage = image
|
||||
} else {
|
||||
closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)!
|
||||
state.cachedCloseImage = (closeImage, theme)
|
||||
}
|
||||
let closeButton = closeButton.update(
|
||||
component: Button(
|
||||
content: AnyComponent(Image(image: closeImage)),
|
||||
action: {
|
||||
component.dismiss()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 30.0, height: 30.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(closeButton
|
||||
.position(CGPoint(x: context.availableSize.width - closeButton.size.width, y: 28.0))
|
||||
)
|
||||
|
||||
let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0
|
||||
|
||||
contentSize.height += 128.0
|
||||
|
||||
let title = title.update(
|
||||
component: Text(text: "Set Emoji Status", font: Font.bold(24.0), color: theme.list.itemPrimaryTextColor),
|
||||
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
contentSize.height += 13.0
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
let textColor = theme.actionSheet.primaryTextColor
|
||||
let linkColor = theme.actionSheet.controlAccentColor
|
||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
let text = text.update(
|
||||
component: BalancedTextComponent(
|
||||
text: .markdown(
|
||||
text: "Do you want to set this emoji status suggested by **\(component.botName)**?",
|
||||
attributes: markdownAttributes
|
||||
),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
),
|
||||
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(text
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += text.size.height
|
||||
contentSize.height += 15.0
|
||||
|
||||
let peerShortcut = peerShortcut.update(
|
||||
component: PremiumPeerShortcutComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
peer: component.accountPeer
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - 32.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(peerShortcut
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + peerShortcut.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += peerShortcut.size.height
|
||||
contentSize.height += 32.0
|
||||
|
||||
let controller = environment.controller() as? WebAppSetEmojiStatusScreen
|
||||
|
||||
let button = button.update(
|
||||
component: ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: theme.list.itemCheckColors.fillColor,
|
||||
foreground: theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
|
||||
cornerRadius: 10.0
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: "Confirm", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
|
||||
),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: { [weak controller] in
|
||||
controller?.complete(result: true)
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - 16.0 * 2.0, height: 50),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(button
|
||||
.clipsToBounds(true)
|
||||
.cornerRadius(10.0)
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += button.size.height
|
||||
|
||||
contentSize.height += 48.0
|
||||
|
||||
return contentSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class WebAppSetEmojiStatusSheetComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
private let context: AccountContext
|
||||
private let botName: String
|
||||
private let accountPeer: EnginePeer
|
||||
private let file: TelegramMediaFile
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
botName: String,
|
||||
accountPeer: EnginePeer,
|
||||
file: TelegramMediaFile
|
||||
) {
|
||||
self.context = context
|
||||
self.botName = botName
|
||||
self.accountPeer = accountPeer
|
||||
self.file = file
|
||||
}
|
||||
|
||||
static func ==(lhs: WebAppSetEmojiStatusSheetComponent, rhs: WebAppSetEmojiStatusSheetComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.botName != rhs.botName {
|
||||
return false
|
||||
}
|
||||
if lhs.accountPeer != rhs.accountPeer {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let sheet = Child(SheetComponent<(EnvironmentType)>.self)
|
||||
let animateOut = StoredActionSlot(Action<Void>.self)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
|
||||
let controller = environment.controller
|
||||
|
||||
let sheet = sheet.update(
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(SheetContent(
|
||||
context: context.component.context,
|
||||
botName: context.component.botName,
|
||||
accountPeer: context.component.accountPeer,
|
||||
file: context.component.file,
|
||||
dismiss: {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
)),
|
||||
backgroundColor: .color(environment.theme.list.modalBlocksBackgroundColor),
|
||||
followContentSizeChanges: true,
|
||||
clipsContent: true,
|
||||
animateOut: animateOut
|
||||
),
|
||||
environment: {
|
||||
environment
|
||||
SheetComponentEnvironment(
|
||||
isDisplaying: environment.value.isVisible,
|
||||
isCentered: environment.metrics.widthClass == .regular,
|
||||
hasInputHeight: !environment.inputHeight.isZero,
|
||||
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
|
||||
dismiss: { animated in
|
||||
if animated {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
context.add(sheet
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
|
||||
return context.availableSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class WebAppSetEmojiStatusScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
private let completion: (Bool) -> Void
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
botName: String,
|
||||
accountPeer: EnginePeer,
|
||||
file: TelegramMediaFile,
|
||||
completion: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.completion = completion
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
component: WebAppSetEmojiStatusSheetComponent(
|
||||
context: context,
|
||||
botName: botName,
|
||||
accountPeer: accountPeer,
|
||||
file: file
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
theme: .default
|
||||
)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private var didComplete = false
|
||||
fileprivate func complete(result: Bool) {
|
||||
guard !self.didComplete else {
|
||||
return
|
||||
}
|
||||
self.didComplete = true
|
||||
self.completion(result)
|
||||
}
|
||||
|
||||
public func dismissAnimated() {
|
||||
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
view.dismissAnimated()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
}
|
@ -92,24 +92,16 @@ function tgBrowserDisconnectObserver() {
|
||||
final class WebAppWebView: WKWebView {
|
||||
var handleScriptMessage: (WKScriptMessage) -> Void = { _ in }
|
||||
|
||||
var customSideInset: CGFloat = 0.0 {
|
||||
var customInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
if self.customSideInset != oldValue {
|
||||
if self.customInsets != oldValue {
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var customBottomInset: CGFloat = 0.0 {
|
||||
didSet {
|
||||
if self.customBottomInset != oldValue {
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override var safeAreaInsets: UIEdgeInsets {
|
||||
return UIEdgeInsets(top: 0.0, left: self.customSideInset, bottom: self.customBottomInset, right: self.customSideInset)
|
||||
return UIEdgeInsets(top: self.customInsets.top, left: self.customInsets.left, bottom: self.customInsets.bottom, right: self.customInsets.right)
|
||||
}
|
||||
|
||||
init(account: Account) {
|
||||
@ -241,8 +233,11 @@ final class WebAppWebView: WKWebView {
|
||||
}
|
||||
|
||||
func updateMetrics(height: CGFloat, isExpanded: Bool, isStable: Bool, transition: ContainedViewLayoutTransition) {
|
||||
let data = "{height:\(height), is_expanded:\(isExpanded ? "true" : "false"), is_state_stable:\(isStable ? "true" : "false")}"
|
||||
self.sendEvent(name: "viewport_changed", data: data)
|
||||
let viewportData = "{height:\(height), is_expanded:\(isExpanded ? "true" : "false"), is_state_stable:\(isStable ? "true" : "false")}"
|
||||
self.sendEvent(name: "viewport_changed", data: viewportData)
|
||||
|
||||
let safeInsetsData = "{top:\(self.customInsets.top), bottom:\(self.customInsets.bottom), left:\(self.customInsets.left), right:\(self.customInsets.right)}"
|
||||
self.sendEvent(name: "safe_area_changed", data: safeInsetsData)
|
||||
}
|
||||
|
||||
var lastTouchTimestamp: Double?
|
||||
|
Loading…
x
Reference in New Issue
Block a user