Merge branch 'master' into stars-subscriptions

This commit is contained in:
Ilya Laktyushin 2024-07-30 11:29:42 +02:00
commit dc68eab568
230 changed files with 13409 additions and 3710 deletions

View File

@ -9587,6 +9587,7 @@ Sorry for the inconvenience.";
"Story.ContextPrivacy.LabelEveryone" = "Everyone";
"Story.Context.Privacy" = "Who Can See";
"Story.Context.Edit" = "Edit Story";
"Story.Context.EditCover" = "Edit Cover";
"Story.Context.SaveToProfile" = "Save to Profile";
"Story.Context.RemoveFromProfile" = "Remove from Profile";
"Story.ToastRemovedFromProfileText" = "Story removed from your profile";
@ -12563,6 +12564,7 @@ Sorry for the inconvenience.";
"WebBrowser.AddressBar.RecentlyVisited.Clear" = "Clear";
"WebBrowser.AddressBar.Bookmarks" = "BOOKMARKS";
"WebBrowser.AddressBar.ShowMore" = "Show More";
"WebBrowser.OpenLinksIn.Title" = "OPEN LINKS IN";
"WebBrowser.AutoLogin" = "Auto-Login via Telegram";
@ -12581,6 +12583,94 @@ Sorry for the inconvenience.";
"WebBrowser.Exceptions.Create.Text" = "Enter a domain that you don't want to be opened in the in-app browser.";
"WebBrowser.Exceptions.Create.Placeholder" = "Enter URL";
"WebBrowser.Exceptions.ClearConfirmation.Text" = "Are you sure you want to clear this list?";
"WebBrowser.Exceptions.ClearConfirmation.Clear" = "Clear";
"WebBrowser.ClearCookies.ClearConfirmation.Text" = "Are you sure you want to clear cookies?";
"WebBrowser.ClearCookies.ClearConfirmation.Clear" = "Clear";
"WebBrowser.ClearCache" = "Clear Cache";
"WebBrowser.ClearCache.ClearConfirmation.Text" = "Are you sure you want to clear cookies?";
"WebBrowser.ClearCache.ClearConfirmation.Clear" = "Clear";
"WebBrowser.ClearCache.Succeed" = "Cookies cleared.";
"WebBrowser.Done" = "Done";
"AccessDenied.LocationWeather" = "Telegram needs access to your location so that you can add the weather widget to your stories.\n\nPlease go to Settings > Privacy > Location Services and set Telegram to ON.";
"Story.Editor.TooltipWeatherLimitText" = "You can't add more than one weather sticker to a story.";
"WebBrowser.AddressPlaceholder" = "Enter URL";
"WebBrowser.Bookmarks.Title" = "Bookmarks";
"WebBrowser.Bookmarks.BookmarkCurrent" = "Bookmark Current Page";
"Story.Privacy.ChooseCover" = "Choose Story Cover";
"Story.Privacy.ChooseCoverInfo" = "Choose a frame from the story to show in your Profile.";
"Story.Privacy.ChooseCoverChannelInfo" = "Choose a frame from the story to show in channel profile.";
"Story.Privacy.ChooseCoverGroupInfo" = "Choose a frame from the story to show in group profile.";
"ChatList.Search.FilterApps" = "Apps";
"ChatList.Search.SectionPopularApps" = "POPULAR APPS";
"ChatList.Search.SectionRecentApps" = "APPS YOU USE";
"ChatList.Search.Apps.Empty.Text" = "No Apps Found";
"VoiceChat.MicrophoneModes" = "Microphone Modes";
"MiniAppList.Title" = "Examples";
"MiniAppList.ListSectionHeader" = "APPS THAT ACCEPT STARS";
"PeerInfo.PaneBotPreviews" = "Preview";
"PeerInfo.OpenAppButton" = "Open App";
"PeerInfo.AppFooterAdmin" = "By publishing this mini app, you agree to the [Telegram Terms of Service for Developers](https://telegram.org/privacy).";
"PeerInfo.AppFooter" = "By launching this mini app, you agree to the [Terms of Service for Mini Apps](https://telegram.org/privacy).";
"BotPreviews.MenuAddPreview" = "Add Preview";
"BotPreviews.MenuReorder" = "Reorder";
"BotPreviews.MenuDeleteLanguage" = "Delete %@";
"BotPreviews.SubtitleLoading" = "loading";
"BotPreviews.SubtitleEmpty" = "no preview added";
"BotPreviews.SubtitleCount_1" = "1 preview";
"BotPreviews.SubtitleCount_any" = "%d previews";
"BotPreviews.SheetDeleteTitle_1" = "Delete 1 Preview?";
"BotPreviews.SheetDeleteTitle_any" = "Delete %d Previews?";
"BotPreviews.LanguageTab.Main" = "Main";
"BotPreviews.LanguageTab.Add" = "+ Add Language";
"BotPreviews.Empty.Title" = "No Preview";
"BotPreviews.Empty.Text_1" = "Upload up to 1 screenshot and video demos for your mini app.";
"BotPreviews.Empty.Text_any" = "Upload up to %d screenshots and video demos for your mini app.";
"BotPreviews.Empty.Add" = "Add Preview";
"BotPreviews.Empty.AddTranslation" = "Create a Translation";
"BotPreviews.Empty.DeleteTranslation" = "Delete this Translation";
"BotPreviews.Empty.Separator" = "or";
"BotPreviews.AlertTooManyPreviews_1" = "You can add at most 1 preview.";
"BotPreviews.AlertTooManyPreviews_any" = "You can add at most 1 previews.";
"BotPreviews.DeleteTranslationAlert.Title" = "Delete Translation";
"BotPreviews.DeleteTranslationAlert.Text" = "Are you sure you want to delete this translation?";
"BotPreviews.TranslationFooter.Text" = "This preview will be displayed for all users who have %@ set as their language.";
"BotPreviews.DefaultFooter.Text" = "This preview will be shown by default. You can also add translations into specific languages.";
"BotPreviews.SelectLanguage.Title" = "Add a Translation";
"BotPreview.ViewContextDelete" = "Delete Preview";
"WebBrowser.Download.Confirmation" = "Do you want to download \"%@\"?";
"WebBrowser.Download.Download" = "Download";
"Story.Cover" = "Story Cover";
"Story.SaveCover" = "Save Cover";
"WebBrowser.CopyLink" = "Copy Link";
"WebBrowser.DeleteBookmark" = "Delete Bookmark";
"WebBrowser.RemoveRecent" = "Remove from Recent";
"Stars.Intro.Transaction.Gift.Title" = "Received Gift";
"Stars.Intro.Transaction.Gift.UnknownUser" = "Unknown User";
"WebApp.PrivacyPolicy" = "Privacy Policy";
"Conversation.OpenProfile" = "OPEN PROFILE";
"Stars.Intro.GiftStars" = "Gift Stars to Friends";
"MediaPicker.CreateSticker" = "Create a sticker from a photo";
"Stickers.CreateSticker" = "Create\nSticker";

View File

@ -836,6 +836,9 @@ public final class BotPreviewEditorTransitionOut {
}
}
public protocol MiniAppListScreenInitialData: AnyObject {
}
public protocol SharedAccountContext: AnyObject {
var sharedContainerPath: String { get }
var basePath: String { get }
@ -903,7 +906,7 @@ public protocol SharedAccountContext: AnyObject {
selectedMessages: Signal<Set<MessageId>?, NoError>,
mode: ChatHistoryListMode
) -> ChatHistoryListNode
func makeChatMessagePreviewItem(context: AccountContext, messages: [Message], theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)?, backgroundNode: ASDisplayNode?, availableReactions: AvailableReactions?, accountPeer: Peer?, isCentered: Bool, isPreview: Bool, isStandalone: Bool) -> ListViewItem
func makeChatMessagePreviewItem(context: AccountContext, messages: [Message], theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: ((UIView?, CGPoint?) -> Void)?, backgroundNode: ASDisplayNode?, availableReactions: AvailableReactions?, accountPeer: Peer?, isCentered: Bool, isPreview: Bool, isStandalone: Bool) -> ListViewItem
func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader
func makeChatMessageAvatarHeaderItem(context: AccountContext, timestamp: Int32, peer: Peer, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader
func makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController?
@ -978,6 +981,8 @@ public protocol SharedAccountContext: AnyObject {
func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController
func makeStoryMediaEditorScreen(context: AccountContext, source: Any?, text: String?, link: (url: String, name: String?)?, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void) -> ViewController
func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, transitionOut: @escaping () -> BotPreviewEditorTransitionOut?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
@ -1004,6 +1009,10 @@ public protocol SharedAccountContext: AnyObject {
func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController
func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController
func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController
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)
func makeDebugSettingsController(context: AccountContext?) -> ViewController?

View File

@ -1187,4 +1187,6 @@ public protocol ChatHistoryListNode: ListView {
func scrollToEndOfHistory()
func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets)
func messageInCurrentHistoryView(_ id: MessageId) -> Message?
var contentPositionChanged: (ListViewVisibleContentOffset) -> Void { get set }
}

View File

@ -139,6 +139,7 @@ public struct PremiumConfiguration {
showPremiumGiftInAttachMenu: false,
showPremiumGiftInTextField: false,
giveawayGiftsPurchaseAvailable: false,
starsGiftsPurchaseAvailable: false,
boostsPerGiftCount: 3,
audioTransciptionTrialMaxDuration: 300,
audioTransciptionTrialCount: 2,
@ -165,6 +166,7 @@ public struct PremiumConfiguration {
public let showPremiumGiftInAttachMenu: Bool
public let showPremiumGiftInTextField: Bool
public let giveawayGiftsPurchaseAvailable: Bool
public let starsGiftsPurchaseAvailable: Bool
public let boostsPerGiftCount: Int32
public let audioTransciptionTrialMaxDuration: Int32
public let audioTransciptionTrialCount: Int32
@ -190,6 +192,7 @@ public struct PremiumConfiguration {
showPremiumGiftInAttachMenu: Bool,
showPremiumGiftInTextField: Bool,
giveawayGiftsPurchaseAvailable: Bool,
starsGiftsPurchaseAvailable: Bool,
boostsPerGiftCount: Int32,
audioTransciptionTrialMaxDuration: Int32,
audioTransciptionTrialCount: Int32,
@ -214,6 +217,7 @@ public struct PremiumConfiguration {
self.showPremiumGiftInAttachMenu = showPremiumGiftInAttachMenu
self.showPremiumGiftInTextField = showPremiumGiftInTextField
self.giveawayGiftsPurchaseAvailable = giveawayGiftsPurchaseAvailable
self.starsGiftsPurchaseAvailable = starsGiftsPurchaseAvailable
self.boostsPerGiftCount = boostsPerGiftCount
self.audioTransciptionTrialMaxDuration = audioTransciptionTrialMaxDuration
self.audioTransciptionTrialCount = audioTransciptionTrialCount
@ -246,6 +250,7 @@ public struct PremiumConfiguration {
showPremiumGiftInAttachMenu: data["premium_gift_attach_menu_icon"] as? Bool ?? defaultValue.showPremiumGiftInAttachMenu,
showPremiumGiftInTextField: data["premium_gift_text_field_icon"] as? Bool ?? defaultValue.showPremiumGiftInTextField,
giveawayGiftsPurchaseAvailable: data["giveaway_gifts_purchase_available"] as? Bool ?? defaultValue.giveawayGiftsPurchaseAvailable,
starsGiftsPurchaseAvailable: data["stars_gifts_enabled"] as? Bool ?? defaultValue.starsGiftsPurchaseAvailable,
boostsPerGiftCount: get(data["boosts_per_sent_gift"]) ?? defaultValue.boostsPerGiftCount,
audioTransciptionTrialMaxDuration: get(data["transcribe_audio_trial_duration_max"]) ?? defaultValue.audioTransciptionTrialMaxDuration,
audioTransciptionTrialCount: get(data["transcribe_audio_trial_weekly_number"]) ?? defaultValue.audioTransciptionTrialCount,

View File

@ -1365,4 +1365,13 @@ public class AttachmentController: ViewController, MinimizableController {
})
return disposableSet
}
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)
snapshotView?.addSubview(contentSnapshotView)
}
return snapshotView
}
}

View File

@ -58,7 +58,7 @@ final class BotCheckoutWebInteractionController: ViewController {
}
override func loadDisplayNode() {
self.displayNode = BotCheckoutWebInteractionControllerNode(presentationData: self.presentationData, url: self.url, intent: self.intent)
self.displayNode = BotCheckoutWebInteractionControllerNode(context: self.context, presentationData: self.presentationData, url: self.url, intent: self.intent)
}
override func viewDidAppear(_ animated: Bool) {

View File

@ -4,6 +4,7 @@ import Display
import AsyncDisplayKit
import WebKit
import TelegramPresentationData
import AccountContext
private class WeakPaymentScriptMessageHandler: NSObject, WKScriptMessageHandler {
private let f: (WKScriptMessage) -> ()
@ -20,12 +21,14 @@ private class WeakPaymentScriptMessageHandler: NSObject, WKScriptMessageHandler
}
final class BotCheckoutWebInteractionControllerNode: ViewControllerTracingNode, WKNavigationDelegate {
private let context: AccountContext
private var presentationData: PresentationData
private let intent: BotCheckoutWebInteractionControllerIntent
private var webView: WKWebView?
init(presentationData: PresentationData, url: String, intent: BotCheckoutWebInteractionControllerIntent) {
init(context: AccountContext, presentationData: PresentationData, url: String, intent: BotCheckoutWebInteractionControllerIntent) {
self.context = context
self.presentationData = presentationData
self.intent = intent
@ -143,9 +146,25 @@ final class BotCheckoutWebInteractionControllerNode: ViewControllerTracingNode,
decisionHandler(.cancel)
completion(true)
} else {
if let url = navigationAction.request.url, let scheme = url.scheme {
let defaultSchemes: [String] = ["http", "https"]
if !defaultSchemes.contains(scheme) {
decisionHandler(.cancel)
self.context.sharedContext.applicationBindings.openUrl(url.absoluteString)
return
}
}
decisionHandler(.allow)
}
} else {
if let url = navigationAction.request.url, let scheme = url.scheme {
let defaultSchemes: [String] = ["http", "https"]
if !defaultSchemes.contains(scheme) {
decisionHandler(.cancel)
self.context.sharedContext.applicationBindings.openUrl(url.absoluteString)
return
}
}
decisionHandler(.allow)
}
}

View File

@ -39,6 +39,15 @@ swift_library(
"//submodules/Svg",
"//submodules/PromptUI",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/PhotoResources",
"//submodules/TelegramUI/Components/ChatControllerInteraction",
"//submodules/ChatPresentationInterfaceState",
"//submodules/UrlWhitelist",
"//submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode",
"//submodules/SearchUI",
"//submodules/SearchBarNode",
"//submodules/TelegramUI/Components/SaveProgressScreen",
"//submodules/TelegramUI/Components/ListActionItemComponent",
],
visibility = [
"//visibility:public",

View File

@ -3,25 +3,39 @@ import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import SwiftSignalKit
import TelegramPresentationData
import AccountContext
import BundleIconComponent
import MultilineTextComponent
import UrlEscaping
final class AddressBarContentComponent: Component {
public typealias EnvironmentType = BrowserNavigationBarEnvironment
let theme: PresentationTheme
let strings: PresentationStrings
let metrics: LayoutMetrics
let url: String
let isSecure: Bool
let isExpanded: Bool
let performAction: ActionSlot<BrowserScreen.Action>
init(
theme: PresentationTheme,
strings: PresentationStrings,
metrics: LayoutMetrics,
url: String,
isSecure: Bool,
isExpanded: Bool,
performAction: ActionSlot<BrowserScreen.Action>
) {
self.theme = theme
self.strings = strings
self.metrics = metrics
self.url = url
self.isSecure = isSecure
self.isExpanded = isExpanded
self.performAction = performAction
}
@ -32,9 +46,18 @@ final class AddressBarContentComponent: Component {
if lhs.strings !== rhs.strings {
return false
}
if lhs.metrics != rhs.metrics {
return false
}
if lhs.url != rhs.url {
return false
}
if lhs.isSecure != rhs.isSecure {
return false
}
if lhs.isExpanded != rhs.isExpanded {
return false
}
return true
}
@ -43,12 +66,25 @@ final class AddressBarContentComponent: Component {
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.integral
}
override var canBecomeFirstResponder: Bool {
var canBecomeFirstResponder = super.canBecomeFirstResponder
if !canBecomeFirstResponder && self.alpha.isZero {
canBecomeFirstResponder = true
}
return canBecomeFirstResponder
}
}
private struct Params: Equatable {
var theme: PresentationTheme
var strings: PresentationStrings
var size: CGSize
var isActive: Bool
var title: String
var isSecure: Bool
var collapseFraction: CGFloat
var isTablet: Bool
static func ==(lhs: Params, rhs: Params) -> Bool {
if lhs.theme !== rhs.theme {
@ -60,14 +96,28 @@ final class AddressBarContentComponent: Component {
if lhs.size != rhs.size {
return false
}
if lhs.isActive != rhs.isActive {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.isSecure != rhs.isSecure {
return false
}
if lhs.collapseFraction != rhs.collapseFraction {
return false
}
if lhs.isTablet != rhs.isTablet {
return false
}
return true
}
}
private let activated: (Bool) -> Void = { _ in }
private let deactivated: (Bool) -> Void = { _ in }
private let updateQuery: (String?) -> Void = { _ in }
private let backgroundLayer: SimpleLayer
private let iconView: UIImageView
@ -79,10 +129,11 @@ final class AddressBarContentComponent: Component {
private let cancelButton: HighlightTrackingButton
private var placeholderContent = ComponentView<Empty>()
private var titleContent = ComponentView<Empty>()
private var textFrame: CGRect?
private var textField: TextField?
private var tapRecognizer: UITapGestureRecognizer?
private var params: Params?
@ -99,12 +150,12 @@ final class AddressBarContentComponent: Component {
self.clearIconView = UIImageView()
self.clearIconButton = HighlightableButton()
self.clearIconView.isHidden = true
self.clearIconButton.isHidden = true
self.clearIconView.isHidden = false
self.clearIconButton.isHidden = false
self.cancelButtonTitle = ComponentView()
self.cancelButton = HighlightTrackingButton()
super.init(frame: CGRect())
self.layer.addSublayer(self.backgroundLayer)
@ -156,76 +207,50 @@ final class AddressBarContentComponent: Component {
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.activateTextInput()
if case .ended = recognizer.state, let component = self.component, !component.isExpanded {
component.performAction.invoke(.openAddressBar)
}
}
private func activateTextInput() {
if self.textField == nil, let textFrame = self.textFrame {
let backgroundFrame = self.backgroundLayer.frame
let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height))
let textField = TextField(frame: textFieldFrame)
textField.autocorrectionType = .no
textField.returnKeyType = .search
self.textField = textField
self.insertSubview(textField, belowSubview: self.clearIconView)
textField.delegate = self
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
}
guard !(self.textField?.isFirstResponder ?? false) else {
return
}
self.activated(true)
self.textField?.becomeFirstResponder()
if let textField = self.textField {
textField.becomeFirstResponder()
Queue.mainQueue().after(0.3, {
textField.selectAll(nil)
})
}
}
private func deactivateTextInput() {
self.textField?.endEditing(true)
}
@objc private func cancelPressed() {
self.updateQuery(nil)
self.deactivated(self.textField?.isFirstResponder ?? false)
self.clearIconView.isHidden = true
self.clearIconButton.isHidden = true
let textField = self.textField
self.textField = nil
self.deactivated(textField?.isFirstResponder ?? false)
self.component?.performAction.invoke(.updateSearchActive(false))
if let textField {
textField.resignFirstResponder()
textField.removeFromSuperview()
}
self.component?.performAction.invoke(.closeAddressBar)
}
@objc private func clearPressed() {
self.updateQuery(nil)
self.textField?.text = ""
self.clearIconView.isHidden = true
self.clearIconButton.isHidden = true
}
func deactivate() {
if let text = self.textField?.text, !text.isEmpty {
self.textField?.endEditing(true)
} else {
self.cancelPressed()
guard let textField = self.textField else {
return
}
textField.text = ""
self.textFieldChanged(textField)
}
public func textFieldDidBeginEditing(_ textField: UITextField) {
}
public func textFieldDidEndEditing(_ textField: UITextField) {
}
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let component = self.component {
let finalUrl = explicitUrl(textField.text ?? "")
component.performAction.invoke(.navigateTo(finalUrl, true))
}
textField.endEditing(true)
return false
}
@ -237,38 +262,59 @@ final class AddressBarContentComponent: Component {
self.clearIconButton.isHidden = text.isEmpty
self.placeholderContent.view?.isHidden = !text.isEmpty
self.updateQuery(text)
self.component?.performAction.invoke(.updateSearchQuery(text))
if let params = self.params {
self.update(theme: params.theme, strings: params.strings, size: params.size, transition: .immediate)
self.update(theme: params.theme, strings: params.strings, size: params.size, isActive: params.isActive, title: params.title, isSecure: params.isSecure, collapseFraction: params.collapseFraction, isTablet: params.isTablet, transition: .immediate)
}
}
func update(component: AddressBarContentComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
func update(component: AddressBarContentComponent, availableSize: CGSize, environment: Environment<BrowserNavigationBarEnvironment>, transition: ComponentTransition) -> CGSize {
let collapseFraction = environment[BrowserNavigationBarEnvironment.self].fraction
let wasExpanded = self.component?.isExpanded ?? false
self.component = component
self.update(theme: component.theme, strings: component.strings, size: availableSize, transition: transition)
if !wasExpanded && component.isExpanded {
self.activateTextInput()
}
if wasExpanded && !component.isExpanded {
self.deactivateTextInput()
}
let isActive = self.textField?.isFirstResponder ?? false
var title: String = ""
if let parsedUrl = URL(string: component.url) {
title = parsedUrl.host ?? component.url
if title.hasPrefix("www.") {
title.removeSubrange(title.startIndex ..< title.index(title.startIndex, offsetBy: 4))
}
title = title.idnaDecoded ?? title
}
self.update(theme: component.theme, strings: component.strings, size: availableSize, isActive: isActive, title: title.lowercased(), isSecure: component.isSecure, collapseFraction: collapseFraction, isTablet: component.metrics.isTablet, transition: transition)
return availableSize
}
public func update(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, transition: ComponentTransition) {
public func update(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, isActive: Bool, title: String, isSecure: Bool, collapseFraction: CGFloat, isTablet: Bool, transition: ComponentTransition) {
let params = Params(
theme: theme,
strings: strings,
size: size
size: size,
isActive: isActive,
title: title,
isSecure: isSecure,
collapseFraction: collapseFraction,
isTablet: isTablet
)
if self.params == params {
return
}
let isActiveWithText = true
let isActiveWithText = self.component?.isExpanded ?? false
if self.params?.theme !== theme {
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Lock"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.iconView.tintColor = theme.rootController.navigationSearchBar.inputIconColor
self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.clearIconView.tintColor = theme.rootController.navigationSearchBar.inputClearButtonColor
@ -280,10 +326,9 @@ final class AddressBarContentComponent: Component {
let inputHeight: CGFloat = 36.0
let topInset: CGFloat = (size.height - inputHeight) / 2.0
let sideTextInset: CGFloat = sideInset + 4.0 + 17.0
self.backgroundLayer.backgroundColor = theme.rootController.navigationSearchBar.inputFillColor.cgColor
self.backgroundLayer.cornerRadius = 10.5
transition.setAlpha(layer: self.backgroundLayer, alpha: max(0.0, min(1.0, 1.0 - collapseFraction * 1.5)))
let cancelTextSize = self.cancelButtonTitle.update(
transition: .immediate,
@ -299,42 +344,82 @@ final class AddressBarContentComponent: Component {
let cancelButtonSpacing: CGFloat = 8.0
var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight))
if isActiveWithText {
if isActiveWithText && !isTablet {
backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing
}
transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame)
transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height)))
self.cancelButton.isUserInteractionEnabled = isActiveWithText && !isTablet
let textX: CGFloat = backgroundFrame.minX + sideTextInset
let textX: CGFloat = backgroundFrame.minX + sideInset
let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height))
self.textFrame = textFrame
if let image = self.iconView.image {
let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + 5.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
transition.setFrame(view: self.iconView, frame: iconFrame)
}
let placeholderSize = self.placeholderContent.update(
transition: transition,
component: AnyComponent(
Text(text: strings.Common_Search, font: Font.regular(17.0), color: theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
Text(text: strings.WebBrowser_AddressPlaceholder, font: Font.regular(17.0), color: theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
),
environment: {},
containerSize: size
)
if let placeholderContentView = self.placeholderContent.view {
if placeholderContentView.superview == nil {
placeholderContentView.alpha = 0.0
placeholderContentView.isHidden = true
self.addSubview(placeholderContentView)
}
let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.midY - placeholderSize.height / 2.0), size: placeholderSize)
transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame)
transition.setAlpha(view: placeholderContentView, alpha: isActiveWithText ? 1.0 : 0.0)
}
let titleSize = self.titleContent.update(
transition: transition,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: title, font: Font.regular(17.0), textColor: theme.rootController.navigationSearchBar.inputTextColor)),
horizontalAlignment: .center,
truncationType: .end,
maximumNumberOfLines: 1
)
),
environment: {},
containerSize: CGSize(width: size.width - 36.0, height: size.height)
)
var titleContentFrame = CGRect(origin: CGPoint(x: isActiveWithText ? textFrame.minX : backgroundFrame.midX - titleSize.width / 2.0, y: backgroundFrame.midY - titleSize.height / 2.0), size: titleSize)
if isSecure && !isActiveWithText {
titleContentFrame.origin.x += 7.0
}
var titleSizeChanged = false
if let titleContentView = self.titleContent.view {
if titleContentView.superview == nil {
self.addSubview(titleContentView)
}
if titleContentView.frame.width != titleContentFrame.size.width {
titleSizeChanged = true
}
transition.setPosition(view: titleContentView, position: titleContentFrame.center)
titleContentView.bounds = CGRect(origin: .zero, size: titleContentFrame.size)
transition.setAlpha(view: titleContentView, alpha: isActiveWithText ? 0.0 : 1.0)
}
if let image = self.iconView.image {
let iconFrame = CGRect(origin: CGPoint(x: titleContentFrame.minX - image.size.width - 3.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
var iconTransition = transition
if titleSizeChanged {
iconTransition = .immediate
}
iconTransition.setFrame(view: self.iconView, frame: iconFrame)
transition.setAlpha(view: self.iconView, alpha: isActiveWithText || !isSecure ? 0.0 : 1.0)
}
if let image = self.clearIconView.image {
let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
transition.setFrame(view: self.clearIconView, frame: iconFrame)
transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0))
transition.setAlpha(view: self.clearIconView, alpha: isActiveWithText ? 1.0 : 0.0)
self.clearIconButton.isUserInteractionEnabled = isActiveWithText
}
if let cancelButtonTitleComponentView = self.cancelButtonTitle.view {
@ -343,12 +428,51 @@ final class AddressBarContentComponent: Component {
cancelButtonTitleComponentView.isUserInteractionEnabled = false
}
transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize))
transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText && !isTablet ? 1.0 : 0.0)
}
let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height))
let textField: TextField
if let current = self.textField {
textField = current
} else {
textField = TextField(frame: textFieldFrame)
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.keyboardType = .URL
textField.returnKeyType = .go
self.insertSubview(textField, belowSubview: self.clearIconView)
self.textField = textField
textField.delegate = self
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
}
var address = self.component?.url ?? ""
if let components = URLComponents(string: address) {
if #available(iOS 16.0, *), let encodedHost = components.encodedHost {
if let decodedHost = components.host, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
} else if let encodedHost = components.host {
if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
}
}
if let textField = self.textField {
textField.textColor = theme.rootController.navigationSearchBar.inputTextColor
transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideTextInset, y: backgroundFrame.minY - UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideTextInset - 32.0, height: backgroundFrame.height)))
if textField.text != address {
textField.text = address
self.clearIconView.isHidden = address.isEmpty
self.clearIconButton.isHidden = address.isEmpty
self.placeholderContent.view?.isHidden = !address.isEmpty
}
textField.textColor = theme.rootController.navigationSearchBar.inputTextColor
transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideInset, y: backgroundFrame.minY - UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideInset - 32.0, height: backgroundFrame.height)))
transition.setAlpha(view: textField, alpha: isActiveWithText ? 1.0 : 0.0)
textField.isUserInteractionEnabled = isActiveWithText
}
}
@ -356,7 +480,7 @@ final class AddressBarContentComponent: Component {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<BrowserNavigationBarEnvironment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
}
}

View File

@ -0,0 +1,706 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import Postbox
import TelegramCore
import AccountContext
import TelegramPresentationData
import ContextUI
import UndoUI
import ListActionItemComponent
final class BrowserAddressListComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let insets: UIEdgeInsets
let metrics: LayoutMetrics
let addressBarFrame: CGRect
let performAction: ActionSlot<BrowserScreen.Action>
let presentInGlobalOverlay: (ViewController) -> Void
init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
insets: UIEdgeInsets,
metrics: LayoutMetrics,
addressBarFrame: CGRect,
performAction: ActionSlot<BrowserScreen.Action>,
presentInGlobalOverlay: @escaping (ViewController) -> Void
) {
self.context = context
self.theme = theme
self.strings = strings
self.insets = insets
self.metrics = metrics
self.addressBarFrame = addressBarFrame
self.performAction = performAction
self.presentInGlobalOverlay = presentInGlobalOverlay
}
static func ==(lhs: BrowserAddressListComponent, rhs: BrowserAddressListComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.insets != rhs.insets {
return false
}
if lhs.metrics != rhs.metrics {
return false
}
if lhs.addressBarFrame != rhs.addressBarFrame {
return false
}
return true
}
private struct ItemLayout: Equatable {
struct Section: Equatable {
var id: Int
var insets: UIEdgeInsets
var itemHeight: CGFloat
var itemCount: Int
var hasMore: Bool
var totalHeight: CGFloat
init(
id: Int,
insets: UIEdgeInsets,
itemHeight: CGFloat,
itemCount: Int,
hasMore: Bool
) {
self.id = id
self.insets = insets
self.itemHeight = itemHeight
self.itemCount = itemCount
self.hasMore = hasMore
var totalHeight = insets.top + itemHeight * CGFloat(itemCount) + insets.bottom
if hasMore {
totalHeight -= itemHeight
totalHeight += 44.0
}
self.totalHeight = totalHeight
}
}
var containerSize: CGSize
var insets: UIEdgeInsets
var sections: [Section]
var contentHeight: CGFloat
init(
containerSize: CGSize,
insets: UIEdgeInsets,
sections: [Section]
) {
self.containerSize = containerSize
self.insets = insets
self.sections = sections
var contentHeight: CGFloat = 0.0
for section in sections {
contentHeight += section.totalHeight
}
self.contentHeight = contentHeight
}
}
private final class ScrollView: UIScrollView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return super.hitTest(point, with: event)
}
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
}
final class View: UIView, UIScrollViewDelegate {
struct State {
let recent: [TelegramMediaWebpage]
let isRecentExpanded: Bool
let bookmarks: [Message]
}
private let outerView = UIButton()
private let shadowView = UIImageView()
private let backgroundView = UIView()
private let scrollView = ScrollView()
private let itemContainerView = UIView()
private let addressTemplateItem = ComponentView<Empty>()
private var visibleSectionHeaders: [Int: ComponentView<Empty>] = [:]
private var visibleItems: [AnyHashable: ComponentView<Empty>] = [:]
private var ignoreScrolling: Bool = false
private var component: BrowserAddressListComponent?
private weak var state: EmptyComponentState?
private var itemLayout: ItemLayout?
private var stateDisposable: Disposable?
private var stateValue: State?
private let isRecentExpanded = ValuePromise<Bool>(false)
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundView.clipsToBounds = true
self.scrollView.alwaysBounceVertical = true
self.scrollView.delegate = self
self.scrollView.showsVerticalScrollIndicator = false
self.addSubview(self.outerView)
self.addSubview(self.shadowView)
self.addSubview(self.backgroundView)
self.backgroundView.addSubview(self.scrollView)
self.scrollView.addSubview(self.itemContainerView)
self.outerView.addTarget(self, action: #selector(self.outerPressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError()
}
deinit {
self.stateDisposable?.dispose()
}
@objc private func outerPressed() {
self.component?.performAction.invoke(.closeAddressBar)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.ignoreScrolling {
self.updateScrolling(transition: .immediate)
}
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.window?.endEditing(true)
cancelContextGestures(view: scrollView)
}
private func updateScrolling(transition: ComponentTransition) {
guard let component = self.component, let itemLayout = self.itemLayout, let state = self.stateValue else {
return
}
var topOffset = -self.scrollView.bounds.minY
topOffset = max(0.0, topOffset)
let visibleBounds = self.scrollView.bounds
var visibleFrame = self.scrollView.frame
visibleFrame.origin.x = 0.0
var validIds: [AnyHashable] = []
var validSectionHeaders: [AnyHashable] = []
var sectionOffset: CGFloat = 0.0
let sideInset: CGFloat = 0.0
let containerInset: CGFloat = 0.0
for sectionIndex in 0 ..< itemLayout.sections.count {
let section = itemLayout.sections[sectionIndex]
do {
var sectionHeaderFrame = CGRect(origin: CGPoint(x: sideInset, y: sectionOffset - self.scrollView.bounds.minY), size: CGSize(width: itemLayout.containerSize.width, height: section.insets.top))
let sectionHeaderMinY = topOffset + containerInset
let sectionHeaderMaxY = containerInset + sectionOffset - self.scrollView.bounds.minY + section.totalHeight - 28.0
sectionHeaderFrame.origin.y = max(sectionHeaderFrame.origin.y, sectionHeaderMinY)
sectionHeaderFrame.origin.y = min(sectionHeaderFrame.origin.y, sectionHeaderMaxY)
if visibleFrame.intersects(sectionHeaderFrame) {
validSectionHeaders.append(section.id)
let sectionHeader: ComponentView<Empty>
var sectionHeaderTransition = transition
if let current = self.visibleSectionHeaders[section.id] {
sectionHeader = current
} else {
if !transition.animation.isImmediate {
sectionHeaderTransition = .immediate
}
sectionHeader = ComponentView()
self.visibleSectionHeaders[section.id] = sectionHeader
}
let sectionTitle: String
if section.id == 0 {
sectionTitle = component.strings.WebBrowser_AddressBar_RecentlyVisited
} else if section.id == 1 {
sectionTitle = component.strings.WebBrowser_AddressBar_Bookmarks
} else {
sectionTitle = ""
}
let _ = sectionHeader.update(
transition: sectionHeaderTransition,
component: AnyComponent(SectionHeaderComponent(
theme: component.theme,
style: .plain,
title: sectionTitle,
insets: component.insets,
actionTitle: section.id == 0 ? component.strings.WebBrowser_AddressBar_RecentlyVisited_Clear : nil,
action: { [weak self] in
if let self, let component = self.component {
let _ = clearRecentlyVisitedLinks(engine: component.context.engine).start()
}
}
)),
environment: {},
containerSize: sectionHeaderFrame.size
)
if let sectionHeaderView = sectionHeader.view {
if sectionHeaderView.superview == nil {
self.backgroundView.addSubview(sectionHeaderView)
if !transition.animation.isImmediate {
sectionHeaderView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
}
let sectionXOffset = self.scrollView.frame.minX
sectionHeaderTransition.setFrame(view: sectionHeaderView, frame: sectionHeaderFrame.offsetBy(dx: sectionXOffset, dy: 0.0))
}
}
}
for i in 0 ..< section.itemCount {
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: sectionOffset + section.insets.top + CGFloat(i) * section.itemHeight), size: CGSize(width: itemLayout.containerSize.width, height: section.itemHeight))
if !visibleBounds.intersects(itemFrame) {
continue
}
var isMore = false
if section.hasMore && i == 3 {
isMore = true
itemFrame.size.height = 44.0
}
var id: String = ""
if section.id == 0 {
id = "recent_\(state.recent[i].content.url ?? "")"
if isMore {
id = "recent_more"
}
} else if section.id == 1 {
id = "bookmark_\(state.bookmarks[i].id.id)"
if isMore {
id = "bookmark_more"
}
}
let itemId = AnyHashable(id)
validIds.append(itemId)
var itemTransition = transition
let visibleItem: ComponentView<Empty>
if let current = self.visibleItems[itemId] {
visibleItem = current
} else {
visibleItem = ComponentView()
if !transition.animation.isImmediate {
itemTransition = .immediate
}
self.visibleItems[itemId] = visibleItem
}
if isMore {
let _ = visibleItem.update(
transition: itemTransition,
component: AnyComponent(
ListActionItemComponent(
theme: component.theme,
title: AnyComponent(Text(
text: component.strings.WebBrowser_AddressBar_ShowMore,
font: Font.regular(17.0),
color: component.theme.list.itemAccentColor
)),
leftIcon: .custom(
AnyComponentWithIdentity(
id: "icon",
component: AnyComponent(Image(
image: PresentationResourcesItemList.downArrowImage(component.theme),
size: CGSize(width: 30.0, height: 30.0)
))
),
false
),
accessory: nil,
action: { [weak self] _ in
self?.isRecentExpanded.set(true)
},
highlighting: .default,
updateIsHighlighted: { view, _ in
})
),
environment: {},
containerSize: itemFrame.size
)
} else {
var webPage: TelegramMediaWebpage?
var itemMessage: Message?
if section.id == 0 {
webPage = state.recent[i]
} else if section.id == 1 {
let message = state.bookmarks[i]
if let primaryUrl = getPrimaryUrl(message: message) {
if let media = message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage {
webPage = media
} else {
webPage = TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: primaryUrl, displayUrl: "", hash: 0, type: nil, websiteName: "", title: message.text, text: "", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil)))
}
itemMessage = message
} else {
continue
}
}
let performAction = component.performAction
let _ = visibleItem.update(
transition: itemTransition,
component: AnyComponent(
BrowserAddressListItemComponent(
context: component.context,
theme: component.theme,
webPage: webPage!,
message: itemMessage,
hasNext: true,
insets: component.insets,
action: {
if let url = webPage?.content.url {
performAction.invoke(.navigateTo(url, false))
}
},
contextAction: { [weak self] webPage, message, sourceView, gesture in
guard let self, let component = self.component, let url = webPage.content.url else {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
var itemList: [ContextMenuItem] = []
itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.WebBrowser_CopyLink, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
UIPasteboard.general.string = url
if let self, let component = self.component {
component.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }))
}
})))
if let message {
itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.WebBrowser_DeleteBookmark, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
if let self, let component = self.component {
let _ = component.context.engine.messages.deleteMessagesInteractively(messageIds: [message.id], type: .forEveryone).startStandalone()
}
})))
} else {
itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.WebBrowser_RemoveRecent, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
if let self, let component = self.component, let url = webPage.content.url {
let _ = removeRecentlyVisitedLink(engine: component.context.engine, url: url).startStandalone()
}
})))
}
let items = ContextController.Items(content: .list(itemList))
let controller = ContextController(
presentationData: presentationData,
source: .extracted(BrowserAddressListContextExtractedContentSource(contentView: sourceView)),
items: .single(items),
recognizer: nil,
gesture: gesture
)
component.presentInGlobalOverlay(controller)
})
),
environment: {},
containerSize: itemFrame.size
)
}
if let itemView = visibleItem.view {
if itemView.superview == nil {
self.itemContainerView.addSubview(itemView)
if !transition.animation.isImmediate {
transition.animateAlpha(view: itemView, from: 0.0, to: 1.0)
}
}
itemTransition.setFrame(view: itemView, frame: itemFrame)
}
}
sectionOffset += section.totalHeight
}
var removeIds: [AnyHashable] = []
for (id, item) in self.visibleItems {
if !validIds.contains(id) {
removeIds.append(id)
if let itemView = item.view {
if !transition.animation.isImmediate {
itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
itemView.removeFromSuperview()
})
} else {
itemView.removeFromSuperview()
}
}
}
}
for id in removeIds {
self.visibleItems.removeValue(forKey: id)
}
var removeSectionHeaderIds: [Int] = []
for (id, item) in self.visibleSectionHeaders {
if !validSectionHeaders.contains(id) {
removeSectionHeaderIds.append(id)
if let itemView = item.view {
if !transition.animation.isImmediate {
itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
itemView.removeFromSuperview()
})
} else {
itemView.removeFromSuperview()
}
}
}
}
for id in removeSectionHeaderIds {
self.visibleSectionHeaders.removeValue(forKey: id)
}
}
func update(component: BrowserAddressListComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
if self.component == nil {
self.stateDisposable = combineLatest(queue: Queue.mainQueue(),
recentlyVisitedLinks(engine: component.context.engine),
self.isRecentExpanded.get(),
component.context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: component.context.account.peerId, threadId: nil), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil, tag: .tag(.webPage))
).start(next: { [weak self] recent, isRecentExpanded, view in
guard let self else {
return
}
var bookmarks: [Message] = []
for entry in view.0.entries.reversed() {
bookmarks.append(entry.message)
}
let isFirstTime = self.stateValue == nil
self.stateValue = State(
recent: recent,
isRecentExpanded: isRecentExpanded,
bookmarks: bookmarks
)
self.state?.updated(transition: isFirstTime ? .immediate : .easeInOut(duration: 0.25))
})
}
self.component = component
self.state = state
self.outerView.isHidden = !component.metrics.isTablet
self.outerView.frame = CGRect(origin: .zero, size: availableSize)
let containerFrame: CGRect
if component.metrics.isTablet {
let containerSize = CGSize(width: component.addressBarFrame.width + 32.0, height: 540.0)
containerFrame = CGRect(origin: CGPoint(x: floor(component.addressBarFrame.center.x - containerSize.width / 2.0), y: 72.0), size: containerSize)
self.backgroundView.layer.cornerRadius = 10.0
} else {
containerFrame = CGRect(origin: .zero, size: availableSize)
self.backgroundView.layer.cornerRadius = 0.0
}
let resetScrolling = self.scrollView.bounds.width != containerFrame.width
if themeUpdated {
self.backgroundView.backgroundColor = component.theme.list.plainBackgroundColor
}
let itemsContainerWidth = availableSize.width
let addressItemSize = self.addressTemplateItem.update(
transition: .immediate,
component: AnyComponent(BrowserAddressListItemComponent(
context: component.context,
theme: component.theme,
webPage: TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "https://telegram.org", displayUrl: "https://telegram.org", hash: 0, type: nil, websiteName: "Telegram", title: "Telegram Telegram", text: "Telegram", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil))),
message: nil,
hasNext: true,
insets: .zero,
action: {},
contextAction: nil
)),
environment: {},
containerSize: CGSize(width: itemsContainerWidth, height: 1000.0)
)
var sections: [ItemLayout.Section] = []
if let state = self.stateValue {
if !state.recent.isEmpty {
var recentCount = state.recent.count
var hasMore = false
if recentCount > 4 && !state.isRecentExpanded {
recentCount = 4
hasMore = true
}
sections.append(ItemLayout.Section(
id: 0,
insets: UIEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0),
itemHeight: addressItemSize.height,
itemCount: recentCount,
hasMore: hasMore
))
}
if !state.bookmarks.isEmpty {
sections.append(ItemLayout.Section(
id: 1,
insets: UIEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0),
itemHeight: addressItemSize.height,
itemCount: state.bookmarks.count,
hasMore: false
))
}
}
let itemLayout = ItemLayout(containerSize: containerFrame.size, insets: .zero, sections: sections)
self.itemLayout = itemLayout
let containerWidth = containerFrame.size.width
let scrollContentHeight = max(itemLayout.contentHeight, containerFrame.size.height)
self.ignoreScrolling = true
transition.setFrame(view: self.scrollView, frame: CGRect(origin: .zero, size: containerFrame.size))
let contentSize = CGSize(width: containerWidth, height: scrollContentHeight)
if contentSize != self.scrollView.contentSize {
self.scrollView.contentSize = contentSize
}
if resetScrolling {
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: containerWidth, height: containerFrame.size.height))
}
self.ignoreScrolling = false
self.updateScrolling(transition: transition)
transition.setFrame(view: self.backgroundView, frame: containerFrame)
transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: .zero, size: CGSize(width: containerWidth, height: scrollContentHeight)))
if component.metrics.isTablet {
transition.setFrame(view: self.shadowView, frame: containerFrame.insetBy(dx: -60.0, dy: -60.0))
self.shadowView.isHidden = false
if self.shadowView.image == nil {
self.shadowView.image = generateShadowImage()
}
} else {
self.shadowView.isHidden = true
}
return availableSize
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if let component = self.component, component.metrics.isTablet {
let addressFrame = CGRect(origin: CGPoint(x: self.backgroundView.frame.minX, y: self.backgroundView.frame.minY - 48.0), size: CGSize(width: self.backgroundView.frame.width, height: 48.0))
if addressFrame.contains(point) {
return nil
}
}
return result
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private func generateShadowImage() -> UIImage? {
return generateImage(CGSize(width: 140.0, height: 140.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.saveGState()
context.setShadow(offset: CGSize(), blur: 60.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor)
let path = UIBezierPath(roundedRect: CGRect(x: 60.0, y: 60.0, width: 20.0, height: 20.0), cornerRadius: 10.0).cgPath
context.addPath(path)
context.fillPath()
context.restoreGState()
context.setBlendMode(.clear)
context.addPath(path)
context.fillPath()
})?.stretchableImage(withLeftCapWidth: 70, topCapHeight: 70)
}
private final class BrowserAddressListContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = false
let ignoreContentTouches: Bool = false
let blurBackground: Bool = true
private let contentView: ContextExtractedContentContainingView
init(contentView: ContextExtractedContentContainingView) {
self.contentView = contentView
}
func takeView() -> ContextControllerTakeViewInfo? {
return ContextControllerTakeViewInfo(containingItem: .view(self.contentView), contentAreaInScreenSpace: UIScreen.main.bounds)
}
func putBack() -> ContextControllerPutBackViewInfo? {
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
}
}
private func cancelContextGestures(view: UIView) {
if let gestureRecognizers = view.gestureRecognizers {
for gesture in gestureRecognizers {
if let gesture = gesture as? ContextGesture {
gesture.cancel()
}
}
}
for subview in view.subviews {
cancelContextGestures(view: subview)
}
}

View File

@ -0,0 +1,380 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import Postbox
import TelegramCore
import MultilineTextComponent
import TelegramPresentationData
import PhotoResources
import AccountContext
import ContextUI
import UrlEscaping
private let iconFont = Font.with(size: 30.0, design: .round, weight: .bold)
private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radius: 6.0, color: UIColor(rgb: 0xFF9500))
final class BrowserAddressListItemComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let webPage: TelegramMediaWebpage
var message: Message?
let hasNext: Bool
let insets: UIEdgeInsets
let action: () -> Void
let contextAction: ((TelegramMediaWebpage, Message?, ContextExtractedContentContainingView, ContextGesture) -> Void)?
init(
context: AccountContext,
theme: PresentationTheme,
webPage: TelegramMediaWebpage,
message: Message?,
hasNext: Bool,
insets: UIEdgeInsets,
action: @escaping () -> Void,
contextAction: ((TelegramMediaWebpage, Message?, ContextExtractedContentContainingView, ContextGesture) -> Void)?
) {
self.context = context
self.theme = theme
self.webPage = webPage
self.message = message
self.hasNext = hasNext
self.insets = insets
self.action = action
self.contextAction = contextAction
}
static func ==(lhs: BrowserAddressListItemComponent, rhs: BrowserAddressListItemComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.webPage != rhs.webPage {
return false
}
if lhs.hasNext != rhs.hasNext {
return false
}
if lhs.insets != rhs.insets {
return false
}
return true
}
final class View: ContextControllerSourceView {
private let extractedContainerView = ContextExtractedContentContainingView()
private let containerButton = HighlightTrackingButton()
private let separatorLayer = SimpleLayer()
private var highlightedBackgroundLayer = SimpleLayer()
private var emptyIcon: UIImageView?
private var emptyLabel: ComponentView<Empty>?
private var icon = TransformImageNode()
private let title = ComponentView<Empty>()
private let subtitle = ComponentView<Empty>()
private var isExtractedToContextMenu: Bool = false
private var component: BrowserAddressListItemComponent?
private weak var state: EmptyComponentState?
private var currentIconImageRepresentation: TelegramMediaImageRepresentation?
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.addSublayer(self.separatorLayer)
self.layer.addSublayer(self.highlightedBackgroundLayer)
self.addSubview(self.extractedContainerView)
self.targetViewForActivationProgress = self.extractedContainerView.contentView
self.highlightedBackgroundLayer.opacity = 0.0
self.extractedContainerView.contentView.addSubview(self.containerButton)
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.containerButton.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.superview?.bringSubviewToFront(self)
self.highlightedBackgroundLayer.removeAnimation(forKey: "opacity")
self.highlightedBackgroundLayer.opacity = 1.0
} else {
self.highlightedBackgroundLayer.opacity = 0.0
self.highlightedBackgroundLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
}
}
self.extractedContainerView.isExtractedToContextPreviewUpdated = { [weak self] value in
guard let self, let component = self.component else {
return
}
self.containerButton.clipsToBounds = value
self.containerButton.backgroundColor = value ? component.theme.list.plainBackgroundColor : nil
self.containerButton.layer.cornerRadius = value ? 10.0 : 0.0
if value {
self.highlightedBackgroundLayer.opacity = 0.0
}
}
self.extractedContainerView.willUpdateIsExtractedToContextPreview = { [weak self] value, transition in
guard let self else {
return
}
self.isExtractedToContextMenu = value
let mappedTransition: ComponentTransition
if value {
mappedTransition = ComponentTransition(transition)
} else {
mappedTransition = ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut))
}
self.state?.updated(transition: mappedTransition)
}
self.activated = { [weak self] gesture, _ in
guard let self, let component = self.component else {
gesture.cancel()
return
}
component.contextAction?(component.webPage, component.message, self.extractedContainerView, gesture)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func pressed() {
guard let component = self.component else {
return
}
component.action()
}
func update(component: BrowserAddressListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
let currentIconImageRepresentation = self.currentIconImageRepresentation
let iconSize = CGSize(width: 40.0, height: 40.0)
let height: CGFloat = 60.0
let leftInset: CGFloat = component.insets.left + 11.0 + iconSize.width + 11.0
let rightInset: CGFloat = 16.0
let titleSpacing: CGFloat = 2.0
let contextInset: CGFloat = self.isExtractedToContextMenu ? 12.0 : 0.0
let title: String
let subtitle: String
var parsedUrl: URL?
var iconImageReferenceAndRepresentation: (AnyMediaReference, TelegramMediaImageRepresentation)?
var updateIconImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
if case let .Loaded(content) = component.webPage.content {
title = content.title ?? content.url
var address = content.url
if let components = URLComponents(string: address) {
if #available(iOS 16.0, *), let encodedHost = components.encodedHost {
if let decodedHost = components.host, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
} else if let encodedHost = components.host {
if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
}
}
address = address.replacingOccurrences(of: "https://www.", with: "")
address = address.replacingOccurrences(of: "https://", with: "")
address = address.replacingOccurrences(of: "tonsite://", with: "")
address = address.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
subtitle = address
parsedUrl = URL(string: content.url)
if let image = content.image {
if let representation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 80, height: 80)) {
if let message = component.message {
iconImageReferenceAndRepresentation = (.message(message: MessageReference(message), media: image), representation)
} else {
iconImageReferenceAndRepresentation = (.standalone(media: image), representation)
}
}
} else if let file = content.file {
if let representation = smallestImageRepresentation(file.previewRepresentations) {
if let message = component.message {
iconImageReferenceAndRepresentation = (.message(message: MessageReference(message), media: file), representation)
} else {
iconImageReferenceAndRepresentation = (.standalone(media: file), representation)
}
}
}
if currentIconImageRepresentation != iconImageReferenceAndRepresentation?.1 {
if let iconImageReferenceAndRepresentation = iconImageReferenceAndRepresentation {
if let imageReference = iconImageReferenceAndRepresentation.0.concrete(TelegramMediaImage.self) {
updateIconImageSignal = chatWebpageSnippetPhoto(account: component.context.account, userLocation: (component.message?.id.peerId).flatMap(MediaResourceUserLocation.peer) ?? .other, photoReference: imageReference)
} else if let fileReference = iconImageReferenceAndRepresentation.0.concrete(TelegramMediaFile.self) {
updateIconImageSignal = chatWebpageSnippetFile(account: component.context.account, userLocation: (component.message?.id.peerId).flatMap(MediaResourceUserLocation.peer) ?? .other, mediaReference: fileReference.abstract, representation: iconImageReferenceAndRepresentation.1)
}
} else {
updateIconImageSignal = .complete()
}
}
} else {
title = ""
subtitle = ""
}
self.component = component
self.state = state
self.currentIconImageRepresentation = iconImageReferenceAndRepresentation?.1
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
let subtitleSize = self.subtitle.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: component.theme.list.itemAccentColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
let centralContentHeight = titleSize.height + subtitleSize.height + titleSpacing
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - centralContentHeight) / 2.0)), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.containerButton.addSubview(titleView)
}
titleView.frame = titleFrame
}
let subtitleFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: subtitleSize)
if let subtitleView = self.subtitle.view {
if subtitleView.superview == nil {
subtitleView.isUserInteractionEnabled = false
self.containerButton.addSubview(subtitleView)
}
subtitleView.frame = subtitleFrame
}
let iconFrame = CGRect(origin: CGPoint(x: 11.0 + component.insets.left, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)
let iconImageLayout = self.icon.asyncLayout()
var iconImageApply: (() -> Void)?
if let iconImageReferenceAndRepresentation = iconImageReferenceAndRepresentation {
let imageCorners = ImageCorners(radius: 6.0)
let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconImageReferenceAndRepresentation.1.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor)
iconImageApply = iconImageLayout(arguments)
}
if let iconImageApply = iconImageApply {
if let updateImageSignal = updateIconImageSignal {
self.icon.setSignal(updateImageSignal)
}
if self.icon.supernode == nil {
self.containerButton.addSubview(self.icon.view)
self.icon.frame = iconFrame
} else {
transition.setFrame(view: self.icon.view, frame: iconFrame)
}
iconImageApply()
if let emptyIcon = self.emptyIcon {
self.emptyIcon = nil
emptyIcon.removeFromSuperview()
}
if let emptyLabel = self.emptyLabel {
self.emptyLabel = nil
emptyLabel.view?.removeFromSuperview()
}
} else {
if self.icon.supernode != nil {
self.icon.view.removeFromSuperview()
}
let icon: UIImageView
let label: ComponentView<Empty>
if let currentEmptyIcon = self.emptyIcon, let currentEmptyLabel = self.emptyLabel {
icon = currentEmptyIcon
label = currentEmptyLabel
} else {
icon = UIImageView()
icon.image = iconTextBackgroundImage
self.containerButton.addSubview(icon)
label = ComponentView()
}
icon.frame = iconFrame
var iconText = ""
if let parsedUrl, let host = parsedUrl.host {
if parsedUrl.path.hasPrefix("/addstickers/") {
iconText = "S"
} else if parsedUrl.path.hasPrefix("/addemoji/") {
iconText = "E"
} else {
iconText = host[..<host.index(after: host.startIndex)].uppercased()
}
}
let labelSize = label.update(
transition: .immediate,
component: AnyComponent(Text(text: iconText, font: iconFont, color: .white)),
environment: {},
containerSize: iconSize
)
let labelFrame = CGRect(origin: CGPoint(x: iconFrame.minX + floorToScreenPixels((iconFrame.width - labelSize.width) / 2.0), y: iconFrame.minY + floorToScreenPixels((iconFrame.height - labelSize.height) / 2.0)), size: labelSize)
if let labelView = label.view {
if labelView.superview == nil {
self.containerButton.addSubview(labelView)
}
labelView.frame = labelFrame
}
}
if themeUpdated {
self.highlightedBackgroundLayer.backgroundColor = component.theme.list.itemHighlightedBackgroundColor.cgColor
self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
}
transition.setFrame(layer: self.highlightedBackgroundLayer, frame: CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: height + UIScreenPixel)))
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel)))
self.separatorLayer.isHidden = !component.hasNext
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: height))
transition.setFrame(view: self.containerButton, frame: containerFrame.insetBy(dx: contextInset, dy: 0.0))
transition.setFrame(view: self.extractedContainerView, frame: containerFrame)
transition.setFrame(view: self.extractedContainerView.contentView, frame: containerFrame)
self.extractedContainerView.contentRect = containerFrame
self.isGestureEnabled = component.contextAction != nil
return CGSize(width: availableSize.width, height: 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)
}
}

View File

@ -0,0 +1,582 @@
import Foundation
import UIKit
import AccountContext
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import PresentationDataUtils
import ChatControllerInteraction
import TelegramUIPreferences
import ChatPresentationInterfaceState
import TextFormat
import UrlWhitelist
import SearchUI
import SearchBarNode
import ChatHistorySearchContainerNode
import ContextUI
import UndoUI
public final class BrowserBookmarksScreen: ViewController {
final class Node: ViewControllerTracingNode, ASScrollViewDelegate {
private let context: AccountContext
private var presentationData: PresentationData
private weak var controller: BrowserBookmarksScreen?
private let controllerInteraction: ChatControllerInteraction
private var searchDisplayController: SearchDisplayController?
fileprivate let historyNode: ChatHistoryListNode
private let bottomPanelNode: BottomPanelNode
private var addedBookmark = false
private var validLayout: (ContainerViewLayout, CGFloat, CGFloat)?
init(context: AccountContext, controller: BrowserBookmarksScreen, presentationData: PresentationData) {
self.context = context
self.controller = controller
self.presentationData = presentationData
var openMessageImpl: ((Message) -> Bool)?
var openContextMenuImpl: ((Message, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void)?
self.controllerInteraction = ChatControllerInteraction(openMessage: { message, _ in
if let openMessageImpl = openMessageImpl {
return openMessageImpl(message)
} else {
return false
}
}, openPeer: { _, _, _, _ in
}, openPeerMention: { _, _ in
}, openMessageContextMenu: { message, _, sourceView, rect, gesture, _ in
openContextMenuImpl?(message, sourceView, rect, gesture)
}, openMessageReactionContextMenu: { _, _, _, _ in
}, updateMessageReaction: { _, _, _, _ in
}, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _, _ in
}, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: nil, clickThroughMessage: { _, _ in
}, toggleMessagesSelection: { _, _ in
}, sendCurrentMessage: { _, _ in
}, sendMessage: { _ in
}, sendSticker: { _, _, _, _, _, _, _, _, _ in
return false
}, sendEmoji: { _, _, _ in
}, sendGif: { _, _, _, _, _ in
return false
}, sendBotContextResultAsGif: { _, _, _, _, _, _ in
return false
}, requestMessageActionCallback: { _, _, _, _ in
}, requestMessageActionUrlAuth: { _, _ in
}, activateSwitchInline: { _, _, _ in
}, openUrl: { [weak controller] url in
if let controller {
controller.openUrl(url.url)
controller.dismiss()
}
}, shareCurrentLocation: {
}, shareAccountContact: {
}, sendBotCommand: { _, _ in
}, openInstantPage: { message, _ in
if let openMessageImpl = openMessageImpl {
let _ = openMessageImpl(message)
}
}, openWallpaper: { _ in
}, openTheme: {_ in
}, openHashtag: { _, _ in
}, updateInputState: { _ in
}, updateInputMode: { _ in
}, openMessageShareMenu: { _ in
}, presentController: { _, _ in
}, presentControllerInCurrent: { _, _ in
}, navigationController: {
return nil
}, chatControllerNode: {
return nil
}, presentGlobalOverlayController: { _, _ in
}, callPeer: { _, _ in
}, longTap: { _, _ in
}, openCheckoutOrReceipt: { _, _ in
}, openSearch: {
}, setupReply: { _ in
}, canSetupReply: { _ in
return .none
}, canSendMessages: {
return false
}, navigateToFirstDateMessage: { _, _ in
}, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in
}, rateCall: { _, _, _ in
}, requestSelectMessagePollOptions: { _, _ in
}, requestOpenMessagePollResults: { _, _ in
}, openAppStorePage: {
}, displayMessageTooltip: { _, _, _, _, _ in
}, seekToTimecode: { _, _, _ in
}, scheduleCurrentMessage: { _ in
}, sendScheduledMessagesNow: { _ in
}, editScheduledMessagesTime: { _ in
}, performTextSelectionAction: { _, _, _, _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in
}, openPollCreation: { _ in
}, displayPollSolution: { _, _ in
}, displayPsa: { _, _ in
}, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _, _ in
}, displayPremiumStickerTooltip: { _, _ in
}, displayEmojiPackTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in
}, openMessageStats: { _ in
}, editMessageMedia: { _, _ in
}, copyText: { _ in
}, displayUndo: { _ in
}, isAnimatingMessage: { _ in
return false
}, getMessageTransitionNode: {
return nil
}, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, openWebView: { _, _, _, _ in
}, activateAdAction: { _, _ in
}, openRequestedPeerSelection: { _, _, _, _ in
}, saveMediaToFiles: { _ in
}, openNoAdsDemo: {
}, openAdsInfo: {
}, displayGiveawayParticipationStatus: { _ in
}, openPremiumStatusInfo: { _, _, _, _ in
}, openRecommendedChannelContextMenu: { _, _, _ in
}, openGroupBoostInfo: { _, _ in
}, openStickerEditor: {
}, openAgeRestrictedMessageMedia: { _, _ in
}, playMessageEffect: { _ in
}, editMessageFactCheck: { _ in
}, requestMessageUpdate: { _, _ in
}, cancelInteractiveKeyboardGestures: {
}, dismissTextInput: {
}, scrollToMessageId: { _ in
}, navigateToStory: { _, _ in
}, attemptedNavigationToPrivateQuote: { _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
let tagMask: MessageTags = .webPage
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
self.historyNode = context.sharedContext.makeChatHistoryListNode(
context: context,
updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData),
chatLocation: .peer(id: context.account.peerId),
chatLocationContextHolder: chatLocationContextHolder,
tag: .tag(tagMask),
source: .default,
subject: nil,
controllerInteraction: self.controllerInteraction,
selectedMessages: .single(nil),
mode: .list(
search: false,
reversed: false,
reverseGroups: false,
displayHeaders: .none,
hintLinks: true,
isGlobalSearch: false
)
)
var addBookmarkImpl: (() -> Void)?
self.bottomPanelNode = BottomPanelNode(theme: presentationData.theme, strings: presentationData.strings, action: {
addBookmarkImpl?()
})
super.init()
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.historyNode)
self.addSubnode(self.bottomPanelNode)
openMessageImpl = { [weak controller] message in
guard let controller else {
return false
}
if let primaryUrl = getPrimaryUrl(message: message) {
controller.openUrl(primaryUrl)
}
controller.dismiss()
return true
}
addBookmarkImpl = { [weak self] in
guard let self else {
return
}
self.controller?.addBookmark()
self.addedBookmark = true
if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
}
}
openContextMenuImpl = { [weak self] message, sourceNode, rect, gesture in
guard let self, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else {
return
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
var itemList: [ContextMenuItem] = []
if let webPage = message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage, let url = webPage.content.url {
itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.WebBrowser_CopyLink, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
UIPasteboard.general.string = url
if let self {
self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
}
})))
}
itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.WebBrowser_DeleteBookmark, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
if let self {
let _ = self.context.engine.messages.deleteMessagesInteractively(messageIds: [message.id], type: .forEveryone).startStandalone()
}
})))
let items = ContextController.Items(content: .list(itemList))
let controller = ContextController(
presentationData: presentationData,
source: .extracted(BrowserBookmarksContextExtractedContentSource(contentNode: sourceNode)),
items: .single(items),
recognizer: nil,
gesture: gesture as? ContextGesture
)
self.controller?.presentInGlobalOverlay(controller)
}
}
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
guard let (layout, navigationBarHeight, _) = self.validLayout, let navigationBar = self.controller?.navigationBar else {
return
}
let tagMask: MessageTags = .webPage
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, hasBackground: true, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.context.account.peerId, threadId: nil, tagMask: tagMask, interfaceInteraction: self.controllerInteraction), cancel: { [weak self] in
self?.controller?.deactivateSearch()
})
self.searchDisplayController?.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
if let strongSelf = self, let placeholderNode {
if isSearchBar {
placeholderNode.supernode?.insertSubnode(subnode, aboveSubnode: placeholderNode)
} else {
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
}
}
}, placeholder: placeholderNode)
}
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode) {
guard let searchDisplayController = self.searchDisplayController else {
return
}
self.searchDisplayController = nil
searchDisplayController.deactivate(placeholder: placeholderNode)
}
func scrollToTop() {
self.historyNode.scrollToEndOfHistory()
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationBarHeight: CGFloat, actualNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight, actualNavigationBarHeight)
let historyFrame = CGRect(origin: .zero, size: layout.size)
transition.updateFrame(node: self.historyNode, frame: historyFrame)
var insets = layout.insets(options: [.input])
insets.top += navigationBarHeight
var headerInsets = layout.insets(options: [.input])
headerInsets.top += actualNavigationBarHeight
let panelHeight = self.bottomPanelNode.updateLayout(width: layout.size.width, sideInset: layout.safeInsets.left, bottomInset: insets.bottom, transition: transition)
var panelOrigin: CGFloat = layout.size.height
if !self.addedBookmark {
panelOrigin -= panelHeight
insets.bottom = panelHeight
}
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOrigin), size: CGSize(width: layout.size.width, height: panelHeight))
transition.updateFrame(node: self.bottomPanelNode, frame: panelFrame)
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: historyFrame.size, insets: insets, headerInsets: headerInsets, duration: duration, curve: curve)
self.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets)
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
}
}
}
private let context: AccountContext
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private let url: String
private let openUrl: (String) -> Void
private let addBookmark: () -> Void
private var controllerNode: Node {
return self.displayNode as! Node
}
private var searchContentNode: NavigationBarSearchContentNode?
private var validLayout: ContainerViewLayout?
private var node: Node {
return self.displayNode as! Node
}
public init(context: AccountContext, url: String, openUrl: @escaping (String) -> Void, addBookmark: @escaping () -> Void) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.url = url
self.openUrl = openUrl
self.addBookmark = addBookmark
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
self.navigationPresentation = .modal
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
self.title = self.presentationData.strings.WebBrowser_Bookmarks_Title
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in
self?.activateSearch()
})
self.navigationBar?.setContentNode(self.searchContentNode, animated: false)
self.scrollToTop = { [weak self] in
if let self {
if let searchContentNode = self.searchContentNode {
searchContentNode.updateExpansionProgress(1.0, animated: true)
}
self.node.scrollToTop()
}
}
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
strongSelf.updateThemeAndStrings()
}
}
}).strict()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.presentationDataDisposable?.dispose()
}
override public func loadDisplayNode() {
self.displayNode = Node(context: self.context, controller: self, presentationData: self.presentationData)
self.node.historyNode.contentPositionChanged = { [weak self] offset in
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
searchContentNode.updateListVisibleContentOffset(offset)
}
}
//
// self.node.historyNode.didEndScrolling = { [weak self] _ in
// if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
// let _ = fixNavigationSearchableListNodeScrolling(strongSelf.node.historyNode, searchNode: searchContentNode)
// }
// }
self.displayNodeDidLoad()
}
private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search)
}
fileprivate func activateSearch() {
if self.displayNavigationBar {
if let scrollToTop = self.scrollToTop {
scrollToTop()
}
if let searchContentNode = self.searchContentNode {
self.node.activateSearch(placeholderNode: searchContentNode.placeholderNode)
}
self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
}
}
fileprivate func deactivateSearch() {
if !self.displayNavigationBar {
self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring))
if let searchContentNode = self.searchContentNode {
self.node.deactivateSearch(placeholderNode: searchContentNode.placeholderNode)
}
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.validLayout = layout
self.controllerNode.containerLayoutUpdated(layout: layout, navigationBarHeight: self.cleanNavigationHeight, actualNavigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
}
@objc private func cancelPressed() {
self.dismiss()
}
}
private class BottomPanelNode: ASDisplayNode {
private let theme: PresentationTheme
private let strings: PresentationStrings
private let action: () -> Void
private let separatorNode: ASDisplayNode
private let button: HighlightTrackingButtonNode
private let iconNode: ASImageNode
private let textNode: ImmediateTextNode
private var validLayout: (CGFloat, CGFloat, CGFloat)?
init(theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void) {
self.theme = theme
self.strings = strings
self.action = action
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor
self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddIcon"), color: theme.rootController.navigationBar.accentTextColor)
self.iconNode.isUserInteractionEnabled = false
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.attributedText = NSAttributedString(string: strings.WebBrowser_Bookmarks_BookmarkCurrent, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor)
self.textNode.isUserInteractionEnabled = false
self.button = HighlightTrackingButtonNode()
super.init()
self.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor
self.addSubnode(self.button)
self.addSubnode(self.separatorNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.textNode)
self.addSubnode(self.button)
self.button.highligthedChanged = { [weak self] highlighted in
if let self {
if highlighted {
self.iconNode.layer.removeAnimation(forKey: "opacity")
self.iconNode.alpha = 0.4
self.textNode.layer.removeAnimation(forKey: "opacity")
self.textNode.alpha = 0.4
} else {
self.iconNode.alpha = 1.0
self.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
self.textNode.alpha = 1.0
self.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
@objc private func buttonPressed() {
self.action()
}
func updateLayout(width: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
self.validLayout = (width, sideInset, bottomInset)
let topInset: CGFloat = 8.0
var bottomInset = bottomInset
bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0)
let buttonHeight: CGFloat = 40.0
let textSize = self.textNode.updateLayout(CGSize(width: width, height: 44.0))
let spacing: CGFloat = 8.0
var contentWidth = textSize.width
var contentOriginX = floorToScreenPixels((width - contentWidth) / 2.0)
if let icon = self.iconNode.image {
contentWidth += icon.size.width + spacing
contentOriginX = floorToScreenPixels((width - contentWidth) / 2.0)
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: contentOriginX, y: 12.0 + UIScreenPixel), size: icon.size))
contentOriginX += icon.size.width + spacing
}
let textFrame = CGRect(origin: CGPoint(x: contentOriginX, y: 17.0), size: textSize)
transition.updateFrame(node: self.textNode, frame: textFrame)
transition.updateFrame(node: self.button, frame: textFrame.insetBy(dx: -10.0, dy: -10.0))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
return topInset + buttonHeight + bottomInset
}
}
final class BrowserBookmarksContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = false
let ignoreContentTouches: Bool = false
let blurBackground: Bool = true
private let contentNode: ContextExtractedContentContainingNode
init(contentNode: ContextExtractedContentContainingNode) {
self.contentNode = contentNode
}
func takeView() -> ContextControllerTakeViewInfo? {
return ContextControllerTakeViewInfo(containingItem: .node(self.contentNode), contentAreaInScreenSpace: UIScreen.main.bounds)
}
func putBack() -> ContextControllerPutBackViewInfo? {
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View File

@ -10,6 +10,7 @@ final class BrowserContentState: Equatable {
enum ContentType: Equatable {
case webPage
case instantPage
case document
}
struct HistoryItem: Equatable {
@ -39,6 +40,7 @@ final class BrowserContentState: Equatable {
let readingProgress: Double
let contentType: ContentType
let favicon: UIImage?
let isSecure: Bool
let canGoBack: Bool
let canGoForward: Bool
@ -53,6 +55,7 @@ final class BrowserContentState: Equatable {
readingProgress: Double,
contentType: ContentType,
favicon: UIImage? = nil,
isSecure: Bool = false,
canGoBack: Bool = false,
canGoForward: Bool = false,
backList: [HistoryItem] = [],
@ -64,6 +67,7 @@ final class BrowserContentState: Equatable {
self.readingProgress = readingProgress
self.contentType = contentType
self.favicon = favicon
self.isSecure = isSecure
self.canGoBack = canGoBack
self.canGoForward = canGoForward
self.backList = backList
@ -89,6 +93,9 @@ final class BrowserContentState: Equatable {
if (lhs.favicon == nil) != (rhs.favicon == nil) {
return false
}
if lhs.isSecure != rhs.isSecure {
return false
}
if lhs.canGoBack != rhs.canGoBack {
return false
}
@ -105,39 +112,43 @@ final class BrowserContentState: Equatable {
}
func withUpdatedTitle(_ title: String) -> BrowserContentState {
return BrowserContentState(title: title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
return BrowserContentState(title: title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
}
func withUpdatedUrl(_ url: String) -> BrowserContentState {
return BrowserContentState(title: self.title, url: url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
return BrowserContentState(title: self.title, url: url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
}
func withUpdatedIsSecure(_ isSecure: Bool) -> BrowserContentState {
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
}
func withUpdatedEstimatedProgress(_ estimatedProgress: Double) -> BrowserContentState {
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
}
func withUpdatedReadingProgress(_ readingProgress: Double) -> BrowserContentState {
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: readingProgress, contentType: self.contentType, favicon: self.favicon, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
}
func withUpdatedFavicon(_ favicon: UIImage?) -> BrowserContentState {
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: favicon, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
}
func withUpdatedCanGoBack(_ canGoBack: Bool) -> BrowserContentState {
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, canGoBack: canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
}
func withUpdatedCanGoForward(_ canGoForward: Bool) -> BrowserContentState {
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, canGoBack: self.canGoBack, canGoForward: canGoForward, backList: self.backList, forwardList: self.forwardList)
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: canGoForward, backList: self.backList, forwardList: self.forwardList)
}
func withUpdatedBackList(_ backList: [HistoryItem]) -> BrowserContentState {
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: backList, forwardList: self.forwardList)
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: backList, forwardList: self.forwardList)
}
func withUpdatedForwardList(_ forwardList: [HistoryItem]) -> BrowserContentState {
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: forwardList)
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: forwardList)
}
}
@ -151,12 +162,14 @@ protocol BrowserContent: UIView {
var present: (ViewController, Any?) -> Void { get set }
var presentInGlobalOverlay: (ViewController) -> Void { get set }
var getNavigationController: () -> NavigationController? { get set }
var openAppUrl: (String) -> Void { get set }
var minimize: () -> Void { get set }
var close: () -> Void { get set }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void { get set }
func resetScrolling()
func reload()
func stop()
@ -173,7 +186,11 @@ protocol BrowserContent: UIView {
func scrollToTop()
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ComponentTransition)
func addToRecentlyVisited()
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition)
func makeContentSnapshotView() -> UIView?
}
struct ContentScrollingUpdate {

View File

@ -0,0 +1,479 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import TelegramCore
import Postbox
import SwiftSignalKit
import TelegramPresentationData
import TelegramUIPreferences
import PresentationDataUtils
import AccountContext
import WebKit
import AppBundle
import PromptUI
import SafariServices
import ShareController
import UndoUI
import UrlEscaping
final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate {
private let context: AccountContext
private var presentationData: PresentationData
private let webView: WKWebView
let uuid: UUID
private var _state: BrowserContentState
private let statePromise: Promise<BrowserContentState>
var currentState: BrowserContentState {
return self._state
}
var state: Signal<BrowserContentState, NoError> {
return self.statePromise.get()
}
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
var openAppUrl: (String) -> Void = { _ in }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
var minimize: () -> Void = { }
var close: () -> Void = { }
var present: (ViewController, Any?) -> Void = { _, _ in }
var presentInGlobalOverlay: (ViewController) -> Void = { _ in }
var getNavigationController: () -> NavigationController? = { return nil }
private var tempFile: TempBoxFile?
init(context: AccountContext, presentationData: PresentationData, file: TelegramMediaFile) {
self.context = context
self.uuid = UUID()
self.presentationData = presentationData
let configuration = WKWebViewConfiguration()
self.webView = WKWebView(frame: CGRect(), configuration: configuration)
self.webView.allowsLinkPreview = true
if #available(iOS 11.0, *) {
self.webView.scrollView.contentInsetAdjustmentBehavior = .never
}
var title: String = "file"
if let path = self.context.account.postbox.mediaBox.completedResourcePath(file.resource) {
var updatedPath = path
if let fileName = file.fileName {
let tempFile = TempBox.shared.file(path: path, fileName: fileName)
updatedPath = tempFile.path
self.tempFile = tempFile
title = fileName
}
let request = URLRequest(url: URL(fileURLWithPath: updatedPath))
self.webView.load(request)
}
self._state = BrowserContentState(title: title, url: "", estimatedProgress: 0.0, readingProgress: 0.0, contentType: .document)
self.statePromise = Promise<BrowserContentState>(self._state)
super.init(frame: .zero)
self.webView.allowsBackForwardNavigationGestures = true
self.webView.scrollView.delegate = self
self.webView.scrollView.clipsToBounds = false
self.webView.navigationDelegate = self
self.webView.uiDelegate = self
if #available(iOS 15.0, *) {
self.backgroundColor = presentationData.theme.list.plainBackgroundColor
self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor
}
self.addSubview(self.webView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
if #available(iOS 15.0, *) {
self.backgroundColor = presentationData.theme.list.plainBackgroundColor
self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor
}
if let (size, insets, fullInsets) = self.validLayout {
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: .immediate)
}
}
var currentFontState = BrowserPresentationState.FontState(size: 100, isSerif: false)
func updateFontState(_ state: BrowserPresentationState.FontState) {
self.updateFontState(state, force: false)
}
func updateFontState(_ state: BrowserPresentationState.FontState, force: Bool) {
self.currentFontState = state
let fontFamily = state.isSerif ? "'Georgia, serif'" : "null"
let textSizeAdjust = state.size != 100 ? "'\(state.size)%'" : "null"
let js = "\(setupFontFunctions) setTelegramFontOverrides(\(fontFamily), \(textSizeAdjust))";
self.webView.evaluateJavaScript(js) { _, _ in }
}
private var didSetupSearch = false
private func setupSearch(completion: @escaping () -> Void) {
guard !self.didSetupSearch else {
completion()
return
}
let bundle = getAppBundle()
guard let scriptPath = bundle.path(forResource: "UIWebViewSearch", ofType: "js") else {
return
}
guard let scriptData = try? Data(contentsOf: URL(fileURLWithPath: scriptPath)) else {
return
}
guard let script = String(data: scriptData, encoding: .utf8) else {
return
}
self.didSetupSearch = true
self.webView.evaluateJavaScript(script, completionHandler: { _, error in
if error != nil {
print()
}
completion()
})
}
private var previousQuery: String?
func setSearch(_ query: String?, completion: ((Int) -> Void)?) {
guard self.previousQuery != query else {
return
}
self.previousQuery = query
self.setupSearch { [weak self] in
if let query = query {
let js = "uiWebview_HighlightAllOccurencesOfString('\(query)')"
self?.webView.evaluateJavaScript(js, completionHandler: { [weak self] _, _ in
let js = "uiWebview_SearchResultCount"
self?.webView.evaluateJavaScript(js, completionHandler: { [weak self] result, _ in
if let result = result as? NSNumber {
self?.searchResultsCount = result.intValue
completion?(result.intValue)
} else {
completion?(0)
}
})
})
} else {
let js = "uiWebview_RemoveAllHighlights()"
self?.webView.evaluateJavaScript(js, completionHandler: nil)
self?.currentSearchResult = 0
self?.searchResultsCount = 0
}
}
}
private var currentSearchResult: Int = 0
private var searchResultsCount: Int = 0
func scrollToPreviousSearchResult(completion: ((Int, Int) -> Void)?) {
let searchResultsCount = self.searchResultsCount
var index = self.currentSearchResult - 1
if index < 0 {
index = searchResultsCount - 1
}
self.currentSearchResult = index
let js = "uiWebview_ScrollTo('\(searchResultsCount - index - 1)')"
self.webView.evaluateJavaScript(js, completionHandler: { _, _ in
completion?(index, searchResultsCount)
})
}
func scrollToNextSearchResult(completion: ((Int, Int) -> Void)?) {
let searchResultsCount = self.searchResultsCount
var index = self.currentSearchResult + 1
if index >= searchResultsCount {
index = 0
}
self.currentSearchResult = index
let js = "uiWebview_ScrollTo('\(searchResultsCount - index - 1)')"
self.webView.evaluateJavaScript(js, completionHandler: { _, _ in
completion?(index, searchResultsCount)
})
}
func stop() {
self.webView.stopLoading()
}
func reload() {
self.webView.reload()
}
func navigateBack() {
self.webView.goBack()
}
func navigateForward() {
self.webView.goForward()
}
func navigateTo(historyItem: BrowserContentState.HistoryItem) {
if let webItem = historyItem.webItem {
self.webView.go(to: webItem)
}
}
func navigateTo(address: String) {
let finalUrl = explicitUrl(address)
guard let url = URL(string: finalUrl) else {
return
}
self.webView.load(URLRequest(url: url))
}
func scrollToTop() {
self.webView.scrollView.setContentOffset(CGPoint(x: 0.0, y: -self.webView.scrollView.contentInset.top), animated: true)
}
private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets)?
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) {
self.validLayout = (size, insets, fullInsets)
self.previousScrollingOffset = ScrollingOffsetState(value: self.webView.scrollView.contentOffset.y, isDraggingOrDecelerating: self.webView.scrollView.isDragging || self.webView.scrollView.isDecelerating)
let webViewFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: size.width - insets.left - insets.right, height: size.height - insets.top - insets.bottom))
var refresh = false
if self.webView.frame.width > 0 && webViewFrame.width != self.webView.frame.width {
refresh = true
}
transition.setFrame(view: self.webView, frame: webViewFrame)
if refresh {
self.webView.reloadInputViews()
}
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)
// if let error = self.currentError {
// let errorSize = self.errorView.update(
// transition: .immediate,
// component: AnyComponent(
// ErrorComponent(
// theme: self.presentationData.theme,
// title: self.presentationData.strings.Browser_ErrorTitle,
// text: error.localizedDescription
// )
// ),
// environment: {},
// containerSize: CGSize(width: size.width - insets.left - insets.right - 72.0, height: size.height)
// )
// if self.errorView.superview == nil {
// self.addSubview(self.errorView)
// self.errorView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
// }
// self.errorView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - errorSize.width) / 2.0), y: insets.top + floorToScreenPixels((size.height - insets.top - insets.bottom - errorSize.height) / 2.0)), size: errorSize)
// } else if self.errorView.superview != nil {
// self.errorView.removeFromSuperview()
// }
}
private func updateState(_ f: (BrowserContentState) -> BrowserContentState) {
let updated = f(self._state)
self._state = updated
self.statePromise.set(.single(self._state))
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "title" {
self.updateState { $0.withUpdatedTitle(self.webView.title ?? "") }
} else if keyPath == "URL" {
self.updateState { $0.withUpdatedUrl(self.webView.url?.absoluteString ?? "") }
self.didSetupSearch = false
} else if keyPath == "estimatedProgress" {
self.updateState { $0.withUpdatedEstimatedProgress(self.webView.estimatedProgress) }
} else if keyPath == "canGoBack" {
self.updateState { $0.withUpdatedCanGoBack(self.webView.canGoBack) }
self.webView.disablesInteractiveTransitionGestureRecognizer = self.webView.canGoBack
} else if keyPath == "canGoForward" {
self.updateState { $0.withUpdatedCanGoForward(self.webView.canGoForward) }
}
}
private struct ScrollingOffsetState: Equatable {
var value: CGFloat
var isDraggingOrDecelerating: Bool
}
private var previousScrollingOffset: ScrollingOffsetState?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateScrollingOffset(isReset: false, transition: .immediate)
}
private func snapScrollingOffsetToInsets() {
let transition = ComponentTransition(animation: .curve(duration: 0.4, curve: .spring))
self.updateScrollingOffset(isReset: false, transition: transition)
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
self.snapScrollingOffsetToInsets()
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.snapScrollingOffsetToInsets()
}
private func updateScrollingOffset(isReset: Bool, transition: ComponentTransition) {
let scrollView = self.webView.scrollView
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
if let previousScrollingOffsetValue = self.previousScrollingOffset {
let currentBounds = scrollView.bounds
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY)
let relativeOffset = scrollView.contentOffset.y - previousScrollingOffsetValue.value
self.onScrollingUpdate(ContentScrollingUpdate(
relativeOffset: relativeOffset,
absoluteOffsetToTopEdge: offsetToTopEdge,
absoluteOffsetToBottomEdge: offsetToBottomEdge,
isReset: isReset,
isInteracting: isInteracting,
transition: transition
))
}
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: isInteracting)
var readingProgress: CGFloat = 0.0
if !scrollView.contentSize.height.isZero {
let value = (scrollView.contentOffset.y + scrollView.contentInset.top) / (scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.top)
readingProgress = max(0.0, min(1.0, value))
}
self.updateState {
$0.withUpdatedReadingProgress(readingProgress)
}
}
func resetScrolling() {
self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4))
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
// self.currentError = nil
self.updateFontState(self.currentFontState, force: true)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.updateState {
$0
.withUpdatedBackList(webView.backForwardList.backList.map { BrowserContentState.HistoryItem(webItem: $0) })
.withUpdatedForwardList(webView.backForwardList.forwardList.map { BrowserContentState.HistoryItem(webItem: $0) })
}
}
// func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
// if (error as NSError).code != -999 {
// self.currentError = error
// } else {
// self.currentError = nil
// }
// if let (size, insets) = self.validLayout {
// self.updateLayout(size: size, insets: insets, transition: .immediate)
// }
// }
//
// func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
// if (error as NSError).code != -999 {
// self.currentError = error
// } else {
// self.currentError = nil
// }
// if let (size, insets) = self.validLayout {
// self.updateLayout(size: size, insets: insets, transition: .immediate)
// }
// }
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
if let url = navigationAction.request.url?.absoluteString {
self.open(url: url, new: true)
}
}
return nil
}
func webViewDidClose(_ webView: WKWebView) {
self.close()
}
@available(iOSApplicationExtension 15.0, iOS 15.0, *)
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
decisionHandler(.prompt)
}
// @available(iOS 13.0, *)
// func webView(_ webView: WKWebView, contextMenuConfigurationForElement elementInfo: WKContextMenuElementInfo, completionHandler: @escaping (UIContextMenuConfiguration?) -> Void) {
// guard let url = elementInfo.linkURL else {
// completionHandler(nil)
// return
// }
// let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
// let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in
// return UIMenu(title: "", children: [
// UIAction(title: presentationData.strings.Browser_ContextMenu_Open, image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: presentationData.theme.contextMenu.primaryColor), handler: { [weak self] _ in
// self?.open(url: url.absoluteString, new: false)
// }),
// UIAction(title: presentationData.strings.Browser_ContextMenu_OpenInNewTab, image: generateTintedImage(image: UIImage(bundleImageName: "Instant View/NewTab"), color: presentationData.theme.contextMenu.primaryColor), handler: { [weak self] _ in
// self?.open(url: url.absoluteString, new: true)
// }),
// UIAction(title: presentationData.strings.Browser_ContextMenu_AddToReadingList, image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReadingList"), color: presentationData.theme.contextMenu.primaryColor), handler: { _ in
// let _ = try? SSReadingList.default()?.addItem(with: url, title: nil, previewText: nil)
// }),
// UIAction(title: presentationData.strings.Browser_ContextMenu_CopyLink, image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: presentationData.theme.contextMenu.primaryColor), handler: { [weak self] _ in
// UIPasteboard.general.string = url.absoluteString
// self?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
// }),
// UIAction(title: presentationData.strings.Browser_ContextMenu_Share, image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: presentationData.theme.contextMenu.primaryColor), handler: { [weak self] _ in
// self?.share(url: url.absoluteString)
// })
// ])
// }
// completionHandler(configuration)
// }
private func open(url: String, new: Bool) {
let subject: BrowserScreen.Subject = .webPage(url: url)
if new, let navigationController = self.getNavigationController() {
navigationController._keepModalDismissProgress = true
self.minimize()
let controller = BrowserScreen(context: self.context, subject: subject)
navigationController._keepModalDismissProgress = true
navigationController.pushViewController(controller)
} else {
self.pushContent(subject)
}
}
private func share(url: String) {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let shareController = ShareController(context: self.context, subject: .url(url))
shareController.actionCompleted = { [weak self] in
self?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}
self.present(shareController, nil)
}
func addToRecentlyVisited() {
}
func makeContentSnapshotView() -> UIView? {
return nil
}
}

View File

@ -26,6 +26,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
private let context: AccountContext
private var presentationData: PresentationData
private var theme: InstantPageTheme
private var settings: InstantPagePresentationSettings = .defaultSettings
private let sourceLocation: InstantPageSourceLocation
private var webPage: TelegramMediaWebpage?
@ -45,6 +46,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
private var pendingAnchor: String?
private var initialState: InstantPageStoredState?
private let wrapperNode: ASDisplayNode
fileprivate let scrollNode: ASScrollNode
private let scrollNodeFooter: ASDisplayNode
private var linkHighlightingNode: LinkHighlightingNode?
@ -65,6 +67,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
var currentAccessibilityAreas: [AccessibilityAreaNode] = []
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
var openAppUrl: (String) -> Void = { _ in }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
var minimize: () -> Void = { }
var close: () -> Void = { }
@ -85,7 +88,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
private let loadProgress = ValuePromise<CGFloat>(1.0, ignoreRepeated: true)
private let readingProgress = ValuePromise<CGFloat>(1.0, ignoreRepeated: true)
private var containerLayout: (size: CGSize, insets: UIEdgeInsets)?
private var containerLayout: (size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets)?
private var setupScrollOffsetOnLayout = false
init(context: AccountContext, presentationData: PresentationData, webPage: TelegramMediaWebpage, anchor: String?, url: String, sourceLocation: InstantPageSourceLocation) {
@ -107,6 +110,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.0, readingProgress: 0.0, contentType: .instantPage)
self.statePromise = Promise<BrowserContentState>(self._state)
self.wrapperNode = ASDisplayNode()
self.scrollNode = ASScrollNode()
self.scrollNode.backgroundColor = self.theme.pageBackgroundColor
@ -126,7 +130,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
))
self.addSubnode(self.scrollNode)
self.addSubnode(self.wrapperNode)
self.wrapperNode.addSubnode(self.scrollNode)
self.scrollNode.addSubnode(self.scrollNodeFooter)
self.scrollNode.view.delaysContentTouches = false
@ -175,8 +180,9 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.theme = instantPageThemeForType(presentationData.theme.overallDarkAppearance ? .dark : .light, settings: .defaultSettings)
self.theme = instantPageThemeForType(presentationData.theme.overallDarkAppearance ? .dark : .light, settings: self.settings)
self.updatePageLayout()
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds)
}
func tapActionAtPoint(_ point: CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction {
@ -295,10 +301,10 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
private func requestLayout(transition: ContainedViewLayoutTransition) {
guard let (size, insets) = self.containerLayout else {
guard let (size, insets, fullInsets) = self.containerLayout else {
return
}
self.updateLayout(size: size, insets: insets, transition: transition)
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: transition)
}
func reload() {
@ -319,8 +325,40 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
var currentFontState = BrowserPresentationState.FontState(size: 100, isSerif: false)
func updateFontState(_ state: BrowserPresentationState.FontState) {
self.currentFontState = state
let fontSize: InstantPagePresentationFontSize
switch state.size {
case 50:
fontSize = .xxsmall
case 75:
fontSize = .xsmall
case 85:
fontSize = .small
case 100:
fontSize = .standard
case 115:
fontSize = .large
case 125:
fontSize = .xlarge
case 150:
fontSize = .xxlarge
default:
fontSize = .standard
}
self.settings = InstantPagePresentationSettings(
themeType: self.presentationData.theme.overallDarkAppearance ? .dark : .light,
fontSize: fontSize,
forceSerif: state.isSerif,
autoNightMode: false,
ignoreAutoNightModeUntil: 0
)
self.theme = instantPageThemeForType(self.presentationData.theme.overallDarkAppearance ? .dark : .light, settings: self.settings)
self.updatePageLayout()
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds)
}
func setSearch(_ query: String?, completion: ((Int) -> Void)?) {
@ -340,12 +378,12 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
}
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ComponentTransition) {
self.updateLayout(size: size, insets: insets, transition: transition.containedViewLayoutTransition)
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) {
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: safeInsets, transition: transition.containedViewLayoutTransition)
}
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
self.containerLayout = (size, insets)
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
self.containerLayout = (size, insets, fullInsets)
var updateVisibleItems = false
let resetContentOffset = self.scrollNode.bounds.size.width.isZero || self.setupScrollOffsetOnLayout || !(self.initialAnchor ?? "").isEmpty
@ -357,6 +395,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
self.scrollNode.view.scrollIndicatorInsets = scrollInsets
}
self.wrapperNode.frame = CGRect(origin: .zero, size: size)
let scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top))
let scrollFrameUpdated = self.scrollNode.bounds.size != scrollFrame.size
if scrollFrameUpdated {
@ -401,7 +441,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
private func updatePageLayout() {
guard let (size, insets) = self.containerLayout, let webPage = self.webPage else {
guard let (size, insets, _) = self.containerLayout, let webPage = self.webPage else {
return
}
@ -732,6 +772,10 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
self.readingProgress.set(readingProgress)
}
func resetScrolling() {
self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4))
}
private func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint {
var contentOffset = CGPoint()
for (_, itemNode) in self.visibleItemsWithNodes {
@ -1069,12 +1113,12 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
})], catchTapsOutside: true)
self.present(controller, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
if let _ = self {
// for (_, itemNode) in self.visibleItemsWithNodes {
// if let (node, _, _) = itemNode.transitionNode(media: media) {
// return (self.scrollNode, node.convert(node.bounds, to: self.scrollNode), self, self.bounds)
// }
// }
if let self {
for (_, itemNode) in self.visibleItemsWithNodes {
if let (node, _, _) = itemNode.transitionNode(media: media) {
return (self.scrollNode, node.convert(node.bounds, to: self.scrollNode), self.wrapperNode, self.wrapperNode.bounds)
}
}
}
return nil
}))
@ -1162,84 +1206,84 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
private func updateTextSelectionRects(_ rects: [CGRect], text: String?) {
// if let text = text, !rects.isEmpty {
// let textSelectionNode: LinkHighlightingNode
// if let current = self.textSelectionNode {
// textSelectionNode = current
// } else {
// textSelectionNode = LinkHighlightingNode(color: UIColor.lightGray.withAlphaComponent(0.4))
// textSelectionNode.isUserInteractionEnabled = false
// self.textSelectionNode = textSelectionNode
// self.scrollNode.addSubnode(textSelectionNode)
// }
// textSelectionNode.frame = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size)
// textSelectionNode.updateRects(rects)
//
//// var coveringRect = rects[0]
//// for i in 1 ..< rects.count {
//// coveringRect = coveringRect.union(rects[i])
//// }
//
//// let context = self.context
//// let strings = self.presentationData.strings
//// let _ = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings])
//// |> take(1)
//// |> deliverOnMainQueue).start(next: { [weak self] sharedData in
//// let translationSettings: TranslationSettings
//// if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {
//// translationSettings = current
//// } else {
//// translationSettings = TranslationSettings.defaultSettings
//// }
////
//// var actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuCopy, accessibilityLabel: strings.Conversation_ContextMenuCopy), action: { [weak self] in
//// UIPasteboard.general.string = text
////
//// if let strongSelf = self {
//// let presentationData = context.sharedContext.currentPresentationData.with { $0 }
//// strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
//// }
//// }), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in
//// if let strongSelf = self, let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content {
//// strongSelf.present(ShareController(context: strongSelf.context, subject: .quote(text: text, url: content.url)), nil)
//// }
//// })]
////
//// let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
//// if canTranslate {
//// actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
//// let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language)
//// controller.pushController = { [weak self] c in
//// (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
//// self?.controller?.push(c)
//// }
//// controller.presentController = { [weak self] c in
//// self?.controller?.present(c, in: .window(.root))
//// }
//// self?.present(controller, nil)
//// }))
//// }
////
//// let controller = makeContextMenuController(actions: actions)
//// controller.dismissed = { [weak self] in
//// self?.updateTextSelectionRects([], text: nil)
//// }
//// self?.present(controller, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
//// if let strongSelf = self {
//// return (strongSelf.scrollNode, coveringRect.insetBy(dx: -3.0, dy: -3.0), strongSelf, strongSelf.bounds)
//// } else {
//// return nil
//// }
//// }))
//// })
//
// textSelectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
// } else if let textSelectionNode = self.textSelectionNode {
// self.textSelectionNode = nil
// textSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak textSelectionNode] _ in
// textSelectionNode?.removeFromSupernode()
// })
// }
if let text = text, !rects.isEmpty {
let textSelectionNode: LinkHighlightingNode
if let current = self.textSelectionNode {
textSelectionNode = current
} else {
textSelectionNode = LinkHighlightingNode(color: UIColor.lightGray.withAlphaComponent(0.4))
textSelectionNode.isUserInteractionEnabled = false
self.textSelectionNode = textSelectionNode
self.scrollNode.addSubnode(textSelectionNode)
}
textSelectionNode.frame = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size)
textSelectionNode.updateRects(rects)
var coveringRect = rects[0]
for i in 1 ..< rects.count {
coveringRect = coveringRect.union(rects[i])
}
let context = self.context
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = self.presentationData.strings
let _ = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings])
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
let translationSettings: TranslationSettings
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {
translationSettings = current
} else {
translationSettings = TranslationSettings.defaultSettings
}
var actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuCopy, accessibilityLabel: strings.Conversation_ContextMenuCopy), action: { [weak self] in
UIPasteboard.general.string = text
if let strongSelf = self {
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}
}), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in
if let strongSelf = self, let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content {
strongSelf.present(ShareController(context: strongSelf.context, subject: .quote(text: text, url: content.url)), nil)
}
})]
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
if canTranslate {
actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language)
controller.pushController = { [weak self] c in
self?.getNavigationController()?._keepModalDismissProgress = true
self?.push(c)
}
controller.presentController = { [weak self] c in
self?.present(c, nil)
}
self?.present(controller, nil)
}))
}
let controller = makeContextMenuController(actions: actions)
controller.dismissed = { [weak self] in
self?.updateTextSelectionRects([], text: nil)
}
self?.present(controller, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
if let strongSelf = self {
return (strongSelf.scrollNode, coveringRect.insetBy(dx: -3.0, dy: -3.0), strongSelf.wrapperNode, strongSelf.wrapperNode.bounds)
} else {
return nil
}
}))
})
textSelectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
} else if let textSelectionNode = self.textSelectionNode {
self.textSelectionNode = nil
textSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak textSelectionNode] _ in
textSelectionNode?.removeFromSupernode()
})
}
}
private func findAnchorItem(_ anchor: String, items: [InstantPageItem]) -> (InstantPageItem, CGFloat, Bool, [InstantPageDetailsItem])? {
@ -1378,4 +1422,14 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds, animated: animated)
}
func addToRecentlyVisited() {
if let webPage = self.webPage {
let _ = addRecentlyVisitedLink(engine: self.context.engine, webPage: webPage).startStandalone()
}
}
func makeContentSnapshotView() -> UIView? {
return nil
}
}

View File

@ -5,7 +5,30 @@ import ComponentFlow
import BlurredBackgroundComponent
import ContextUI
final class BrowserNavigationBarEnvironment: Equatable {
public let fraction: CGFloat
public init(fraction: CGFloat) {
self.fraction = fraction
}
public static func ==(lhs: BrowserNavigationBarEnvironment, rhs: BrowserNavigationBarEnvironment) -> Bool {
if lhs.fraction != rhs.fraction {
return false
}
return true
}
}
final class BrowserNavigationBarComponent: CombinedComponent {
public class ExternalState {
public fileprivate(set) var centerItemFrame: CGRect
public init() {
self.centerItemFrame = .zero
}
}
let backgroundColor: UIColor
let separatorColor: UIColor
let textColor: UIColor
@ -14,12 +37,15 @@ final class BrowserNavigationBarComponent: CombinedComponent {
let topInset: CGFloat
let height: CGFloat
let sideInset: CGFloat
let metrics: LayoutMetrics
let externalState: ExternalState?
let leftItems: [AnyComponentWithIdentity<Empty>]
let rightItems: [AnyComponentWithIdentity<Empty>]
let centerItem: AnyComponentWithIdentity<Empty>?
let centerItem: AnyComponentWithIdentity<BrowserNavigationBarEnvironment>?
let readingProgress: CGFloat
let loadingProgress: Double?
let collapseFraction: CGFloat
let activate: () -> Void
init(
backgroundColor: UIColor,
@ -30,12 +56,15 @@ final class BrowserNavigationBarComponent: CombinedComponent {
topInset: CGFloat,
height: CGFloat,
sideInset: CGFloat,
metrics: LayoutMetrics,
externalState: ExternalState?,
leftItems: [AnyComponentWithIdentity<Empty>],
rightItems: [AnyComponentWithIdentity<Empty>],
centerItem: AnyComponentWithIdentity<Empty>?,
centerItem: AnyComponentWithIdentity<BrowserNavigationBarEnvironment>?,
readingProgress: CGFloat,
loadingProgress: Double?,
collapseFraction: CGFloat
collapseFraction: CGFloat,
activate: @escaping () -> Void
) {
self.backgroundColor = backgroundColor
self.separatorColor = separatorColor
@ -45,12 +74,15 @@ final class BrowserNavigationBarComponent: CombinedComponent {
self.topInset = topInset
self.height = height
self.sideInset = sideInset
self.metrics = metrics
self.externalState = externalState
self.leftItems = leftItems
self.rightItems = rightItems
self.centerItem = centerItem
self.readingProgress = readingProgress
self.loadingProgress = loadingProgress
self.collapseFraction = collapseFraction
self.activate = activate
}
static func ==(lhs: BrowserNavigationBarComponent, rhs: BrowserNavigationBarComponent) -> Bool {
@ -78,6 +110,9 @@ final class BrowserNavigationBarComponent: CombinedComponent {
if lhs.sideInset != rhs.sideInset {
return false
}
if lhs.metrics != rhs.metrics {
return false
}
if lhs.leftItems != rhs.leftItems {
return false
}
@ -106,16 +141,19 @@ final class BrowserNavigationBarComponent: CombinedComponent {
let loadingProgress = Child(LoadingProgressComponent.self)
let leftItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let centerItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let centerItems = ChildMap(environment: BrowserNavigationBarEnvironment.self, keyedBy: AnyHashable.self)
let activate = Child(Button.self)
return { context in
var availableWidth = context.availableSize.width
let sideInset: CGFloat = 16.0 + context.component.sideInset
let sideInset: CGFloat = (context.component.metrics.isTablet ? 20.0 : 16.0) + context.component.sideInset
let collapsedHeight: CGFloat = 24.0
let expandedHeight = context.component.height
let contentHeight: CGFloat = expandedHeight * (1.0 - context.component.collapseFraction) + collapsedHeight * context.component.collapseFraction
let size = CGSize(width: context.availableSize.width, height: context.component.topInset + contentHeight)
let verticalOffset: CGFloat = context.component.metrics.isTablet ? -2.0 : 0.0
let itemSpacing: CGFloat = context.component.metrics.isTablet ? 26.0 : 8.0
let background = background.update(
component: Rectangle(color: context.component.backgroundColor.withAlphaComponent(1.0)),
@ -145,7 +183,7 @@ final class BrowserNavigationBarComponent: CombinedComponent {
availableSize: CGSize(width: size.width, height: size.height),
transition: context.transition
)
var leftItemList: [_UpdatedChildComponent] = []
for item in context.component.leftItems {
let item = leftItems[item.id].update(
@ -167,18 +205,18 @@ final class BrowserNavigationBarComponent: CombinedComponent {
rightItemList.append(item)
availableWidth -= item.size.width
}
if !leftItemList.isEmpty || !rightItemList.isEmpty {
availableWidth -= 32.0
}
context.add(background
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
)
var readingProgressAlpha = context.component.collapseFraction
if leftItemList.isEmpty && rightItemList.isEmpty {
readingProgressAlpha = 0.0
}
context.add(readingProgress
.position(CGPoint(x: readingProgress.size.width / 2.0, y: size.height / 2.0))
.opacity(context.component.centerItem?.id == AnyHashable("search") ? 0.0 : 1.0)
.opacity(readingProgressAlpha)
)
context.add(separator
@ -193,51 +231,86 @@ final class BrowserNavigationBarComponent: CombinedComponent {
var leftItemX = sideInset
for item in leftItemList {
context.add(item
.position(CGPoint(x: leftItemX + item.size.width / 2.0 - (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0))
.position(CGPoint(x: leftItemX + item.size.width / 2.0 - (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0 + verticalOffset))
.scale(1.0 - 0.35 * context.component.collapseFraction)
.opacity(1.0 - context.component.collapseFraction)
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
)
leftItemX -= item.size.width + 8.0
centerLeftInset += item.size.width + 8.0
leftItemX += item.size.width + itemSpacing
centerLeftInset += item.size.width + itemSpacing
}
var centerRightInset = sideInset - 5.0
var rightItemX = context.availableSize.width - (sideInset - 5.0)
for item in rightItemList.reversed() {
context.add(item
.position(CGPoint(x: rightItemX - item.size.width / 2.0 + (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0))
.position(CGPoint(x: rightItemX - item.size.width / 2.0 + (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0 + verticalOffset))
.scale(1.0 - 0.35 * context.component.collapseFraction)
.opacity(1.0 - context.component.collapseFraction)
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
)
rightItemX -= item.size.width + 8.0
centerRightInset += item.size.width + 8.0
rightItemX -= item.size.width + itemSpacing
centerRightInset += item.size.width + itemSpacing
}
let maxCenterInset = max(centerLeftInset, centerRightInset)
if !leftItemList.isEmpty || !rightItemList.isEmpty {
availableWidth -= 28.0
availableWidth -= itemSpacing * CGFloat(max(0, leftItemList.count - 1)) + itemSpacing * CGFloat(max(0, rightItemList.count - 1)) + 30.0
}
availableWidth -= context.component.sideInset * 2.0
let canCenter = availableWidth > 660.0
availableWidth = min(660.0, availableWidth)
let environment = BrowserNavigationBarEnvironment(fraction: context.component.collapseFraction)
let centerItem = context.component.centerItem.flatMap { item in
centerItems[item.id].update(
component: item.component,
environment: { environment },
availableSize: CGSize(width: availableWidth, height: expandedHeight),
transition: context.transition
)
}
var centerX = maxCenterInset + (context.availableSize.width - maxCenterInset * 2.0) / 2.0
if "".isEmpty {
if canCenter {
centerX = context.availableSize.width / 2.0
} else {
centerX = centerLeftInset + (context.availableSize.width - centerLeftInset - centerRightInset) / 2.0
}
}
if let centerItem = centerItem {
let centerItemPosition = CGPoint(x: centerX, y: context.component.topInset + contentHeight / 2.0 + verticalOffset)
context.add(centerItem
.position(CGPoint(x: maxCenterInset + (context.availableSize.width - maxCenterInset * 2.0) / 2.0, y: context.component.topInset + contentHeight / 2.0))
.position(centerItemPosition)
.scale(1.0 - 0.35 * context.component.collapseFraction)
.appear(.default(scale: false, alpha: true))
.disappear(.default(scale: false, alpha: true))
)
context.component.externalState?.centerItemFrame = centerItem.size.centered(around: centerItemPosition)
}
if context.component.collapseFraction == 1.0 {
let activateAction = context.component.activate
let activate = activate.update(
component: Button(
content: AnyComponent(Rectangle(color: UIColor(rgb: 0x000000, alpha: 0.001))),
action: {
activateAction()
}
),
availableSize: size,
transition: .immediate
)
context.add(activate
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
)
}
return size

View File

@ -0,0 +1,472 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import TelegramCore
import Postbox
import SwiftSignalKit
import TelegramPresentationData
import TelegramUIPreferences
import PresentationDataUtils
import AccountContext
import WebKit
import AppBundle
import PromptUI
import SafariServices
import ShareController
import UndoUI
import UrlEscaping
import PDFKit
final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate {
private let context: AccountContext
private var presentationData: PresentationData
private let webView: PDFView
private let scrollView: UIScrollView!
let uuid: UUID
private var _state: BrowserContentState
private let statePromise: Promise<BrowserContentState>
var currentState: BrowserContentState {
return self._state
}
var state: Signal<BrowserContentState, NoError> {
return self.statePromise.get()
}
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
var openAppUrl: (String) -> Void = { _ in }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
var minimize: () -> Void = { }
var close: () -> Void = { }
var present: (ViewController, Any?) -> Void = { _, _ in }
var presentInGlobalOverlay: (ViewController) -> Void = { _ in }
var getNavigationController: () -> NavigationController? = { return nil }
private var tempFile: TempBoxFile?
init(context: AccountContext, presentationData: PresentationData, file: TelegramMediaFile) {
self.context = context
self.uuid = UUID()
self.presentationData = presentationData
self.webView = PDFView()
self.webView.maxScaleFactor = 4.0;
self.webView.minScaleFactor = self.webView.scaleFactorForSizeToFit
self.webView.autoScales = true
var scrollView: UIScrollView?
for view in self.webView.subviews {
if let view = view as? UIScrollView {
scrollView = view
} else {
for subview in view.subviews {
if let subview = subview as? UIScrollView {
scrollView = subview
}
}
}
}
self.scrollView = scrollView
var title: String = "file"
if let path = self.context.account.postbox.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) {
// var updatedPath = path
// if let fileName = file.fileName {
// let tempFile = TempBox.shared.file(path: path, fileName: fileName)
// updatedPath = tempFile.path
// self.tempFile = tempFile
// title = fileName
// }
self.webView.document = PDFDocument(data: data)
title = file.fileName ?? "file"
}
self._state = BrowserContentState(title: title, url: "", estimatedProgress: 0.0, readingProgress: 0.0, contentType: .document)
self.statePromise = Promise<BrowserContentState>(self._state)
super.init(frame: .zero)
if #available(iOS 15.0, *) {
self.backgroundColor = presentationData.theme.list.plainBackgroundColor
}
self.addSubview(self.webView)
Queue.mainQueue().after(1.0) {
scrollView?.delegate = self
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
if #available(iOS 15.0, *) {
self.backgroundColor = presentationData.theme.list.plainBackgroundColor
}
if let (size, insets, fullInsets) = self.validLayout {
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: .immediate)
}
}
var currentFontState = BrowserPresentationState.FontState(size: 100, isSerif: false)
func updateFontState(_ state: BrowserPresentationState.FontState) {
self.updateFontState(state, force: false)
}
func updateFontState(_ state: BrowserPresentationState.FontState, force: Bool) {
self.currentFontState = state
// let fontFamily = state.isSerif ? "'Georgia, serif'" : "null"
// let textSizeAdjust = state.size != 100 ? "'\(state.size)%'" : "null"
// let js = "\(setupFontFunctions) setTelegramFontOverrides(\(fontFamily), \(textSizeAdjust))";
// self.webView.evaluateJavaScript(js) { _, _ in }
}
private var didSetupSearch = false
private func setupSearch(completion: @escaping () -> Void) {
// guard !self.didSetupSearch else {
// completion()
// return
// }
//
// let bundle = getAppBundle()
// guard let scriptPath = bundle.path(forResource: "UIWebViewSearch", ofType: "js") else {
// return
// }
// guard let scriptData = try? Data(contentsOf: URL(fileURLWithPath: scriptPath)) else {
// return
// }
// guard let script = String(data: scriptData, encoding: .utf8) else {
// return
// }
// self.didSetupSearch = true
// self.webView.evaluateJavaScript(script, completionHandler: { _, error in
// if error != nil {
// print()
// }
// completion()
// })
}
private var previousQuery: String?
func setSearch(_ query: String?, completion: ((Int) -> Void)?) {
// guard self.previousQuery != query else {
// return
// }
// self.previousQuery = query
// self.setupSearch { [weak self] in
// if let query = query {
// let js = "uiWebview_HighlightAllOccurencesOfString('\(query)')"
// self?.webView.evaluateJavaScript(js, completionHandler: { [weak self] _, _ in
// let js = "uiWebview_SearchResultCount"
// self?.webView.evaluateJavaScript(js, completionHandler: { [weak self] result, _ in
// if let result = result as? NSNumber {
// self?.searchResultsCount = result.intValue
// completion?(result.intValue)
// } else {
// completion?(0)
// }
// })
// })
// } else {
// let js = "uiWebview_RemoveAllHighlights()"
// self?.webView.evaluateJavaScript(js, completionHandler: nil)
//
// self?.currentSearchResult = 0
// self?.searchResultsCount = 0
// }
// }
}
private var currentSearchResult: Int = 0
private var searchResultsCount: Int = 0
func scrollToPreviousSearchResult(completion: ((Int, Int) -> Void)?) {
// let searchResultsCount = self.searchResultsCount
// var index = self.currentSearchResult - 1
// if index < 0 {
// index = searchResultsCount - 1
// }
// self.currentSearchResult = index
//
// let js = "uiWebview_ScrollTo('\(searchResultsCount - index - 1)')"
// self.webView.evaluateJavaScript(js, completionHandler: { _, _ in
// completion?(index, searchResultsCount)
// })
}
func scrollToNextSearchResult(completion: ((Int, Int) -> Void)?) {
// let searchResultsCount = self.searchResultsCount
// var index = self.currentSearchResult + 1
// if index >= searchResultsCount {
// index = 0
// }
// self.currentSearchResult = index
//
// let js = "uiWebview_ScrollTo('\(searchResultsCount - index - 1)')"
// self.webView.evaluateJavaScript(js, completionHandler: { _, _ in
// completion?(index, searchResultsCount)
// })
}
func stop() {
// self.webView.stopLoading()
}
func reload() {
// self.webView.reload()
}
func navigateBack() {
// self.webView.goBack()
}
func navigateForward() {
// self.webView.goForward()
}
func navigateTo(historyItem: BrowserContentState.HistoryItem) {
// if let webItem = historyItem.webItem {
// self.webView.go(to: webItem)
// }
}
func navigateTo(address: String) {
// let finalUrl = explicitUrl(address)
// guard let url = URL(string: finalUrl) else {
// return
// }
// self.webView.load(URLRequest(url: url))
}
func scrollToTop() {
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: -self.scrollView.contentInset.top), animated: true)
}
private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets)?
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) {
self.validLayout = (size, insets, fullInsets)
self.previousScrollingOffset = ScrollingOffsetState(value: self.scrollView.contentOffset.y, isDraggingOrDecelerating: self.scrollView.isDragging || self.scrollView.isDecelerating)
let webViewFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: size.width - insets.left - insets.right, height: size.height - insets.top - insets.bottom))
var refresh = false
if self.webView.frame.width > 0 && webViewFrame.width != self.webView.frame.width {
refresh = true
}
transition.setFrame(view: self.webView, frame: webViewFrame)
if refresh {
self.webView.reloadInputViews()
}
// if let error = self.currentError {
// let errorSize = self.errorView.update(
// transition: .immediate,
// component: AnyComponent(
// ErrorComponent(
// theme: self.presentationData.theme,
// title: self.presentationData.strings.Browser_ErrorTitle,
// text: error.localizedDescription
// )
// ),
// environment: {},
// containerSize: CGSize(width: size.width - insets.left - insets.right - 72.0, height: size.height)
// )
// if self.errorView.superview == nil {
// self.addSubview(self.errorView)
// self.errorView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
// }
// self.errorView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - errorSize.width) / 2.0), y: insets.top + floorToScreenPixels((size.height - insets.top - insets.bottom - errorSize.height) / 2.0)), size: errorSize)
// } else if self.errorView.superview != nil {
// self.errorView.removeFromSuperview()
// }
}
private func updateState(_ f: (BrowserContentState) -> BrowserContentState) {
let updated = f(self._state)
self._state = updated
self.statePromise.set(.single(self._state))
}
private struct ScrollingOffsetState: Equatable {
var value: CGFloat
var isDraggingOrDecelerating: Bool
}
private var previousScrollingOffset: ScrollingOffsetState?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateScrollingOffset(isReset: false, transition: .immediate)
}
private func snapScrollingOffsetToInsets() {
let transition = ComponentTransition(animation: .curve(duration: 0.4, curve: .spring))
self.updateScrollingOffset(isReset: false, transition: transition)
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
self.snapScrollingOffsetToInsets()
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.snapScrollingOffsetToInsets()
}
private func updateScrollingOffset(isReset: Bool, transition: ComponentTransition) {
guard let scrollView = self.scrollView else {
return
}
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
if let previousScrollingOffsetValue = self.previousScrollingOffset {
let currentBounds = scrollView.bounds
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY)
let relativeOffset = scrollView.contentOffset.y - previousScrollingOffsetValue.value
self.onScrollingUpdate(ContentScrollingUpdate(
relativeOffset: relativeOffset,
absoluteOffsetToTopEdge: offsetToTopEdge,
absoluteOffsetToBottomEdge: offsetToBottomEdge,
isReset: isReset,
isInteracting: isInteracting,
transition: transition
))
}
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: isInteracting)
var readingProgress: CGFloat = 0.0
if !scrollView.contentSize.height.isZero {
let value = (scrollView.contentOffset.y + scrollView.contentInset.top) / (scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.top)
readingProgress = max(0.0, min(1.0, value))
}
self.updateState {
$0.withUpdatedReadingProgress(readingProgress)
}
}
func resetScrolling() {
self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4))
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
// self.currentError = nil
self.updateFontState(self.currentFontState, force: true)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.updateState {
$0
.withUpdatedBackList(webView.backForwardList.backList.map { BrowserContentState.HistoryItem(webItem: $0) })
.withUpdatedForwardList(webView.backForwardList.forwardList.map { BrowserContentState.HistoryItem(webItem: $0) })
}
}
// func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
// if (error as NSError).code != -999 {
// self.currentError = error
// } else {
// self.currentError = nil
// }
// if let (size, insets) = self.validLayout {
// self.updateLayout(size: size, insets: insets, transition: .immediate)
// }
// }
//
// func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
// if (error as NSError).code != -999 {
// self.currentError = error
// } else {
// self.currentError = nil
// }
// if let (size, insets) = self.validLayout {
// self.updateLayout(size: size, insets: insets, transition: .immediate)
// }
// }
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
if let url = navigationAction.request.url?.absoluteString {
self.open(url: url, new: true)
}
}
return nil
}
func webViewDidClose(_ webView: WKWebView) {
self.close()
}
@available(iOSApplicationExtension 15.0, iOS 15.0, *)
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
decisionHandler(.prompt)
}
// @available(iOS 13.0, *)
// func webView(_ webView: WKWebView, contextMenuConfigurationForElement elementInfo: WKContextMenuElementInfo, completionHandler: @escaping (UIContextMenuConfiguration?) -> Void) {
// guard let url = elementInfo.linkURL else {
// completionHandler(nil)
// return
// }
// let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
// let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in
// return UIMenu(title: "", children: [
// UIAction(title: presentationData.strings.Browser_ContextMenu_Open, image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: presentationData.theme.contextMenu.primaryColor), handler: { [weak self] _ in
// self?.open(url: url.absoluteString, new: false)
// }),
// UIAction(title: presentationData.strings.Browser_ContextMenu_OpenInNewTab, image: generateTintedImage(image: UIImage(bundleImageName: "Instant View/NewTab"), color: presentationData.theme.contextMenu.primaryColor), handler: { [weak self] _ in
// self?.open(url: url.absoluteString, new: true)
// }),
// UIAction(title: presentationData.strings.Browser_ContextMenu_AddToReadingList, image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReadingList"), color: presentationData.theme.contextMenu.primaryColor), handler: { _ in
// let _ = try? SSReadingList.default()?.addItem(with: url, title: nil, previewText: nil)
// }),
// UIAction(title: presentationData.strings.Browser_ContextMenu_CopyLink, image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: presentationData.theme.contextMenu.primaryColor), handler: { [weak self] _ in
// UIPasteboard.general.string = url.absoluteString
// self?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
// }),
// UIAction(title: presentationData.strings.Browser_ContextMenu_Share, image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: presentationData.theme.contextMenu.primaryColor), handler: { [weak self] _ in
// self?.share(url: url.absoluteString)
// })
// ])
// }
// completionHandler(configuration)
// }
private func open(url: String, new: Bool) {
let subject: BrowserScreen.Subject = .webPage(url: url)
if new, let navigationController = self.getNavigationController() {
navigationController._keepModalDismissProgress = true
self.minimize()
let controller = BrowserScreen(context: self.context, subject: subject)
navigationController._keepModalDismissProgress = true
navigationController.pushViewController(controller)
} else {
self.pushContent(subject)
}
}
private func share(url: String) {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let shareController = ShareController(context: self.context, subject: .url(url))
shareController.actionCompleted = { [weak self] in
self?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}
self.present(shareController, nil)
}
func addToRecentlyVisited() {
}
func makeContentSnapshotView() -> UIView? {
return nil
}
}

View File

@ -0,0 +1,88 @@
import Foundation
import Postbox
import TelegramCore
import SwiftSignalKit
import TelegramUIPreferences
private struct RecentlyVisitedLinkItemId {
public let rawValue: MemoryBuffer
var value: String {
return String(data: self.rawValue.makeData(), encoding: .utf8) ?? ""
}
init(_ rawValue: MemoryBuffer) {
self.rawValue = rawValue
}
init?(_ value: String) {
if let data = value.data(using: .utf8) {
self.rawValue = MemoryBuffer(data: data)
} else {
return nil
}
}
}
public final class RecentVisitedLinkItem: Codable {
private enum CodingKeys: String, CodingKey {
case webPage
}
public let webPage: TelegramMediaWebpage
public init(webPage: TelegramMediaWebpage) {
self.webPage = webPage
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let webPageData = try container.decodeIfPresent(Data.self, forKey: .webPage) {
self.webPage = PostboxDecoder(buffer: MemoryBuffer(data: webPageData)).decodeRootObject() as! TelegramMediaWebpage
} else {
fatalError()
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let encoder = PostboxEncoder()
encoder.encodeRootObject(self.webPage)
let webPageData = encoder.makeData()
try container.encode(webPageData, forKey: .webPage)
}
}
func addRecentlyVisitedLink(engine: TelegramEngine, webPage: TelegramMediaWebpage) -> Signal<Never, NoError> {
if let url = webPage.content.url, let itemId = RecentlyVisitedLinkItemId(url) {
return engine.orderedLists.addOrMoveToFirstPosition(collectionId: ApplicationSpecificOrderedItemListCollectionId.browserRecentlyVisited, id: itemId.rawValue, item: RecentVisitedLinkItem(webPage: webPage), removeTailIfCountExceeds: 10)
} else {
return .complete()
}
}
func removeRecentlyVisitedLink(engine: TelegramEngine, url: String) -> Signal<Never, NoError> {
if let itemId = RecentlyVisitedLinkItemId(url) {
return engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.browserRecentlyVisited, id: itemId.rawValue)
} else {
return .complete()
}
}
func clearRecentlyVisitedLinks(engine: TelegramEngine) -> Signal<Never, NoError> {
return engine.orderedLists.clear(collectionId: ApplicationSpecificOrderedItemListCollectionId.browserRecentlyVisited)
}
func recentlyVisitedLinks(engine: TelegramEngine) -> Signal<[TelegramMediaWebpage], NoError> {
return engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: ApplicationSpecificOrderedItemListCollectionId.browserRecentlyVisited))
|> map { items -> [TelegramMediaWebpage] in
var result: [TelegramMediaWebpage] = []
for item in items {
if let link = item.contents.get(RecentVisitedLinkItem.self) {
result.append(link.webPage)
}
}
return result
}
}

View File

@ -73,13 +73,19 @@ private final class BrowserScreenComponent: CombinedComponent {
static var body: Body {
let navigationBar = Child(BrowserNavigationBarComponent.self)
let toolbar = Child(BrowserToolbarComponent.self)
let addressList = Child(BrowserAddressListComponent.self)
let navigationBarExternalState = BrowserNavigationBarComponent.ExternalState()
return { context in
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
let performAction = context.component.performAction
let performHoldAction = context.component.performHoldAction
let navigationContent: AnyComponentWithIdentity<Empty>?
let isTablet = environment.metrics.isTablet
let canOpenIn = !(context.component.contentState?.url.hasPrefix("tonsite") ?? false)
let navigationContent: AnyComponentWithIdentity<BrowserNavigationBarEnvironment>?
var navigationLeftItems: [AnyComponentWithIdentity<Empty>]
var navigationRightItems: [AnyComponentWithIdentity<Empty>]
if context.component.presentationState.isSearching {
@ -96,51 +102,204 @@ private final class BrowserScreenComponent: CombinedComponent {
navigationLeftItems = []
navigationRightItems = []
} else {
let title = context.component.contentState?.title ?? ""
navigationContent = AnyComponentWithIdentity(
id: "title_\(title)",
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: title, font: Font.bold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor, paragraphAlignment: .center)), horizontalAlignment: .center, maximumNumberOfLines: 1)
)
)
navigationLeftItems = [
AnyComponentWithIdentity(
id: "close",
let contentType = context.component.contentState?.contentType ?? .instantPage
switch contentType {
case .webPage:
navigationContent = AnyComponentWithIdentity(
id: "addressBar",
component: AnyComponent(
Button(
content: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.WebBrowser_Done, font: Font.regular(17.0), textColor: environment.theme.rootController.navigationBar.accentTextColor, paragraphAlignment: .center)), horizontalAlignment: .left, maximumNumberOfLines: 1)
),
action: {
performAction.invoke(.close)
}
AddressBarContentComponent(
theme: environment.theme,
strings: environment.strings,
metrics: environment.metrics,
url: context.component.contentState?.url ?? "",
isSecure: context.component.contentState?.isSecure ?? false,
isExpanded: context.component.presentationState.addressFocused,
performAction: performAction
)
)
)
]
navigationRightItems = [
AnyComponentWithIdentity(
id: "settings",
case .instantPage, .document:
let title = context.component.contentState?.title ?? ""
navigationContent = AnyComponentWithIdentity(
id: "titleBar_\(title)",
component: AnyComponent(
ReferenceButtonComponent(
content: AnyComponent(
LottieComponent(
content: LottieComponent.AppBundleContent(
name: "anim_moredots"
TitleBarContentComponent(
theme: environment.theme,
title: title
)
)
)
}
if context.component.presentationState.addressFocused && !isTablet {
navigationLeftItems = []
navigationRightItems = []
} else {
navigationLeftItems = [
AnyComponentWithIdentity(
id: "close",
component: AnyComponent(
Button(
content: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.WebBrowser_Done, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.accentTextColor, paragraphAlignment: .center)), horizontalAlignment: .left, maximumNumberOfLines: 1)
),
action: {
performAction.invoke(.close)
}
)
)
)
]
if isTablet {
#if DEBUG
navigationLeftItems.append(
AnyComponentWithIdentity(
id: "minimize",
component: AnyComponent(
Button(
content: AnyComponent(
BundleIconComponent(
name: "Media Gallery/PictureInPictureButton",
tintColor: environment.theme.rootController.navigationBar.accentTextColor
)
),
color: environment.theme.rootController.navigationBar.accentTextColor,
size: CGSize(width: 30.0, height: 30.0)
action: {
performAction.invoke(.close)
}
)
),
tag: settingsTag,
action: {
performAction.invoke(.openSettings)
}
)
)
)
)
]
#endif
let canGoBack = context.component.contentState?.canGoBack ?? false
let canGoForward = context.component.contentState?.canGoForward ?? false
navigationLeftItems.append(
AnyComponentWithIdentity(
id: "back",
component: AnyComponent(
Button(
content: AnyComponent(
BundleIconComponent(
name: "Instant View/Back",
tintColor: environment.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(canGoBack ? 1.0 : 0.4)
)
),
action: {
performAction.invoke(.navigateBack)
}
)
)
)
)
navigationLeftItems.append(
AnyComponentWithIdentity(
id: "forward",
component: AnyComponent(
Button(
content: AnyComponent(
BundleIconComponent(
name: "Instant View/Forward",
tintColor: environment.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(canGoForward ? 1.0 : 0.4)
)
),
action: {
performAction.invoke(.navigateForward)
}
)
)
)
)
}
navigationRightItems = [
AnyComponentWithIdentity(
id: "settings",
component: AnyComponent(
ReferenceButtonComponent(
content: AnyComponent(
LottieComponent(
content: LottieComponent.AppBundleContent(
name: "anim_moredots"
),
color: environment.theme.rootController.navigationBar.accentTextColor,
size: CGSize(width: 30.0, height: 30.0)
)
),
tag: settingsTag,
action: {
performAction.invoke(.openSettings)
}
)
)
)
]
if isTablet {
navigationRightItems.insert(
AnyComponentWithIdentity(
id: "bookmarks",
component: AnyComponent(
Button(
content: AnyComponent(
BundleIconComponent(
name: "Instant View/Bookmark",
tintColor: environment.theme.rootController.navigationBar.accentTextColor
)
),
action: {
performAction.invoke(.openBookmarks)
}
)
)
),
at: 0
)
navigationRightItems.insert(
AnyComponentWithIdentity(
id: "share",
component: AnyComponent(
Button(
content: AnyComponent(
BundleIconComponent(
name: "Chat List/NavigationShare",
tintColor: environment.theme.rootController.navigationBar.accentTextColor
)
),
action: {
performAction.invoke(.share)
}
)
)
),
at: 0
)
if canOpenIn {
navigationRightItems.append(
AnyComponentWithIdentity(
id: "openIn",
component: AnyComponent(
Button(
content: AnyComponent(
BundleIconComponent(
name: "Instant View/Browser",
tintColor: environment.theme.rootController.navigationBar.accentTextColor
)
),
action: {
performAction.invoke(.openIn)
}
)
)
)
)
}
}
}
}
let collapseFraction = context.component.presentationState.isSearching ? 0.0 : context.component.panelCollapseFraction
@ -155,12 +314,17 @@ private final class BrowserScreenComponent: CombinedComponent {
topInset: environment.statusBarHeight,
height: environment.navigationHeight - environment.statusBarHeight,
sideInset: environment.safeInsets.left,
metrics: environment.metrics,
externalState: navigationBarExternalState,
leftItems: navigationLeftItems,
rightItems: navigationRightItems,
centerItem: navigationContent,
readingProgress: context.component.contentState?.readingProgress ?? 0.0,
loadingProgress: context.component.contentState?.estimatedProgress,
collapseFraction: collapseFraction
collapseFraction: collapseFraction,
activate: {
performAction.invoke(.expand)
}
),
availableSize: context.availableSize,
transition: context.transition
@ -193,6 +357,7 @@ private final class BrowserScreenComponent: CombinedComponent {
textColor: environment.theme.rootController.navigationBar.primaryTextColor,
canGoBack: context.component.contentState?.canGoBack ?? false,
canGoForward: context.component.contentState?.canGoForward ?? false,
canOpenIn: canOpenIn,
performAction: performAction,
performHoldAction: performHoldAction
)
@ -207,22 +372,77 @@ private final class BrowserScreenComponent: CombinedComponent {
toolbarBottomInset = environment.safeInsets.bottom
}
let toolbar = toolbar.update(
component: BrowserToolbarComponent(
backgroundColor: environment.theme.rootController.navigationBar.blurredBackgroundColor,
separatorColor: environment.theme.rootController.navigationBar.separatorColor,
textColor: environment.theme.rootController.navigationBar.primaryTextColor,
bottomInset: toolbarBottomInset,
sideInset: environment.safeInsets.left,
item: toolbarContent,
collapseFraction: collapseFraction
),
availableSize: context.availableSize,
transition: context.transition
)
context.add(toolbar
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0))
)
var toolbarSize: CGFloat = 0.0
if isTablet && !context.component.presentationState.isSearching {
} else {
let toolbar = toolbar.update(
component: BrowserToolbarComponent(
backgroundColor: environment.theme.rootController.navigationBar.blurredBackgroundColor,
separatorColor: environment.theme.rootController.navigationBar.separatorColor,
textColor: environment.theme.rootController.navigationBar.primaryTextColor,
bottomInset: toolbarBottomInset,
sideInset: environment.safeInsets.left,
item: toolbarContent,
collapseFraction: collapseFraction
),
availableSize: context.availableSize,
transition: context.transition
)
context.add(toolbar
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0))
.appear(ComponentTransition.Appear { _, view, transition in
transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: view.frame.height), to: CGPoint(), additive: true)
})
.disappear(ComponentTransition.Disappear { view, transition, completion in
transition.animatePosition(view: view, from: CGPoint(), to: CGPoint(x: 0.0, y: view.frame.height), additive: true, completion: { _ in
completion()
})
})
)
toolbarSize = toolbar.size.height
}
if context.component.presentationState.addressFocused {
let addressListSize: CGSize
if isTablet {
addressListSize = context.availableSize
} else {
addressListSize = CGSize(width: context.availableSize.width, height: context.availableSize.height - navigationBar.size.height - toolbarSize)
}
let controller = environment.controller
let addressList = addressList.update(
component: BrowserAddressListComponent(
context: context.component.context,
theme: environment.theme,
strings: environment.strings,
insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right),
metrics: environment.metrics,
addressBarFrame: navigationBarExternalState.centerItemFrame,
performAction: performAction,
presentInGlobalOverlay: { c in
controller()?.presentInGlobalOverlay(c)
}
),
availableSize: addressListSize,
transition: context.transition
)
if isTablet {
context.add(addressList
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
} else {
context.add(addressList
.position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height + addressList.size.height / 2.0))
.clipsToBounds(true)
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
}
}
return context.availableSize
}
@ -239,6 +459,7 @@ struct BrowserPresentationState: Equatable {
var searchResultIndex: Int
var searchResultCount: Int
var searchQueryIsEmpty: Bool
var addressFocused: Bool
}
public class BrowserScreen: ViewController, MinimizableController {
@ -262,6 +483,10 @@ public class BrowserScreen: ViewController, MinimizableController {
case updateFontIsSerif(Bool)
case addBookmark
case openBookmarks
case openAddressBar
case closeAddressBar
case navigateTo(String, Bool)
case expand
}
fileprivate final class Node: ViewControllerTracingNode {
@ -271,7 +496,6 @@ public class BrowserScreen: ViewController, MinimizableController {
private let contentContainerView = UIView()
fileprivate let contentNavigationContainer = ComponentView<Empty>()
fileprivate var content: [BrowserContent] = []
fileprivate var contentState: BrowserContentState?
private var contentStateDisposable = MetaDisposable()
@ -292,13 +516,20 @@ public class BrowserScreen: ViewController, MinimizableController {
self.presentationState = BrowserPresentationState(
fontState: BrowserPresentationState.FontState(size: 100, isSerif: false),
isSearching: false, searchResultIndex: 0, searchResultCount: 0, searchQueryIsEmpty: true
isSearching: false,
searchResultIndex: 0,
searchResultCount: 0,
searchQueryIsEmpty: true,
addressFocused: false
)
super.init()
self.pushContent(controller.subject, transition: .immediate)
if let content = self.content.last {
content.addToRecentlyVisited()
}
self.performAction.connect { [weak self] action in
guard let self, let content = self.content.last, let url = self.contentState?.url else {
return
@ -341,7 +572,7 @@ public class BrowserScreen: ViewController, MinimizableController {
let text: String
var savedMessages = false
if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId {
text = presentationData.strings.WebBrowser_LinkForwardTooltip_SavedMessages_One
text = presentationData.strings.WebBrowser_LinkAddedToBookmarks
savedMessages = true
} else {
if peers.count == 1, let peer = peers.first {
@ -388,7 +619,7 @@ public class BrowserScreen: ViewController, MinimizableController {
case .openSettings:
self.openSettings()
case let .updateSearchActive(active):
self.updatePresentationState(animated: true, { state in
self.updatePresentationState(transition: .easeInOut(duration: 0.2), { state in
var updatedState = state
updatedState.isSearching = active
updatedState.searchQueryIsEmpty = true
@ -485,10 +716,38 @@ public class BrowserScreen: ViewController, MinimizableController {
content.updateFontState(self.presentationState.fontState)
case .addBookmark:
if let content = self.content.last {
self.addBookmark(content.currentState.url)
self.addBookmark(content.currentState.url, showArrow: true)
}
case .openBookmarks:
break
self.openBookmarks()
case .openAddressBar:
self.updatePresentationState(transition: .spring(duration: 0.4), { state in
var updatedState = state
updatedState.addressFocused = true
return updatedState
})
case .closeAddressBar:
self.updatePresentationState(transition: .spring(duration: 0.4), { state in
var updatedState = state
updatedState.addressFocused = false
return updatedState
})
case let .navigateTo(address, addToRecent):
if let content = self.content.last as? BrowserWebContent {
content.navigateTo(address: address)
if addToRecent {
content.addToRecentlyVisited()
}
}
self.updatePresentationState(transition: .spring(duration: 0.4), { state in
var updatedState = state
updatedState.addressFocused = false
return updatedState
})
case .expand:
if let content = self.content.last {
content.resetScrolling()
}
}
}
@ -517,16 +776,22 @@ public class BrowserScreen: ViewController, MinimizableController {
self.view.addSubview(self.contentContainerView)
}
func updatePresentationState(animated: Bool = false, _ f: (BrowserPresentationState) -> BrowserPresentationState) {
func updatePresentationState(transition: ComponentTransition = .immediate, _ f: (BrowserPresentationState) -> BrowserPresentationState) {
self.presentationState = f(self.presentationState)
self.requestLayout(transition: animated ? .easeInOut(duration: 0.2) : .immediate)
self.requestLayout(transition: transition)
}
func pushContent(_ content: BrowserScreen.Subject, transition: ComponentTransition) {
let browserContent: BrowserContent
switch content {
case let .webPage(url):
browserContent = BrowserWebContent(context: self.context, presentationData: self.presentationData, url: url)
let webContent = BrowserWebContent(context: self.context, presentationData: self.presentationData, url: url)
webContent.cancelInteractiveTransitionGestures = { [weak self] in
if let self, let view = self.controller?.view {
cancelInteractiveTransitionGestures(view: view)
}
}
browserContent = webContent
case let .instantPage(webPage, anchor, sourceLocation):
let instantPageContent = BrowserInstantPageContent(context: self.context, presentationData: self.presentationData, webPage: webPage, anchor: anchor, url: webPage.content.url ?? "", sourceLocation: sourceLocation)
instantPageContent.openPeer = { [weak self] peer in
@ -536,6 +801,10 @@ public class BrowserScreen: ViewController, MinimizableController {
self.openPeer(peer)
}
browserContent = instantPageContent
case let .document(file):
browserContent = BrowserDocumentContent(context: self.context, presentationData: self.presentationData, file: file)
case let .pdfDocument(file):
browserContent = BrowserPdfContent(context: self.context, presentationData: self.presentationData, file: file)
}
browserContent.pushContent = { [weak self] content in
guard let self else {
@ -543,6 +812,14 @@ public class BrowserScreen: ViewController, MinimizableController {
}
self.pushContent(content, transition: .spring(duration: 0.4))
}
browserContent.openAppUrl = { [weak self] url in
guard let self else {
return
}
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: false, presentationData: self.presentationData, navigationController: self.controller?.navigationController as? NavigationController, dismissInput: { [weak self] in
self?.view.window?.endEditing(true)
})
}
browserContent.present = { [weak self] c, a in
guard let self, let controller = self.controller else {
return
@ -596,7 +873,7 @@ public class BrowserScreen: ViewController, MinimizableController {
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), animated: true))
}
func addBookmark(_ url: String) {
func addBookmark(_ url: String, showArrow: Bool) {
let _ = enqueueMessages(
account: self.context.account,
peerId: self.context.account.peerId,
@ -615,7 +892,9 @@ public class BrowserScreen: ViewController, MinimizableController {
).start()
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: presentationData.strings.WebBrowser_LinkAddedToBookmarks), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in
let lastController = self.controller?.navigationController?.viewControllers.last as? ViewController
lastController?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: presentationData.strings.WebBrowser_LinkAddedToBookmarks), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in
if let self, action == .info {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
@ -708,6 +987,20 @@ public class BrowserScreen: ViewController, MinimizableController {
}, animated: true)
}
func openBookmarks() {
guard let url = self.contentState?.url else {
return
}
let controller = BrowserBookmarksScreen(context: self.context, url: url, openUrl: { [weak self] url in
if let self {
self.performAction.invoke(.navigateTo(url, true))
}
}, addBookmark: { [weak self] in
self?.addBookmark(url, showArrow: false)
})
self.controller?.push(controller)
}
func openSettings() {
guard let referenceView = self.componentHost.findTaggedView(tag: settingsTag) as? ReferenceButtonComponent.View else {
return
@ -736,7 +1029,7 @@ public class BrowserScreen: ViewController, MinimizableController {
let _ = (settings
|> deliverOnMainQueue).start(next: { [weak self] settings in
guard let self, let controller = self.controller else {
guard let self, let controller = self.controller, let contentState = self.contentState, let layout = self.validLayout?.0 else {
return
}
@ -771,7 +1064,7 @@ public class BrowserScreen: ViewController, MinimizableController {
defaultWebBrowser = "safari"
}
let url = self.contentState?.url ?? ""
let url = contentState.url
let openInOptions = availableOpenInOptions(context: self.context, item: .url(url: url))
let openInTitle: String
let openInUrl: String
@ -787,40 +1080,59 @@ public class BrowserScreen: ViewController, MinimizableController {
openInUrl = url
}
let items: [ContextMenuItem] = [
.custom(fontItem, false),
.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_FontSanFrancisco, icon: forceIsSerif ? emptyIcon : checkIcon, action: { (controller, action) in
let canOpenIn = !(self.contentState?.url.hasPrefix("tonsite") ?? false)
var items: [ContextMenuItem] = []
items.append(.custom(fontItem, false))
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_FontSanFrancisco, icon: forceIsSerif ? emptyIcon : checkIcon, action: { (controller, action) in
performAction.invoke(.updateFontIsSerif(false))
action(.default)
})),
.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_FontNewYork, textFont: .custom(font: Font.with(size: 17.0, design: .serif, traits: []), height: nil, verticalOffset: nil), icon: forceIsSerif ? checkIcon : emptyIcon, action: { (controller, action) in
})))
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_FontNewYork, textFont: .custom(font: Font.with(size: 17.0, design: .serif, traits: []), height: nil, verticalOffset: nil), icon: forceIsSerif ? checkIcon : emptyIcon, action: { (controller, action) in
performAction.invoke(.updateFontIsSerif(true))
action(.default)
})),
.separator,
.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Reload, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Reload"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
})))
items.append(.separator)
if case .webPage = contentState.contentType {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Reload, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Reload"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
performAction.invoke(.reload)
action(.default)
})),
.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Search"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
})))
}
if [.webPage].contains(contentState.contentType) {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Search"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
performAction.invoke(.updateSearchActive(true))
action(.default)
})),
.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Share, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
})))
}
if !layout.metrics.isTablet {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Share, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
performAction.invoke(.share)
action(.default)
})),
.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_AddBookmark, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
})))
}
if [.webPage, .instantPage].contains(contentState.contentType) {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_AddBookmark, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
performAction.invoke(.addBookmark)
action(.default)
})),
.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_OpenInBrowser(openInTitle).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] (controller, action) in
if let self {
self.context.sharedContext.applicationBindings.openUrl(openInUrl)
}
action(.default)
}))
]
})))
if !layout.metrics.isTablet && canOpenIn {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_OpenInBrowser(openInTitle).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] (controller, action) in
if let self {
self.context.sharedContext.applicationBindings.openUrl(openInUrl)
}
action(.default)
})))
}
}
let contextController = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))))
self.controller?.present(contextController, in: .window(.root))
@ -878,6 +1190,10 @@ public class BrowserScreen: ViewController, MinimizableController {
}
}
if update.isReset {
scrollingPanelOffsetFraction = 0.0
}
if scrollingPanelOffsetFraction != self.scrollingPanelOffsetFraction {
self.scrollingPanelOffsetFraction = scrollingPanelOffsetFraction
self.requestLayout(transition: transition)
@ -1006,7 +1322,7 @@ public class BrowserScreen: ViewController, MinimizableController {
BrowserContentComponent(
content: content,
insets: UIEdgeInsets(
top: environment.statusBarHeight,
top: layout.statusBarHeight ?? 0.0,
left: layout.safeInsets.left,
bottom: layout.intrinsicInsets.bottom,
right: layout.safeInsets.right
@ -1050,21 +1366,37 @@ public class BrowserScreen: ViewController, MinimizableController {
public enum Subject {
case webPage(url: String)
case instantPage(webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation)
case document(file: TelegramMediaFile)
case pdfDocument(file: TelegramMediaFile)
}
private let context: AccountContext
private let subject: Subject
var openPreviousOnClose = false
private var openPreviousOnClose = false
public init(context: AccountContext, subject: Subject) {
private var validLayout: ContainerViewLayout?
public static let supportedDocumentMimeTypes: [String] = [
// "text/plain",
// "text/rtf",
// "application/pdf",
// "application/msword",
// "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
// "application/vnd.ms-excel",
// "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
// "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
// "application/vnd.openxmlformats-officedocument.presentationml.presentation"
]
public init(context: AccountContext, subject: Subject, openPreviousOnClose: Bool = false) {
self.context = context
self.subject = subject
self.openPreviousOnClose = openPreviousOnClose
super.init(navigationBarPresentationData: nil)
self.navigationPresentation = .modal
self.navigationPresentation = .modalInCompactLayout
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown)
@ -1088,16 +1420,58 @@ public class BrowserScreen: ViewController, MinimizableController {
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
super.containerLayoutUpdated(layout, transition: transition)
self.node.containerLayoutUpdated(layout: layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.height, transition: ComponentTransition(transition))
var navigationHeight = self.navigationLayout(layout: layout).navigationFrame.height
if layout.metrics.isTablet, layout.size.width > layout.size.height {
navigationHeight += 6.0
}
self.node.containerLayoutUpdated(layout: layout, navigationBarHeight: navigationHeight, transition: ComponentTransition(transition))
}
public func requestMinimize(topEdgeOffset: CGFloat?, initialVelocity: CGFloat?) {
self.openPreviousOnClose = false
self.node.minimize(topEdgeOffset: topEdgeOffset, damping: 180.0, initialVelocity: initialVelocity)
}
public var isMinimized = false
private var didPlayAppearanceAnimation = false
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !self.didPlayAppearanceAnimation, let layout = self.validLayout, layout.metrics.isTablet {
self.node.layer.animatePosition(from: CGPoint(x: 0.0, y: layout.size.height), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
}
public override func dismiss(completion: (() -> Void)? = nil) {
if let layout = self.validLayout, layout.metrics.isTablet {
self.node.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: layout.size.height), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in
super.dismiss(completion: completion)
})
} else {
super.dismiss(completion: completion)
}
}
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.openPreviousOnClose, let navigationController = self.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer, let controller = minimizedContainer.controllers.last {
navigationController.maximizeViewController(controller, animated: true)
}
}
public var isMinimized = false {
didSet {
if let webContent = self.node.content.last as? BrowserWebContent {
if !self.isMinimized {
webContent.webView.setNeedsLayout()
}
}
}
}
public var isMinimizable = true
public var minimizedIcon: UIImage? {
@ -1107,6 +1481,8 @@ public class BrowserScreen: ViewController, MinimizableController {
return contentState.favicon
case .instantPage:
return UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon")?.withRenderingMode(.alwaysTemplate)
case .document:
return nil
}
}
return nil
@ -1118,6 +1494,20 @@ public class BrowserScreen: ViewController, MinimizableController {
}
return nil
}
public func makeContentSnapshotView() -> UIView? {
if let contentSnapshot = self.node.content.last?.makeContentSnapshotView(), let layout = self.validLayout {
if let wrapperView = self.view.snapshotView(afterScreenUpdates: false) {
contentSnapshot.frame = contentSnapshot.frame.offsetBy(dx: 0.0, dy: self.navigationLayout(layout: layout).navigationFrame.height)
wrapperView.addSubview(contentSnapshot)
return wrapperView
} else {
return contentSnapshot
}
} else {
return self.view.snapshotView(afterScreenUpdates: false)
}
}
}
private final class BrowserReferenceContentSource: ContextReferenceContentSource {
@ -1183,9 +1573,12 @@ private final class BrowserContentComponent: Component {
}
let collapsedHeight: CGFloat = 24.0
let topInset: CGFloat = component.insets.top + component.navigationBarHeight * (1.0 - component.scrollingPanelOffsetFraction) + collapsedHeight * component.scrollingPanelOffsetFraction
let topInset: CGFloat = component.navigationBarHeight * (1.0 - component.scrollingPanelOffsetFraction) + (component.insets.top + collapsedHeight) * component.scrollingPanelOffsetFraction
let bottomInset = (49.0 + component.insets.bottom) * (1.0 - component.scrollingPanelOffsetFraction)
component.content.updateLayout(size: availableSize, insets: UIEdgeInsets(top: topInset, left: component.insets.left, bottom: bottomInset, right: component.insets.right), transition: transition)
let insets = UIEdgeInsets(top: topInset, left: component.insets.left, bottom: bottomInset, right: component.insets.right)
let fullInsets = UIEdgeInsets(top: component.insets.top + component.navigationBarHeight, left: component.insets.left, bottom: 49.0 + component.insets.bottom, right: component.insets.right)
component.content.updateLayout(size: availableSize, insets: insets, fullInsets: fullInsets, safeInsets: component.insets, transition: transition)
transition.setFrame(view: component.content, frame: CGRect(origin: .zero, size: availableSize))
return availableSize
@ -1200,3 +1593,19 @@ private final class BrowserContentComponent: Component {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}
private func cancelInteractiveTransitionGestures(view: UIView) {
if let gestureRecognizers = view.gestureRecognizers {
for gesture in gestureRecognizers {
if let gesture = gesture as? InteractiveTransitionGestureRecognizer {
gesture.cancel()
} else if let scrollView = gesture.view as? UIScrollView, gesture.isEnabled, scrollView.tag == 0x5C4011 {
gesture.isEnabled = false
gesture.isEnabled = true
}
}
}
if let superview = view.superview {
cancelInteractiveTransitionGestures(view: superview)
}
}

View File

@ -8,6 +8,8 @@ import AccountContext
import BundleIconComponent
final class SearchBarContentComponent: Component {
public typealias EnvironmentType = BrowserNavigationBarEnvironment
let theme: PresentationTheme
let strings: PresentationStrings
let performAction: ActionSlot<BrowserScreen.Action>
@ -351,7 +353,7 @@ final class SearchBarContentComponent: Component {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<BrowserNavigationBarEnvironment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}

View File

@ -0,0 +1,85 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import SwiftSignalKit
import TelegramPresentationData
import AccountContext
import BundleIconComponent
import MultilineTextComponent
import UrlEscaping
final class TitleBarContentComponent: Component {
public typealias EnvironmentType = BrowserNavigationBarEnvironment
let theme: PresentationTheme
let title: String
init(
theme: PresentationTheme,
title: String
) {
self.theme = theme
self.title = title
}
static func ==(lhs: TitleBarContentComponent, rhs: TitleBarContentComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.title != rhs.title {
return false
}
return true
}
final class View: UIView {
private var titleContent = ComponentView<Empty>()
private var component: TitleBarContentComponent?
init() {
super.init(frame: CGRect())
}
required public init?(coder: NSCoder) {
fatalError()
}
func update(component: TitleBarContentComponent, availableSize: CGSize, environment: Environment<BrowserNavigationBarEnvironment>, transition: ComponentTransition) -> CGSize {
self.component = component
let titleSize = self.titleContent.update(
transition: transition,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.rootController.navigationBar.primaryTextColor)),
horizontalAlignment: .center,
truncationType: .end,
maximumNumberOfLines: 1
)
),
environment: {},
containerSize: CGSize(width: availableSize.width - 36.0, height: availableSize.height)
)
let titleContentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - titleSize.height) / 2.0)), size: titleSize)
if let titleContentView = self.titleContent.view {
if titleContentView.superview == nil {
self.addSubview(titleContentView)
}
transition.setPosition(view: titleContentView, position: titleContentFrame.center)
titleContentView.bounds = CGRect(origin: .zero, size: titleContentFrame.size)
}
return availableSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<BrowserNavigationBarEnvironment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
}
}

View File

@ -124,6 +124,7 @@ final class NavigationToolbarContentComponent: CombinedComponent {
let textColor: UIColor
let canGoBack: Bool
let canGoForward: Bool
let canOpenIn: Bool
let performAction: ActionSlot<BrowserScreen.Action>
let performHoldAction: (UIView, ContextGesture?, BrowserScreen.Action) -> Void
@ -132,6 +133,7 @@ final class NavigationToolbarContentComponent: CombinedComponent {
textColor: UIColor,
canGoBack: Bool,
canGoForward: Bool,
canOpenIn: Bool,
performAction: ActionSlot<BrowserScreen.Action>,
performHoldAction: @escaping (UIView, ContextGesture?, BrowserScreen.Action) -> Void
) {
@ -139,6 +141,7 @@ final class NavigationToolbarContentComponent: CombinedComponent {
self.textColor = textColor
self.canGoBack = canGoBack
self.canGoForward = canGoForward
self.canOpenIn = canOpenIn
self.performAction = performAction
self.performHoldAction = performHoldAction
}
@ -156,6 +159,9 @@ final class NavigationToolbarContentComponent: CombinedComponent {
if lhs.canGoForward != rhs.canGoForward {
return false
}
if lhs.canOpenIn != rhs.canOpenIn {
return false
}
return true
}
@ -170,10 +176,16 @@ final class NavigationToolbarContentComponent: CombinedComponent {
let availableSize = context.availableSize
let performAction = context.component.performAction
let performHoldAction = context.component.performHoldAction
let sideInset: CGFloat = 5.0
let buttonSize = CGSize(width: 50.0, height: availableSize.height)
let spacing = (availableSize.width - buttonSize.width * 5.0 - sideInset * 2.0) / 4.0
var buttonCount = 4
if context.component.canOpenIn {
buttonCount += 1
}
let spacing = (availableSize.width - buttonSize.width * CGFloat(buttonCount) - sideInset * 2.0) / CGFloat(buttonCount - 1)
let canGoBack = context.component.canGoBack
let back = back.update(
@ -269,24 +281,26 @@ final class NavigationToolbarContentComponent: CombinedComponent {
.position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width + spacing + bookmark.size.width / 2.0, y: availableSize.height / 2.0))
)
let openIn = openIn.update(
component: Button(
content: AnyComponent(
BundleIconComponent(
name: "Instant View/Browser",
tintColor: context.component.accentColor
)
),
action: {
performAction.invoke(.openIn)
}
).minSize(buttonSize),
availableSize: buttonSize,
transition: .easeInOut(duration: 0.2)
)
context.add(openIn
.position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width + spacing + bookmark.size.width + spacing + openIn.size.width / 2.0, y: availableSize.height / 2.0))
)
if context.component.canOpenIn {
let openIn = openIn.update(
component: Button(
content: AnyComponent(
BundleIconComponent(
name: "Instant View/Browser",
tintColor: context.component.accentColor
)
),
action: {
performAction.invoke(.openIn)
}
).minSize(buttonSize),
availableSize: buttonSize,
transition: .easeInOut(duration: 0.2)
)
context.add(openIn
.position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width + spacing + bookmark.size.width + spacing + openIn.size.width / 2.0, y: availableSize.height / 2.0))
)
}
return availableSize
}

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +0,0 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import TelegramCore
import AccountContext
import Svg
private var faviconCache: [String: UIImage] = [:]
func fetchFavicon(context: AccountContext, url: String, size: CGSize) -> Signal<UIImage?, NoError> {
if let icon = faviconCache[url] {
return .single(icon)
}
return context.engine.resources.httpData(url: url)
|> map(Optional.init)
|> `catch` { _ -> Signal<Data?, NoError> in
return .single(nil)
}
|> map { data in
if let data {
if let image = UIImage(data: data) {
return image
} else if url.lowercased().contains(".svg"), let preparedData = prepareSvgImage(data, false), let image = renderPreparedImage(preparedData, size, .clear, UIScreenScale, false) {
return image
}
return nil
} else {
return nil
}
}
|> beforeNext { image in
if let image {
Queue.mainQueue().async {
faviconCache[url] = image
}
}
}
}

View File

@ -0,0 +1,174 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import TelegramPresentationData
import MultilineTextComponent
final class SectionHeaderComponent: Component {
enum Style {
case blocks
case plain
}
let theme: PresentationTheme
let style: Style
let title: String
let insets: UIEdgeInsets
let actionTitle: String?
let action: (() -> Void)?
init(
theme: PresentationTheme,
style: Style,
title: String,
insets: UIEdgeInsets,
actionTitle: String?,
action: (() -> Void)?
) {
self.theme = theme
self.style = style
self.title = title
self.insets = insets
self.actionTitle = actionTitle
self.action = action
}
static func ==(lhs: SectionHeaderComponent, rhs: SectionHeaderComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.style != rhs.style {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.insets != rhs.insets {
return false
}
if lhs.actionTitle != rhs.actionTitle {
return false
}
return true
}
final class View: UIView {
private let title = ComponentView<Empty>()
private let backgroundView: BlurredBackgroundView
private let action = ComponentView<Empty>()
private var component: SectionHeaderComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
super.init(frame: frame)
self.addSubview(self.backgroundView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: SectionHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
self.component = component
self.state = state
let height: CGFloat = 28.0
let leftInset: CGFloat = 16.0 + component.insets.left
let rightInset: CGFloat = 0.0
let previousTitleFrame = self.title.view?.frame
if themeUpdated {
switch component.style {
case .plain:
self.backgroundView.isHidden = false
self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
case .blocks:
self.backgroundView.isHidden = true
}
}
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.regular(13.0), textColor: component.theme.list.itemSecondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.addSubview(titleView)
}
titleView.frame = titleFrame
if let previousTitleFrame, previousTitleFrame.origin.x != titleFrame.origin.x {
transition.animatePosition(view: titleView, from: CGPoint(x: previousTitleFrame.origin.x - titleFrame.origin.x, y: 0.0), to: CGPoint(), additive: true)
}
}
if let actionTitle = component.actionTitle {
let actionSize = self.action.update(
transition: .immediate,
component: AnyComponent(
Button(content: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: actionTitle, font: Font.regular(13.0), textColor: component.theme.list.itemSecondaryTextColor))
)), action: { [weak self] in
if let self, let component = self.component {
component.action?()
}
})
),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
if let view = self.action.view {
if view.superview == nil {
self.addSubview(view)
if !transition.animation.isImmediate {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
}
}
let actionFrame = CGRect(origin: CGPoint(x: availableSize.width - leftInset - actionSize.width, y: floor((height - titleSize.height) / 2.0)), size: actionSize)
view.frame = actionFrame
}
} else if let view = self.action.view, view.superview != nil {
if !transition.animation.isImmediate {
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { finished in
if finished {
view.removeFromSuperview()
view.layer.removeAllAnimations()
}
})
view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
} else {
view.removeFromSuperview()
}
}
let size = CGSize(width: availableSize.width, height: height)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundView.update(size: size, transition: transition.containedViewLayoutTransition)
return size
}
}
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)
}
}

View File

@ -0,0 +1,110 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import AccountContext
import TextFormat
import UrlWhitelist
import Svg
private var faviconCache: [String: UIImage] = [:]
func fetchFavicon(context: AccountContext, url: String, size: CGSize) -> Signal<UIImage?, NoError> {
if let icon = faviconCache[url] {
return .single(icon)
}
return context.engine.resources.httpData(url: url)
|> map(Optional.init)
|> `catch` { _ -> Signal<Data?, NoError> in
return .single(nil)
}
|> map { data in
if let data {
if let image = UIImage(data: data) {
return image
} else if url.lowercased().contains(".svg"), let preparedData = prepareSvgImage(data, false), let image = renderPreparedImage(preparedData, size, .clear, UIScreenScale, false) {
return image
}
return nil
} else {
return nil
}
}
|> beforeNext { image in
if let image {
Queue.mainQueue().async {
faviconCache[url] = image
}
}
}
}
func getPrimaryUrl(message: Message) -> String? {
var primaryUrl: String?
if let webPage = message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage, let url = webPage.content.url {
primaryUrl = url
} else {
var entities = message.textEntitiesAttribute?.entities
if entities == nil {
let parsedEntities = generateTextEntities(message.text, enabledTypes: .all)
if !parsedEntities.isEmpty {
entities = parsedEntities
}
}
if let entities {
loop: for entity in entities {
switch entity.type {
case .Url, .Email:
var range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
let nsString = message.text as NSString
if range.location + range.length > nsString.length {
range.location = max(0, nsString.length - range.length)
range.length = nsString.length - range.location
}
let tempUrlString = nsString.substring(with: range)
var (urlString, concealed) = parseUrl(url: tempUrlString, wasConcealed: false)
var parsedUrl = URL(string: urlString)
if (parsedUrl == nil || parsedUrl!.host == nil || parsedUrl!.host!.isEmpty) && !urlString.contains("@") {
urlString = "http://" + urlString
parsedUrl = URL(string: urlString)
}
var host: String? = concealed ? urlString : parsedUrl?.host
if host == nil {
host = urlString
}
if let _ = parsedUrl, let _ = host {
primaryUrl = urlString
}
break loop
case let .TextUrl(url):
let messageText = message.text
var range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
let nsString = messageText as NSString
if range.location + range.length > nsString.length {
range.location = max(0, nsString.length - range.length)
range.length = nsString.length - range.location
}
var (urlString, concealed) = parseUrl(url: url, wasConcealed: false)
var parsedUrl = URL(string: urlString)
if (parsedUrl == nil || parsedUrl!.host == nil || parsedUrl!.host!.isEmpty) && !urlString.contains("@") {
urlString = "http://" + urlString
parsedUrl = URL(string: urlString)
}
let host: String? = concealed ? urlString : parsedUrl?.host
if let _ = parsedUrl, let _ = host {
primaryUrl = urlString
}
break loop
default:
break
}
}
}
}
return primaryUrl
}

View File

@ -149,10 +149,13 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
})
})))
items.append(.separator)
case .popularApps:
break
}
}
if case .search(.recentApps) = source {
} else if case .search(.popularApps) = source {
} else {
let isSavedMessages = peerId == context.account.peerId

View File

@ -330,6 +330,10 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
}
self.paneContainerNode.requesDismissInput = {
parentController()?.view.endEditing(true)
}
self.filterContainerNode.filterPressed = { [weak self] filter in
guard let strongSelf = self else {
return

View File

@ -88,8 +88,7 @@ private final class ItemNode: ASDisplayNode {
title = presentationData.strings.ChatList_Search_FilterChannels
icon = nil
case .apps:
//TODO:localize
title = "Apps"
title = presentationData.strings.ChatList_Search_FilterApps
icon = nil
case .media:
title = presentationData.strings.ChatList_Search_FilterMedia

View File

@ -34,7 +34,7 @@ import AvatarNode
private enum ChatListRecentEntryStableId: Hashable {
case topPeers
case peerId(EnginePeer.Id)
case peerId(EnginePeer.Id, ChatListRecentEntry.Section)
}
private enum ChatListRecentEntry: Comparable, Identifiable {
@ -51,8 +51,8 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
switch self {
case .topPeers:
return .topPeers
case let .peer(_, peer, _, _, _, _, _, _, _, _, _):
return .peerId(peer.peer.peerId)
case let .peer(_, peer, section, _, _, _, _, _, _, _, _):
return .peerId(peer.peer.peerId, section)
}
}
@ -254,16 +254,15 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
}
}
} else if case .apps = key {
//TODO:localize
if case .popularApps = section {
header = ChatListSearchItemHeader(type: .text("POPULAR APPS", 1), theme: theme, strings: strings)
header = ChatListSearchItemHeader(type: .text(presentationData.strings.ChatList_Search_SectionPopularApps, 1), theme: theme, strings: strings)
} else {
if let isChannelsTabExpanded {
header = ChatListSearchItemHeader(type: .text("APPS YOU USE", 0), theme: theme, strings: strings, actionTitle: isChannelsTabExpanded ? presentationData.strings.ChatList_Search_SectionActionShowLess : presentationData.strings.ChatList_Search_SectionActionShowMore, action: {
header = ChatListSearchItemHeader(type: .text(presentationData.strings.ChatList_Search_SectionRecentApps, 0), theme: theme, strings: strings, actionTitle: isChannelsTabExpanded ? presentationData.strings.ChatList_Search_SectionActionShowLess : presentationData.strings.ChatList_Search_SectionActionShowMore, action: {
toggleChannelsTabExpanded()
})
} else {
header = ChatListSearchItemHeader(type: .text("APPS YOU USE", 0), theme: theme, strings: strings, actionTitle: nil, action: nil)
header = ChatListSearchItemHeader(type: .text(presentationData.strings.ChatList_Search_SectionRecentApps, 0), theme: theme, strings: strings, actionTitle: nil, action: nil)
}
}
} else {
@ -298,13 +297,17 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
}
},
deletePeer: deletePeer,
contextAction: (key == .channels) ? nil : peerContextAction.flatMap { peerContextAction in
contextAction: (key == .channels || section == .popularApps) ? nil : peerContextAction.flatMap { peerContextAction in
return { node, gesture, location in
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
let source: ChatListSearchContextActionSource
if key == .apps {
source = .recentApps
if case .popularApps = section {
source = .popularApps
} else {
source = .recentApps
}
} else {
source = .recentSearch
}
@ -1081,6 +1084,7 @@ public enum ChatListSearchContextActionSource {
case recentPeers
case recentSearch
case recentApps
case popularApps
case search(EngineMessage.Id?)
}
@ -1449,8 +1453,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if key == .channels {
emptyRecentTextNode.attributedText = NSAttributedString(string: presentationData.strings.ChatList_Search_RecommendedChannelsEmpty_Text, font: Font.regular(15.0), textColor: presentationData.theme.list.freeTextColor)
} else if key == .apps {
//TODO:localize
emptyRecentTextNode.attributedText = NSAttributedString(string: "No Apps Found", font: Font.regular(15.0), textColor: presentationData.theme.list.freeTextColor)
emptyRecentTextNode.attributedText = NSAttributedString(string: presentationData.strings.ChatList_Search_Apps_Empty_Text, font: Font.regular(15.0), textColor: presentationData.theme.list.freeTextColor)
}
self.emptyRecentTextNode = emptyRecentTextNode
@ -3412,7 +3415,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
continue
}
let peerNotificationSettings = notificationSettings[id]
//TODO:localize
let subpeerSummary: RecentlySearchedPeerSubpeerSummary? = nil
var peerStoryStats: PeerStoryStats?
if let value = storyStats[peer.id] {
@ -3543,9 +3545,19 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} else if case .apps = key {
if let navigationController = self.navigationController {
if isRecommended {
#if DEBUG
let _ = (self.context.sharedContext.makeMiniAppListScreenInitialData(context: self.context)
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
guard let self, let navigationController = self.navigationController else {
return
}
navigationController.pushViewController(self.context.sharedContext.makeMiniAppListScreen(context: self.context, initialData: initialData))
})
#else
if let peerInfoScreen = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
navigationController.pushViewController(peerInfoScreen)
}
#endif
} else if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.parentController {
self.context.sharedContext.openWebApp(
context: self.context,
@ -3560,7 +3572,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
skipTermsOfService: true
)
} else {
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
navigationController: navigationController,
context: self.context,

View File

@ -192,6 +192,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
var currentPaneUpdated: ((ChatListSearchPaneKey?, CGFloat, ContainedViewLayoutTransition) -> Void)?
var requestExpandTabs: (() -> Bool)?
var requesDismissInput: (() -> Void)?
private var currentAvailablePanes: [ChatListSearchPaneKey]?
@ -227,12 +228,20 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
}
if case .apps = key {
self.requesDismissInput?()
}
} else if self.pendingSwitchToPaneKey != key {
self.pendingSwitchToPaneKey = key
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
}
if case .apps = key {
self.requesDismissInput?()
}
}
}
@ -322,6 +331,10 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
let switchToKey = availablePanes[updatedIndex]
if switchToKey != self.currentPaneKey && self.currentPanes[switchToKey] != nil{
self.currentPaneKey = switchToKey
if case .apps = switchToKey {
self.requesDismissInput?()
}
}
}
self.transitionFraction = 0.0

View File

@ -93,7 +93,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case knockoutWallpaper(PresentationTheme, Bool)
case experimentalCompatibility(Bool)
case enableDebugDataDisplay(Bool)
case acceleratedStickers(Bool)
case rippleEffect(Bool)
case browserExperiment(Bool)
case localTranscription(Bool)
case enableReactionOverrides(Bool)
@ -127,7 +127,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2:
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2:
return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue
@ -216,7 +216,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 37
case .enableDebugDataDisplay:
return 38
case .acceleratedStickers:
case .rippleEffect:
return 39
case .browserExperiment:
return 40
@ -1228,12 +1228,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .acceleratedStickers(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Accelerated Stickers", value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .rippleEffect(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Ripple", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.acceleratedStickers = value
settings.rippleEffect = value
return PreferencesEntry(settings)
})
}).start()
@ -1452,7 +1452,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility))
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
entries.append(.rippleEffect(experimentalSettings.rippleEffect))
#if DEBUG
entries.append(.browserExperiment(experimentalSettings.browserExperiment))
#else

View File

@ -353,24 +353,26 @@ public final class DeviceAccess {
} else {
completion(true)
}
} else if [.restricted, .denied].contains(status), let presentationData = presentationData {
let text: String
if case .restricted = status {
text = presentationData.strings.AccessDenied_CameraRestricted
} else {
switch cameraSubject {
case .video:
text = presentationData.strings.AccessDenied_Camera
case .videoCall:
text = presentationData.strings.AccessDenied_VideoCallCamera
case .qrCode:
text = presentationData.strings.AccessDenied_QrCamera
}
}
} else if [.restricted, .denied].contains(status) {
completion(false)
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
openSettings()
})]), nil)
if let presentationData = presentationData {
let text: String
if case .restricted = status {
text = presentationData.strings.AccessDenied_CameraRestricted
} else {
switch cameraSubject {
case .video:
text = presentationData.strings.AccessDenied_Camera
case .videoCall:
text = presentationData.strings.AccessDenied_VideoCallCamera
case .qrCode:
text = presentationData.strings.AccessDenied_QrCamera
}
}
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
openSettings()
})]), nil)
}
} else if case .authorized = status {
completion(true)
} else {

View File

@ -90,6 +90,10 @@ public struct ContainerViewLayout: Equatable {
return ContainerViewLayout(size: self.size, metrics: self.metrics, deviceMetrics: self.deviceMetrics, intrinsicInsets: intrinsicInsets, safeInsets: self.safeInsets, additionalInsets: self.additionalInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver)
}
public func withUpdatedAdditionalInsets(_ additionalInsets: UIEdgeInsets) -> ContainerViewLayout {
return ContainerViewLayout(size: self.size, metrics: self.metrics, deviceMetrics: self.deviceMetrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, additionalInsets: additionalInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver)
}
public func withUpdatedInputHeight(_ inputHeight: CGFloat?) -> ContainerViewLayout {
return ContainerViewLayout(size: self.size, metrics: self.metrics, deviceMetrics: self.deviceMetrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, additionalInsets: self.additionalInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver)
}

View File

@ -30,6 +30,10 @@ public protocol MinimizableController: ViewController {
func requestMinimize(topEdgeOffset: CGFloat?, initialVelocity: CGFloat?)
func makeContentSnapshotView() -> UIView?
func prepareContentSnapshotView()
func resetContentSnapshotView()
func shouldDismissImmediately() -> Bool
}
@ -66,6 +70,14 @@ public extension MinimizableController {
return self.displayNode.view.snapshotView(afterScreenUpdates: false)
}
func prepareContentSnapshotView() {
}
func resetContentSnapshotView() {
}
func shouldDismissImmediately() -> Bool {
return true
}

View File

@ -443,7 +443,20 @@ open class NavigationController: UINavigationController, ContainableController,
globalScrollToTopNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: layout.size.width, height: 1.0))
}
let overlayContainerLayout = layout
var overlayContainerLayout = layout
var updatedSize = layout.size
var updatedIntrinsicInsets = layout.intrinsicInsets
var updatedAdditionalInsets = layout.additionalInsets
if let minimizedContainer = self.minimizedContainer {
if (layout.inputHeight ?? 0.0).isZero {
let minimizedContainerHeight = minimizedContainer.collapsedHeight(layout: layout)
updatedSize.height -= minimizedContainerHeight
updatedIntrinsicInsets.bottom = 0.0
updatedAdditionalInsets.bottom += minimizedContainerHeight
}
}
overlayContainerLayout = overlayContainerLayout.withUpdatedAdditionalInsets(updatedAdditionalInsets)
if let inCallStatusBar = self.inCallStatusBar {
let isLandscape = layout.size.width > layout.size.height
@ -843,8 +856,6 @@ open class NavigationController: UINavigationController, ContainableController,
layout.additionalInsets.left = max(layout.intrinsicInsets.left, additionalSideInsets.left)
layout.additionalInsets.right = max(layout.intrinsicInsets.right, additionalSideInsets.right)
var updatedSize = layout.size
var updatedIntrinsicInsets = layout.intrinsicInsets
if case .flat = navigationLayout.root, let minimizedContainer = self.minimizedContainer {
if minimizedContainer.supernode !== self.displayNode {
if let rootContainer = self.rootContainer, case let .flat(flatContainer) = rootContainer {
@ -857,10 +868,6 @@ open class NavigationController: UINavigationController, ContainableController,
self.displayNode.insertSubnode(minimizedContainer, at: 0)
}
}
if (layout.inputHeight ?? 0.0).isZero {
updatedSize.height -= minimizedContainer.collapsedHeight(layout: layout)
updatedIntrinsicInsets.bottom = 0.0
}
}
switch navigationLayout.root {

View File

@ -50,6 +50,15 @@ func makeNavigationLayout(mode: NavigationControllerMode, layout: ContainerViewL
case .regular:
requiresModal = true
}
case .modalInCompactLayout:
switch layout.metrics.widthClass {
case .compact:
requiresModal = true
case .regular:
requiresModal = true
beginsModal = true
isFlat = true
}
}
if requiresModal {
controller._presentedInModal = true

View File

@ -90,6 +90,7 @@ final class NavigationModalContainer: ASDisplayNode, ASScrollViewDelegate, ASGes
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.clipsToBounds = false
self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
self.scrollNode.view.tag = 0x5C4011
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
guard let strongSelf = self, !strongSelf.isDismissed else {

View File

@ -47,6 +47,13 @@ open class PortalSourceView: UIView {
}
}
public func removePortal(view: PortalView) {
if let index = self.portalReferences.firstIndex(where: { $0.portalView === view }) {
self.portalReferences.remove(at: index)
}
view.disablePortal()
}
func setGlobalPortal(view: GlobalPortalView?) {
if let globalPortalView = self.globalPortalView {
self.globalPortalView = nil

View File

@ -23,6 +23,11 @@ public class PortalView {
}
}
func disablePortal() {
self.view.sourceView = nil
self.sourceView = nil
}
public func reloadPortal() {
if let sourceView = self.sourceView as? PortalSourceView {
self.reloadPortal(sourceView: sourceView)

View File

@ -65,6 +65,7 @@ public enum ViewControllerNavigationPresentation {
case flatModal
case standaloneModal
case modalInLargeLayout
case modalInCompactLayout
}
public enum TabBarItemContextActionType {

View File

@ -97,6 +97,7 @@ swift_library(
"//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState",
"//submodules/StickerPackPreviewUI:StickerPackPreviewUI",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/LottieComponentResourceContent",
"//submodules/ImageTransparency",
"//submodules/GalleryUI",
"//submodules/MediaPlayer:UniversalMediaPlayer",

View File

@ -209,7 +209,7 @@ public final class DrawingLinkEntityView: DrawingEntityView, UITextViewDelegate
if !self.linkEntity.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
string = self.linkEntity.name.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
} else {
string = self.linkEntity.url.uppercased()
string = self.linkEntity.url.uppercased().replacingOccurrences(of: "http://", with: "").replacingOccurrences(of: "https://", with: "")
}
let text = NSMutableAttributedString(string: string)
let range = NSMakeRange(0, text.length)

View File

@ -3212,10 +3212,12 @@ public final class DrawingToolsInteraction {
self.isActive = false
}
public func insertEntity(_ entity: DrawingEntity, scale: CGFloat? = nil, position: CGPoint? = nil) {
public func insertEntity(_ entity: DrawingEntity, scale: CGFloat? = nil, position: CGPoint? = nil, select: Bool = true) {
self.entitiesView.prepareNewEntity(entity, scale: scale, position: position)
self.entitiesView.add(entity)
self.entitiesView.selectEntity(entity, animate: !(entity is DrawingTextEntity))
if select {
self.entitiesView.selectEntity(entity, animate: !(entity is DrawingTextEntity))
}
if let entityView = self.entitiesView.getView(for: entity.uuid) {
if let textEntityView = entityView as? DrawingTextEntityView {

View File

@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import AccountContext
import TelegramCore
@ -9,6 +10,8 @@ import TelegramAnimatedStickerNode
import StickerResources
import MediaEditor
import TelegramStringFormatting
import LottieComponent
import LottieComponentResourceContent
private func generateIcon(style: DrawingWeatherEntity.Style) -> UIImage? {
guard let image = UIImage(bundleImageName: "Chat/Attach Menu/Location") else {
@ -53,9 +56,8 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
let backgroundView: UIView
let textView: DrawingTextView
let iconView: UIImageView
private let imageNode: TransformImageNode
private var animationNode: AnimatedStickerNode?
private var animation = ComponentView<Empty>()
private var didSetUpAnimationNode = false
private let stickerFetchedDisposable = MetaDisposable()
@ -88,20 +90,14 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
self.textView.spellCheckingType = .no
self.textView.textContainer.maximumNumberOfLines = 2
self.textView.textContainer.lineBreakMode = .byTruncatingTail
self.iconView = UIImageView()
self.imageNode = TransformImageNode()
super.init(context: context, entity: entity)
self.textView.delegate = self
self.addSubview(self.backgroundView)
self.addSubview(self.textView)
self.addSubview(self.iconView)
self.update(animated: false)
self.setup()
}
required init?(coder: NSCoder) {
@ -134,17 +130,35 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
let iconSize = min(80.0, floor(self.bounds.height * 0.7))
let iconOffset: CGFloat = 0.3
self.iconView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconSize * iconOffset), y: floorToScreenPixels((self.bounds.height - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize))
self.imageNode.frame = self.iconView.frame.offsetBy(dx: 0.0, dy: 2.0)
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconSize * iconOffset), y: floorToScreenPixels((self.bounds.height - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize))
if let animationNode = self.animationNode {
animationNode.frame = self.iconView.frame
animationNode.updateLayout(size: self.iconView.frame.size)
if let icon = self.weatherEntity.icon {
let _ = self.animation.update(
transition: .immediate,
component: AnyComponent(
LottieComponent(
content: LottieComponent.ResourceContent(
context: self.context,
file: icon,
attemptSynchronously: true,
providesPlaceholder: true
),
color: nil,
placeholderColor: UIColor(rgb: 0x000000, alpha: 0.1),
loop: !["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"].contains(self.weatherEntity.emoji)
)
),
environment: {},
containerSize: iconFrame.size
)
if let animationView = self.animation.view {
if animationView.superview == nil {
self.addSubview(animationView)
}
animationView.frame = iconFrame
}
}
let imageSize = CGSize(width: iconSize, height: iconSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
self.textView.frame = CGRect(origin: CGPoint(x: self.bounds.width - self.textSize.width - 6.0, y: floorToScreenPixels((self.bounds.height - self.textSize.height) / 2.0)), size: self.textSize)
self.backgroundView.frame = self.bounds
}
@ -263,10 +277,8 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
self.sizeToFit()
if self.currentStyle != self.weatherEntity.style {
self.currentStyle = self.weatherEntity.style
self.iconView.image = generateIcon(style: self.weatherEntity.style)
}
self.currentStyle = self.weatherEntity.style
self.backgroundView.layer.cornerRadius = self.textSize.height * 0.2
if #available(iOS 13.0, *) {
@ -276,42 +288,6 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
super.update(animated: animated)
}
private func setup() {
if let file = self.weatherEntity.icon {
self.iconView.isHidden = true
self.addSubnode(self.imageNode)
if let dimensions = file.dimensions {
if file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm" {
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0))
if self.animationNode == nil {
let animationNode = DefaultAnimatedStickerNodeImpl()
animationNode.autoplay = true
self.animationNode = animationNode
animationNode.started = { [weak self] in
self?.imageNode.isHidden = true
}
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
self.addSubnode(animationNode)
}
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, userLocation: .other, file: file, small: false, size: fittedDimensions))
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: file.resource).start())
} else {
if let animationNode = self.animationNode {
animationNode.visibility = false
self.animationNode = nil
animationNode.removeFromSupernode()
self.imageNode.isHidden = false
self.didSetUpAnimationNode = false
}
self.imageNode.setSignal(chatMessageSticker(account: self.context.account, userLocation: .other, file: file, small: false, synchronousLoad: false))
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: false)).start())
}
self.setNeedsLayout()
}
}
}
override func updateSelectionView() {
guard let selectionView = self.selectionView as? DrawingWeatherEntitySelectionView else {
return

View File

@ -264,6 +264,10 @@ private let darkTheme = InstantPageTheme(
private func fontSizeMultiplierForVariant(_ variant: InstantPagePresentationFontSize) -> CGFloat {
switch variant {
case .xxsmall:
return 0.5
case .xsmall:
return 0.75
case .small:
return 0.85
case .standard:
@ -271,7 +275,7 @@ private func fontSizeMultiplierForVariant(_ variant: InstantPagePresentationFont
case .large:
return 1.15
case .xlarge:
return 1.3
return 1.25
case .xxlarge:
return 1.5
}

View File

@ -1,4 +1,4 @@
wimport Foundation
import Foundation
import UIKit
import AsyncDisplayKit
import Display

View File

@ -16,6 +16,7 @@ import UrlWhitelist
import AccountContext
import TelegramStringFormatting
import WallpaperResources
import UrlEscaping
private let iconFont = Font.with(size: 30.0, design: .round, weight: .bold)
@ -333,7 +334,21 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
mutableDescriptionText.append(NSAttributedString(string: text + "\n", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor))
}
let plainUrlString = NSAttributedString(string: content.url.replacingOccurrences(of: "https://", with: ""), font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemAccentColor)
var address = content.url
if let components = URLComponents(string: address) {
if #available(iOS 16.0, *), let encodedHost = components.encodedHost {
if let decodedHost = components.host, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
} else if let encodedHost = components.host {
if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
}
}
address = address.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
let plainUrlString = NSAttributedString(string: address.replacingOccurrences(of: "https://", with: "").replacingOccurrences(of: "tonsite://", with: ""), font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemAccentColor)
let urlString = NSMutableAttributedString()
urlString.append(plainUrlString)
urlString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.URL), value: content.url, range: NSMakeRange(0, urlString.length))
@ -397,8 +412,13 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
let rawUrlString = urlString
var parsedUrl = URL(string: urlString)
if (parsedUrl == nil || parsedUrl!.host == nil || parsedUrl!.host!.isEmpty) && !urlString.contains("@") {
urlString = "http://" + urlString
parsedUrl = URL(string: urlString)
if let mappedURL = URL(string: "https://\(urlString)"), let host = mappedURL.host, host.lowercased().hasSuffix(".ton") {
urlString = "tonsite://" + urlString
parsedUrl = URL(string: urlString)
} else {
urlString = "http://" + urlString
parsedUrl = URL(string: urlString)
}
}
var host: String? = concealed ? urlString : parsedUrl?.host
if host == nil {
@ -432,8 +452,23 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
mutableDescriptionText.append(NSAttributedString(string: messageText + "\n", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor))
}
var address = urlString
if let components = URLComponents(string: address) {
if #available(iOS 16.0, *), let encodedHost = components.encodedHost {
if let decodedHost = components.host, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
} else if let encodedHost = components.host {
if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
}
}
address = address.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
urlString = address
let urlAttributedString = NSMutableAttributedString()
urlAttributedString.append(NSAttributedString(string: urlString.replacingOccurrences(of: "https://", with: ""), font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemAccentColor))
urlAttributedString.append(NSAttributedString(string: urlString.replacingOccurrences(of: "https://", with: "").replacingOccurrences(of: "tonsite://", with: ""), font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemAccentColor))
if item.presentationData.theme.theme.list.itemAccentColor.isEqual(item.presentationData.theme.theme.list.itemPrimaryTextColor) {
urlAttributedString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: NSMakeRange(0, urlAttributedString.length))
}
@ -465,8 +500,13 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
let rawUrlString = urlString
var parsedUrl = URL(string: urlString)
if (parsedUrl == nil || parsedUrl!.host == nil || parsedUrl!.host!.isEmpty) && !urlString.contains("@") {
urlString = "http://" + urlString
parsedUrl = URL(string: urlString)
if let mappedURL = URL(string: "https://\(urlString)"), let host = mappedURL.host, host.lowercased().hasSuffix(".ton") {
urlString = "tonsite://" + urlString
parsedUrl = URL(string: urlString)
} else {
urlString = "http://" + urlString
parsedUrl = URL(string: urlString)
}
}
let host: String? = concealed ? urlString : parsedUrl?.host
if let url = parsedUrl, let host = host {
@ -487,8 +527,23 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
mutableDescriptionText.append(NSAttributedString(string: messageText + "\n", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor))
}
var address = urlString
if let components = URLComponents(string: address) {
if #available(iOS 16.0, *), let encodedHost = components.encodedHost {
if let decodedHost = components.host, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
} else if let encodedHost = components.host {
if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
}
}
address = address.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
urlString = address
let urlAttributedString = NSMutableAttributedString()
urlAttributedString.append(NSAttributedString(string: urlString.replacingOccurrences(of: "https://", with: ""), font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemAccentColor))
urlAttributedString.append(NSAttributedString(string: urlString.replacingOccurrences(of: "https://", with: "").replacingOccurrences(of: "tonsite://", with: ""), font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemAccentColor))
if item.presentationData.theme.theme.list.itemAccentColor.isEqual(item.presentationData.theme.theme.list.itemPrimaryTextColor) {
urlAttributedString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: NSMakeRange(0, urlAttributedString.length))
}

View File

@ -1799,9 +1799,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.titleView.title = collection.localizedTitle ?? presentationData.strings.Attachment_Gallery
} else {
switch mode {
case .default, .createSticker:
case .default:
self.titleView.title = presentationData.strings.MediaPicker_Recents
self.titleView.isEnabled = true
case .createSticker:
self.titleView.title = presentationData.strings.MediaPicker_Recents
self.titleView.subtitle = presentationData.strings.MediaPicker_CreateSticker
self.titleView.isEnabled = true
case .story:
self.titleView.title = presentationData.strings.MediaPicker_Recents
self.titleView.isEnabled = true

View File

@ -10,12 +10,14 @@ final class MediaPickerTitleView: UIView {
let contextSourceNode: ContextReferenceContentNode
private let buttonNode: HighlightTrackingButtonNode
private let titleNode: ImmediateTextNode
private let subtitleNode: ImmediateTextNode
private let arrowNode: ASImageNode
private let segmentedControlNode: SegmentedControlNode
public var theme: PresentationTheme {
didSet {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme))
}
}
@ -23,7 +25,16 @@ final class MediaPickerTitleView: UIView {
public var title: String = "" {
didSet {
if self.title != oldValue {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)
self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor)
self.setNeedsLayout()
}
}
}
public var subtitle: String = "" {
didSet {
if self.subtitle != oldValue {
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
self.setNeedsLayout()
}
}
@ -36,7 +47,7 @@ final class MediaPickerTitleView: UIView {
}
}
public func updateTitle(title: String, isEnabled: Bool, animated: Bool) {
public func updateTitle(title: String, subtitle: String = "", isEnabled: Bool, animated: Bool) {
if animated {
if self.title != title {
if let snapshotView = self.titleNode.view.snapshotContentTree() {
@ -49,6 +60,17 @@ final class MediaPickerTitleView: UIView {
self.titleNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
if self.subtitle != subtitle {
if let snapshotView = self.subtitleNode.view.snapshotContentTree() {
snapshotView.frame = self.subtitleNode.frame
self.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
snapshotView.removeFromSuperview()
})
self.subtitleNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
if self.isEnabled != isEnabled {
if let snapshotView = self.arrowNode.view.snapshotContentTree() {
snapshotView.frame = self.arrowNode.frame
@ -62,6 +84,7 @@ final class MediaPickerTitleView: UIView {
}
}
self.title = title
self.subtitle = subtitle
self.isEnabled = isEnabled
}
@ -76,6 +99,7 @@ final class MediaPickerTitleView: UIView {
if self.segmentsHidden != oldValue {
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
transition.updateAlpha(node: self.titleNode, alpha: self.segmentsHidden ? 1.0 : 0.0)
transition.updateAlpha(node: self.subtitleNode, alpha: self.segmentsHidden ? 1.0 : 0.0)
transition.updateAlpha(node: self.arrowNode, alpha: self.segmentsHidden ? 1.0 : 0.0)
transition.updateAlpha(node: self.segmentedControlNode, alpha: self.segmentsHidden ? 0.0 : 1.0)
self.segmentedControlNode.isUserInteractionEnabled = !self.segmentsHidden
@ -115,6 +139,9 @@ final class MediaPickerTitleView: UIView {
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
self.subtitleNode = ImmediateTextNode()
self.subtitleNode.displaysAsynchronously = false
self.arrowNode = ASImageNode()
self.arrowNode.displaysAsynchronously = false
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/DownArrow"), color: theme.rootController.navigationBar.secondaryTextColor)
@ -145,6 +172,7 @@ final class MediaPickerTitleView: UIView {
self.addSubnode(self.contextSourceNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode)
self.addSubnode(self.arrowNode)
self.addSubnode(self.buttonNode)
self.addSubnode(self.segmentedControlNode)
@ -166,10 +194,18 @@ final class MediaPickerTitleView: UIView {
self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - controlSize.width) / 2.0), y: floorToScreenPixels((size.height - controlSize.height) / 2.0)), size: controlSize)
let titleSize = self.titleNode.updateLayout(CGSize(width: 210.0, height: 44.0))
self.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize)
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: 210.0, height: 44.0))
var totalHeight: CGFloat = titleSize.height
if subtitleSize.height > 0.0 {
totalHeight += subtitleSize.height
}
self.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - totalHeight) / 2.0)), size: titleSize)
self.subtitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSize.width) / 2.0), y: floorToScreenPixels((size.height - totalHeight) / 2.0) + subtitleSize.height + 7.0), size: subtitleSize)
if let arrowSize = self.arrowNode.image?.size {
self.arrowNode.frame = CGRect(origin: CGPoint(x: self.titleNode.frame.maxX + 5.0, y: floorToScreenPixels((size.height - arrowSize.height) / 2.0) + 1.0 - UIScreenPixel), size: arrowSize)
self.arrowNode.frame = CGRect(origin: CGPoint(x: self.titleNode.frame.maxX + 5.0, y: floorToScreenPixels((size.height - totalHeight) / 2.0) + titleSize.height / 2.0 - arrowSize.height / 2.0 + 1.0 - UIScreenPixel), size: arrowSize)
}
self.buttonNode.frame = CGRect(origin: .zero, size: size)
}

View File

@ -286,7 +286,7 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
}))
}
options.append(OpenInOption(identifier: "2gis", application: .other(title: "2GIS", identifier: 481627348, scheme: "dgis", store: nil), action: {
options.append(OpenInOption(identifier: "2gis", application: .other(title: "2GIS", identifier: 481627348, scheme: "dgis", store: "ru"), action: {
let coordinates = "\(lon),\(lat)"
if let _ = directions {
return .openUrl(url: "dgis://2gis.ru/routeSearch/to/\(coordinates)/go")

View File

@ -1104,7 +1104,7 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
self.hideButtonNode.isHidden = confirmation
case .email:
self.inputNode.textField.keyboardType = .emailAddress
self.inputNode.textField.returnKeyType = .done
self.inputNode.textField.returnKeyType = .next
self.hideButtonNode.isHidden = true
if #available(iOS 12.0, *) {
@ -1134,7 +1134,7 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
}
case .hint:
self.inputNode.textField.keyboardType = .asciiCapable
self.inputNode.textField.returnKeyType = .done
self.inputNode.textField.returnKeyType = .next
self.hideButtonNode.isHidden = true
self.inputNode.textField.autocorrectionType = .no

View File

@ -580,7 +580,7 @@ private func canEditAdminRights(accountPeerId: EnginePeer.Id, channelPeer: Engin
switch initialParticipant {
case .creator:
return false
case let .member(_, _, adminInfo, _, _):
case let .member(_, _, adminInfo, _, _, _):
if let adminInfo = adminInfo {
return adminInfo.canBeEditedByAccountPeer || adminInfo.promotedBy == accountPeerId
} else {
@ -703,7 +703,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
let currentRightsFlags: TelegramChatAdminRightsFlags
if let updatedFlags = state.updatedFlags {
currentRightsFlags = updatedFlags
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _) = initialParticipant, let adminRights = maybeAdminRights {
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _, _) = initialParticipant, let adminRights = maybeAdminRights {
currentRightsFlags = adminRights.rights.rights
} else if let initialParticipant = initialParticipant, case let .creator(_, maybeAdminRights, _) = initialParticipant, let adminRights = maybeAdminRights {
currentRightsFlags = adminRights.rights.rights
@ -761,7 +761,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
}
} else {
if case let .user(adminPeer) = adminPeer, adminPeer.botInfo != nil, case .group = channel.info, invite, let channelPeer = channelPeer, canEditAdminRights(accountPeerId: accountPeerId, channelPeer: channelPeer, initialParticipant: initialParticipant) {
if let initialParticipant = initialParticipant, case let .member(_, _, adminInfo, _, _) = initialParticipant, adminInfo != nil {
if let initialParticipant = initialParticipant, case let .member(_, _, adminInfo, _, _, _) = initialParticipant, adminInfo != nil {
} else {
entries.append(.adminRights(presentationData.theme, presentationData.strings.Bot_AddToChat_Add_AdminRights, state.adminRights))
@ -784,7 +784,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
let currentRightsFlags: TelegramChatAdminRightsFlags
if let updatedFlags = state.updatedFlags {
currentRightsFlags = updatedFlags
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _) = initialParticipant, let adminRights = maybeAdminRights {
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _, _) = initialParticipant, let adminRights = maybeAdminRights {
currentRightsFlags = adminRights.rights.rights
} else {
currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins).subtracting(.canBeAnonymous)
@ -846,14 +846,14 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
canTransfer = true
}
if let initialParticipant = initialParticipant, case let .member(_, _, adminInfo, _, _) = initialParticipant, admin.id != accountPeerId, adminInfo != nil {
if let initialParticipant = initialParticipant, case let .member(_, _, adminInfo, _, _, _) = initialParticipant, admin.id != accountPeerId, adminInfo != nil {
if channel.flags.contains(.isCreator) {
canDismiss = true
} else {
switch initialParticipant {
case .creator:
break
case let .member(_, _, adminInfo, _, _):
case let .member(_, _, adminInfo, _, _, _):
if let adminInfo = adminInfo {
if adminInfo.promotedBy == accountPeerId || adminInfo.canBeEditedByAccountPeer {
canDismiss = true
@ -862,7 +862,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
}
}
}
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminInfo, _, _) = initialParticipant, let adminInfo = maybeAdminInfo {
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminInfo, _, _, _) = initialParticipant, let adminInfo = maybeAdminInfo {
var index = 0
rightsLoop: for right in rightsOrder {
let enabled: Bool = false
@ -955,7 +955,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
entries.append(.rank(presentationData.theme, presentationData.strings, isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder, currentRank ?? "", rankEnabled))
} else {
if case let .user(adminPeer) = adminPeer, adminPeer.botInfo != nil, invite {
if let initialParticipant = initialParticipant, case let .member(_, _, adminRights, _, _) = initialParticipant, adminRights != nil {
if let initialParticipant = initialParticipant, case let .member(_, _, adminRights, _, _, _) = initialParticipant, adminRights != nil {
} else {
entries.append(.adminRights(presentationData.theme, presentationData.strings.Bot_AddToChat_Add_AdminRights, state.adminRights))
}
@ -988,7 +988,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
let currentRightsFlags: TelegramChatAdminRightsFlags
if let updatedFlags = state.updatedFlags {
currentRightsFlags = updatedFlags
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _) = initialParticipant, let adminRights = maybeAdminRights {
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _, _) = initialParticipant, let adminRights = maybeAdminRights {
currentRightsFlags = adminRights.rights.rights.subtracting(.canAddAdmins).subtracting(.canBeAnonymous)
} else {
currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins).subtracting(.canBeAnonymous)
@ -1016,7 +1016,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
entries.append(.rankInfo(presentationData.theme, presentationData.strings.Group_EditAdmin_RankInfo(placeholder).string, invite))
}
if let initialParticipant = initialParticipant, case let .member(_, _, adminInfo, _, _) = initialParticipant, admin.id != accountPeerId, let adminInfo {
if let initialParticipant = initialParticipant, case let .member(_, _, adminInfo, _, _, _) = initialParticipant, admin.id != accountPeerId, let adminInfo {
var canDismiss = false
if accountIsCreator {
canDismiss = true
@ -1319,7 +1319,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
case let .creator(_, adminInfo, rank):
currentRank = rank
currentFlags = adminInfo?.rights.rights ?? maskRightsFlags.subtracting(.canBeAnonymous)
case let .member(_, _, adminInfo, _, rank):
case let .member(_, _, adminInfo, _, rank, _):
if updateFlags == nil {
if adminInfo?.rights == nil {
if channel.flags.contains(.isCreator) {

View File

@ -266,7 +266,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
switch participant.participant {
case .creator:
peerText = strings.Channel_Management_LabelOwner
case let .member(_, _, adminInfo, _, _):
case let .member(_, _, adminInfo, _, _, _):
if let adminInfo = adminInfo {
if let peer = participant.peers[adminInfo.promotedBy] {
if peer.id == participant.peer.id {
@ -424,14 +424,14 @@ private func channelAdminsControllerEntries(presentationData: PresentationData,
switch lhs.participant {
case .creator:
lhsInvitedAt = Int32.min
case let .member(_, invitedAt, _, _, _):
case let .member(_, invitedAt, _, _, _, _):
lhsInvitedAt = invitedAt
}
let rhsInvitedAt: Int32
switch rhs.participant {
case .creator:
rhsInvitedAt = Int32.min
case let .member(_, invitedAt, _, _, _):
case let .member(_, invitedAt, _, _, _, _):
rhsInvitedAt = invitedAt
}
return lhsInvitedAt < rhsInvitedAt
@ -443,7 +443,7 @@ private func channelAdminsControllerEntries(presentationData: PresentationData,
case .creator:
canEdit = false
canOpen = isGroup && peer.flags.contains(.isCreator)
case let .member(id, _, adminInfo, _, _):
case let .member(id, _, adminInfo, _, _, _):
if id == accountPeerId {
canEdit = false
} else if let adminInfo = adminInfo {
@ -508,14 +508,14 @@ private func channelAdminsControllerEntries(presentationData: PresentationData,
switch lhs.participant {
case .creator:
lhsInvitedAt = Int32.min
case let .member(_, invitedAt, _, _, _):
case let .member(_, invitedAt, _, _, _, _):
lhsInvitedAt = invitedAt
}
let rhsInvitedAt: Int32
switch rhs.participant {
case .creator:
rhsInvitedAt = Int32.min
case let .member(_, invitedAt, _, _, _):
case let .member(_, invitedAt, _, _, _, _):
rhsInvitedAt = invitedAt
}
return lhsInvitedAt < rhsInvitedAt
@ -530,7 +530,7 @@ private func channelAdminsControllerEntries(presentationData: PresentationData,
} else {
canEdit = false
}
case let .member(id, _, adminInfo, _, _):
case let .member(id, _, adminInfo, _, _, _):
if id == accountPeerId {
editable = false
} else if let adminInfo = adminInfo {
@ -741,7 +741,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
switch participant.participant {
case .creator:
return
case let .member(_, _, _, banInfo, _):
case let .member(_, _, _, banInfo, _, _):
if let banInfo = banInfo {
var canUnban = false
if banInfo.restrictedBy != context.account.peerId {
@ -870,7 +870,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
var peers: [EnginePeer.Id: EnginePeer] = [:]
peers[creator.id] = creator
peers[peer.id] = peer
result.append(RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: .internal_groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer._asPeer(), peers: peers.mapValues({ $0._asPeer() })))
result.append(RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: .internal_groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer._asPeer(), peers: peers.mapValues({ $0._asPeer() })))
case .member:
break
}

View File

@ -303,7 +303,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation
let currentRightsFlags: TelegramChatBannedRightsFlags
if let updatedFlags = state.updatedFlags {
currentRightsFlags = updatedFlags
} else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo {
} else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _, _) = initialParticipant, let banInfo = maybeBanInfo {
currentRightsFlags = banInfo.rights.flags
} else {
currentRightsFlags = defaultBannedRights.flags
@ -312,7 +312,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation
let currentTimeout: Int32
if let updatedTimeout = state.updatedTimeout {
currentTimeout = updatedTimeout
} else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo {
} else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _, _) = initialParticipant, let banInfo = maybeBanInfo {
currentTimeout = banInfo.rights.untilDate
} else {
currentTimeout = Int32.max
@ -351,7 +351,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation
entries.append(.timeout(presentationData.theme, presentationData.strings.GroupPermission_Duration, currentTimeoutString))
if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo?, _) = initialParticipant, let initialBannedBy = initialBannedBy {
if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo?, _, _) = initialParticipant, let initialBannedBy = initialBannedBy {
entries.append(.exceptionInfo(presentationData.theme, presentationData.strings.GroupPermission_AddedInfo(initialBannedBy.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), stringForRelativeSymbolicTimestamp(strings: presentationData.strings, relativeTimestamp: banInfo.timestamp, relativeTo: state.referenceTimestamp, dateTimeFormat: presentationData.dateTimeFormat)).string))
entries.append(.delete(presentationData.theme, presentationData.strings.GroupPermission_Delete))
}
@ -363,7 +363,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation
let currentRightsFlags: TelegramChatBannedRightsFlags
if let updatedFlags = state.updatedFlags {
currentRightsFlags = updatedFlags
} else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo {
} else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _, _) = initialParticipant, let banInfo = maybeBanInfo {
currentRightsFlags = banInfo.rights.flags
} else {
currentRightsFlags = defaultBannedRightsFlags
@ -372,7 +372,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation
let currentTimeout: Int32
if let updatedTimeout = state.updatedTimeout {
currentTimeout = updatedTimeout
} else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo {
} else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _, _) = initialParticipant, let banInfo = maybeBanInfo {
currentTimeout = banInfo.rights.untilDate
} else {
currentTimeout = Int32.max
@ -411,7 +411,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation
entries.append(.timeout(presentationData.theme, presentationData.strings.GroupPermission_Duration, currentTimeoutString))
if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo?, _) = initialParticipant, let initialBannedBy = initialBannedBy {
if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo?, _, _) = initialParticipant, let initialBannedBy = initialBannedBy {
entries.append(.exceptionInfo(presentationData.theme, presentationData.strings.GroupPermission_AddedInfo(initialBannedBy.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), stringForRelativeSymbolicTimestamp(strings: presentationData.strings, relativeTimestamp: banInfo.timestamp, relativeTo: state.referenceTimestamp, dateTimeFormat: presentationData.dateTimeFormat)).string))
entries.append(.delete(presentationData.theme, presentationData.strings.GroupPermission_Delete))
}
@ -462,7 +462,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
var effectiveRightsFlags: TelegramChatBannedRightsFlags
if let updatedFlags = state.updatedFlags {
effectiveRightsFlags = updatedFlags
} else if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo?, _) = initialParticipant {
} else if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo?, _, _) = initialParticipant {
effectiveRightsFlags = banInfo.rights.flags
} else {
effectiveRightsFlags = defaultBannedRightsFlags
@ -671,7 +671,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
}
if updateFlags == nil && updateTimeout == nil {
if case let .member(_, _, _, maybeBanInfo, _) = initialParticipant {
if case let .member(_, _, _, maybeBanInfo, _, _) = initialParticipant {
if maybeBanInfo == nil {
updateFlags = defaultBannedRightsFlags
updateTimeout = Int32.max
@ -683,7 +683,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
let currentRightsFlags: TelegramChatBannedRightsFlags
if let updatedFlags = updateFlags {
currentRightsFlags = updatedFlags
} else if case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo {
} else if case let .member(_, _, _, maybeBanInfo, _, _) = initialParticipant, let banInfo = maybeBanInfo {
currentRightsFlags = banInfo.rights.flags
} else {
currentRightsFlags = defaultBannedRightsFlags
@ -692,7 +692,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
let currentTimeout: Int32
if let updateTimeout = updateTimeout {
currentTimeout = updateTimeout
} else if case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo {
} else if case let .member(_, _, _, maybeBanInfo, _, _) = initialParticipant, let banInfo = maybeBanInfo {
currentTimeout = banInfo.rights.untilDate
} else {
currentTimeout = Int32.max
@ -724,7 +724,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
}
var previousRights: TelegramChatBannedRights?
if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo, _) = initialParticipant, banInfo != nil {
if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo, _, _) = initialParticipant, banInfo != nil {
previousRights = banInfo?.rights
}
@ -825,7 +825,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
}
let title: String
if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo, _) = initialParticipant, banInfo != nil {
if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo, _, _) = initialParticipant, banInfo != nil {
title = presentationData.strings.GroupPermission_Title
} else {
title = presentationData.strings.GroupPermission_NewTitle

View File

@ -160,7 +160,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry {
case let .peerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, participant, editing, enabled):
var text: ItemListPeerItemText = .none
switch participant.participant {
case let .member(_, _, _, banInfo, _):
case let .member(_, _, _, banInfo, _, _):
if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] {
text = .text(strings.Channel_Management_RemovedBy(EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder)).string, .secondary)
}
@ -306,7 +306,7 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat
switch participant.participant {
case .creator:
return
case let .member(_, _, adminInfo, _, _):
case let .member(_, _, adminInfo, _, _, _):
if let adminInfo = adminInfo, adminInfo.promotedBy != context.account.peerId {
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Channel_Members_AddBannedErrorAdmin, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
return

View File

@ -511,7 +511,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
case .creator:
canPromote = false
canRestrict = false
case let .member(_, _, adminRights, bannedRights, _):
case let .member(_, _, adminRights, bannedRights, _, _):
if channel.hasPermission(.addAdmins) {
canPromote = true
} else {
@ -740,7 +740,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
case .creator:
canPromote = false
canRestrict = false
case let .member(_, _, adminRights, bannedRights, _):
case let .member(_, _, adminRights, bannedRights, _, _):
if channel.hasPermission(.addAdmins) {
canPromote = true
} else {
@ -778,7 +778,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
switch participant.participant {
case .creator:
label = presentationData.strings.Channel_Management_LabelOwner
case let .member(_, _, adminInfo, _, _):
case let .member(_, _, adminInfo, _, _, _):
if adminInfo != nil {
label = presentationData.strings.Channel_Management_LabelEditor
}
@ -809,7 +809,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
switch participant.participant {
case .creator:
label = presentationData.strings.Channel_Management_LabelOwner
case let .member(_, _, adminInfo, _, _):
case let .member(_, _, adminInfo, _, _, _):
if let adminInfo = adminInfo {
if let peer = participant.peers[adminInfo.promotedBy] {
if peer.id == participant.peer.id {
@ -822,7 +822,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
}
case .searchBanned:
switch participant.participant {
case let .member(_, _, _, banInfo, _):
case let .member(_, _, _, banInfo, _, _):
if let banInfo = banInfo {
var exceptionsString = ""
let sendMediaRights = banSendMediaSubList().map { $0.0 }
@ -844,7 +844,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
}
case .searchKicked:
switch participant.participant {
case let .member(_, _, _, banInfo, _):
case let .member(_, _, _, banInfo, _, _):
if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] {
label = presentationData.strings.Channel_Management_RemovedBy(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
}
@ -972,11 +972,11 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
peers[creator.id] = creator
}
peers[peer.id] = EnginePeer(peer)
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: .legacyGroup(group))), promotedBy: creatorPeer?.id ?? context.account.peerId, canBeEditedByAccountPeer: creatorPeer?.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }))
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: .legacyGroup(group))), promotedBy: creatorPeer?.id ?? context.account.peerId, canBeEditedByAccountPeer: creatorPeer?.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }))
case .member:
var peers: [EnginePeer.Id: EnginePeer] = [:]
peers[peer.id] = EnginePeer(peer)
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }))
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }))
}
matchingMembers.append(renderedParticipant)
}
@ -1057,7 +1057,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
switch participant.participant {
case .creator:
label = presentationData.strings.Channel_Management_LabelOwner
case let .member(_, _, adminInfo, _, _):
case let .member(_, _, adminInfo, _, _, _):
if adminInfo != nil {
label = presentationData.strings.Channel_Management_LabelEditor
}
@ -1073,7 +1073,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
switch participant.participant {
case .creator:
label = presentationData.strings.Channel_Management_LabelOwner
case let .member(_, _, adminInfo, _, _):
case let .member(_, _, adminInfo, _, _, _):
if let adminInfo = adminInfo {
if let peer = participant.peers[adminInfo.promotedBy] {
if peer.id == participant.peer.id {
@ -1086,7 +1086,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
}
case .searchBanned:
switch participant.participant {
case let .member(_, _, _, banInfo, _):
case let .member(_, _, _, banInfo, _, _):
if let banInfo = banInfo {
var exceptionsString = ""
let sendMediaRights = banSendMediaSubList().map { $0.0 }
@ -1108,7 +1108,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
}
case .searchKicked:
switch participant.participant {
case let .member(_, _, _, banInfo, _):
case let .member(_, _, _, banInfo, _, _):
if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] {
label = presentationData.strings.Channel_Management_RemovedBy(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
}

View File

@ -399,11 +399,11 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
var peers: [EnginePeer.Id: EnginePeer] = [:]
peers[creator.id] = creator
peers[peer.id] = EnginePeer(peer)
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(mainPeer))), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences)
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(mainPeer))), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences)
case .member:
var peers: [EnginePeer.Id: EnginePeer] = [:]
peers[peer.id] = EnginePeer(peer)
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences)
renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences)
}
entries.append(.peer(index, renderedParticipant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled, false, false))

View File

@ -381,7 +381,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
case let .peerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, participant, editing, enabled, canOpen, defaultBannedRights):
var text: ItemListPeerItemText = .none
switch participant.participant {
case let .member(_, _, _, banInfo, _):
case let .member(_, _, _, banInfo, _, _):
var exceptionsString = ""
if let banInfo = banInfo {
let sendMediaRights = banSendMediaSubList().map { $0.0 }
@ -937,7 +937,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent
switch participant.participant {
case .creator:
return
case let .member(_, _, adminInfo, _, _):
case let .member(_, _, adminInfo, _, _, _):
if let adminInfo = adminInfo, adminInfo.promotedBy != context.account.peerId {
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Channel_Members_AddBannedErrorAdmin, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
return

View File

@ -56,6 +56,10 @@ public func representationFetchRangeForDisplayAtSize(representation: TelegramMed
}
public func chatMessagePhotoDatas(postbox: Postbox, userLocation: MediaResourceUserLocation, customUserContentType: MediaResourceUserContentType? = nil, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false, useMiniThumbnailIfAvailable: Bool = false, forceThumbnail: Bool = false, automaticFetch: Bool = true) -> Signal<Tuple4<Data?, Data?, ChatMessagePhotoQuality, Bool>, NoError> {
return chatMessagePhotoDatas(mediaBox: postbox.mediaBox, userLocation: userLocation, customUserContentType: customUserContentType, photoReference: photoReference, fullRepresentationSize: fullRepresentationSize, autoFetchFullSize: autoFetchFullSize, tryAdditionalRepresentations: tryAdditionalRepresentations, synchronousLoad: synchronousLoad, useMiniThumbnailIfAvailable: useMiniThumbnailIfAvailable, forceThumbnail: forceThumbnail, automaticFetch: automaticFetch)
}
func chatMessagePhotoDatas(mediaBox: MediaBox, userLocation: MediaResourceUserLocation, customUserContentType: MediaResourceUserContentType? = nil, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false, useMiniThumbnailIfAvailable: Bool = false, forceThumbnail: Bool = false, automaticFetch: Bool = true) -> Signal<Tuple4<Data?, Data?, ChatMessagePhotoQuality, Bool>, NoError> {
if !forceThumbnail, let progressiveRepresentation = progressiveImageRepresentation(photoReference.media.representations), progressiveRepresentation.progressiveSizes.count > 1 {
enum SizeSource {
case miniThumbnail(data: Data)
@ -93,7 +97,7 @@ public func chatMessagePhotoDatas(postbox: Postbox, userLocation: MediaResourceU
case let .miniThumbnail(data):
return .single((source, data))
case let .image(size):
return postbox.mediaBox.resourceData(progressiveRepresentation.resource, size: Int64(progressiveRepresentation.progressiveSizes.last!), in: 0 ..< size, mode: .incremental, notifyAboutIncomplete: true, attemptSynchronously: synchronousLoad)
return mediaBox.resourceData(progressiveRepresentation.resource, size: Int64(progressiveRepresentation.progressiveSizes.last!), in: 0 ..< size, mode: .incremental, notifyAboutIncomplete: true, attemptSynchronously: synchronousLoad)
|> map { (data, _) -> (SizeSource, Data?) in
return (source, data)
}
@ -131,9 +135,9 @@ public func chatMessagePhotoDatas(postbox: Postbox, userLocation: MediaResourceU
var fetchDisposable: Disposable?
if automaticFetch {
if autoFetchFullSize {
fetchDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: customUserContentType ?? .image, reference: photoReference.resourceReference(progressiveRepresentation.resource), range: (0 ..< Int64(largestByteSize), .default), statsCategory: .image).start()
fetchDisposable = fetchedMediaResource(mediaBox: mediaBox, userLocation: userLocation, userContentType: customUserContentType ?? .image, reference: photoReference.resourceReference(progressiveRepresentation.resource), range: (0 ..< Int64(largestByteSize), .default), statsCategory: .image).start()
} else if useMiniThumbnailIfAvailable {
fetchDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: customUserContentType ?? .image, reference: photoReference.resourceReference(progressiveRepresentation.resource), range: (0 ..< Int64(thumbnailByteSize), .default), statsCategory: .image).start()
fetchDisposable = fetchedMediaResource(mediaBox: mediaBox, userLocation: userLocation, userContentType: customUserContentType ?? .image, reference: photoReference.resourceReference(progressiveRepresentation.resource), range: (0 ..< Int64(thumbnailByteSize), .default), statsCategory: .image).start()
}
}
@ -145,8 +149,8 @@ public func chatMessagePhotoDatas(postbox: Postbox, userLocation: MediaResourceU
}
if !forceThumbnail || photoReference.media.immediateThumbnailData == nil, let smallestRepresentation = smallestImageRepresentation(photoReference.media.representations), let largestRepresentation = photoReference.media.representationForDisplayAtSize(PixelDimensions(width: Int32(fullRepresentationSize.width), height: Int32(fullRepresentationSize.height))), let fullRepresentation = largestImageRepresentation(photoReference.media.representations) {
let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
let maybeLargestSize = postbox.mediaBox.resourceData(fullRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
let maybeFullSize = mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
let maybeLargestSize = mediaBox.resourceData(fullRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
let signal = combineLatest(maybeFullSize, maybeLargestSize)
|> take(1)
@ -163,16 +167,16 @@ public func chatMessagePhotoDatas(postbox: Postbox, userLocation: MediaResourceU
if let _ = decodedThumbnailData {
fetchedThumbnail = .complete()
} else {
fetchedThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: customUserContentType ?? .image, reference: photoReference.resourceReference(smallestRepresentation.resource), statsCategory: .image)
fetchedThumbnail = fetchedMediaResource(mediaBox: mediaBox, userLocation: userLocation, userContentType: customUserContentType ?? .image, reference: photoReference.resourceReference(smallestRepresentation.resource), statsCategory: .image)
}
let fetchedFullSize = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: customUserContentType ?? .image, reference: photoReference.resourceReference(largestRepresentation.resource), statsCategory: .image)
let fetchedFullSize = fetchedMediaResource(mediaBox: mediaBox, userLocation: userLocation, userContentType: customUserContentType ?? .image, reference: photoReference.resourceReference(largestRepresentation.resource), statsCategory: .image)
let anyThumbnail: [Signal<(MediaResourceData, ChatMessagePhotoQuality), NoError>]
if tryAdditionalRepresentations {
anyThumbnail = photoReference.media.representations.filter({ representation in
return representation != largestRepresentation
}).map({ representation -> Signal<(MediaResourceData, ChatMessagePhotoQuality), NoError> in
return postbox.mediaBox.resourceData(representation.resource)
return mediaBox.resourceData(representation.resource)
|> take(1)
|> map { data -> (MediaResourceData, ChatMessagePhotoQuality) in
if representation.dimensions.width > 200 || representation.dimensions.height > 200 {
@ -193,7 +197,7 @@ public func chatMessagePhotoDatas(postbox: Postbox, userLocation: MediaResourceU
return EmptyDisposable
} else {
let fetchedDisposable = fetchedThumbnail.start()
let thumbnailDisposable = postbox.mediaBox.resourceData(smallestRepresentation.resource, attemptSynchronously: synchronousLoad).start(next: { next in
let thumbnailDisposable = mediaBox.resourceData(smallestRepresentation.resource, attemptSynchronously: synchronousLoad).start(next: { next in
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
}, error: subscriber.putError, completed: subscriber.putCompletion)
@ -222,7 +226,7 @@ public func chatMessagePhotoDatas(postbox: Postbox, userLocation: MediaResourceU
if autoFetchFullSize && !useMiniThumbnailIfAvailable {
fullSizeData = Signal<Tuple2<Data?, Bool>, NoError> { subscriber in
let fetchedFullSizeDisposable = fetchedFullSize.start()
let fullSizeDisposable = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad).start(next: { next in
let fullSizeDisposable = mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad).start(next: { next in
subscriber.putNext(Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete))
}, error: subscriber.putError, completed: subscriber.putCompletion)
@ -232,7 +236,7 @@ public func chatMessagePhotoDatas(postbox: Postbox, userLocation: MediaResourceU
}
}
} else {
fullSizeData = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
fullSizeData = mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
|> map { next -> Tuple2<Data?, Bool> in
return Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)
}
@ -600,6 +604,13 @@ public func chatMessagePhoto(postbox: Postbox, userLocation: MediaResourceUserLo
}
}
public func chatMessagePhoto(mediaBox: MediaBox, userLocation: MediaResourceUserLocation, userContentType customUserContentType: MediaResourceUserContentType? = nil, photoReference: ImageMediaReference, synchronousLoad: Bool = false, highQuality: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
return chatMessagePhotoInternal(photoData: chatMessagePhotoDatas(mediaBox: mediaBox, userLocation: userLocation, customUserContentType: customUserContentType, photoReference: photoReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad), synchronousLoad: synchronousLoad)
|> map { _, _, generate in
return generate
}
}
public enum ChatMessagePhotoQuality {
case none
case blurred

View File

@ -7,6 +7,7 @@ import TelegramCore
import TelegramPresentationData
import AccountContext
import UrlEscaping
import ActivityIndicator
private final class WebBrowserDomainInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate {
private var theme: PresentationTheme
@ -116,7 +117,12 @@ private final class WebBrowserDomainInputFieldNode: ASDisplayNode, ASEditableTex
private let domainRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.?)*([a-zA-Z]*)?(:)?(/)?$", options: [])
private let pathRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}/", options: [])
var inProgress = false
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if self.inProgress {
return false
}
if text == "\n" {
self.complete?()
return false
@ -168,6 +174,7 @@ private final class WebBrowserDomainAlertContentNode: AlertContentNode {
private let titleNode: ASTextNode
private let textNode: ASTextNode
let activityIndicator: ActivityIndicator
let inputFieldNode: WebBrowserDomainInputFieldNode
private let actionNodesSeparator: ASDisplayNode
@ -198,6 +205,9 @@ private final class WebBrowserDomainAlertContentNode: AlertContentNode {
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 2
self.activityIndicator = ActivityIndicator(type: .custom(ptheme.rootController.navigationBar.secondaryTextColor, 20.0, 1.5, false), speed: .slow)
self.activityIndicator.isHidden = true
self.inputFieldNode = WebBrowserDomainInputFieldNode(theme: ptheme, placeholder: strings.WebBrowser_Exceptions_Create_Placeholder)
self.inputFieldNode.text = ""
@ -224,7 +234,8 @@ private final class WebBrowserDomainAlertContentNode: AlertContentNode {
self.addSubnode(self.textNode)
self.addSubnode(self.inputFieldNode)
self.addSubnode(self.activityIndicator)
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
@ -335,9 +346,13 @@ private final class WebBrowserDomainAlertContentNode: AlertContentNode {
let inputFieldWidth = resultWidth
let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
let inputHeight = inputFieldHeight
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight))
let inputFrame = CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight)
transition.updateFrame(node: self.inputFieldNode, frame: inputFrame)
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
let activitySize = CGSize(width: 20.0, height: 20.0)
transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: inputFrame.maxX - activitySize.width - 23.0, y: inputFrame.midY - activitySize.height / 2.0 - 3.0), size: activitySize))
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + actionsHeight + 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)))
@ -404,11 +419,16 @@ public func webBrowserDomainController(context: AccountContext, updatedPresentat
var dismissImpl: ((Bool) -> Void)?
var applyImpl: (() -> Void)?
var inProgress = false
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
apply(nil)
if !inProgress {
dismissImpl?(true)
apply(nil)
}
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: {
applyImpl?()
if !inProgress {
applyImpl?()
}
})]
let contentNode = WebBrowserDomainAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions)
@ -419,9 +439,12 @@ public func webBrowserDomainController(context: AccountContext, updatedPresentat
guard let contentNode = contentNode else {
return
}
inProgress = true
contentNode.inputFieldNode.inProgress = true
contentNode.activityIndicator.isHidden = false
let updatedLink = explicitUrl(contentNode.link)
if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true]) {
dismissImpl?(true)
apply(updatedLink)
} else {
contentNode.animateError()

View File

@ -7,32 +7,43 @@ import TelegramPresentationData
import TelegramCore
import AccountContext
import ItemListUI
import PhotoResources
public class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem {
private enum RevealOptionKey: Int32 {
case delete
}
final class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let context: AccountContext?
let context: AccountContext
let title: String
let label: String
public let sectionId: ItemListSectionId
let icon: TelegramMediaImage?
let sectionId: ItemListSectionId
let style: ItemListStyle
let deleted: (() -> Void)?
public init(
init(
presentationData: ItemListPresentationData,
context: AccountContext? = nil,
context: AccountContext,
title: String,
label: String,
icon: TelegramMediaImage?,
sectionId: ItemListSectionId,
style: ItemListStyle
style: ItemListStyle,
deleted: (() -> Void)?
) {
self.presentationData = presentationData
self.context = context
self.title = title
self.label = label
self.icon = icon
self.sectionId = sectionId
self.style = style
self.deleted = deleted
}
public 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) {
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 = WebBrowserDomainExceptionItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
@ -48,7 +59,7 @@ public class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem {
}
}
public 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) {
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? WebBrowserDomainExceptionItemNode {
let makeLayout = nodeValue.asyncLayout()
@ -65,33 +76,34 @@ public class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem {
}
}
public var selectable: Bool = false
var selectable: Bool = false
public func selected(listView: ListView){
func selected(listView: ListView){
}
}
public class WebBrowserDomainExceptionItemNode: ListViewItemNode, ItemListItemNode {
final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
let iconNode: ASImageNode
let iconNode: TransformImageNode
let titleNode: TextNode
let labelNode: TextNode
private let activateArea: AccessibilityAreaNode
private var item: WebBrowserDomainExceptionItem?
private var layoutParams: ListViewItemLayoutParams?
override public var canBeSelected: Bool {
return false
}
public var tag: ItemListItemTag? = nil
var tag: ItemListItemTag? = nil
public init() {
init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = .white
@ -105,7 +117,7 @@ public class WebBrowserDomainExceptionItemNode: ListViewItemNode, ItemListItemNo
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.iconNode = ASImageNode()
self.iconNode = TransformImageNode()
self.iconNode.isLayerBacked = true
self.iconNode.displaysAsynchronously = false
@ -117,15 +129,16 @@ public class WebBrowserDomainExceptionItemNode: ListViewItemNode, ItemListItemNo
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.iconNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.activateArea)
}
public func asyncLayout() -> (_ item: WebBrowserDomainExceptionItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
func asyncLayout() -> (_ item: WebBrowserDomainExceptionItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
@ -143,7 +156,7 @@ public class WebBrowserDomainExceptionItemNode: ListViewItemNode, ItemListItemNo
let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor
let leftInset = 16.0 + params.leftInset + 43.0
let leftInset = 16.0 + params.leftInset + 46.0
let titleColor: UIColor = item.presentationData.theme.list.itemPrimaryTextColor
let labelColor: UIColor = item.presentationData.theme.list.itemAccentColor
@ -180,6 +193,7 @@ public class WebBrowserDomainExceptionItemNode: ListViewItemNode, ItemListItemNo
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
if let strongSelf = self {
strongSelf.item = item
strongSelf.layoutParams = params
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = item.title
@ -191,6 +205,15 @@ public class WebBrowserDomainExceptionItemNode: ListViewItemNode, ItemListItemNo
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
}
let iconSize = CGSize(width: 40.0, height: 40.0)
var imageSize = iconSize
if currentItem?.icon?.id != item.icon?.id, let icon = item.icon {
strongSelf.iconNode.setSignal(chatMessagePhoto(mediaBox: item.context.sharedContext.accountManager.mediaBox, userLocation: .other, photoReference: .standalone(media: icon)))
}
if let icon = item.icon, let dimensions = largestImageRepresentation(icon.representations)?.dimensions.cgSize {
imageSize = dimensions.aspectFilled(imageSize)
}
let _ = titleApply()
let _ = labelApply()
@ -256,25 +279,69 @@ public class WebBrowserDomainExceptionItemNode: ListViewItemNode, ItemListItemNo
centralContentHeight += titleSpacing
centralContentHeight += labelLayout.size.height
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - centralContentHeight) / 2.0)), size: titleLayout.size)
let titleFrame = CGRect(origin: CGPoint(x: leftInset + strongSelf.revealOffset, y: floor((height - centralContentHeight) / 2.0)), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame
let labelFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size)
let labelFrame = CGRect(origin: CGPoint(x: leftInset + strongSelf.revealOffset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame
let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + 11.0 + strongSelf.revealOffset, y: floorToScreenPixels((contentSize.height - iconSize.height) / 2.0)), size: iconSize)
strongSelf.iconNode.frame = iconFrame
strongSelf.iconNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 7.0), imageSize: imageSize, boundingSize: iconSize, intrinsicInsets: .zero))()
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
var revealOptions: [ItemListRevealOption] = []
revealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor))
strongSelf.setRevealOptions((left: [], right: revealOptions))
}
})
}
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
super.updateRevealOffset(offset: offset, transition: transition)
if let params = self.layoutParams {
let leftInset: CGFloat = 16.0 + params.leftInset + 46.0
var iconFrame = self.iconNode.frame
iconFrame.origin.x = params.leftInset + 11.0 + offset
transition.updateFrame(node: self.iconNode, frame: iconFrame)
var titleFrame = self.titleNode.frame
titleFrame.origin.x = leftInset + offset
transition.updateFrame(node: self.titleNode, frame: titleFrame)
var subtitleFrame = self.labelNode.frame
subtitleFrame.origin.x = leftInset + offset
transition.updateFrame(node: self.labelNode, frame: subtitleFrame)
}
}
override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
if let item = self.item {
switch option.key {
case RevealOptionKey.delete.rawValue:
item.deleted?()
default:
break
}
}
self.setRevealOptionsOpened(false, animated: true)
self.revealOptionsInteractivelyClosed()
}
}

View File

@ -6,6 +6,7 @@ import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import PresentationDataUtils
import ItemListUI
import AccountContext
import OpenInExternalAppUI
@ -13,25 +14,33 @@ import ItemListPeerActionItem
import UndoUI
import WebKit
import LinkPresentation
import CoreServices
import PersistentStringHash
private final class WebBrowserSettingsControllerArguments {
let context: AccountContext
let updateDefaultBrowser: (String?) -> Void
let clearCookies: () -> Void
let clearCache: () -> Void
let addException: () -> Void
let removeException: (String) -> Void
let clearExceptions: () -> Void
init(
context: AccountContext,
updateDefaultBrowser: @escaping (String?) -> Void,
clearCookies: @escaping () -> Void,
clearCache: @escaping () -> Void,
addException: @escaping () -> Void,
removeException: @escaping (String) -> Void,
clearExceptions: @escaping () -> Void
) {
self.context = context
self.updateDefaultBrowser = updateDefaultBrowser
self.clearCookies = clearCookies
self.clearCache = clearCache
self.addException = addException
self.removeException = removeException
self.clearExceptions = clearExceptions
}
}
@ -47,6 +56,7 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
case browser(PresentationTheme, String, OpenInApplication?, String?, Bool, Int32)
case clearCookies(PresentationTheme, String)
case clearCache(PresentationTheme, String)
case clearCookiesInfo(PresentationTheme, String)
case exceptionsHeader(PresentationTheme, String)
@ -59,14 +69,39 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
switch self {
case .browserHeader, .browser:
return WebBrowserSettingsSection.browsers.rawValue
case .clearCookies, .clearCookiesInfo:
case .clearCookies, .clearCache, .clearCookiesInfo:
return WebBrowserSettingsSection.clearCookies.rawValue
case .exceptionsHeader, .exceptionsAdd, .exception, .exceptionsClear, .exceptionsInfo:
return WebBrowserSettingsSection.exceptions.rawValue
}
}
var stableId: Int32 {
var stableId: UInt64 {
switch self {
case .browserHeader:
return 0
case let .browser(_, _, _, _, _, index):
return UInt64(1 + index)
case .clearCookies:
return 102
case .clearCache:
return 103
case .clearCookiesInfo:
return 104
case .exceptionsHeader:
return 105
case .exceptionsAdd:
return 106
case let .exception(_, _, exception):
return 2000 + exception.domain.persistentHashValue
case .exceptionsClear:
return 1000
case .exceptionsInfo:
return 1001
}
}
var sortId: Int32 {
switch self {
case .browserHeader:
return 0
@ -74,14 +109,16 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
return 1 + index
case .clearCookies:
return 102
case .clearCookiesInfo:
case .clearCache:
return 103
case .exceptionsHeader:
case .clearCookiesInfo:
return 104
case .exceptionsAdd:
case .exceptionsHeader:
return 105
case .exceptionsAdd:
return 106
case let .exception(index, _, _):
return 106 + index
return 107 + index
case .exceptionsClear:
return 1000
case .exceptionsInfo:
@ -109,6 +146,12 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .clearCache(lhsTheme, lhsText):
if case let .clearCache(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .clearCookiesInfo(lhsTheme, lhsText):
if case let .clearCookiesInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -149,7 +192,7 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
}
static func <(lhs: WebBrowserSettingsControllerEntry, rhs: WebBrowserSettingsControllerEntry) -> Bool {
return lhs.stableId < rhs.stableId
return lhs.sortId < rhs.sortId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
@ -165,12 +208,18 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.accentDeleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
arguments.clearCookies()
})
case let .clearCache(_, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.accentDeleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
arguments.clearCache()
})
case let .clearCookiesInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .exceptionsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .exception(_, _, exception):
return WebBrowserDomainExceptionItem(presentationData: presentationData, context: arguments.context, title: exception.title, label: exception.domain, sectionId: self.section, style: .blocks)
return WebBrowserDomainExceptionItem(presentationData: presentationData, context: arguments.context, title: exception.title, label: exception.domain, icon: exception.icon, sectionId: self.section, style: .blocks, deleted: {
arguments.removeException(exception.domain)
})
case let .exceptionsAdd(_, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
arguments.addException()
@ -201,13 +250,14 @@ private func webBrowserSettingsControllerEntries(context: AccountContext, presen
if settings.defaultWebBrowser == nil {
entries.append(.clearCookies(presentationData.theme, presentationData.strings.WebBrowser_ClearCookies))
// entries.append(.clearCache(presentationData.theme, presentationData.strings.WebBrowser_ClearCache))
entries.append(.clearCookiesInfo(presentationData.theme, presentationData.strings.WebBrowser_ClearCookies_Info))
entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_Title))
entries.append(.exceptionsAdd(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_AddException))
var exceptionIndex: Int32 = 0
for exception in settings.exceptions {
for exception in settings.exceptions.reversed() {
entries.append(.exception(exceptionIndex, presentationData.theme, exception))
exceptionIndex += 1
}
@ -224,7 +274,9 @@ private func webBrowserSettingsControllerEntries(context: AccountContext, presen
public func webBrowserSettingsController(context: AccountContext) -> ViewController {
var clearCookiesImpl: (() -> Void)?
var clearCacheImpl: (() -> Void)?
var addExceptionImpl: (() -> Void)?
var removeExceptionImpl: ((String) -> Void)?
var clearExceptionsImpl: (() -> Void)?
let arguments = WebBrowserSettingsControllerArguments(
@ -237,9 +289,15 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl
clearCookies: {
clearCookiesImpl?()
},
clearCache: {
clearCacheImpl?()
},
addException: {
addExceptionImpl?()
},
removeException: { domain in
removeExceptionImpl?(domain)
},
clearExceptions: {
clearExceptionsImpl?()
}
@ -261,6 +319,9 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl
if previousSettings.defaultWebBrowser != settings.defaultWebBrowser {
animateChanges = true
}
if previousSettings.exceptions.count != settings.exceptions.count {
animateChanges = true
}
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.WebBrowser_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
@ -272,27 +333,75 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl
let controller = ItemListController(context: context, state: signal)
clearCookiesImpl = { [weak controller] in
WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{})
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controller?.present(UndoOverlayController(
presentationData: presentationData,
content: .info(
title: nil,
text: presentationData.strings.WebBrowser_ClearCookies_Succeed,
timeout: nil,
customUndoText: nil
),
elevatedLayout: false,
position: .bottom,
action: { _ in return false }), in: .current
let alertController = textAlertController(
context: context,
updatedPresentationData: nil,
title: nil,
text: presentationData.strings.WebBrowser_ClearCookies_ClearConfirmation_Text,
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.WebBrowser_ClearCookies_ClearConfirmation_Clear, action: {
WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeCookies, WKWebsiteDataTypeLocalStorage, WKWebsiteDataTypeSessionStorage], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{})
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controller?.present(UndoOverlayController(
presentationData: presentationData,
content: .info(
title: nil,
text: presentationData.strings.WebBrowser_ClearCookies_Succeed,
timeout: nil,
customUndoText: nil
),
elevatedLayout: false,
position: .bottom,
action: { _ in return false }), in: .current
)
})
]
)
controller?.present(alertController, in: .window(.root))
}
clearCacheImpl = { [weak controller] in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let alertController = textAlertController(
context: context,
updatedPresentationData: nil,
title: nil,
text: presentationData.strings.WebBrowser_ClearCache_ClearConfirmation_Text,
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.WebBrowser_ClearCache_ClearConfirmation_Clear, action: {
WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{})
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controller?.present(UndoOverlayController(
presentationData: presentationData,
content: .info(
title: nil,
text: presentationData.strings.WebBrowser_ClearCache_Succeed,
timeout: nil,
customUndoText: nil
),
elevatedLayout: false,
position: .bottom,
action: { _ in return false }), in: .current
)
})
]
)
controller?.present(alertController, in: .window(.root))
}
addExceptionImpl = { [weak controller] in
var dismissImpl: (() -> Void)?
let linkController = webBrowserDomainController(context: context, apply: { url in
if let url {
let _ = fetchDomainExceptionInfo(url: url).startStandalone(next: { newException in
let _ = (fetchDomainExceptionInfo(context: context, url: url)
|> deliverOnMainQueue).startStandalone(next: { newException in
let _ = updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, { currentSettings in
var currentExceptions = currentSettings.exceptions
for exception in currentExceptions {
@ -303,18 +412,44 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl
currentExceptions.append(newException)
return currentSettings.withUpdatedExceptions(currentExceptions)
}).start()
dismissImpl?()
})
}
})
dismissImpl = { [weak linkController] in
linkController?.view.endEditing(true)
linkController?.dismissAnimated()
}
controller?.present(linkController, in: .window(.root))
}
clearExceptionsImpl = {
removeExceptionImpl = { domain in
let _ = updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, { currentSettings in
return currentSettings.withUpdatedExceptions([])
let updatedExceptions = currentSettings.exceptions.filter { $0.domain != domain }
return currentSettings.withUpdatedExceptions(updatedExceptions)
}).start()
}
clearExceptionsImpl = { [weak controller] in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let alertController = textAlertController(
context: context,
updatedPresentationData: nil,
title: nil,
text: presentationData.strings.WebBrowser_Exceptions_ClearConfirmation_Text,
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.WebBrowser_Exceptions_ClearConfirmation_Clear, action: {
let _ = updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, { currentSettings in
return currentSettings.withUpdatedExceptions([])
}).start()
})
]
)
controller?.present(alertController, in: .window(.root))
}
return controller
}
@ -333,22 +468,59 @@ private func cleanDomain(url: String) -> (domain: String, fullUrl: String) {
}
}
private func fetchDomainExceptionInfo(url: String) -> Signal<WebBrowserException, NoError> {
private func fetchDomainExceptionInfo(context: AccountContext, url: String) -> Signal<WebBrowserException, NoError> {
let (domain, domainUrl) = cleanDomain(url: url)
if #available(iOS 13.0, *), let url = URL(string: domainUrl) {
return Signal { subscriber in
let metadataProvider = LPMetadataProvider()
metadataProvider.shouldFetchSubresources = true
metadataProvider.startFetchingMetadata(for: url, completionHandler: { metadata, _ in
let title = metadata?.value(forKey: "_siteName") as? String ?? metadata?.title
subscriber.putNext(WebBrowserException(domain: domain, title: title ?? domain))
subscriber.putCompletion()
let completeWithImage: (Data?) -> Void = { imageData in
var image: TelegramMediaImage?
if let imageData, let parsedImage = UIImage(data: imageData) {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: imageData)
image = TelegramMediaImage(
imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: Int64.random(in: Int64.min ... Int64.max)),
representations: [
TelegramMediaImageRepresentation(
dimensions: PixelDimensions(width: Int32(parsedImage.size.width), height: Int32(parsedImage.size.height)),
resource: resource,
progressiveSizes: [],
immediateThumbnailData: nil,
hasVideo: false,
isPersonal: false
)
],
immediateThumbnailData: nil,
reference: nil,
partialReference: nil,
flags: []
)
}
let title = metadata?.value(forKey: "_siteName") as? String ?? metadata?.title
subscriber.putNext(WebBrowserException(domain: domain, title: title ?? domain, icon: image))
subscriber.putCompletion()
}
if let imageProvider = metadata?.iconProvider {
imageProvider.loadFileRepresentation(forTypeIdentifier: kUTTypeImage as String, completionHandler: { imageUrl, _ in
guard let imageUrl, let imageData = try? Data(contentsOf: imageUrl) else {
completeWithImage(nil)
return
}
completeWithImage(imageData)
})
} else {
completeWithImage(nil)
}
})
return ActionDisposable {
metadataProvider.cancel()
}
}
} else {
return .single(WebBrowserException(domain: domain, title: domain))
return .single(WebBrowserException(domain: domain, title: domain, icon: nil))
}
}

View File

@ -315,10 +315,10 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil))
let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil))
let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil))
let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let timestamp = self.referenceTimestamp

View File

@ -464,10 +464,10 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil))
let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil))
let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil))
let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))
let peer7: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(6)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil))

View File

@ -462,7 +462,7 @@ public final class SparseItemGrid: ASDisplayNode {
self.itemSpacing = 1.0
let itemsPerRow: CGFloat
if containerLayout.fixedItemAspect != nil && itemCount <= 2 {
if containerLayout.fixedItemAspect != nil && itemCount <= 2 && containerLayout.adjustForSmallCount {
itemsPerRow = 2.0
centerItems = itemCount == 1
} else {
@ -550,6 +550,11 @@ public final class SparseItemGrid: ASDisplayNode {
var offset: CGFloat {
return self.scrollView.contentOffset.y
}
var contentBottomOffset: CGFloat {
let bottomInset = self.layout?.containerLayout.insets.bottom ?? 0.0
return -self.scrollView.contentOffset.y + self.scrollView.contentSize.height - bottomInset
}
let coveringOffsetUpdated: (Viewport, ContainedViewLayoutTransition) -> Void
let offsetUpdated: (Viewport, ContainedViewLayoutTransition) -> Void
@ -1442,6 +1447,10 @@ public final class SparseItemGrid: ASDisplayNode {
return self.fromViewport.coveringInsetOffset * (1.0 - self.currentProgress) + self.toViewport.coveringInsetOffset * self.currentProgress
}
var contentBottomOffset: CGFloat {
return self.fromViewport.contentBottomOffset * (1.0 - self.currentProgress) + self.toViewport.contentBottomOffset * self.currentProgress
}
var offset: CGFloat {
return self.fromViewport.offset * (1.0 - self.currentProgress) + self.toViewport.offset * self.currentProgress
}
@ -1599,6 +1608,7 @@ public final class SparseItemGrid: ASDisplayNode {
var lockScrollingAtTop: Bool
var fixedItemHeight: CGFloat?
var fixedItemAspect: CGFloat?
var adjustForSmallCount: Bool
}
private var tapRecognizer: UITapGestureRecognizer?
@ -1632,6 +1642,16 @@ public final class SparseItemGrid: ASDisplayNode {
}
}
public var contentBottomOffset: CGFloat {
if let currentViewportTransition = self.currentViewportTransition {
return currentViewportTransition.contentBottomOffset
} else if let currentViewport = self.currentViewport {
return currentViewport.contentBottomOffset
} else {
return 0.0
}
}
public var scrollingOffset: CGFloat {
if let currentViewportTransition = self.currentViewportTransition {
return currentViewportTransition.offset
@ -1914,7 +1934,7 @@ public final class SparseItemGrid: ASDisplayNode {
}
}
public func update(size: CGSize, insets: UIEdgeInsets, useSideInsets: Bool, scrollIndicatorInsets: UIEdgeInsets, lockScrollingAtTop: Bool, fixedItemHeight: CGFloat?, fixedItemAspect: CGFloat?, items: Items, theme: PresentationTheme, synchronous: SparseItemGrid.Synchronous, transition: ComponentTransition = .immediate) {
public func update(size: CGSize, insets: UIEdgeInsets, useSideInsets: Bool, scrollIndicatorInsets: UIEdgeInsets, lockScrollingAtTop: Bool, fixedItemHeight: CGFloat?, fixedItemAspect: CGFloat?, adjustForSmallCount: Bool = true, items: Items, theme: PresentationTheme, synchronous: SparseItemGrid.Synchronous, transition: ComponentTransition = .immediate) {
self.theme = theme
var headerInset: CGFloat = 0.0
@ -1955,7 +1975,7 @@ public final class SparseItemGrid: ASDisplayNode {
var insets = insets
insets.top += headerInset
let containerLayout = ContainerLayout(size: size, insets: insets, useSideInsets: useSideInsets, scrollIndicatorInsets: scrollIndicatorInsets, lockScrollingAtTop: lockScrollingAtTop, fixedItemHeight: fixedItemHeight, fixedItemAspect: fixedItemAspect)
let containerLayout = ContainerLayout(size: size, insets: insets, useSideInsets: useSideInsets, scrollIndicatorInsets: scrollIndicatorInsets, lockScrollingAtTop: lockScrollingAtTop, fixedItemHeight: fixedItemHeight, fixedItemAspect: fixedItemAspect, adjustForSmallCount: adjustForSmallCount)
self.containerLayout = containerLayout
self.items = items
self.scrollingArea.isHidden = lockScrollingAtTop

View File

@ -2049,6 +2049,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
let peer = Promise<EnginePeer?>()
peer.set(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
let canViewStatsValue = Atomic<Bool>(value: true)
let peerData = context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.CanViewStats(id: peerId),
TelegramEngine.EngineData.Item.Peer.AdsRestricted(id: peerId),
@ -2081,6 +2082,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, revenueState, revenueTransactions, starsState, starsTransactions, peerData, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
let (canViewStats, adsRestricted, canViewRevenue, canViewStarsRevenue) = peerData
let _ = canViewStatsValue.swap(canViewStats)
var isGroup = false
if let peer, case let .channel(channel) = peer, case .group = channel.info {
isGroup = true
@ -2157,9 +2160,17 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
case .stats:
index = 0
case .boosts:
index = 1
if canViewStats {
index = 1
} else {
index = 0
}
case .monetization:
index = 2
if canViewStats {
index = 2
} else {
index = 1
}
}
var tabs: [String] = []
if canViewStats {
@ -2195,12 +2206,21 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
}
controller.titleControlValueChanged = { value in
updateState { state in
let canViewStats = canViewStatsValue.with { $0 }
let section: ChannelStatsSection
switch value {
case 0:
section = .stats
if canViewStats {
section = .stats
} else {
section = .boosts
}
case 1:
section = .boosts
if canViewStats {
section = .boosts
} else {
section = .monetization
}
case 2:
section = .monetization
let _ = (ApplicationSpecificNotice.monetizationIntroDismissed(accountManager: context.sharedContext.accountManager)

View File

@ -686,7 +686,7 @@ private func canEditAdminRights(accountPeerId: EnginePeer.Id, channelPeer: Engin
switch initialParticipant {
case .creator:
return false
case let .member(_, _, adminInfo, _, _):
case let .member(_, _, adminInfo, _, _, _):
if let adminInfo = adminInfo {
return adminInfo.canBeEditedByAccountPeer || adminInfo.promotedBy == accountPeerId
} else {

View File

@ -178,10 +178,10 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode {
if let stats = item.stats as? RevenueStats {
let cryptoValue = formatTonAmountText(stats.balances.availableBalance, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)
amountString = tonAmountAttributedString(cryptoValue, integralFont: integralFont, fractionalFont: fractionalFont, color: item.presentationData.theme.list.itemPrimaryTextColor)
value = stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, rate: stats.usdRate))"
value = stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))"
} else if let stats = item.stats as? StarsRevenueStats {
amountString = NSAttributedString(string: presentationStringsFormattedNumber(Int32(stats.balances.availableBalance), item.presentationData.dateTimeFormat.groupingSeparator), font: integralFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
value = stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, divide: false, rate: stats.usdRate))"
value = stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))"
isStars = true
} else {
fatalError()

View File

@ -774,7 +774,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
formatTonAmountText(stats.balances.availableBalance, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
item.presentationData.strings.Monetization_StarsProceeds_Available,
(stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, rate: stats.usdRate))", .generic),
(stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.ton
)
@ -784,7 +784,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
formatTonAmountText(stats.balances.currentBalance, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
item.presentationData.strings.Monetization_StarsProceeds_Current,
(stats.balances.currentBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.currentBalance, rate: stats.usdRate))", .generic),
(stats.balances.currentBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.currentBalance, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.ton
)
@ -794,7 +794,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
formatTonAmountText(stats.balances.overallRevenue, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
item.presentationData.strings.Monetization_StarsProceeds_Total,
(stats.balances.overallRevenue == 0 ? "" : "\(formatTonUsdValue(stats.balances.overallRevenue, rate: stats.usdRate))", .generic),
(stats.balances.overallRevenue == 0 ? "" : "\(formatTonUsdValue(stats.balances.overallRevenue, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.ton
)
@ -804,7 +804,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
presentationStringsFormattedNumber(Int32(additionalStats.balances.availableBalance), item.presentationData.dateTimeFormat.groupingSeparator),
" ",
(additionalStats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(additionalStats.balances.availableBalance, divide: false, rate: additionalStats.usdRate))", .generic),
(additionalStats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(additionalStats.balances.availableBalance, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)
@ -814,7 +814,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
presentationStringsFormattedNumber(Int32(additionalStats.balances.currentBalance), item.presentationData.dateTimeFormat.groupingSeparator),
" ",
(additionalStats.balances.currentBalance == 0 ? "" : "\(formatTonUsdValue(additionalStats.balances.currentBalance, divide: false, rate: additionalStats.usdRate))", .generic),
(additionalStats.balances.currentBalance == 0 ? "" : "\(formatTonUsdValue(additionalStats.balances.currentBalance, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)
@ -824,7 +824,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
presentationStringsFormattedNumber(Int32(additionalStats.balances.overallRevenue), item.presentationData.dateTimeFormat.groupingSeparator),
" ",
(additionalStats.balances.overallRevenue == 0 ? "" : "\(formatTonUsdValue(additionalStats.balances.overallRevenue, divide: false, rate: additionalStats.usdRate))", .generic),
(additionalStats.balances.overallRevenue == 0 ? "" : "\(formatTonUsdValue(additionalStats.balances.overallRevenue, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)
@ -838,7 +838,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
formatTonAmountText(stats.balances.availableBalance, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
item.presentationData.strings.Monetization_Overview_Available,
(stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, rate: stats.usdRate))", .generic),
(stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.ton
)
@ -848,7 +848,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
formatTonAmountText(stats.balances.currentBalance, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
item.presentationData.strings.Monetization_Overview_Current,
(stats.balances.currentBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.currentBalance, rate: stats.usdRate))", .generic),
(stats.balances.currentBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.currentBalance, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.ton
)
@ -858,7 +858,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
formatTonAmountText(stats.balances.overallRevenue, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
item.presentationData.strings.Monetization_Overview_Total,
(stats.balances.overallRevenue == 0 ? "" : "\(formatTonUsdValue(stats.balances.overallRevenue, rate: stats.usdRate))", .generic),
(stats.balances.overallRevenue == 0 ? "" : "\(formatTonUsdValue(stats.balances.overallRevenue, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.ton
)
@ -873,7 +873,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
presentationStringsFormattedNumber(Int32(stats.balances.availableBalance), item.presentationData.dateTimeFormat.groupingSeparator),
item.presentationData.strings.Monetization_StarsProceeds_Available,
(stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, rate: stats.usdRate))", .generic),
(stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)
@ -883,7 +883,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
presentationStringsFormattedNumber(Int32(stats.balances.currentBalance), item.presentationData.dateTimeFormat.groupingSeparator),
item.presentationData.strings.Monetization_StarsProceeds_Current,
(stats.balances.currentBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.currentBalance, rate: stats.usdRate))", .generic),
(stats.balances.currentBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.currentBalance, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)
@ -893,7 +893,7 @@ class StatsOverviewItemNode: ListViewItemNode {
item.presentationData,
presentationStringsFormattedNumber(Int32(stats.balances.overallRevenue), item.presentationData.dateTimeFormat.groupingSeparator),
item.presentationData.strings.Monetization_StarsProceeds_Total,
(stats.balances.overallRevenue == 0 ? "" : "\(formatTonUsdValue(stats.balances.overallRevenue, rate: stats.usdRate))", .generic),
(stats.balances.overallRevenue == 0 ? "" : "\(formatTonUsdValue(stats.balances.overallRevenue, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)

View File

@ -105,6 +105,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-944407322] = { return Api.BotMenuButton.parse_botMenuButton($0) }
dict[1113113093] = { return Api.BotMenuButton.parse_botMenuButtonCommands($0) }
dict[1966318984] = { return Api.BotMenuButton.parse_botMenuButtonDefault($0) }
dict[602479523] = { return Api.BotPreviewMedia.parse_botPreviewMedia($0) }
dict[-2076642874] = { return Api.BroadcastRevenueBalances.parse_broadcastRevenueBalances($0) }
dict[1434332356] = { return Api.BroadcastRevenueTransaction.parse_broadcastRevenueTransactionProceeds($0) }
dict[1121127726] = { return Api.BroadcastRevenueTransaction.parse_broadcastRevenueTransactionRefund($0) }
@ -177,12 +178,12 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1078612597] = { return Api.ChannelLocation.parse_channelLocationEmpty($0) }
dict[-847783593] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilter($0) }
dict[-1798033689] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilterEmpty($0) }
dict[-1072953408] = { return Api.ChannelParticipant.parse_channelParticipant($0) }
dict[-885426663] = { return Api.ChannelParticipant.parse_channelParticipant($0) }
dict[885242707] = { return Api.ChannelParticipant.parse_channelParticipantAdmin($0) }
dict[1844969806] = { return Api.ChannelParticipant.parse_channelParticipantBanned($0) }
dict[803602899] = { return Api.ChannelParticipant.parse_channelParticipantCreator($0) }
dict[453242886] = { return Api.ChannelParticipant.parse_channelParticipantLeft($0) }
dict[900251559] = { return Api.ChannelParticipant.parse_channelParticipantSelf($0) }
dict[1331723247] = { return Api.ChannelParticipant.parse_channelParticipantSelf($0) }
dict[-1268741783] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsAdmins($0) }
dict[338142689] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsBanned($0) }
dict[-1328445861] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsBots($0) }
@ -191,7 +192,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-531931925] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsMentions($0) }
dict[-566281095] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsRecent($0) }
dict[106343499] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsSearch($0) }
dict[179174543] = { return Api.Chat.parse_channel($0) }
dict[-29067075] = { return Api.Chat.parse_channel($0) }
dict[399807445] = { return Api.Chat.parse_channelForbidden($0) }
dict[1103884886] = { return Api.Chat.parse_chat($0) }
dict[693512293] = { return Api.Chat.parse_chatEmpty($0) }
@ -201,7 +202,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1626209256] = { return Api.ChatBannedRights.parse_chatBannedRights($0) }
dict[-1146407795] = { return Api.ChatFull.parse_channelFull($0) }
dict[640893467] = { return Api.ChatFull.parse_chatFull($0) }
dict[-1965998484] = { return Api.ChatInvite.parse_chatInvite($0) }
dict[-26920803] = { return Api.ChatInvite.parse_chatInvite($0) }
dict[1516793212] = { return Api.ChatInvite.parse_chatInviteAlready($0) }
dict[1634294960] = { return Api.ChatInvite.parse_chatInvitePeek($0) }
dict[-1940201511] = { return Api.ChatInviteImporter.parse_chatInviteImporter($0) }
@ -354,6 +355,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1690108678] = { return Api.InputEncryptedFile.parse_inputEncryptedFileUploaded($0) }
dict[-181407105] = { return Api.InputFile.parse_inputFile($0) }
dict[-95482955] = { return Api.InputFile.parse_inputFileBig($0) }
dict[1658620744] = { return Api.InputFile.parse_inputFileStoryDocument($0) }
dict[-1160743548] = { return Api.InputFileLocation.parse_inputDocumentFileLocation($0) }
dict[-182231723] = { return Api.InputFileLocation.parse_inputEncryptedFileLocation($0) }
dict[-539317279] = { return Api.InputFileLocation.parse_inputFileLocation($0) }
@ -617,7 +619,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1959634180] = { return Api.MessagePeerVote.parse_messagePeerVoteInputOption($0) }
dict[1177089766] = { return Api.MessagePeerVote.parse_messagePeerVoteMultiple($0) }
dict[182649427] = { return Api.MessageRange.parse_messageRange($0) }
dict[1328256121] = { return Api.MessageReactions.parse_messageReactions($0) }
dict[171155211] = { return Api.MessageReactions.parse_messageReactions($0) }
dict[-285158328] = { return Api.MessageReactor.parse_messageReactor($0) }
dict[-2083123262] = { return Api.MessageReplies.parse_messageReplies($0) }
dict[-1346631205] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) }
dict[240843065] = { return Api.MessageReplyHeader.parse_messageReplyStoryHeader($0) }
@ -766,6 +769,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1992950669] = { return Api.Reaction.parse_reactionCustomEmoji($0) }
dict[455247544] = { return Api.Reaction.parse_reactionEmoji($0) }
dict[2046153753] = { return Api.Reaction.parse_reactionEmpty($0) }
dict[1379771627] = { return Api.Reaction.parse_reactionPaid($0) }
dict[-1546531968] = { return Api.ReactionCount.parse_reactionCount($0) }
dict[1268654752] = { return Api.ReactionNotificationsFrom.parse_reactionNotificationsFromAll($0) }
dict[-1161583078] = { return Api.ReactionNotificationsFrom.parse_reactionNotificationsFromContacts($0) }
@ -883,7 +887,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-797707802] = { return Api.StarsSubscription.parse_starsSubscription($0) }
dict[88173912] = { return Api.StarsSubscriptionPricing.parse_starsSubscriptionPricing($0) }
dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($0) }
dict[455361027] = { return Api.StarsTransaction.parse_starsTransaction($0) }
dict[1127934763] = { return Api.StarsTransaction.parse_starsTransaction($0) }
dict[-670195363] = { return Api.StarsTransactionPeer.parse_starsTransactionPeer($0) }
dict[1617438738] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAds($0) }
dict[-1269320843] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAppStore($0) }
@ -1173,7 +1177,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1542017919] = { return Api.auth.SentCodeType.parse_sentCodeTypeSmsWord($0) }
dict[-391678544] = { return Api.bots.BotInfo.parse_botInfo($0) }
dict[428978491] = { return Api.bots.PopularAppBots.parse_popularAppBots($0) }
dict[1357069389] = { return Api.bots.PreviewInfo.parse_previewInfo($0) }
dict[212278628] = { return Api.bots.PreviewInfo.parse_previewInfo($0) }
dict[-309659827] = { return Api.channels.AdminLogResults.parse_adminLogResults($0) }
dict[-541588713] = { return Api.channels.ChannelParticipant.parse_channelParticipant($0) }
dict[-1699676497] = { return Api.channels.ChannelParticipants.parse_channelParticipants($0) }
@ -1328,7 +1332,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[961445665] = { return Api.payments.StarsRevenueAdsAccountUrl.parse_starsRevenueAdsAccountUrl($0) }
dict[-919881925] = { return Api.payments.StarsRevenueStats.parse_starsRevenueStats($0) }
dict[497778871] = { return Api.payments.StarsRevenueWithdrawalUrl.parse_starsRevenueWithdrawalUrl($0) }
dict[-2064727699] = { return Api.payments.StarsStatus.parse_starsStatus($0) }
dict[-1141231252] = { return Api.payments.StarsStatus.parse_starsStatus($0) }
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
dict[541839704] = { return Api.phone.ExportedGroupCallInvite.parse_exportedGroupCallInvite($0) }
dict[-1636664659] = { return Api.phone.GroupCall.parse_groupCall($0) }
@ -1497,6 +1501,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.BotMenuButton:
_1.serialize(buffer, boxed)
case let _1 as Api.BotPreviewMedia:
_1.serialize(buffer, boxed)
case let _1 as Api.BroadcastRevenueBalances:
_1.serialize(buffer, boxed)
case let _1 as Api.BroadcastRevenueTransaction:
@ -1823,6 +1829,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.MessageReactions:
_1.serialize(buffer, boxed)
case let _1 as Api.MessageReactor:
_1.serialize(buffer, boxed)
case let _1 as Api.MessageReplies:
_1.serialize(buffer, boxed)
case let _1 as Api.MessageReplyHeader:

View File

@ -1,3 +1,135 @@
public extension Api {
indirect enum InputInvoice: TypeConstructorDescription {
case inputInvoiceChatInviteSubscription(hash: String)
case inputInvoiceMessage(peer: Api.InputPeer, msgId: Int32)
case inputInvoicePremiumGiftCode(purpose: Api.InputStorePaymentPurpose, option: Api.PremiumGiftCodeOption)
case inputInvoiceSlug(slug: String)
case inputInvoiceStars(purpose: Api.InputStorePaymentPurpose)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputInvoiceChatInviteSubscription(let hash):
if boxed {
buffer.appendInt32(887591921)
}
serializeString(hash, buffer: buffer, boxed: false)
break
case .inputInvoiceMessage(let peer, let msgId):
if boxed {
buffer.appendInt32(-977967015)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
break
case .inputInvoicePremiumGiftCode(let purpose, let option):
if boxed {
buffer.appendInt32(-1734841331)
}
purpose.serialize(buffer, true)
option.serialize(buffer, true)
break
case .inputInvoiceSlug(let slug):
if boxed {
buffer.appendInt32(-1020867857)
}
serializeString(slug, buffer: buffer, boxed: false)
break
case .inputInvoiceStars(let purpose):
if boxed {
buffer.appendInt32(1710230755)
}
purpose.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputInvoiceChatInviteSubscription(let hash):
return ("inputInvoiceChatInviteSubscription", [("hash", hash as Any)])
case .inputInvoiceMessage(let peer, let msgId):
return ("inputInvoiceMessage", [("peer", peer as Any), ("msgId", msgId as Any)])
case .inputInvoicePremiumGiftCode(let purpose, let option):
return ("inputInvoicePremiumGiftCode", [("purpose", purpose as Any), ("option", option as Any)])
case .inputInvoiceSlug(let slug):
return ("inputInvoiceSlug", [("slug", slug as Any)])
case .inputInvoiceStars(let purpose):
return ("inputInvoiceStars", [("purpose", purpose as Any)])
}
}
public static func parse_inputInvoiceChatInviteSubscription(_ reader: BufferReader) -> InputInvoice? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.InputInvoice.inputInvoiceChatInviteSubscription(hash: _1!)
}
else {
return nil
}
}
public static func parse_inputInvoiceMessage(_ reader: BufferReader) -> InputInvoice? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputInvoice.inputInvoiceMessage(peer: _1!, msgId: _2!)
}
else {
return nil
}
}
public static func parse_inputInvoicePremiumGiftCode(_ reader: BufferReader) -> InputInvoice? {
var _1: Api.InputStorePaymentPurpose?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputStorePaymentPurpose
}
var _2: Api.PremiumGiftCodeOption?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.PremiumGiftCodeOption
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputInvoice.inputInvoicePremiumGiftCode(purpose: _1!, option: _2!)
}
else {
return nil
}
}
public static func parse_inputInvoiceSlug(_ reader: BufferReader) -> InputInvoice? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.InputInvoice.inputInvoiceSlug(slug: _1!)
}
else {
return nil
}
}
public static func parse_inputInvoiceStars(_ reader: BufferReader) -> InputInvoice? {
var _1: Api.InputStorePaymentPurpose?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputStorePaymentPurpose
}
let _c1 = _1 != nil
if _c1 {
return Api.InputInvoice.inputInvoiceStars(purpose: _1!)
}
else {
return nil
}
}
}
}
public extension Api {
indirect enum InputMedia: TypeConstructorDescription {
case inputMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String)
@ -808,281 +940,3 @@ public extension Api {
}
}
public extension Api {
enum InputPaymentCredentials: TypeConstructorDescription {
case inputPaymentCredentials(flags: Int32, data: Api.DataJSON)
case inputPaymentCredentialsApplePay(paymentData: Api.DataJSON)
case inputPaymentCredentialsGooglePay(paymentToken: Api.DataJSON)
case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputPaymentCredentials(let flags, let data):
if boxed {
buffer.appendInt32(873977640)
}
serializeInt32(flags, buffer: buffer, boxed: false)
data.serialize(buffer, true)
break
case .inputPaymentCredentialsApplePay(let paymentData):
if boxed {
buffer.appendInt32(178373535)
}
paymentData.serialize(buffer, true)
break
case .inputPaymentCredentialsGooglePay(let paymentToken):
if boxed {
buffer.appendInt32(-1966921727)
}
paymentToken.serialize(buffer, true)
break
case .inputPaymentCredentialsSaved(let id, let tmpPassword):
if boxed {
buffer.appendInt32(-1056001329)
}
serializeString(id, buffer: buffer, boxed: false)
serializeBytes(tmpPassword, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputPaymentCredentials(let flags, let data):
return ("inputPaymentCredentials", [("flags", flags as Any), ("data", data as Any)])
case .inputPaymentCredentialsApplePay(let paymentData):
return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData as Any)])
case .inputPaymentCredentialsGooglePay(let paymentToken):
return ("inputPaymentCredentialsGooglePay", [("paymentToken", paymentToken as Any)])
case .inputPaymentCredentialsSaved(let id, let tmpPassword):
return ("inputPaymentCredentialsSaved", [("id", id as Any), ("tmpPassword", tmpPassword as Any)])
}
}
public static func parse_inputPaymentCredentials(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.DataJSON?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPaymentCredentials.inputPaymentCredentials(flags: _1!, data: _2!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsApplePay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
if _c1 {
return Api.InputPaymentCredentials.inputPaymentCredentialsApplePay(paymentData: _1!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsGooglePay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
if _c1 {
return Api.InputPaymentCredentials.inputPaymentCredentialsGooglePay(paymentToken: _1!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsSaved(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: String?
_1 = parseString(reader)
var _2: Buffer?
_2 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPaymentCredentials.inputPaymentCredentialsSaved(id: _1!, tmpPassword: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
indirect enum InputPeer: TypeConstructorDescription {
case inputPeerChannel(channelId: Int64, accessHash: Int64)
case inputPeerChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int64)
case inputPeerChat(chatId: Int64)
case inputPeerEmpty
case inputPeerSelf
case inputPeerUser(userId: Int64, accessHash: Int64)
case inputPeerUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int64)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputPeerChannel(let channelId, let accessHash):
if boxed {
buffer.appendInt32(666680316)
}
serializeInt64(channelId, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputPeerChannelFromMessage(let peer, let msgId, let channelId):
if boxed {
buffer.appendInt32(-1121318848)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt64(channelId, buffer: buffer, boxed: false)
break
case .inputPeerChat(let chatId):
if boxed {
buffer.appendInt32(900291769)
}
serializeInt64(chatId, buffer: buffer, boxed: false)
break
case .inputPeerEmpty:
if boxed {
buffer.appendInt32(2134579434)
}
break
case .inputPeerSelf:
if boxed {
buffer.appendInt32(2107670217)
}
break
case .inputPeerUser(let userId, let accessHash):
if boxed {
buffer.appendInt32(-571955892)
}
serializeInt64(userId, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputPeerUserFromMessage(let peer, let msgId, let userId):
if boxed {
buffer.appendInt32(-1468331492)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt64(userId, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputPeerChannel(let channelId, let accessHash):
return ("inputPeerChannel", [("channelId", channelId as Any), ("accessHash", accessHash as Any)])
case .inputPeerChannelFromMessage(let peer, let msgId, let channelId):
return ("inputPeerChannelFromMessage", [("peer", peer as Any), ("msgId", msgId as Any), ("channelId", channelId as Any)])
case .inputPeerChat(let chatId):
return ("inputPeerChat", [("chatId", chatId as Any)])
case .inputPeerEmpty:
return ("inputPeerEmpty", [])
case .inputPeerSelf:
return ("inputPeerSelf", [])
case .inputPeerUser(let userId, let accessHash):
return ("inputPeerUser", [("userId", userId as Any), ("accessHash", accessHash as Any)])
case .inputPeerUserFromMessage(let peer, let msgId, let userId):
return ("inputPeerUserFromMessage", [("peer", peer as Any), ("msgId", msgId as Any), ("userId", userId as Any)])
}
}
public static func parse_inputPeerChannel(_ reader: BufferReader) -> InputPeer? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPeer.inputPeerChannel(channelId: _1!, accessHash: _2!)
}
else {
return nil
}
}
public static func parse_inputPeerChannelFromMessage(_ reader: BufferReader) -> InputPeer? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.InputPeer.inputPeerChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!)
}
else {
return nil
}
}
public static func parse_inputPeerChat(_ reader: BufferReader) -> InputPeer? {
var _1: Int64?
_1 = reader.readInt64()
let _c1 = _1 != nil
if _c1 {
return Api.InputPeer.inputPeerChat(chatId: _1!)
}
else {
return nil
}
}
public static func parse_inputPeerEmpty(_ reader: BufferReader) -> InputPeer? {
return Api.InputPeer.inputPeerEmpty
}
public static func parse_inputPeerSelf(_ reader: BufferReader) -> InputPeer? {
return Api.InputPeer.inputPeerSelf
}
public static func parse_inputPeerUser(_ reader: BufferReader) -> InputPeer? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPeer.inputPeerUser(userId: _1!, accessHash: _2!)
}
else {
return nil
}
}
public static func parse_inputPeerUserFromMessage(_ reader: BufferReader) -> InputPeer? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.InputPeer.inputPeerUserFromMessage(peer: _1!, msgId: _2!, userId: _3!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,281 @@
public extension Api {
enum InputPaymentCredentials: TypeConstructorDescription {
case inputPaymentCredentials(flags: Int32, data: Api.DataJSON)
case inputPaymentCredentialsApplePay(paymentData: Api.DataJSON)
case inputPaymentCredentialsGooglePay(paymentToken: Api.DataJSON)
case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputPaymentCredentials(let flags, let data):
if boxed {
buffer.appendInt32(873977640)
}
serializeInt32(flags, buffer: buffer, boxed: false)
data.serialize(buffer, true)
break
case .inputPaymentCredentialsApplePay(let paymentData):
if boxed {
buffer.appendInt32(178373535)
}
paymentData.serialize(buffer, true)
break
case .inputPaymentCredentialsGooglePay(let paymentToken):
if boxed {
buffer.appendInt32(-1966921727)
}
paymentToken.serialize(buffer, true)
break
case .inputPaymentCredentialsSaved(let id, let tmpPassword):
if boxed {
buffer.appendInt32(-1056001329)
}
serializeString(id, buffer: buffer, boxed: false)
serializeBytes(tmpPassword, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputPaymentCredentials(let flags, let data):
return ("inputPaymentCredentials", [("flags", flags as Any), ("data", data as Any)])
case .inputPaymentCredentialsApplePay(let paymentData):
return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData as Any)])
case .inputPaymentCredentialsGooglePay(let paymentToken):
return ("inputPaymentCredentialsGooglePay", [("paymentToken", paymentToken as Any)])
case .inputPaymentCredentialsSaved(let id, let tmpPassword):
return ("inputPaymentCredentialsSaved", [("id", id as Any), ("tmpPassword", tmpPassword as Any)])
}
}
public static func parse_inputPaymentCredentials(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.DataJSON?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPaymentCredentials.inputPaymentCredentials(flags: _1!, data: _2!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsApplePay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
if _c1 {
return Api.InputPaymentCredentials.inputPaymentCredentialsApplePay(paymentData: _1!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsGooglePay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
if _c1 {
return Api.InputPaymentCredentials.inputPaymentCredentialsGooglePay(paymentToken: _1!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsSaved(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: String?
_1 = parseString(reader)
var _2: Buffer?
_2 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPaymentCredentials.inputPaymentCredentialsSaved(id: _1!, tmpPassword: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
indirect enum InputPeer: TypeConstructorDescription {
case inputPeerChannel(channelId: Int64, accessHash: Int64)
case inputPeerChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int64)
case inputPeerChat(chatId: Int64)
case inputPeerEmpty
case inputPeerSelf
case inputPeerUser(userId: Int64, accessHash: Int64)
case inputPeerUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int64)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputPeerChannel(let channelId, let accessHash):
if boxed {
buffer.appendInt32(666680316)
}
serializeInt64(channelId, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputPeerChannelFromMessage(let peer, let msgId, let channelId):
if boxed {
buffer.appendInt32(-1121318848)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt64(channelId, buffer: buffer, boxed: false)
break
case .inputPeerChat(let chatId):
if boxed {
buffer.appendInt32(900291769)
}
serializeInt64(chatId, buffer: buffer, boxed: false)
break
case .inputPeerEmpty:
if boxed {
buffer.appendInt32(2134579434)
}
break
case .inputPeerSelf:
if boxed {
buffer.appendInt32(2107670217)
}
break
case .inputPeerUser(let userId, let accessHash):
if boxed {
buffer.appendInt32(-571955892)
}
serializeInt64(userId, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputPeerUserFromMessage(let peer, let msgId, let userId):
if boxed {
buffer.appendInt32(-1468331492)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt64(userId, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputPeerChannel(let channelId, let accessHash):
return ("inputPeerChannel", [("channelId", channelId as Any), ("accessHash", accessHash as Any)])
case .inputPeerChannelFromMessage(let peer, let msgId, let channelId):
return ("inputPeerChannelFromMessage", [("peer", peer as Any), ("msgId", msgId as Any), ("channelId", channelId as Any)])
case .inputPeerChat(let chatId):
return ("inputPeerChat", [("chatId", chatId as Any)])
case .inputPeerEmpty:
return ("inputPeerEmpty", [])
case .inputPeerSelf:
return ("inputPeerSelf", [])
case .inputPeerUser(let userId, let accessHash):
return ("inputPeerUser", [("userId", userId as Any), ("accessHash", accessHash as Any)])
case .inputPeerUserFromMessage(let peer, let msgId, let userId):
return ("inputPeerUserFromMessage", [("peer", peer as Any), ("msgId", msgId as Any), ("userId", userId as Any)])
}
}
public static func parse_inputPeerChannel(_ reader: BufferReader) -> InputPeer? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPeer.inputPeerChannel(channelId: _1!, accessHash: _2!)
}
else {
return nil
}
}
public static func parse_inputPeerChannelFromMessage(_ reader: BufferReader) -> InputPeer? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.InputPeer.inputPeerChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!)
}
else {
return nil
}
}
public static func parse_inputPeerChat(_ reader: BufferReader) -> InputPeer? {
var _1: Int64?
_1 = reader.readInt64()
let _c1 = _1 != nil
if _c1 {
return Api.InputPeer.inputPeerChat(chatId: _1!)
}
else {
return nil
}
}
public static func parse_inputPeerEmpty(_ reader: BufferReader) -> InputPeer? {
return Api.InputPeer.inputPeerEmpty
}
public static func parse_inputPeerSelf(_ reader: BufferReader) -> InputPeer? {
return Api.InputPeer.inputPeerSelf
}
public static func parse_inputPeerUser(_ reader: BufferReader) -> InputPeer? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPeer.inputPeerUser(userId: _1!, accessHash: _2!)
}
else {
return nil
}
}
public static func parse_inputPeerUserFromMessage(_ reader: BufferReader) -> InputPeer? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.InputPeer.inputPeerUserFromMessage(peer: _1!, msgId: _2!, userId: _3!)
}
else {
return nil
}
}
}
}
public extension Api {
enum InputPeerNotifySettings: TypeConstructorDescription {
case inputPeerNotifySettings(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, sound: Api.NotificationSound?, storiesMuted: Api.Bool?, storiesHideSender: Api.Bool?, storiesSound: Api.NotificationSound?)
@ -510,321 +788,3 @@ public extension Api {
}
}
public extension Api {
enum InputQuickReplyShortcut: TypeConstructorDescription {
case inputQuickReplyShortcut(shortcut: String)
case inputQuickReplyShortcutId(shortcutId: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputQuickReplyShortcut(let shortcut):
if boxed {
buffer.appendInt32(609840449)
}
serializeString(shortcut, buffer: buffer, boxed: false)
break
case .inputQuickReplyShortcutId(let shortcutId):
if boxed {
buffer.appendInt32(18418929)
}
serializeInt32(shortcutId, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputQuickReplyShortcut(let shortcut):
return ("inputQuickReplyShortcut", [("shortcut", shortcut as Any)])
case .inputQuickReplyShortcutId(let shortcutId):
return ("inputQuickReplyShortcutId", [("shortcutId", shortcutId as Any)])
}
}
public static func parse_inputQuickReplyShortcut(_ reader: BufferReader) -> InputQuickReplyShortcut? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.InputQuickReplyShortcut.inputQuickReplyShortcut(shortcut: _1!)
}
else {
return nil
}
}
public static func parse_inputQuickReplyShortcutId(_ reader: BufferReader) -> InputQuickReplyShortcut? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.InputQuickReplyShortcut.inputQuickReplyShortcutId(shortcutId: _1!)
}
else {
return nil
}
}
}
}
public extension Api {
indirect enum InputReplyTo: TypeConstructorDescription {
case inputReplyToMessage(flags: Int32, replyToMsgId: Int32, topMsgId: Int32?, replyToPeerId: Api.InputPeer?, quoteText: String?, quoteEntities: [Api.MessageEntity]?, quoteOffset: Int32?)
case inputReplyToStory(peer: Api.InputPeer, storyId: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputReplyToMessage(let flags, let replyToMsgId, let topMsgId, let replyToPeerId, let quoteText, let quoteEntities, let quoteOffset):
if boxed {
buffer.appendInt32(583071445)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(replyToMsgId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {replyToPeerId!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(quoteText!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(quoteEntities!.count))
for item in quoteEntities! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 4) != 0 {serializeInt32(quoteOffset!, buffer: buffer, boxed: false)}
break
case .inputReplyToStory(let peer, let storyId):
if boxed {
buffer.appendInt32(1484862010)
}
peer.serialize(buffer, true)
serializeInt32(storyId, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputReplyToMessage(let flags, let replyToMsgId, let topMsgId, let replyToPeerId, let quoteText, let quoteEntities, let quoteOffset):
return ("inputReplyToMessage", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("topMsgId", topMsgId as Any), ("replyToPeerId", replyToPeerId as Any), ("quoteText", quoteText as Any), ("quoteEntities", quoteEntities as Any), ("quoteOffset", quoteOffset as Any)])
case .inputReplyToStory(let peer, let storyId):
return ("inputReplyToStory", [("peer", peer as Any), ("storyId", storyId as Any)])
}
}
public static func parse_inputReplyToMessage(_ reader: BufferReader) -> InputReplyTo? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
var _4: Api.InputPeer?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.InputPeer
} }
var _5: String?
if Int(_1!) & Int(1 << 2) != 0 {_5 = parseString(reader) }
var _6: [Api.MessageEntity]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self)
} }
var _7: Int32?
if Int(_1!) & Int(1 << 4) != 0 {_7 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.InputReplyTo.inputReplyToMessage(flags: _1!, replyToMsgId: _2!, topMsgId: _3, replyToPeerId: _4, quoteText: _5, quoteEntities: _6, quoteOffset: _7)
}
else {
return nil
}
}
public static func parse_inputReplyToStory(_ reader: BufferReader) -> InputReplyTo? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputReplyTo.inputReplyToStory(peer: _1!, storyId: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
enum InputSecureFile: TypeConstructorDescription {
case inputSecureFile(id: Int64, accessHash: Int64)
case inputSecureFileUploaded(id: Int64, parts: Int32, md5Checksum: String, fileHash: Buffer, secret: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputSecureFile(let id, let accessHash):
if boxed {
buffer.appendInt32(1399317950)
}
serializeInt64(id, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputSecureFileUploaded(let id, let parts, let md5Checksum, let fileHash, let secret):
if boxed {
buffer.appendInt32(859091184)
}
serializeInt64(id, buffer: buffer, boxed: false)
serializeInt32(parts, buffer: buffer, boxed: false)
serializeString(md5Checksum, buffer: buffer, boxed: false)
serializeBytes(fileHash, buffer: buffer, boxed: false)
serializeBytes(secret, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputSecureFile(let id, let accessHash):
return ("inputSecureFile", [("id", id as Any), ("accessHash", accessHash as Any)])
case .inputSecureFileUploaded(let id, let parts, let md5Checksum, let fileHash, let secret):
return ("inputSecureFileUploaded", [("id", id as Any), ("parts", parts as Any), ("md5Checksum", md5Checksum as Any), ("fileHash", fileHash as Any), ("secret", secret as Any)])
}
}
public static func parse_inputSecureFile(_ reader: BufferReader) -> InputSecureFile? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputSecureFile.inputSecureFile(id: _1!, accessHash: _2!)
}
else {
return nil
}
}
public static func parse_inputSecureFileUploaded(_ reader: BufferReader) -> InputSecureFile? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: Buffer?
_4 = parseBytes(reader)
var _5: Buffer?
_5 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.InputSecureFile.inputSecureFileUploaded(id: _1!, parts: _2!, md5Checksum: _3!, fileHash: _4!, secret: _5!)
}
else {
return nil
}
}
}
}
public extension Api {
enum InputSecureValue: TypeConstructorDescription {
case inputSecureValue(flags: Int32, type: Api.SecureValueType, data: Api.SecureData?, frontSide: Api.InputSecureFile?, reverseSide: Api.InputSecureFile?, selfie: Api.InputSecureFile?, translation: [Api.InputSecureFile]?, files: [Api.InputSecureFile]?, plainData: Api.SecurePlainData?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputSecureValue(let flags, let type, let data, let frontSide, let reverseSide, let selfie, let translation, let files, let plainData):
if boxed {
buffer.appendInt32(-618540889)
}
serializeInt32(flags, buffer: buffer, boxed: false)
type.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {data!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {frontSide!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {reverseSide!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {selfie!.serialize(buffer, true)}
if Int(flags) & Int(1 << 6) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(translation!.count))
for item in translation! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(files!.count))
for item in files! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 5) != 0 {plainData!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputSecureValue(let flags, let type, let data, let frontSide, let reverseSide, let selfie, let translation, let files, let plainData):
return ("inputSecureValue", [("flags", flags as Any), ("type", type as Any), ("data", data as Any), ("frontSide", frontSide as Any), ("reverseSide", reverseSide as Any), ("selfie", selfie as Any), ("translation", translation as Any), ("files", files as Any), ("plainData", plainData as Any)])
}
}
public static func parse_inputSecureValue(_ reader: BufferReader) -> InputSecureValue? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.SecureValueType?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.SecureValueType
}
var _3: Api.SecureData?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.SecureData
} }
var _4: Api.InputSecureFile?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.InputSecureFile
} }
var _5: Api.InputSecureFile?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.InputSecureFile
} }
var _6: Api.InputSecureFile?
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.InputSecureFile
} }
var _7: [Api.InputSecureFile]?
if Int(_1!) & Int(1 << 6) != 0 {if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputSecureFile.self)
} }
var _8: [Api.InputSecureFile]?
if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputSecureFile.self)
} }
var _9: Api.SecurePlainData?
if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() {
_9 = Api.parse(reader, signature: signature) as? Api.SecurePlainData
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.InputSecureValue.inputSecureValue(flags: _1!, type: _2!, data: _3, frontSide: _4, reverseSide: _5, selfie: _6, translation: _7, files: _8, plainData: _9)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,321 @@
public extension Api {
enum InputQuickReplyShortcut: TypeConstructorDescription {
case inputQuickReplyShortcut(shortcut: String)
case inputQuickReplyShortcutId(shortcutId: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputQuickReplyShortcut(let shortcut):
if boxed {
buffer.appendInt32(609840449)
}
serializeString(shortcut, buffer: buffer, boxed: false)
break
case .inputQuickReplyShortcutId(let shortcutId):
if boxed {
buffer.appendInt32(18418929)
}
serializeInt32(shortcutId, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputQuickReplyShortcut(let shortcut):
return ("inputQuickReplyShortcut", [("shortcut", shortcut as Any)])
case .inputQuickReplyShortcutId(let shortcutId):
return ("inputQuickReplyShortcutId", [("shortcutId", shortcutId as Any)])
}
}
public static func parse_inputQuickReplyShortcut(_ reader: BufferReader) -> InputQuickReplyShortcut? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.InputQuickReplyShortcut.inputQuickReplyShortcut(shortcut: _1!)
}
else {
return nil
}
}
public static func parse_inputQuickReplyShortcutId(_ reader: BufferReader) -> InputQuickReplyShortcut? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.InputQuickReplyShortcut.inputQuickReplyShortcutId(shortcutId: _1!)
}
else {
return nil
}
}
}
}
public extension Api {
indirect enum InputReplyTo: TypeConstructorDescription {
case inputReplyToMessage(flags: Int32, replyToMsgId: Int32, topMsgId: Int32?, replyToPeerId: Api.InputPeer?, quoteText: String?, quoteEntities: [Api.MessageEntity]?, quoteOffset: Int32?)
case inputReplyToStory(peer: Api.InputPeer, storyId: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputReplyToMessage(let flags, let replyToMsgId, let topMsgId, let replyToPeerId, let quoteText, let quoteEntities, let quoteOffset):
if boxed {
buffer.appendInt32(583071445)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(replyToMsgId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {replyToPeerId!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(quoteText!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(quoteEntities!.count))
for item in quoteEntities! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 4) != 0 {serializeInt32(quoteOffset!, buffer: buffer, boxed: false)}
break
case .inputReplyToStory(let peer, let storyId):
if boxed {
buffer.appendInt32(1484862010)
}
peer.serialize(buffer, true)
serializeInt32(storyId, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputReplyToMessage(let flags, let replyToMsgId, let topMsgId, let replyToPeerId, let quoteText, let quoteEntities, let quoteOffset):
return ("inputReplyToMessage", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("topMsgId", topMsgId as Any), ("replyToPeerId", replyToPeerId as Any), ("quoteText", quoteText as Any), ("quoteEntities", quoteEntities as Any), ("quoteOffset", quoteOffset as Any)])
case .inputReplyToStory(let peer, let storyId):
return ("inputReplyToStory", [("peer", peer as Any), ("storyId", storyId as Any)])
}
}
public static func parse_inputReplyToMessage(_ reader: BufferReader) -> InputReplyTo? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
var _4: Api.InputPeer?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.InputPeer
} }
var _5: String?
if Int(_1!) & Int(1 << 2) != 0 {_5 = parseString(reader) }
var _6: [Api.MessageEntity]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self)
} }
var _7: Int32?
if Int(_1!) & Int(1 << 4) != 0 {_7 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.InputReplyTo.inputReplyToMessage(flags: _1!, replyToMsgId: _2!, topMsgId: _3, replyToPeerId: _4, quoteText: _5, quoteEntities: _6, quoteOffset: _7)
}
else {
return nil
}
}
public static func parse_inputReplyToStory(_ reader: BufferReader) -> InputReplyTo? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputReplyTo.inputReplyToStory(peer: _1!, storyId: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
enum InputSecureFile: TypeConstructorDescription {
case inputSecureFile(id: Int64, accessHash: Int64)
case inputSecureFileUploaded(id: Int64, parts: Int32, md5Checksum: String, fileHash: Buffer, secret: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputSecureFile(let id, let accessHash):
if boxed {
buffer.appendInt32(1399317950)
}
serializeInt64(id, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputSecureFileUploaded(let id, let parts, let md5Checksum, let fileHash, let secret):
if boxed {
buffer.appendInt32(859091184)
}
serializeInt64(id, buffer: buffer, boxed: false)
serializeInt32(parts, buffer: buffer, boxed: false)
serializeString(md5Checksum, buffer: buffer, boxed: false)
serializeBytes(fileHash, buffer: buffer, boxed: false)
serializeBytes(secret, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputSecureFile(let id, let accessHash):
return ("inputSecureFile", [("id", id as Any), ("accessHash", accessHash as Any)])
case .inputSecureFileUploaded(let id, let parts, let md5Checksum, let fileHash, let secret):
return ("inputSecureFileUploaded", [("id", id as Any), ("parts", parts as Any), ("md5Checksum", md5Checksum as Any), ("fileHash", fileHash as Any), ("secret", secret as Any)])
}
}
public static func parse_inputSecureFile(_ reader: BufferReader) -> InputSecureFile? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputSecureFile.inputSecureFile(id: _1!, accessHash: _2!)
}
else {
return nil
}
}
public static func parse_inputSecureFileUploaded(_ reader: BufferReader) -> InputSecureFile? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: Buffer?
_4 = parseBytes(reader)
var _5: Buffer?
_5 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.InputSecureFile.inputSecureFileUploaded(id: _1!, parts: _2!, md5Checksum: _3!, fileHash: _4!, secret: _5!)
}
else {
return nil
}
}
}
}
public extension Api {
enum InputSecureValue: TypeConstructorDescription {
case inputSecureValue(flags: Int32, type: Api.SecureValueType, data: Api.SecureData?, frontSide: Api.InputSecureFile?, reverseSide: Api.InputSecureFile?, selfie: Api.InputSecureFile?, translation: [Api.InputSecureFile]?, files: [Api.InputSecureFile]?, plainData: Api.SecurePlainData?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputSecureValue(let flags, let type, let data, let frontSide, let reverseSide, let selfie, let translation, let files, let plainData):
if boxed {
buffer.appendInt32(-618540889)
}
serializeInt32(flags, buffer: buffer, boxed: false)
type.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {data!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {frontSide!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {reverseSide!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {selfie!.serialize(buffer, true)}
if Int(flags) & Int(1 << 6) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(translation!.count))
for item in translation! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(files!.count))
for item in files! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 5) != 0 {plainData!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputSecureValue(let flags, let type, let data, let frontSide, let reverseSide, let selfie, let translation, let files, let plainData):
return ("inputSecureValue", [("flags", flags as Any), ("type", type as Any), ("data", data as Any), ("frontSide", frontSide as Any), ("reverseSide", reverseSide as Any), ("selfie", selfie as Any), ("translation", translation as Any), ("files", files as Any), ("plainData", plainData as Any)])
}
}
public static func parse_inputSecureValue(_ reader: BufferReader) -> InputSecureValue? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.SecureValueType?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.SecureValueType
}
var _3: Api.SecureData?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.SecureData
} }
var _4: Api.InputSecureFile?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.InputSecureFile
} }
var _5: Api.InputSecureFile?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.InputSecureFile
} }
var _6: Api.InputSecureFile?
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.InputSecureFile
} }
var _7: [Api.InputSecureFile]?
if Int(_1!) & Int(1 << 6) != 0 {if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputSecureFile.self)
} }
var _8: [Api.InputSecureFile]?
if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputSecureFile.self)
} }
var _9: Api.SecurePlainData?
if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() {
_9 = Api.parse(reader, signature: signature) as? Api.SecurePlainData
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.InputSecureValue.inputSecureValue(flags: _1!, type: _2!, data: _3, frontSide: _4, reverseSide: _5, selfie: _6, translation: _7, files: _8, plainData: _9)
}
else {
return nil
}
}
}
}
public extension Api {
indirect enum InputSingleMedia: TypeConstructorDescription {
case inputSingleMedia(flags: Int32, media: Api.InputMedia, randomId: Int64, message: String, entities: [Api.MessageEntity]?)
@ -760,177 +1078,3 @@ public extension Api {
}
}
public extension Api {
indirect enum InputUser: TypeConstructorDescription {
case inputUser(userId: Int64, accessHash: Int64)
case inputUserEmpty
case inputUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int64)
case inputUserSelf
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputUser(let userId, let accessHash):
if boxed {
buffer.appendInt32(-233744186)
}
serializeInt64(userId, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputUserEmpty:
if boxed {
buffer.appendInt32(-1182234929)
}
break
case .inputUserFromMessage(let peer, let msgId, let userId):
if boxed {
buffer.appendInt32(497305826)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt64(userId, buffer: buffer, boxed: false)
break
case .inputUserSelf:
if boxed {
buffer.appendInt32(-138301121)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputUser(let userId, let accessHash):
return ("inputUser", [("userId", userId as Any), ("accessHash", accessHash as Any)])
case .inputUserEmpty:
return ("inputUserEmpty", [])
case .inputUserFromMessage(let peer, let msgId, let userId):
return ("inputUserFromMessage", [("peer", peer as Any), ("msgId", msgId as Any), ("userId", userId as Any)])
case .inputUserSelf:
return ("inputUserSelf", [])
}
}
public static func parse_inputUser(_ reader: BufferReader) -> InputUser? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputUser.inputUser(userId: _1!, accessHash: _2!)
}
else {
return nil
}
}
public static func parse_inputUserEmpty(_ reader: BufferReader) -> InputUser? {
return Api.InputUser.inputUserEmpty
}
public static func parse_inputUserFromMessage(_ reader: BufferReader) -> InputUser? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.InputUser.inputUserFromMessage(peer: _1!, msgId: _2!, userId: _3!)
}
else {
return nil
}
}
public static func parse_inputUserSelf(_ reader: BufferReader) -> InputUser? {
return Api.InputUser.inputUserSelf
}
}
}
public extension Api {
enum InputWallPaper: TypeConstructorDescription {
case inputWallPaper(id: Int64, accessHash: Int64)
case inputWallPaperNoFile(id: Int64)
case inputWallPaperSlug(slug: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputWallPaper(let id, let accessHash):
if boxed {
buffer.appendInt32(-433014407)
}
serializeInt64(id, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputWallPaperNoFile(let id):
if boxed {
buffer.appendInt32(-1770371538)
}
serializeInt64(id, buffer: buffer, boxed: false)
break
case .inputWallPaperSlug(let slug):
if boxed {
buffer.appendInt32(1913199744)
}
serializeString(slug, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputWallPaper(let id, let accessHash):
return ("inputWallPaper", [("id", id as Any), ("accessHash", accessHash as Any)])
case .inputWallPaperNoFile(let id):
return ("inputWallPaperNoFile", [("id", id as Any)])
case .inputWallPaperSlug(let slug):
return ("inputWallPaperSlug", [("slug", slug as Any)])
}
}
public static func parse_inputWallPaper(_ reader: BufferReader) -> InputWallPaper? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputWallPaper.inputWallPaper(id: _1!, accessHash: _2!)
}
else {
return nil
}
}
public static func parse_inputWallPaperNoFile(_ reader: BufferReader) -> InputWallPaper? {
var _1: Int64?
_1 = reader.readInt64()
let _c1 = _1 != nil
if _c1 {
return Api.InputWallPaper.inputWallPaperNoFile(id: _1!)
}
else {
return nil
}
}
public static func parse_inputWallPaperSlug(_ reader: BufferReader) -> InputWallPaper? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.InputWallPaper.inputWallPaperSlug(slug: _1!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,177 @@
public extension Api {
indirect enum InputUser: TypeConstructorDescription {
case inputUser(userId: Int64, accessHash: Int64)
case inputUserEmpty
case inputUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int64)
case inputUserSelf
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputUser(let userId, let accessHash):
if boxed {
buffer.appendInt32(-233744186)
}
serializeInt64(userId, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputUserEmpty:
if boxed {
buffer.appendInt32(-1182234929)
}
break
case .inputUserFromMessage(let peer, let msgId, let userId):
if boxed {
buffer.appendInt32(497305826)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt64(userId, buffer: buffer, boxed: false)
break
case .inputUserSelf:
if boxed {
buffer.appendInt32(-138301121)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputUser(let userId, let accessHash):
return ("inputUser", [("userId", userId as Any), ("accessHash", accessHash as Any)])
case .inputUserEmpty:
return ("inputUserEmpty", [])
case .inputUserFromMessage(let peer, let msgId, let userId):
return ("inputUserFromMessage", [("peer", peer as Any), ("msgId", msgId as Any), ("userId", userId as Any)])
case .inputUserSelf:
return ("inputUserSelf", [])
}
}
public static func parse_inputUser(_ reader: BufferReader) -> InputUser? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputUser.inputUser(userId: _1!, accessHash: _2!)
}
else {
return nil
}
}
public static func parse_inputUserEmpty(_ reader: BufferReader) -> InputUser? {
return Api.InputUser.inputUserEmpty
}
public static func parse_inputUserFromMessage(_ reader: BufferReader) -> InputUser? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.InputUser.inputUserFromMessage(peer: _1!, msgId: _2!, userId: _3!)
}
else {
return nil
}
}
public static func parse_inputUserSelf(_ reader: BufferReader) -> InputUser? {
return Api.InputUser.inputUserSelf
}
}
}
public extension Api {
enum InputWallPaper: TypeConstructorDescription {
case inputWallPaper(id: Int64, accessHash: Int64)
case inputWallPaperNoFile(id: Int64)
case inputWallPaperSlug(slug: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputWallPaper(let id, let accessHash):
if boxed {
buffer.appendInt32(-433014407)
}
serializeInt64(id, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputWallPaperNoFile(let id):
if boxed {
buffer.appendInt32(-1770371538)
}
serializeInt64(id, buffer: buffer, boxed: false)
break
case .inputWallPaperSlug(let slug):
if boxed {
buffer.appendInt32(1913199744)
}
serializeString(slug, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputWallPaper(let id, let accessHash):
return ("inputWallPaper", [("id", id as Any), ("accessHash", accessHash as Any)])
case .inputWallPaperNoFile(let id):
return ("inputWallPaperNoFile", [("id", id as Any)])
case .inputWallPaperSlug(let slug):
return ("inputWallPaperSlug", [("slug", slug as Any)])
}
}
public static func parse_inputWallPaper(_ reader: BufferReader) -> InputWallPaper? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputWallPaper.inputWallPaper(id: _1!, accessHash: _2!)
}
else {
return nil
}
}
public static func parse_inputWallPaperNoFile(_ reader: BufferReader) -> InputWallPaper? {
var _1: Int64?
_1 = reader.readInt64()
let _c1 = _1 != nil
if _c1 {
return Api.InputWallPaper.inputWallPaperNoFile(id: _1!)
}
else {
return nil
}
}
public static func parse_inputWallPaperSlug(_ reader: BufferReader) -> InputWallPaper? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.InputWallPaper.inputWallPaperSlug(slug: _1!)
}
else {
return nil
}
}
}
}
public extension Api {
enum InputWebDocument: TypeConstructorDescription {
case inputWebDocument(url: String, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute])
@ -900,139 +1074,3 @@ public extension Api {
}
}
public extension Api {
enum KeyboardButtonRow: TypeConstructorDescription {
case keyboardButtonRow(buttons: [Api.KeyboardButton])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .keyboardButtonRow(let buttons):
if boxed {
buffer.appendInt32(2002815875)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(buttons.count))
for item in buttons {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .keyboardButtonRow(let buttons):
return ("keyboardButtonRow", [("buttons", buttons as Any)])
}
}
public static func parse_keyboardButtonRow(_ reader: BufferReader) -> KeyboardButtonRow? {
var _1: [Api.KeyboardButton]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButton.self)
}
let _c1 = _1 != nil
if _c1 {
return Api.KeyboardButtonRow.keyboardButtonRow(buttons: _1!)
}
else {
return nil
}
}
}
}
public extension Api {
enum LabeledPrice: TypeConstructorDescription {
case labeledPrice(label: String, amount: Int64)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .labeledPrice(let label, let amount):
if boxed {
buffer.appendInt32(-886477832)
}
serializeString(label, buffer: buffer, boxed: false)
serializeInt64(amount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .labeledPrice(let label, let amount):
return ("labeledPrice", [("label", label as Any), ("amount", amount as Any)])
}
}
public static func parse_labeledPrice(_ reader: BufferReader) -> LabeledPrice? {
var _1: String?
_1 = parseString(reader)
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.LabeledPrice.labeledPrice(label: _1!, amount: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
enum LangPackDifference: TypeConstructorDescription {
case langPackDifference(langCode: String, fromVersion: Int32, version: Int32, strings: [Api.LangPackString])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .langPackDifference(let langCode, let fromVersion, let version, let strings):
if boxed {
buffer.appendInt32(-209337866)
}
serializeString(langCode, buffer: buffer, boxed: false)
serializeInt32(fromVersion, buffer: buffer, boxed: false)
serializeInt32(version, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(strings.count))
for item in strings {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .langPackDifference(let langCode, let fromVersion, let version, let strings):
return ("langPackDifference", [("langCode", langCode as Any), ("fromVersion", fromVersion as Any), ("version", version as Any), ("strings", strings as Any)])
}
}
public static func parse_langPackDifference(_ reader: BufferReader) -> LangPackDifference? {
var _1: String?
_1 = parseString(reader)
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
var _4: [Api.LangPackString]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackString.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.LangPackDifference.langPackDifference(langCode: _1!, fromVersion: _2!, version: _3!, strings: _4!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,139 @@
public extension Api {
enum KeyboardButtonRow: TypeConstructorDescription {
case keyboardButtonRow(buttons: [Api.KeyboardButton])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .keyboardButtonRow(let buttons):
if boxed {
buffer.appendInt32(2002815875)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(buttons.count))
for item in buttons {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .keyboardButtonRow(let buttons):
return ("keyboardButtonRow", [("buttons", buttons as Any)])
}
}
public static func parse_keyboardButtonRow(_ reader: BufferReader) -> KeyboardButtonRow? {
var _1: [Api.KeyboardButton]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButton.self)
}
let _c1 = _1 != nil
if _c1 {
return Api.KeyboardButtonRow.keyboardButtonRow(buttons: _1!)
}
else {
return nil
}
}
}
}
public extension Api {
enum LabeledPrice: TypeConstructorDescription {
case labeledPrice(label: String, amount: Int64)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .labeledPrice(let label, let amount):
if boxed {
buffer.appendInt32(-886477832)
}
serializeString(label, buffer: buffer, boxed: false)
serializeInt64(amount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .labeledPrice(let label, let amount):
return ("labeledPrice", [("label", label as Any), ("amount", amount as Any)])
}
}
public static func parse_labeledPrice(_ reader: BufferReader) -> LabeledPrice? {
var _1: String?
_1 = parseString(reader)
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.LabeledPrice.labeledPrice(label: _1!, amount: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
enum LangPackDifference: TypeConstructorDescription {
case langPackDifference(langCode: String, fromVersion: Int32, version: Int32, strings: [Api.LangPackString])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .langPackDifference(let langCode, let fromVersion, let version, let strings):
if boxed {
buffer.appendInt32(-209337866)
}
serializeString(langCode, buffer: buffer, boxed: false)
serializeInt32(fromVersion, buffer: buffer, boxed: false)
serializeInt32(version, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(strings.count))
for item in strings {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .langPackDifference(let langCode, let fromVersion, let version, let strings):
return ("langPackDifference", [("langCode", langCode as Any), ("fromVersion", fromVersion as Any), ("version", version as Any), ("strings", strings as Any)])
}
}
public static func parse_langPackDifference(_ reader: BufferReader) -> LangPackDifference? {
var _1: String?
_1 = parseString(reader)
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
var _4: [Api.LangPackString]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackString.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.LangPackDifference.langPackDifference(langCode: _1!, fromVersion: _2!, version: _3!, strings: _4!)
}
else {
return nil
}
}
}
}
public extension Api {
enum LangPackLanguage: TypeConstructorDescription {
case langPackLanguage(flags: Int32, name: String, nativeName: String, langCode: String, baseLangCode: String?, pluralCode: String, stringsCount: Int32, translatedCount: Int32, translationsUrl: String)

View File

@ -200,13 +200,13 @@ public extension Api {
}
public extension Api {
enum MessageReactions: TypeConstructorDescription {
case messageReactions(flags: Int32, results: [Api.ReactionCount], recentReactions: [Api.MessagePeerReaction]?)
case messageReactions(flags: Int32, results: [Api.ReactionCount], recentReactions: [Api.MessagePeerReaction]?, topReactors: [Api.MessageReactor]?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageReactions(let flags, let results, let recentReactions):
case .messageReactions(let flags, let results, let recentReactions, let topReactors):
if boxed {
buffer.appendInt32(1328256121)
buffer.appendInt32(171155211)
}
serializeInt32(flags, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
@ -219,14 +219,19 @@ public extension Api {
for item in recentReactions! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(topReactors!.count))
for item in topReactors! {
item.serialize(buffer, true)
}}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageReactions(let flags, let results, let recentReactions):
return ("messageReactions", [("flags", flags as Any), ("results", results as Any), ("recentReactions", recentReactions as Any)])
case .messageReactions(let flags, let results, let recentReactions, let topReactors):
return ("messageReactions", [("flags", flags as Any), ("results", results as Any), ("recentReactions", recentReactions as Any), ("topReactors", topReactors as Any)])
}
}
@ -241,11 +246,62 @@ public extension Api {
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerReaction.self)
} }
var _4: [Api.MessageReactor]?
if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageReactor.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 << 4) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.MessageReactions.messageReactions(flags: _1!, results: _2!, recentReactions: _3, topReactors: _4)
}
else {
return nil
}
}
}
}
public extension Api {
enum MessageReactor: TypeConstructorDescription {
case messageReactor(flags: Int32, peerId: Api.Peer, count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageReactor(let flags, let peerId, let count):
if boxed {
buffer.appendInt32(-285158328)
}
serializeInt32(flags, buffer: buffer, boxed: false)
peerId.serialize(buffer, true)
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageReactor(let flags, let peerId, let count):
return ("messageReactor", [("flags", flags as Any), ("peerId", peerId as Any), ("count", count as Any)])
}
}
public static func parse_messageReactor(_ reader: BufferReader) -> MessageReactor? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Peer?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.MessageReactions.messageReactions(flags: _1!, results: _2!, recentReactions: _3)
return Api.MessageReactor.messageReactor(flags: _1!, peerId: _2!, count: _3!)
}
else {
return nil

View File

@ -724,6 +724,48 @@ public extension Api {
}
}
public extension Api {
indirect enum BotPreviewMedia: TypeConstructorDescription {
case botPreviewMedia(date: Int32, media: Api.MessageMedia)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .botPreviewMedia(let date, let media):
if boxed {
buffer.appendInt32(602479523)
}
serializeInt32(date, buffer: buffer, boxed: false)
media.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .botPreviewMedia(let date, let media):
return ("botPreviewMedia", [("date", date as Any), ("media", media as Any)])
}
}
public static func parse_botPreviewMedia(_ reader: BufferReader) -> BotPreviewMedia? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.MessageMedia?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.MessageMedia
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.BotPreviewMedia.botPreviewMedia(date: _1!, media: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
enum BroadcastRevenueBalances: TypeConstructorDescription {
case broadcastRevenueBalances(currentBalance: Int64, availableBalance: Int64, overallRevenue: Int64)
@ -1160,53 +1202,3 @@ public extension Api {
}
}
public extension Api {
enum BusinessIntro: TypeConstructorDescription {
case businessIntro(flags: Int32, title: String, description: String, sticker: Api.Document?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .businessIntro(let flags, let title, let description, let sticker):
if boxed {
buffer.appendInt32(1510606445)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
serializeString(description, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {sticker!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .businessIntro(let flags, let title, let description, let sticker):
return ("businessIntro", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("sticker", sticker as Any)])
}
}
public static func parse_businessIntro(_ reader: BufferReader) -> BusinessIntro? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: String?
_3 = parseString(reader)
var _4: Api.Document?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.Document
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.BusinessIntro.businessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4)
}
else {
return nil
}
}
}
}

View File

@ -309,6 +309,7 @@ public extension Api {
case reactionCustomEmoji(documentId: Int64)
case reactionEmoji(emoticon: String)
case reactionEmpty
case reactionPaid
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -329,6 +330,12 @@ public extension Api {
buffer.appendInt32(2046153753)
}
break
case .reactionPaid:
if boxed {
buffer.appendInt32(1379771627)
}
break
}
}
@ -341,6 +348,8 @@ public extension Api {
return ("reactionEmoji", [("emoticon", emoticon as Any)])
case .reactionEmpty:
return ("reactionEmpty", [])
case .reactionPaid:
return ("reactionPaid", [])
}
}
@ -369,6 +378,9 @@ public extension Api {
public static func parse_reactionEmpty(_ reader: BufferReader) -> Reaction? {
return Api.Reaction.reactionEmpty
}
public static func parse_reactionPaid(_ reader: BufferReader) -> Reaction? {
return Api.Reaction.reactionPaid
}
}
}
@ -858,139 +870,3 @@ public extension Api {
}
}
public extension Api {
enum ReportReason: TypeConstructorDescription {
case inputReportReasonChildAbuse
case inputReportReasonCopyright
case inputReportReasonFake
case inputReportReasonGeoIrrelevant
case inputReportReasonIllegalDrugs
case inputReportReasonOther
case inputReportReasonPersonalDetails
case inputReportReasonPornography
case inputReportReasonSpam
case inputReportReasonViolence
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputReportReasonChildAbuse:
if boxed {
buffer.appendInt32(-1376497949)
}
break
case .inputReportReasonCopyright:
if boxed {
buffer.appendInt32(-1685456582)
}
break
case .inputReportReasonFake:
if boxed {
buffer.appendInt32(-170010905)
}
break
case .inputReportReasonGeoIrrelevant:
if boxed {
buffer.appendInt32(-606798099)
}
break
case .inputReportReasonIllegalDrugs:
if boxed {
buffer.appendInt32(177124030)
}
break
case .inputReportReasonOther:
if boxed {
buffer.appendInt32(-1041980751)
}
break
case .inputReportReasonPersonalDetails:
if boxed {
buffer.appendInt32(-1631091139)
}
break
case .inputReportReasonPornography:
if boxed {
buffer.appendInt32(777640226)
}
break
case .inputReportReasonSpam:
if boxed {
buffer.appendInt32(1490799288)
}
break
case .inputReportReasonViolence:
if boxed {
buffer.appendInt32(505595789)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputReportReasonChildAbuse:
return ("inputReportReasonChildAbuse", [])
case .inputReportReasonCopyright:
return ("inputReportReasonCopyright", [])
case .inputReportReasonFake:
return ("inputReportReasonFake", [])
case .inputReportReasonGeoIrrelevant:
return ("inputReportReasonGeoIrrelevant", [])
case .inputReportReasonIllegalDrugs:
return ("inputReportReasonIllegalDrugs", [])
case .inputReportReasonOther:
return ("inputReportReasonOther", [])
case .inputReportReasonPersonalDetails:
return ("inputReportReasonPersonalDetails", [])
case .inputReportReasonPornography:
return ("inputReportReasonPornography", [])
case .inputReportReasonSpam:
return ("inputReportReasonSpam", [])
case .inputReportReasonViolence:
return ("inputReportReasonViolence", [])
}
}
public static func parse_inputReportReasonChildAbuse(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonChildAbuse
}
public static func parse_inputReportReasonCopyright(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonCopyright
}
public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonFake
}
public static func parse_inputReportReasonGeoIrrelevant(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonGeoIrrelevant
}
public static func parse_inputReportReasonIllegalDrugs(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonIllegalDrugs
}
public static func parse_inputReportReasonOther(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonOther
}
public static func parse_inputReportReasonPersonalDetails(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonPersonalDetails
}
public static func parse_inputReportReasonPornography(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonPornography
}
public static func parse_inputReportReasonSpam(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonSpam
}
public static func parse_inputReportReasonViolence(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonViolence
}
}
}

View File

@ -1,3 +1,139 @@
public extension Api {
enum ReportReason: TypeConstructorDescription {
case inputReportReasonChildAbuse
case inputReportReasonCopyright
case inputReportReasonFake
case inputReportReasonGeoIrrelevant
case inputReportReasonIllegalDrugs
case inputReportReasonOther
case inputReportReasonPersonalDetails
case inputReportReasonPornography
case inputReportReasonSpam
case inputReportReasonViolence
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputReportReasonChildAbuse:
if boxed {
buffer.appendInt32(-1376497949)
}
break
case .inputReportReasonCopyright:
if boxed {
buffer.appendInt32(-1685456582)
}
break
case .inputReportReasonFake:
if boxed {
buffer.appendInt32(-170010905)
}
break
case .inputReportReasonGeoIrrelevant:
if boxed {
buffer.appendInt32(-606798099)
}
break
case .inputReportReasonIllegalDrugs:
if boxed {
buffer.appendInt32(177124030)
}
break
case .inputReportReasonOther:
if boxed {
buffer.appendInt32(-1041980751)
}
break
case .inputReportReasonPersonalDetails:
if boxed {
buffer.appendInt32(-1631091139)
}
break
case .inputReportReasonPornography:
if boxed {
buffer.appendInt32(777640226)
}
break
case .inputReportReasonSpam:
if boxed {
buffer.appendInt32(1490799288)
}
break
case .inputReportReasonViolence:
if boxed {
buffer.appendInt32(505595789)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputReportReasonChildAbuse:
return ("inputReportReasonChildAbuse", [])
case .inputReportReasonCopyright:
return ("inputReportReasonCopyright", [])
case .inputReportReasonFake:
return ("inputReportReasonFake", [])
case .inputReportReasonGeoIrrelevant:
return ("inputReportReasonGeoIrrelevant", [])
case .inputReportReasonIllegalDrugs:
return ("inputReportReasonIllegalDrugs", [])
case .inputReportReasonOther:
return ("inputReportReasonOther", [])
case .inputReportReasonPersonalDetails:
return ("inputReportReasonPersonalDetails", [])
case .inputReportReasonPornography:
return ("inputReportReasonPornography", [])
case .inputReportReasonSpam:
return ("inputReportReasonSpam", [])
case .inputReportReasonViolence:
return ("inputReportReasonViolence", [])
}
}
public static func parse_inputReportReasonChildAbuse(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonChildAbuse
}
public static func parse_inputReportReasonCopyright(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonCopyright
}
public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonFake
}
public static func parse_inputReportReasonGeoIrrelevant(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonGeoIrrelevant
}
public static func parse_inputReportReasonIllegalDrugs(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonIllegalDrugs
}
public static func parse_inputReportReasonOther(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonOther
}
public static func parse_inputReportReasonPersonalDetails(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonPersonalDetails
}
public static func parse_inputReportReasonPornography(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonPornography
}
public static func parse_inputReportReasonSpam(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonSpam
}
public static func parse_inputReportReasonViolence(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonViolence
}
}
}
public extension Api {
enum RequestPeerType: TypeConstructorDescription {
case requestPeerTypeBroadcast(flags: Int32, hasUsername: Api.Bool?, userAdminRights: Api.ChatAdminRights?, botAdminRights: Api.ChatAdminRights?)
@ -688,399 +824,3 @@ public extension Api {
}
}
public extension Api {
enum SavedContact: TypeConstructorDescription {
case savedPhoneContact(phone: String, firstName: String, lastName: String, date: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .savedPhoneContact(let phone, let firstName, let lastName, let date):
if boxed {
buffer.appendInt32(289586518)
}
serializeString(phone, buffer: buffer, boxed: false)
serializeString(firstName, buffer: buffer, boxed: false)
serializeString(lastName, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .savedPhoneContact(let phone, let firstName, let lastName, let date):
return ("savedPhoneContact", [("phone", phone as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("date", date as Any)])
}
}
public static func parse_savedPhoneContact(_ reader: BufferReader) -> SavedContact? {
var _1: String?
_1 = parseString(reader)
var _2: String?
_2 = parseString(reader)
var _3: String?
_3 = parseString(reader)
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.SavedContact.savedPhoneContact(phone: _1!, firstName: _2!, lastName: _3!, date: _4!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SavedDialog: TypeConstructorDescription {
case savedDialog(flags: Int32, peer: Api.Peer, topMessage: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .savedDialog(let flags, let peer, let topMessage):
if boxed {
buffer.appendInt32(-1115174036)
}
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(topMessage, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .savedDialog(let flags, let peer, let topMessage):
return ("savedDialog", [("flags", flags as Any), ("peer", peer as Any), ("topMessage", topMessage as Any)])
}
}
public static func parse_savedDialog(_ reader: BufferReader) -> SavedDialog? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Peer?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.SavedDialog.savedDialog(flags: _1!, peer: _2!, topMessage: _3!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SavedReactionTag: TypeConstructorDescription {
case savedReactionTag(flags: Int32, reaction: Api.Reaction, title: String?, count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .savedReactionTag(let flags, let reaction, let title, let count):
if boxed {
buffer.appendInt32(-881854424)
}
serializeInt32(flags, buffer: buffer, boxed: false)
reaction.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .savedReactionTag(let flags, let reaction, let title, let count):
return ("savedReactionTag", [("flags", flags as Any), ("reaction", reaction as Any), ("title", title as Any), ("count", count as Any)])
}
}
public static func parse_savedReactionTag(_ reader: BufferReader) -> SavedReactionTag? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Reaction?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Reaction
}
var _3: String?
if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) }
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.SavedReactionTag.savedReactionTag(flags: _1!, reaction: _2!, title: _3, count: _4!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SearchResultsCalendarPeriod: TypeConstructorDescription {
case searchResultsCalendarPeriod(date: Int32, minMsgId: Int32, maxMsgId: Int32, count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .searchResultsCalendarPeriod(let date, let minMsgId, let maxMsgId, let count):
if boxed {
buffer.appendInt32(-911191137)
}
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt32(minMsgId, buffer: buffer, boxed: false)
serializeInt32(maxMsgId, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .searchResultsCalendarPeriod(let date, let minMsgId, let maxMsgId, let count):
return ("searchResultsCalendarPeriod", [("date", date as Any), ("minMsgId", minMsgId as Any), ("maxMsgId", maxMsgId as Any), ("count", count as Any)])
}
}
public static func parse_searchResultsCalendarPeriod(_ reader: BufferReader) -> SearchResultsCalendarPeriod? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.SearchResultsCalendarPeriod.searchResultsCalendarPeriod(date: _1!, minMsgId: _2!, maxMsgId: _3!, count: _4!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SearchResultsPosition: TypeConstructorDescription {
case searchResultPosition(msgId: Int32, date: Int32, offset: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .searchResultPosition(let msgId, let date, let offset):
if boxed {
buffer.appendInt32(2137295719)
}
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt32(offset, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .searchResultPosition(let msgId, let date, let offset):
return ("searchResultPosition", [("msgId", msgId as Any), ("date", date as Any), ("offset", offset as Any)])
}
}
public static func parse_searchResultPosition(_ reader: BufferReader) -> SearchResultsPosition? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.SearchResultsPosition.searchResultPosition(msgId: _1!, date: _2!, offset: _3!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SecureCredentialsEncrypted: TypeConstructorDescription {
case secureCredentialsEncrypted(data: Buffer, hash: Buffer, secret: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .secureCredentialsEncrypted(let data, let hash, let secret):
if boxed {
buffer.appendInt32(871426631)
}
serializeBytes(data, buffer: buffer, boxed: false)
serializeBytes(hash, buffer: buffer, boxed: false)
serializeBytes(secret, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .secureCredentialsEncrypted(let data, let hash, let secret):
return ("secureCredentialsEncrypted", [("data", data as Any), ("hash", hash as Any), ("secret", secret as Any)])
}
}
public static func parse_secureCredentialsEncrypted(_ reader: BufferReader) -> SecureCredentialsEncrypted? {
var _1: Buffer?
_1 = parseBytes(reader)
var _2: Buffer?
_2 = parseBytes(reader)
var _3: Buffer?
_3 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.SecureCredentialsEncrypted.secureCredentialsEncrypted(data: _1!, hash: _2!, secret: _3!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SecureData: TypeConstructorDescription {
case secureData(data: Buffer, dataHash: Buffer, secret: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .secureData(let data, let dataHash, let secret):
if boxed {
buffer.appendInt32(-1964327229)
}
serializeBytes(data, buffer: buffer, boxed: false)
serializeBytes(dataHash, buffer: buffer, boxed: false)
serializeBytes(secret, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .secureData(let data, let dataHash, let secret):
return ("secureData", [("data", data as Any), ("dataHash", dataHash as Any), ("secret", secret as Any)])
}
}
public static func parse_secureData(_ reader: BufferReader) -> SecureData? {
var _1: Buffer?
_1 = parseBytes(reader)
var _2: Buffer?
_2 = parseBytes(reader)
var _3: Buffer?
_3 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.SecureData.secureData(data: _1!, dataHash: _2!, secret: _3!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SecureFile: TypeConstructorDescription {
case secureFile(id: Int64, accessHash: Int64, size: Int64, dcId: Int32, date: Int32, fileHash: Buffer, secret: Buffer)
case secureFileEmpty
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .secureFile(let id, let accessHash, let size, let dcId, let date, let fileHash, let secret):
if boxed {
buffer.appendInt32(2097791614)
}
serializeInt64(id, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
serializeInt64(size, buffer: buffer, boxed: false)
serializeInt32(dcId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeBytes(fileHash, buffer: buffer, boxed: false)
serializeBytes(secret, buffer: buffer, boxed: false)
break
case .secureFileEmpty:
if boxed {
buffer.appendInt32(1679398724)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .secureFile(let id, let accessHash, let size, let dcId, let date, let fileHash, let secret):
return ("secureFile", [("id", id as Any), ("accessHash", accessHash as Any), ("size", size as Any), ("dcId", dcId as Any), ("date", date as Any), ("fileHash", fileHash as Any), ("secret", secret as Any)])
case .secureFileEmpty:
return ("secureFileEmpty", [])
}
}
public static func parse_secureFile(_ reader: BufferReader) -> SecureFile? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int64?
_3 = reader.readInt64()
var _4: Int32?
_4 = reader.readInt32()
var _5: Int32?
_5 = reader.readInt32()
var _6: Buffer?
_6 = parseBytes(reader)
var _7: Buffer?
_7 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.SecureFile.secureFile(id: _1!, accessHash: _2!, size: _3!, dcId: _4!, date: _5!, fileHash: _6!, secret: _7!)
}
else {
return nil
}
}
public static func parse_secureFileEmpty(_ reader: BufferReader) -> SecureFile? {
return Api.SecureFile.secureFileEmpty
}
}
}

View File

@ -1,3 +1,399 @@
public extension Api {
enum SavedContact: TypeConstructorDescription {
case savedPhoneContact(phone: String, firstName: String, lastName: String, date: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .savedPhoneContact(let phone, let firstName, let lastName, let date):
if boxed {
buffer.appendInt32(289586518)
}
serializeString(phone, buffer: buffer, boxed: false)
serializeString(firstName, buffer: buffer, boxed: false)
serializeString(lastName, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .savedPhoneContact(let phone, let firstName, let lastName, let date):
return ("savedPhoneContact", [("phone", phone as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("date", date as Any)])
}
}
public static func parse_savedPhoneContact(_ reader: BufferReader) -> SavedContact? {
var _1: String?
_1 = parseString(reader)
var _2: String?
_2 = parseString(reader)
var _3: String?
_3 = parseString(reader)
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.SavedContact.savedPhoneContact(phone: _1!, firstName: _2!, lastName: _3!, date: _4!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SavedDialog: TypeConstructorDescription {
case savedDialog(flags: Int32, peer: Api.Peer, topMessage: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .savedDialog(let flags, let peer, let topMessage):
if boxed {
buffer.appendInt32(-1115174036)
}
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(topMessage, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .savedDialog(let flags, let peer, let topMessage):
return ("savedDialog", [("flags", flags as Any), ("peer", peer as Any), ("topMessage", topMessage as Any)])
}
}
public static func parse_savedDialog(_ reader: BufferReader) -> SavedDialog? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Peer?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.SavedDialog.savedDialog(flags: _1!, peer: _2!, topMessage: _3!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SavedReactionTag: TypeConstructorDescription {
case savedReactionTag(flags: Int32, reaction: Api.Reaction, title: String?, count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .savedReactionTag(let flags, let reaction, let title, let count):
if boxed {
buffer.appendInt32(-881854424)
}
serializeInt32(flags, buffer: buffer, boxed: false)
reaction.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .savedReactionTag(let flags, let reaction, let title, let count):
return ("savedReactionTag", [("flags", flags as Any), ("reaction", reaction as Any), ("title", title as Any), ("count", count as Any)])
}
}
public static func parse_savedReactionTag(_ reader: BufferReader) -> SavedReactionTag? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Reaction?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Reaction
}
var _3: String?
if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) }
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.SavedReactionTag.savedReactionTag(flags: _1!, reaction: _2!, title: _3, count: _4!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SearchResultsCalendarPeriod: TypeConstructorDescription {
case searchResultsCalendarPeriod(date: Int32, minMsgId: Int32, maxMsgId: Int32, count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .searchResultsCalendarPeriod(let date, let minMsgId, let maxMsgId, let count):
if boxed {
buffer.appendInt32(-911191137)
}
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt32(minMsgId, buffer: buffer, boxed: false)
serializeInt32(maxMsgId, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .searchResultsCalendarPeriod(let date, let minMsgId, let maxMsgId, let count):
return ("searchResultsCalendarPeriod", [("date", date as Any), ("minMsgId", minMsgId as Any), ("maxMsgId", maxMsgId as Any), ("count", count as Any)])
}
}
public static func parse_searchResultsCalendarPeriod(_ reader: BufferReader) -> SearchResultsCalendarPeriod? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.SearchResultsCalendarPeriod.searchResultsCalendarPeriod(date: _1!, minMsgId: _2!, maxMsgId: _3!, count: _4!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SearchResultsPosition: TypeConstructorDescription {
case searchResultPosition(msgId: Int32, date: Int32, offset: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .searchResultPosition(let msgId, let date, let offset):
if boxed {
buffer.appendInt32(2137295719)
}
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt32(offset, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .searchResultPosition(let msgId, let date, let offset):
return ("searchResultPosition", [("msgId", msgId as Any), ("date", date as Any), ("offset", offset as Any)])
}
}
public static func parse_searchResultPosition(_ reader: BufferReader) -> SearchResultsPosition? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.SearchResultsPosition.searchResultPosition(msgId: _1!, date: _2!, offset: _3!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SecureCredentialsEncrypted: TypeConstructorDescription {
case secureCredentialsEncrypted(data: Buffer, hash: Buffer, secret: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .secureCredentialsEncrypted(let data, let hash, let secret):
if boxed {
buffer.appendInt32(871426631)
}
serializeBytes(data, buffer: buffer, boxed: false)
serializeBytes(hash, buffer: buffer, boxed: false)
serializeBytes(secret, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .secureCredentialsEncrypted(let data, let hash, let secret):
return ("secureCredentialsEncrypted", [("data", data as Any), ("hash", hash as Any), ("secret", secret as Any)])
}
}
public static func parse_secureCredentialsEncrypted(_ reader: BufferReader) -> SecureCredentialsEncrypted? {
var _1: Buffer?
_1 = parseBytes(reader)
var _2: Buffer?
_2 = parseBytes(reader)
var _3: Buffer?
_3 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.SecureCredentialsEncrypted.secureCredentialsEncrypted(data: _1!, hash: _2!, secret: _3!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SecureData: TypeConstructorDescription {
case secureData(data: Buffer, dataHash: Buffer, secret: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .secureData(let data, let dataHash, let secret):
if boxed {
buffer.appendInt32(-1964327229)
}
serializeBytes(data, buffer: buffer, boxed: false)
serializeBytes(dataHash, buffer: buffer, boxed: false)
serializeBytes(secret, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .secureData(let data, let dataHash, let secret):
return ("secureData", [("data", data as Any), ("dataHash", dataHash as Any), ("secret", secret as Any)])
}
}
public static func parse_secureData(_ reader: BufferReader) -> SecureData? {
var _1: Buffer?
_1 = parseBytes(reader)
var _2: Buffer?
_2 = parseBytes(reader)
var _3: Buffer?
_3 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.SecureData.secureData(data: _1!, dataHash: _2!, secret: _3!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SecureFile: TypeConstructorDescription {
case secureFile(id: Int64, accessHash: Int64, size: Int64, dcId: Int32, date: Int32, fileHash: Buffer, secret: Buffer)
case secureFileEmpty
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .secureFile(let id, let accessHash, let size, let dcId, let date, let fileHash, let secret):
if boxed {
buffer.appendInt32(2097791614)
}
serializeInt64(id, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
serializeInt64(size, buffer: buffer, boxed: false)
serializeInt32(dcId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeBytes(fileHash, buffer: buffer, boxed: false)
serializeBytes(secret, buffer: buffer, boxed: false)
break
case .secureFileEmpty:
if boxed {
buffer.appendInt32(1679398724)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .secureFile(let id, let accessHash, let size, let dcId, let date, let fileHash, let secret):
return ("secureFile", [("id", id as Any), ("accessHash", accessHash as Any), ("size", size as Any), ("dcId", dcId as Any), ("date", date as Any), ("fileHash", fileHash as Any), ("secret", secret as Any)])
case .secureFileEmpty:
return ("secureFileEmpty", [])
}
}
public static func parse_secureFile(_ reader: BufferReader) -> SecureFile? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int64?
_3 = reader.readInt64()
var _4: Int32?
_4 = reader.readInt32()
var _5: Int32?
_5 = reader.readInt32()
var _6: Buffer?
_6 = parseBytes(reader)
var _7: Buffer?
_7 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.SecureFile.secureFile(id: _1!, accessHash: _2!, size: _3!, dcId: _4!, date: _5!, fileHash: _6!, secret: _7!)
}
else {
return nil
}
}
public static func parse_secureFileEmpty(_ reader: BufferReader) -> SecureFile? {
return Api.SecureFile.secureFileEmpty
}
}
}
public extension Api {
enum SecurePasswordKdfAlgo: TypeConstructorDescription {
case securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt: Buffer)

View File

@ -826,7 +826,7 @@ public extension Api {
switch self {
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod):
if boxed {
buffer.appendInt32(455361027)
buffer.appendInt32(1127934763)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(id, buffer: buffer, boxed: false)
@ -845,7 +845,7 @@ public extension Api {
for item in extendedMedia! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 11) != 0 {serializeInt32(subscriptionPeriod!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 12) != 0 {serializeInt32(subscriptionPeriod!, buffer: buffer, boxed: false)}
break
}
}
@ -891,7 +891,7 @@ public extension Api {
_13 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageMedia.self)
} }
var _14: Int32?
if Int(_1!) & Int(1 << 11) != 0 {_14 = reader.readInt32() }
if Int(_1!) & Int(1 << 12) != 0 {_14 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -905,7 +905,7 @@ public extension Api {
let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil
let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil
let _c13 = (Int(_1!) & Int(1 << 9) == 0) || _13 != nil
let _c14 = (Int(_1!) & Int(1 << 11) == 0) || _14 != nil
let _c14 = (Int(_1!) & Int(1 << 12) == 0) || _14 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 {
return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14)
}

View File

@ -1,12 +1,12 @@
public extension Api.bots {
enum PreviewInfo: TypeConstructorDescription {
case previewInfo(media: [Api.MessageMedia], langCodes: [String])
case previewInfo(media: [Api.BotPreviewMedia], langCodes: [String])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .previewInfo(let media, let langCodes):
if boxed {
buffer.appendInt32(1357069389)
buffer.appendInt32(212278628)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(media.count))
@ -30,9 +30,9 @@ public extension Api.bots {
}
public static func parse_previewInfo(_ reader: BufferReader) -> PreviewInfo? {
var _1: [Api.MessageMedia]?
var _1: [Api.BotPreviewMedia]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageMedia.self)
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotPreviewMedia.self)
}
var _2: [String]?
if let _ = reader.readInt32() {

View File

@ -1,3 +1,53 @@
public extension Api {
enum BusinessIntro: TypeConstructorDescription {
case businessIntro(flags: Int32, title: String, description: String, sticker: Api.Document?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .businessIntro(let flags, let title, let description, let sticker):
if boxed {
buffer.appendInt32(1510606445)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
serializeString(description, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {sticker!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .businessIntro(let flags, let title, let description, let sticker):
return ("businessIntro", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("sticker", sticker as Any)])
}
}
public static func parse_businessIntro(_ reader: BufferReader) -> BusinessIntro? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: String?
_3 = parseString(reader)
var _4: Api.Document?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.Document
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.BusinessIntro.businessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4)
}
else {
return nil
}
}
}
}
public extension Api {
enum BusinessLocation: TypeConstructorDescription {
case businessLocation(flags: Int32, geoPoint: Api.GeoPoint?, address: String)

View File

@ -1246,13 +1246,13 @@ public extension Api.payments {
}
public extension Api.payments {
enum StarsStatus: TypeConstructorDescription {
case starsStatus(flags: Int32, balance: Int64, subscriptions: [Api.StarsSubscription]?, subscriptionsNextOffset: String?, history: [Api.StarsTransaction]?, nextOffset: String?, chats: [Api.Chat], users: [Api.User])
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 history, let nextOffset, let chats, let users):
case .starsStatus(let flags, let balance, let subscriptions, let subscriptionsNextOffset, let subscriptionsMissingBalance, let history, let nextOffset, let chats, let users):
if boxed {
buffer.appendInt32(-2064727699)
buffer.appendInt32(-1141231252)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(balance, buffer: buffer, boxed: false)
@ -1262,6 +1262,7 @@ public extension Api.payments {
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! {
@ -1284,8 +1285,8 @@ public extension Api.payments {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starsStatus(let flags, let balance, let subscriptions, let subscriptionsNextOffset, 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), ("history", history as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
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)])
}
}
@ -1300,30 +1301,33 @@ public extension Api.payments {
} }
var _4: String?
if Int(_1!) & Int(1 << 2) != 0 {_4 = parseString(reader) }
var _5: [Api.StarsTransaction]?
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() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTransaction.self)
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTransaction.self)
} }
var _6: String?
if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) }
var _7: [Api.Chat]?
var _7: String?
if Int(_1!) & Int(1 << 0) != 0 {_7 = parseString(reader) }
var _8: [Api.Chat]?
if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _8: [Api.User]?
var _9: [Api.User]?
if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
_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 << 3) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
let _c7 = _7 != 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
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
return Api.payments.StarsStatus.starsStatus(flags: _1!, balance: _2!, subscriptions: _3, subscriptionsNextOffset: _4, history: _5, nextOffset: _6, chats: _7!, users: _8!)
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

View File

@ -2201,17 +2201,17 @@ public extension Api.functions.auth {
}
}
public extension Api.functions.bots {
static func addPreviewMedia(bot: Api.InputUser, langCode: String, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.MessageMedia>) {
static func addPreviewMedia(bot: Api.InputUser, langCode: String, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.BotPreviewMedia>) {
let buffer = Buffer()
buffer.appendInt32(911238190)
buffer.appendInt32(397326170)
bot.serialize(buffer, true)
serializeString(langCode, buffer: buffer, boxed: false)
media.serialize(buffer, true)
return (FunctionDescription(name: "bots.addPreviewMedia", parameters: [("bot", String(describing: bot)), ("langCode", String(describing: langCode)), ("media", String(describing: media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in
return (FunctionDescription(name: "bots.addPreviewMedia", parameters: [("bot", String(describing: bot)), ("langCode", String(describing: langCode)), ("media", String(describing: media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BotPreviewMedia? in
let reader = BufferReader(buffer)
var result: Api.MessageMedia?
var result: Api.BotPreviewMedia?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.MessageMedia
result = Api.parse(reader, signature: signature) as? Api.BotPreviewMedia
}
return result
})
@ -2285,18 +2285,18 @@ public extension Api.functions.bots {
}
}
public extension Api.functions.bots {
static func editPreviewMedia(bot: Api.InputUser, langCode: String, media: Api.InputMedia, newMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.MessageMedia>) {
static func editPreviewMedia(bot: Api.InputUser, langCode: String, media: Api.InputMedia, newMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.BotPreviewMedia>) {
let buffer = Buffer()
buffer.appendInt32(1892426154)
buffer.appendInt32(-2061148049)
bot.serialize(buffer, true)
serializeString(langCode, buffer: buffer, boxed: false)
media.serialize(buffer, true)
newMedia.serialize(buffer, true)
return (FunctionDescription(name: "bots.editPreviewMedia", parameters: [("bot", String(describing: bot)), ("langCode", String(describing: langCode)), ("media", String(describing: media)), ("newMedia", String(describing: newMedia))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in
return (FunctionDescription(name: "bots.editPreviewMedia", parameters: [("bot", String(describing: bot)), ("langCode", String(describing: langCode)), ("media", String(describing: media)), ("newMedia", String(describing: newMedia))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BotPreviewMedia? in
let reader = BufferReader(buffer)
var result: Api.MessageMedia?
var result: Api.BotPreviewMedia?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.MessageMedia
result = Api.parse(reader, signature: signature) as? Api.BotPreviewMedia
}
return result
})
@ -2383,15 +2383,15 @@ public extension Api.functions.bots {
}
}
public extension Api.functions.bots {
static func getPreviewMedias(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.MessageMedia]>) {
static func getPreviewMedias(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.BotPreviewMedia]>) {
let buffer = Buffer()
buffer.appendInt32(1720252591)
buffer.appendInt32(-1566222003)
bot.serialize(buffer, true)
return (FunctionDescription(name: "bots.getPreviewMedias", parameters: [("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.MessageMedia]? in
return (FunctionDescription(name: "bots.getPreviewMedias", parameters: [("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.BotPreviewMedia]? in
let reader = BufferReader(buffer)
var result: [Api.MessageMedia]?
var result: [Api.BotPreviewMedia]?
if let _ = reader.readInt32() {
result = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageMedia.self)
result = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotPreviewMedia.self)
}
return result
})
@ -7934,6 +7934,24 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func sendPaidReaction(peer: Api.InputPeer, msgId: Int32, count: Int32, randomId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(508941107)
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
serializeInt64(randomId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.sendPaidReaction", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("count", String(describing: count)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.messages {
static func sendQuickReplyMessages(peer: Api.InputPeer, shortcutId: Int32, id: [Int32], randomId: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
@ -8133,14 +8151,15 @@ public extension Api.functions.messages {
}
}
public extension Api.functions.messages {
static func setChatAvailableReactions(flags: Int32, peer: Api.InputPeer, availableReactions: Api.ChatReactions, reactionsLimit: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
static func setChatAvailableReactions(flags: Int32, peer: Api.InputPeer, availableReactions: Api.ChatReactions, reactionsLimit: Int32?, paidEnabled: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1511328724)
buffer.appendInt32(-2041895551)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
availableReactions.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(reactionsLimit!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "messages.setChatAvailableReactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("availableReactions", String(describing: availableReactions)), ("reactionsLimit", String(describing: reactionsLimit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
if Int(flags) & Int(1 << 1) != 0 {paidEnabled!.serialize(buffer, true)}
return (FunctionDescription(name: "messages.setChatAvailableReactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("availableReactions", String(describing: availableReactions)), ("reactionsLimit", String(describing: reactionsLimit)), ("paidEnabled", String(describing: paidEnabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
@ -8799,6 +8818,22 @@ public extension Api.functions.payments {
})
}
}
public extension Api.functions.payments {
static func fulfillStarsSubscription(peer: Api.InputPeer, subscriptionId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-866391117)
peer.serialize(buffer, true)
serializeString(subscriptionId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "payments.fulfillStarsSubscription", parameters: [("peer", String(describing: peer)), ("subscriptionId", String(describing: subscriptionId))]), 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 getBankCardData(number: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.BankCardData>) {
let buffer = Buffer()
@ -8974,12 +9009,13 @@ public extension Api.functions.payments {
}
}
public extension Api.functions.payments {
static func getStarsSubscriptions(peer: Api.InputPeer, offset: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.StarsStatus>) {
static func getStarsSubscriptions(flags: Int32, peer: Api.InputPeer, offset: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.StarsStatus>) {
let buffer = Buffer()
buffer.appendInt32(-2145547570)
buffer.appendInt32(52761285)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeString(offset, buffer: buffer, boxed: false)
return (FunctionDescription(name: "payments.getStarsSubscriptions", parameters: [("peer", String(describing: peer)), ("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in
return (FunctionDescription(name: "payments.getStarsSubscriptions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in
let reader = BufferReader(buffer)
var result: Api.payments.StarsStatus?
if let signature = reader.readInt32() {
@ -9005,14 +9041,15 @@ public extension Api.functions.payments {
}
}
public extension Api.functions.payments {
static func getStarsTransactions(flags: Int32, peer: Api.InputPeer, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.StarsStatus>) {
static func getStarsTransactions(flags: Int32, subscriptionId: String?, peer: Api.InputPeer, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.StarsStatus>) {
let buffer = Buffer()
buffer.appendInt32(-1751937702)
buffer.appendInt32(1775912279)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 3) != 0 {serializeString(subscriptionId!, buffer: buffer, boxed: false)}
peer.serialize(buffer, true)
serializeString(offset, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "payments.getStarsTransactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in
return (FunctionDescription(name: "payments.getStarsTransactions", parameters: [("flags", String(describing: flags)), ("subscriptionId", String(describing: subscriptionId)), ("peer", String(describing: peer)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in
let reader = BufferReader(buffer)
var result: Api.payments.StarsStatus?
if let signature = reader.readInt32() {

View File

@ -148,21 +148,23 @@ public extension Api {
}
public extension Api {
enum ChannelParticipant: TypeConstructorDescription {
case channelParticipant(userId: Int64, date: Int32)
case channelParticipant(flags: Int32, userId: Int64, date: Int32, subscriptionUntilDate: Int32?)
case channelParticipantAdmin(flags: Int32, userId: Int64, inviterId: Int64?, promotedBy: Int64, date: Int32, adminRights: Api.ChatAdminRights, rank: String?)
case channelParticipantBanned(flags: Int32, peer: Api.Peer, kickedBy: Int64, date: Int32, bannedRights: Api.ChatBannedRights)
case channelParticipantCreator(flags: Int32, userId: Int64, adminRights: Api.ChatAdminRights, rank: String?)
case channelParticipantLeft(peer: Api.Peer)
case channelParticipantSelf(flags: Int32, userId: Int64, inviterId: Int64, date: Int32)
case channelParticipantSelf(flags: Int32, userId: Int64, inviterId: Int64, date: Int32, subscriptionUntilDate: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .channelParticipant(let userId, let date):
case .channelParticipant(let flags, let userId, let date, let subscriptionUntilDate):
if boxed {
buffer.appendInt32(-1072953408)
buffer.appendInt32(-885426663)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(userId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(subscriptionUntilDate!, buffer: buffer, boxed: false)}
break
case .channelParticipantAdmin(let flags, let userId, let inviterId, let promotedBy, let date, let adminRights, let rank):
if boxed {
@ -201,22 +203,23 @@ public extension Api {
}
peer.serialize(buffer, true)
break
case .channelParticipantSelf(let flags, let userId, let inviterId, let date):
case .channelParticipantSelf(let flags, let userId, let inviterId, let date, let subscriptionUntilDate):
if boxed {
buffer.appendInt32(900251559)
buffer.appendInt32(1331723247)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(userId, buffer: buffer, boxed: false)
serializeInt64(inviterId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(subscriptionUntilDate!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .channelParticipant(let userId, let date):
return ("channelParticipant", [("userId", userId as Any), ("date", date as Any)])
case .channelParticipant(let flags, let userId, let date, let subscriptionUntilDate):
return ("channelParticipant", [("flags", flags as Any), ("userId", userId as Any), ("date", date as Any), ("subscriptionUntilDate", subscriptionUntilDate as Any)])
case .channelParticipantAdmin(let flags, let userId, let inviterId, let promotedBy, let date, let adminRights, let rank):
return ("channelParticipantAdmin", [("flags", flags as Any), ("userId", userId as Any), ("inviterId", inviterId as Any), ("promotedBy", promotedBy as Any), ("date", date as Any), ("adminRights", adminRights as Any), ("rank", rank as Any)])
case .channelParticipantBanned(let flags, let peer, let kickedBy, let date, let bannedRights):
@ -225,20 +228,26 @@ public extension Api {
return ("channelParticipantCreator", [("flags", flags as Any), ("userId", userId as Any), ("adminRights", adminRights as Any), ("rank", rank as Any)])
case .channelParticipantLeft(let peer):
return ("channelParticipantLeft", [("peer", peer as Any)])
case .channelParticipantSelf(let flags, let userId, let inviterId, let date):
return ("channelParticipantSelf", [("flags", flags as Any), ("userId", userId as Any), ("inviterId", inviterId as Any), ("date", date as Any)])
case .channelParticipantSelf(let flags, let userId, let inviterId, let date, let subscriptionUntilDate):
return ("channelParticipantSelf", [("flags", flags as Any), ("userId", userId as Any), ("inviterId", inviterId as Any), ("date", date as Any), ("subscriptionUntilDate", subscriptionUntilDate as Any)])
}
}
public static func parse_channelParticipant(_ reader: BufferReader) -> ChannelParticipant? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int32?
_2 = reader.readInt32()
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.ChannelParticipant.channelParticipant(userId: _1!, date: _2!)
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.ChannelParticipant.channelParticipant(flags: _1!, userId: _2!, date: _3!, subscriptionUntilDate: _4)
}
else {
return nil
@ -346,12 +355,15 @@ public extension Api {
_3 = reader.readInt64()
var _4: Int32?
_4 = reader.readInt32()
var _5: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.ChannelParticipant.channelParticipantSelf(flags: _1!, userId: _2!, inviterId: _3!, date: _4!)
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.ChannelParticipant.channelParticipantSelf(flags: _1!, userId: _2!, inviterId: _3!, date: _4!, subscriptionUntilDate: _5)
}
else {
return nil
@ -522,7 +534,7 @@ public extension Api {
}
public extension Api {
indirect enum Chat: TypeConstructorDescription {
case channel(flags: Int32, flags2: Int32, id: Int64, accessHash: Int64?, title: String, username: String?, photo: Api.ChatPhoto, date: Int32, restrictionReason: [Api.RestrictionReason]?, adminRights: Api.ChatAdminRights?, bannedRights: Api.ChatBannedRights?, defaultBannedRights: Api.ChatBannedRights?, participantsCount: Int32?, usernames: [Api.Username]?, storiesMaxId: Int32?, color: Api.PeerColor?, profileColor: Api.PeerColor?, emojiStatus: Api.EmojiStatus?, level: Int32?)
case channel(flags: Int32, flags2: Int32, id: Int64, accessHash: Int64?, title: String, username: String?, photo: Api.ChatPhoto, date: Int32, restrictionReason: [Api.RestrictionReason]?, adminRights: Api.ChatAdminRights?, bannedRights: Api.ChatBannedRights?, defaultBannedRights: Api.ChatBannedRights?, participantsCount: Int32?, usernames: [Api.Username]?, storiesMaxId: Int32?, color: Api.PeerColor?, profileColor: Api.PeerColor?, emojiStatus: Api.EmojiStatus?, level: Int32?, subscriptionUntilDate: Int32?)
case channelForbidden(flags: Int32, id: Int64, accessHash: Int64, title: String, untilDate: Int32?)
case chat(flags: Int32, id: Int64, title: String, photo: Api.ChatPhoto, participantsCount: Int32, date: Int32, version: Int32, migratedTo: Api.InputChannel?, adminRights: Api.ChatAdminRights?, defaultBannedRights: Api.ChatBannedRights?)
case chatEmpty(id: Int64)
@ -530,9 +542,9 @@ public extension Api {
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .channel(let flags, let flags2, let id, let accessHash, let title, let username, let photo, let date, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount, let usernames, let storiesMaxId, let color, let profileColor, let emojiStatus, let level):
case .channel(let flags, let flags2, let id, let accessHash, let title, let username, let photo, let date, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount, let usernames, let storiesMaxId, let color, let profileColor, let emojiStatus, let level, let subscriptionUntilDate):
if boxed {
buffer.appendInt32(179174543)
buffer.appendInt32(-29067075)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(flags2, buffer: buffer, boxed: false)
@ -561,6 +573,7 @@ public extension Api {
if Int(flags2) & Int(1 << 8) != 0 {profileColor!.serialize(buffer, true)}
if Int(flags2) & Int(1 << 9) != 0 {emojiStatus!.serialize(buffer, true)}
if Int(flags2) & Int(1 << 10) != 0 {serializeInt32(level!, buffer: buffer, boxed: false)}
if Int(flags2) & Int(1 << 11) != 0 {serializeInt32(subscriptionUntilDate!, buffer: buffer, boxed: false)}
break
case .channelForbidden(let flags, let id, let accessHash, let title, let untilDate):
if boxed {
@ -605,8 +618,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .channel(let flags, let flags2, let id, let accessHash, let title, let username, let photo, let date, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount, let usernames, let storiesMaxId, let color, let profileColor, let emojiStatus, let level):
return ("channel", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("title", title as Any), ("username", username as Any), ("photo", photo as Any), ("date", date as Any), ("restrictionReason", restrictionReason as Any), ("adminRights", adminRights as Any), ("bannedRights", bannedRights as Any), ("defaultBannedRights", defaultBannedRights as Any), ("participantsCount", participantsCount as Any), ("usernames", usernames as Any), ("storiesMaxId", storiesMaxId as Any), ("color", color as Any), ("profileColor", profileColor as Any), ("emojiStatus", emojiStatus as Any), ("level", level as Any)])
case .channel(let flags, let flags2, let id, let accessHash, let title, let username, let photo, let date, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount, let usernames, let storiesMaxId, let color, let profileColor, let emojiStatus, let level, let subscriptionUntilDate):
return ("channel", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("title", title as Any), ("username", username as Any), ("photo", photo as Any), ("date", date as Any), ("restrictionReason", restrictionReason as Any), ("adminRights", adminRights as Any), ("bannedRights", bannedRights as Any), ("defaultBannedRights", defaultBannedRights as Any), ("participantsCount", participantsCount as Any), ("usernames", usernames as Any), ("storiesMaxId", storiesMaxId as Any), ("color", color as Any), ("profileColor", profileColor as Any), ("emojiStatus", emojiStatus as Any), ("level", level as Any), ("subscriptionUntilDate", subscriptionUntilDate as Any)])
case .channelForbidden(let flags, let id, let accessHash, let title, let untilDate):
return ("channelForbidden", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("title", title as Any), ("untilDate", untilDate as Any)])
case .chat(let flags, let id, let title, let photo, let participantsCount, let date, let version, let migratedTo, let adminRights, let defaultBannedRights):
@ -675,6 +688,8 @@ public extension Api {
} }
var _19: Int32?
if Int(_2!) & Int(1 << 10) != 0 {_19 = reader.readInt32() }
var _20: Int32?
if Int(_2!) & Int(1 << 11) != 0 {_20 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -694,8 +709,9 @@ public extension Api {
let _c17 = (Int(_2!) & Int(1 << 8) == 0) || _17 != nil
let _c18 = (Int(_2!) & Int(1 << 9) == 0) || _18 != nil
let _c19 = (Int(_2!) & Int(1 << 10) == 0) || _19 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 {
return Api.Chat.channel(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, title: _5!, username: _6, photo: _7!, date: _8!, restrictionReason: _9, adminRights: _10, bannedRights: _11, defaultBannedRights: _12, participantsCount: _13, usernames: _14, storiesMaxId: _15, color: _16, profileColor: _17, emojiStatus: _18, level: _19)
let _c20 = (Int(_2!) & Int(1 << 11) == 0) || _20 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 {
return Api.Chat.channel(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, title: _5!, username: _6, photo: _7!, date: _8!, restrictionReason: _9, adminRights: _10, bannedRights: _11, defaultBannedRights: _12, participantsCount: _13, usernames: _14, storiesMaxId: _15, color: _16, profileColor: _17, emojiStatus: _18, level: _19, subscriptionUntilDate: _20)
}
else {
return nil
@ -1288,7 +1304,7 @@ public extension Api {
switch self {
case .chatInvite(let flags, let title, let about, let photo, let participantsCount, let participants, let color, let subscriptionPricing, let subscriptionFormId):
if boxed {
buffer.appendInt32(-1965998484)
buffer.appendInt32(-26920803)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
@ -1302,7 +1318,7 @@ public extension Api {
}}
serializeInt32(color, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 10) != 0 {subscriptionPricing!.serialize(buffer, true)}
if Int(flags) & Int(1 << 10) != 0 {serializeInt64(subscriptionFormId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 12) != 0 {serializeInt64(subscriptionFormId!, buffer: buffer, boxed: false)}
break
case .chatInviteAlready(let chat):
if boxed {
@ -1355,7 +1371,7 @@ public extension Api {
_8 = Api.parse(reader, signature: signature) as? Api.StarsSubscriptionPricing
} }
var _9: Int64?
if Int(_1!) & Int(1 << 10) != 0 {_9 = reader.readInt64() }
if Int(_1!) & Int(1 << 12) != 0 {_9 = reader.readInt64() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil
@ -1364,7 +1380,7 @@ public extension Api {
let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil
let _c7 = _7 != nil
let _c8 = (Int(_1!) & Int(1 << 10) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 10) == 0) || _9 != nil
let _c9 = (Int(_1!) & Int(1 << 12) == 0) || _9 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.ChatInvite.chatInvite(flags: _1!, title: _2!, about: _3, photo: _4!, participantsCount: _5!, participants: _6, color: _7!, subscriptionPricing: _8, subscriptionFormId: _9)
}

View File

@ -412,6 +412,7 @@ public extension Api {
enum InputFile: TypeConstructorDescription {
case inputFile(id: Int64, parts: Int32, name: String, md5Checksum: String)
case inputFileBig(id: Int64, parts: Int32, name: String)
case inputFileStoryDocument(id: Api.InputDocument)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -432,6 +433,12 @@ public extension Api {
serializeInt32(parts, buffer: buffer, boxed: false)
serializeString(name, buffer: buffer, boxed: false)
break
case .inputFileStoryDocument(let id):
if boxed {
buffer.appendInt32(1658620744)
}
id.serialize(buffer, true)
break
}
}
@ -441,6 +448,8 @@ public extension Api {
return ("inputFile", [("id", id as Any), ("parts", parts as Any), ("name", name as Any), ("md5Checksum", md5Checksum as Any)])
case .inputFileBig(let id, let parts, let name):
return ("inputFileBig", [("id", id as Any), ("parts", parts as Any), ("name", name as Any)])
case .inputFileStoryDocument(let id):
return ("inputFileStoryDocument", [("id", id as Any)])
}
}
@ -481,6 +490,19 @@ public extension Api {
return nil
}
}
public static func parse_inputFileStoryDocument(_ reader: BufferReader) -> InputFile? {
var _1: Api.InputDocument?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputDocument
}
let _c1 = _1 != nil
if _c1 {
return Api.InputFile.inputFileStoryDocument(id: _1!)
}
else {
return nil
}
}
}
}
@ -1002,135 +1024,3 @@ public extension Api {
}
}
public extension Api {
indirect enum InputInvoice: TypeConstructorDescription {
case inputInvoiceChatInviteSubscription(hash: String)
case inputInvoiceMessage(peer: Api.InputPeer, msgId: Int32)
case inputInvoicePremiumGiftCode(purpose: Api.InputStorePaymentPurpose, option: Api.PremiumGiftCodeOption)
case inputInvoiceSlug(slug: String)
case inputInvoiceStars(purpose: Api.InputStorePaymentPurpose)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputInvoiceChatInviteSubscription(let hash):
if boxed {
buffer.appendInt32(887591921)
}
serializeString(hash, buffer: buffer, boxed: false)
break
case .inputInvoiceMessage(let peer, let msgId):
if boxed {
buffer.appendInt32(-977967015)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
break
case .inputInvoicePremiumGiftCode(let purpose, let option):
if boxed {
buffer.appendInt32(-1734841331)
}
purpose.serialize(buffer, true)
option.serialize(buffer, true)
break
case .inputInvoiceSlug(let slug):
if boxed {
buffer.appendInt32(-1020867857)
}
serializeString(slug, buffer: buffer, boxed: false)
break
case .inputInvoiceStars(let purpose):
if boxed {
buffer.appendInt32(1710230755)
}
purpose.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputInvoiceChatInviteSubscription(let hash):
return ("inputInvoiceChatInviteSubscription", [("hash", hash as Any)])
case .inputInvoiceMessage(let peer, let msgId):
return ("inputInvoiceMessage", [("peer", peer as Any), ("msgId", msgId as Any)])
case .inputInvoicePremiumGiftCode(let purpose, let option):
return ("inputInvoicePremiumGiftCode", [("purpose", purpose as Any), ("option", option as Any)])
case .inputInvoiceSlug(let slug):
return ("inputInvoiceSlug", [("slug", slug as Any)])
case .inputInvoiceStars(let purpose):
return ("inputInvoiceStars", [("purpose", purpose as Any)])
}
}
public static func parse_inputInvoiceChatInviteSubscription(_ reader: BufferReader) -> InputInvoice? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.InputInvoice.inputInvoiceChatInviteSubscription(hash: _1!)
}
else {
return nil
}
}
public static func parse_inputInvoiceMessage(_ reader: BufferReader) -> InputInvoice? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputInvoice.inputInvoiceMessage(peer: _1!, msgId: _2!)
}
else {
return nil
}
}
public static func parse_inputInvoicePremiumGiftCode(_ reader: BufferReader) -> InputInvoice? {
var _1: Api.InputStorePaymentPurpose?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputStorePaymentPurpose
}
var _2: Api.PremiumGiftCodeOption?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.PremiumGiftCodeOption
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputInvoice.inputInvoicePremiumGiftCode(purpose: _1!, option: _2!)
}
else {
return nil
}
}
public static func parse_inputInvoiceSlug(_ reader: BufferReader) -> InputInvoice? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.InputInvoice.inputInvoiceSlug(slug: _1!)
}
else {
return nil
}
}
public static func parse_inputInvoiceStars(_ reader: BufferReader) -> InputInvoice? {
var _1: Api.InputStorePaymentPurpose?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputStorePaymentPurpose
}
let _c1 = _1 != nil
if _c1 {
return Api.InputInvoice.inputInvoiceStars(purpose: _1!)
}
else {
return nil
}
}
}
}

View File

@ -2627,8 +2627,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
if !isScheduled && canSpeak {
if #available(iOS 15.0, *) {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Microphone Modes", textColor: .primary, icon: { theme in
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_MicrophoneModes, textColor: .primary, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Noise"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)

View File

@ -478,7 +478,7 @@ struct AccountMutableState {
for chat in chats {
switch chat {
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _):
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _):
if let participantsCount = participantsCount {
self.addOperation(.UpdateCachedPeerData(chat.peerId, { current in
var previous: CachedChannelData

View File

@ -61,7 +61,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: "", photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
case let .chatForbidden(id, title):
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
case let .channel(flags, flags2, id, accessHash, title, username, photo, date, restrictionReason, adminRights, bannedRights, defaultBannedRights, _, usernames, _, color, profileColor, emojiStatus, boostLevel):
case let .channel(flags, flags2, id, accessHash, title, username, photo, date, restrictionReason, adminRights, bannedRights, defaultBannedRights, _, usernames, _, color, profileColor, emojiStatus, boostLevel, subscriptionUntilDate):
let isMin = (flags & (1 << 12)) != 0
let participationStatus: TelegramChannelParticipationStatus
@ -173,7 +173,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
}
}
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: accessHashValue, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: 0, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChatAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChatBannedRights.init), defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden, nameColor: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId, emojiStatus: emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:)), approximateBoostLevel: boostLevel)
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: accessHashValue, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: 0, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChatAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChatBannedRights.init), defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden, nameColor: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId, emojiStatus: emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:)), approximateBoostLevel: boostLevel, subscriptionUntilDate: subscriptionUntilDate)
case let .channelForbidden(flags, id, accessHash, title, untilDate):
let info: TelegramChannelInfo
if (flags & Int32(1 << 8)) != 0 {
@ -182,7 +182,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
info = .broadcast(TelegramChannelBroadcastInfo(flags: []))
}
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: .personal(accessHash), title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max), defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil)
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: .personal(accessHash), title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max), defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil)
}
}
@ -190,7 +190,7 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
switch rhs {
case .chat, .chatEmpty, .chatForbidden, .channelForbidden:
return parseTelegramGroupOrChannel(chat: rhs)
case let .channel(flags, flags2, _, accessHash, title, username, photo, _, _, _, _, defaultBannedRights, _, usernames, _, color, profileColor, emojiStatus, boostLevel):
case let .channel(flags, flags2, _, accessHash, title, username, photo, _, _, _, _, defaultBannedRights, _, usernames, _, color, profileColor, emojiStatus, boostLevel, subscriptionUntilDate):
let isMin = (flags & (1 << 12)) != 0
if accessHash != nil && !isMin {
return parseTelegramGroupOrChannel(chat: rhs)
@ -254,7 +254,7 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
let parsedEmojiStatus = emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:))
return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden, nameColor: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId, emojiStatus: parsedEmojiStatus, approximateBoostLevel: boostLevel)
return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden, nameColor: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId, emojiStatus: parsedEmojiStatus, approximateBoostLevel: boostLevel, subscriptionUntilDate: subscriptionUntilDate)
} else {
return parseTelegramGroupOrChannel(chat: rhs)
}
@ -308,6 +308,6 @@ func mergeChannel(lhs: TelegramChannel?, rhs: TelegramChannel) -> TelegramChanne
let storiesHidden: Bool? = rhs.storiesHidden ?? lhs.storiesHidden
return TelegramChannel(id: lhs.id, accessHash: accessHash, title: rhs.title, username: rhs.username, photo: rhs.photo, creationDate: rhs.creationDate, version: rhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: rhs.restrictionInfo, adminRights: rhs.adminRights, bannedRights: rhs.bannedRights, defaultBannedRights: rhs.defaultBannedRights, usernames: rhs.usernames, storiesHidden: storiesHidden, nameColor: rhs.nameColor, backgroundEmojiId: rhs.backgroundEmojiId, profileColor: rhs.profileColor, profileBackgroundEmojiId: rhs.profileBackgroundEmojiId, emojiStatus: rhs.emojiStatus, approximateBoostLevel: rhs.approximateBoostLevel)
return TelegramChannel(id: lhs.id, accessHash: accessHash, title: rhs.title, username: rhs.username, photo: rhs.photo, creationDate: rhs.creationDate, version: rhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: rhs.restrictionInfo, adminRights: rhs.adminRights, bannedRights: rhs.bannedRights, defaultBannedRights: rhs.defaultBannedRights, usernames: rhs.usernames, storiesHidden: storiesHidden, nameColor: rhs.nameColor, backgroundEmojiId: rhs.backgroundEmojiId, profileColor: rhs.profileColor, profileBackgroundEmojiId: rhs.profileBackgroundEmojiId, emojiStatus: rhs.emojiStatus, approximateBoostLevel: rhs.approximateBoostLevel, subscriptionUntilDate: rhs.subscriptionUntilDate)
}

View File

@ -72,13 +72,13 @@ public struct ChannelParticipantBannedInfo: PostboxCoding, Equatable {
public enum ChannelParticipant: PostboxCoding, Equatable {
case creator(id: PeerId, adminInfo: ChannelParticipantAdminInfo?, rank: String?)
case member(id: PeerId, invitedAt: Int32, adminInfo: ChannelParticipantAdminInfo?, banInfo: ChannelParticipantBannedInfo?, rank: String?)
case member(id: PeerId, invitedAt: Int32, adminInfo: ChannelParticipantAdminInfo?, banInfo: ChannelParticipantBannedInfo?, rank: String?, subscriptionUntilDate: Int32?)
public var peerId: PeerId {
switch self {
case let .creator(id, _, _):
return id
case let .member(id, _, _, _, _):
case let .member(id, _, _, _, _, _):
return id
}
}
@ -87,15 +87,15 @@ public enum ChannelParticipant: PostboxCoding, Equatable {
switch self {
case let .creator(_, _, rank):
return rank
case let .member(_, _, _, _, rank):
case let .member(_, _, _, _, rank, _):
return rank
}
}
public static func ==(lhs: ChannelParticipant, rhs: ChannelParticipant) -> Bool {
switch lhs {
case let .member(lhsId, lhsInvitedAt, lhsAdminInfo, lhsBanInfo, lhsRank):
if case let .member(rhsId, rhsInvitedAt, rhsAdminInfo, rhsBanInfo, rhsRank) = rhs {
case let .member(lhsId, lhsInvitedAt, lhsAdminInfo, lhsBanInfo, lhsRank, lhsSubscriptionUntilDate):
if case let .member(rhsId, rhsInvitedAt, rhsAdminInfo, rhsBanInfo, rhsRank, rhsSubscriptionUntilDate) = rhs {
if lhsId != rhsId {
return false
}
@ -111,6 +111,9 @@ public enum ChannelParticipant: PostboxCoding, Equatable {
if lhsRank != rhsRank {
return false
}
if lhsSubscriptionUntilDate != rhsSubscriptionUntilDate {
return false
}
return true
} else {
return false
@ -127,17 +130,17 @@ public enum ChannelParticipant: PostboxCoding, Equatable {
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("r", orElse: 0) {
case ChannelParticipantValue.member.rawValue:
self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: decoder.decodeObjectForKey("ai", decoder: { ChannelParticipantAdminInfo(decoder: $0) }) as? ChannelParticipantAdminInfo, banInfo: decoder.decodeObjectForKey("bi", decoder: { ChannelParticipantBannedInfo(decoder: $0) }) as? ChannelParticipantBannedInfo, rank: decoder.decodeOptionalStringForKey("rank"))
self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: decoder.decodeObjectForKey("ai", decoder: { ChannelParticipantAdminInfo(decoder: $0) }) as? ChannelParticipantAdminInfo, banInfo: decoder.decodeObjectForKey("bi", decoder: { ChannelParticipantBannedInfo(decoder: $0) }) as? ChannelParticipantBannedInfo, rank: decoder.decodeOptionalStringForKey("rank"), subscriptionUntilDate: decoder.decodeOptionalInt32ForKey("subscriptionUntilDate"))
case ChannelParticipantValue.creator.rawValue:
self = .creator(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), adminInfo: decoder.decodeObjectForKey("ai", decoder: { ChannelParticipantAdminInfo(decoder: $0) }) as? ChannelParticipantAdminInfo, rank: decoder.decodeOptionalStringForKey("rank"))
default:
self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: nil, banInfo: nil, rank: nil)
self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil)
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case let .member(id, invitedAt, adminInfo, banInfo, rank):
case let .member(id, invitedAt, adminInfo, banInfo, rank, subscriptionUntilDate):
encoder.encodeInt32(ChannelParticipantValue.member.rawValue, forKey: "r")
encoder.encodeInt64(id.toInt64(), forKey: "i")
encoder.encodeInt32(invitedAt, forKey: "t")
@ -156,6 +159,11 @@ public enum ChannelParticipant: PostboxCoding, Equatable {
} else {
encoder.encodeNil(forKey: "rank")
}
if let subscriptionUntilDate = subscriptionUntilDate {
encoder.encodeInt32(subscriptionUntilDate, forKey: "subscriptionUntilDate")
} else {
encoder.encodeNil(forKey: "subscriptionUntilDate")
}
case let .creator(id, adminInfo, rank):
encoder.encodeInt32(ChannelParticipantValue.creator.rawValue, forKey: "r")
encoder.encodeInt64(id.toInt64(), forKey: "i")
@ -197,20 +205,20 @@ public final class CachedChannelParticipants: PostboxCoding, Equatable {
extension ChannelParticipant {
init(apiParticipant: Api.ChannelParticipant) {
switch apiParticipant {
case let .channelParticipant(userId, date):
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), invitedAt: date, adminInfo: nil, banInfo: nil, rank: nil)
case let .channelParticipant(_, userId, date, subscriptionUntilDate):
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), invitedAt: date, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: subscriptionUntilDate)
case let .channelParticipantCreator(_, userId, adminRights, rank):
self = .creator(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(apiAdminRights: adminRights) ?? TelegramChatAdminRights(rights: []), promotedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), canBeEditedByAccountPeer: true), rank: rank)
case let .channelParticipantBanned(flags, userId, restrictedBy, date, bannedRights):
let hasLeft = (flags & (1 << 0)) != 0
let banInfo = ChannelParticipantBannedInfo(rights: TelegramChatBannedRights(apiBannedRights: bannedRights), restrictedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(restrictedBy)), timestamp: date, isMember: !hasLeft)
self = .member(id: userId.peerId, invitedAt: date, adminInfo: nil, banInfo: banInfo, rank: nil)
self = .member(id: userId.peerId, invitedAt: date, adminInfo: nil, banInfo: banInfo, rank: nil, subscriptionUntilDate: nil)
case let .channelParticipantAdmin(flags, userId, _, promotedBy, date, adminRights, rank: rank):
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), invitedAt: date, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(apiAdminRights: adminRights) ?? TelegramChatAdminRights(rights: []), promotedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(promotedBy)), canBeEditedByAccountPeer: (flags & (1 << 0)) != 0), banInfo: nil, rank: rank)
case let .channelParticipantSelf(_, userId, _, date):
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), invitedAt: date, adminInfo: nil, banInfo: nil, rank: nil)
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), invitedAt: date, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(apiAdminRights: adminRights) ?? TelegramChatAdminRights(rights: []), promotedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(promotedBy)), canBeEditedByAccountPeer: (flags & (1 << 0)) != 0), banInfo: nil, rank: rank, subscriptionUntilDate: nil)
case let .channelParticipantSelf(_, userId, _, date, subscriptionUntilDate):
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), invitedAt: date, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: subscriptionUntilDate)
case let .channelParticipantLeft(userId):
self = .member(id: userId.peerId, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil)
self = .member(id: userId.peerId, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil)
}
}
}

View File

@ -5,7 +5,8 @@ import TelegramApi
extension ReactionsMessageAttribute {
func withUpdatedResults(_ reactions: Api.MessageReactions) -> ReactionsMessageAttribute {
switch reactions {
case let .messageReactions(flags, results, recentReactions):
case let .messageReactions(flags, results, recentReactions, topReactors):
let _ = topReactors
let min = (flags & (1 << 0)) != 0
let canViewList = (flags & (1 << 2)) != 0
let isTags = (flags & (1 << 3)) != 0
@ -205,7 +206,8 @@ public func mergedMessageReactions(attributes: [MessageAttribute], isTags: Bool)
extension ReactionsMessageAttribute {
convenience init(apiReactions: Api.MessageReactions) {
switch apiReactions {
case let .messageReactions(flags, results, recentReactions):
case let .messageReactions(flags, results, recentReactions, topReactors):
let _ = topReactors
let canViewList = (flags & (1 << 2)) != 0
let isTags = (flags & (1 << 3)) != 0
let parsedRecentReactions: [ReactionsMessageAttribute.RecentPeer]

View File

@ -287,6 +287,11 @@ public final class AccountStateManager {
return self.storyUpdatesPipe.signal()
}
fileprivate let botPreviewUpdatesPipe = ValuePipe<[InternalBotPreviewUpdate]>()
public var botPreviewUpdates: Signal<[InternalBotPreviewUpdate], NoError> {
return self.botPreviewUpdatesPipe.signal()
}
private var updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:]
private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext()
private var updatedRevenueBalancesContext = UpdatedRevenueBalancesSubscriberContext()
@ -1856,6 +1861,18 @@ public final class AccountStateManager {
}
}
var botPreviewUpdates: Signal<[InternalBotPreviewUpdate], NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.botPreviewUpdates.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
}
}
func injectBotPreviewUpdates(updates: [InternalBotPreviewUpdate]) {
self.impl.with { impl in
impl.botPreviewUpdatesPipe.putNext(updates)
}
}
var updateConfigRequested: (() -> Void)?
var isPremiumUpdated: (() -> Void)?

View File

@ -14,7 +14,7 @@ private func copyOrMoveResourceData(from fromResource: MediaResource, to toResou
}
}
func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox, force: Bool) {
func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox, force: Bool, skipPreviews: Bool = false) {
if let fromImage = from as? TelegramMediaImage, let toImage = to as? TelegramMediaImage {
let fromSmallestRepresentation = smallestImageRepresentation(fromImage.representations)
if let fromSmallestRepresentation = fromSmallestRepresentation, let toSmallestRepresentation = smallestImageRepresentation(toImage.representations) {
@ -32,11 +32,13 @@ func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox, force:
}
}
} else if let fromFile = from as? TelegramMediaFile, let toFile = to as? TelegramMediaFile {
if let fromPreview = smallestImageRepresentation(fromFile.previewRepresentations), let toPreview = smallestImageRepresentation(toFile.previewRepresentations) {
copyOrMoveResourceData(from: fromPreview.resource, to: toPreview.resource, mediaBox: postbox.mediaBox)
}
if let fromVideoThumbnail = fromFile.videoThumbnails.first, let toVideoThumbnail = toFile.videoThumbnails.first, fromVideoThumbnail.resource.id != toVideoThumbnail.resource.id {
copyOrMoveResourceData(from: fromVideoThumbnail.resource, to: toVideoThumbnail.resource, mediaBox: postbox.mediaBox)
if !skipPreviews {
if let fromPreview = smallestImageRepresentation(fromFile.previewRepresentations), let toPreview = smallestImageRepresentation(toFile.previewRepresentations) {
copyOrMoveResourceData(from: fromPreview.resource, to: toPreview.resource, mediaBox: postbox.mediaBox)
}
if let fromVideoThumbnail = fromFile.videoThumbnails.first, let toVideoThumbnail = toFile.videoThumbnails.first, fromVideoThumbnail.resource.id != toVideoThumbnail.resource.id {
copyOrMoveResourceData(from: fromVideoThumbnail.resource, to: toVideoThumbnail.resource, mediaBox: postbox.mediaBox)
}
}
let videoFirstFrameFromPath = postbox.mediaBox.cachedRepresentationCompletePath(fromFile.resource.id, keepDuration: .general, representationId: "first-frame")
let videoFirstFrameToPath = postbox.mediaBox.cachedRepresentationCompletePath(toFile.resource.id, keepDuration: .general, representationId: "first-frame")

View File

@ -682,7 +682,7 @@ func _internal_updatePeerReactionSettings(account: Account, peerId: PeerId, reac
reactionLimitValue = maxReactionCount
}
return account.network.request(Api.functions.messages.setChatAvailableReactions(flags: flags, peer: inputPeer, availableReactions: mappedReactions, reactionsLimit: reactionLimitValue))
return account.network.request(Api.functions.messages.setChatAvailableReactions(flags: flags, peer: inputPeer, availableReactions: mappedReactions, reactionsLimit: reactionLimitValue, paidEnabled: nil))
|> map(Optional.init)
|> `catch` { error -> Signal<Api.Updates?, UpdatePeerAllowedReactionsError> in
if error.errorDescription == "CHAT_NOT_MODIFIED" {

View File

@ -182,7 +182,7 @@ extension Api.Chat {
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id))
case let .chatForbidden(id, _):
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id))
case let .channel(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .channel(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id))
case let .channelForbidden(_, id, _, _, _):
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id))

View File

@ -16,6 +16,7 @@ public enum ServerProvidedSuggestion: String {
case setupBirthday = "BIRTHDAY_SETUP"
case todayBirthdays = "BIRTHDAY_CONTACTS_TODAY"
case gracePremium = "PREMIUM_GRACE"
case starsSubscriptionLowBalance = "STARS_SUBSCRIPTION_LOW_BALANCE"
}
private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:])

View File

@ -627,40 +627,87 @@ extension TelegramBusinessChatLinks {
public final class CachedUserData: CachedPeerData {
public final class BotPreview: Codable, Equatable {
private enum CodingKeys: String, CodingKey {
case media
case items
case alternativeLanguageCodes
}
public let media: [Media]
public final class Item: Codable, Equatable {
private enum CodingKeys: String, CodingKey {
case media = "m"
case timestamp = "t"
}
public let media: Media
public let timestamp: Int32
public init(media: Media, timestamp: Int32) {
self.media = media
self.timestamp = timestamp
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let mediaData = try container.decode(Data.self, forKey: .media)
guard let media = PostboxDecoder(buffer: MemoryBuffer(data: mediaData)).decodeRootObject() as? Media else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "media"))
}
self.media = media
self.timestamp = try container.decode(Int32.self, forKey: .timestamp)
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let encoder = PostboxEncoder()
encoder.encodeRootObject(media)
try container.encode(encoder.makeData(), forKey: .media)
try container.encode(self.timestamp, forKey: .timestamp)
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
if lhs === rhs {
return true
}
if !lhs.media.isEqual(to: rhs.media) {
return false
}
return true
}
}
public init(media: [Media]) {
self.media = media
public let items: [Item]
public let alternativeLanguageCodes: [String]
public init(items: [Item], alternativeLanguageCodes: [String]) {
self.items = items
self.alternativeLanguageCodes = alternativeLanguageCodes
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let mediaData = try container.decode([Data].self, forKey: .media)
self.media = mediaData.compactMap { data -> Media? in
return PostboxDecoder(buffer: MemoryBuffer(data: data)).decodeRootObject() as? Media
}
self.items = try container.decode([Item].self, forKey: .items)
self.alternativeLanguageCodes = try container.decode([String].self, forKey: .alternativeLanguageCodes)
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let mediaData = self.media.map { media -> Data in
let encoder = PostboxEncoder()
encoder.encodeRootObject(media)
return encoder.makeData()
}
try container.encode(mediaData, forKey: .media)
try container.encode(self.items, forKey: .items)
try container.encode(self.alternativeLanguageCodes, forKey: .alternativeLanguageCodes)
}
public static func ==(lhs: BotPreview, rhs: BotPreview) -> Bool {
if lhs === rhs {
return true
}
if !areMediaArraysEqual(lhs.media, rhs.media) {
if lhs.items != rhs.items {
return false
}
if lhs.alternativeLanguageCodes != rhs.alternativeLanguageCodes {
return false
}
return true

View File

@ -157,6 +157,8 @@ extension MessageReaction.Reaction {
self = .builtin(emoticon)
case let .reactionCustomEmoji(documentId):
self = .custom(documentId)
case .reactionPaid:
return nil
}
}

Some files were not shown because too many files have changed in this diff Show More